Home > Enterprise >  XmlService error getting nested child element (Google AppScript)
XmlService error getting nested child element (Google AppScript)

Time:09-09

Looking for assistance with an error I'm getting when attempting to get a nested node value in an XML file using XmlService and Google's AppScript. The node name is "asciiformat" and its position in the XML file is according to below (this is a simplified version; find full XML structure below AppScript code):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#">
    <entry>
        <tes:ftpjob xmlns:tes="http://www.software.com">
            <tes:asciiformat>N</tes:asciiformat>

Here's the error from AppScript:

TypeError: ftpJobInfo.getChildren is not a function
doGet   @ Code.gs:17

Here's the code:

function doGet(e) {
  let fileID = '1qHbA97eVaJtPWU3g_f9dEtwg-rlvkpc2';
  url = DriveApp.getFileById(fileID).getBlob().getDataAsString();  
  let document = XmlService.parse(url);

  let root = document.getRootElement();

  let ns = root.getNamespace();
  let entries = root.getChildren("entry", ns);
  let entry, ftpJobInfo, ftpJobInfoDetails;

  let id, title, asciiFormat;
  for (let counter = 0; counter < entries.length; counter  ) {
    entry = entries[counter];
 
    ftpJobInfo = entry.getContent(8);
    ftpJobInfoDetails = ftpJobInfo.getChildren('tes:ftpjob');
    
    asciiFormat = ftpJobInfoDetails.getContent(0).getValue();

    Logger.log("prayer hands");
  }
}

Here's the full XML file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#">
    <title>tes-6.5/tes:FTPJob.getList</title>
    <category term="0" scheme="row" label=""/>
    <category term="0" scheme="level" label=""/>
    <category term="111111111111111111111111" scheme="securitypolicy" label=""/>
    <category term="000000000000000000000000" scheme="customsecuritypolicy" label=""/>
    <generator uri="http://www.software.com">TES</generator>
    <modified>2022-08-04T15:41:11.027-06:00</modified>
    <entry>
        <id>9966</id>
        <title>01 PMI SFTP</title>
        <category term="0" scheme="row" label=""/>
        <category term="0" scheme="level" label=""/>
        <category term="111111111111111111111111" scheme="securitypolicy" label=""/>
        <category term="000000000000000000000000" scheme="customsecuritypolicy" label=""/>
        <source>com.software.FTPJobView</source>
        <tes:ftpjob xmlns:tes="http://www.software.com">
            <tes:asciiformat>N</tes:asciiformat>
            <tes:ftphost>1.1.1.1:722</tes:ftphost>
            <tes:filename>*.txt</tes:filename>
            <tes:ftpoperation>2</tes:ftpoperation>
            <tes:ftpprotocol>sftp</tes:ftpprotocol>
            <tes:ftpuserid>25</tes:ftpuserid>
            <tes:localpath>\\localpath</tes:localpath>
            <tes:newfilename/>
            <tes:replacefiles>N</tes:replacefiles>
            <tes:userpasswordauthentication>Y</tes:userpasswordauthentication>
            <tes:id>9966</tes:id><tes:typename>FTPJOB</tes:typename>
            <tes:name>01 PMI SFTP</tes:name><tes:parentname>\location\location\location</tes:parentname>
            <tes:active>Y</tes:active>
            <tes:ownerid>16</tes:ownerid>
            <tes:agentid>65</tes:agentid>
            <tes:command>\\location</tes:command>
            <tes:createtime>2012-08-08T11:05:27-0600</tes:createtime>
            <tes:dependencylogic>1</tes:dependencylogic>
            <tes:fullpath>\location\location\location</tes:fullpath>
            <tes:lastchangetime>2019-07-19T17:27:42-0600</tes:lastchangetime>
            <tes:runuserid>25</tes:runuserid>
        </tes:ftpjob>
    </entry>
</feed>

CodePudding user response:

This function will return the value of any node in an XML file by looping through all elements and attempting to match the name. It's returning the getValue() parameter (which would be better if it was an input variable, but I hard coded it to match your request).

Small update to account for if using prefix. Should work either way if nameToFind has colon or not.

function getElementValue() {
  const nameToFind = "tes:asciiformat";
  const fileID = '1qHbA97eVaJtPWU3g_f9dEtwg-rlvkpc2';
  const url = DriveApp.getFileById(fileID).getBlob().getDataAsString();

  var zElements = XmlService.parse(url).getRootElement()
    .getDescendants().map(x => x.asElement()).filter(y => y != null);

  zElements.forEach(function (x, zIndex) {
    var zCheckName = ((nameToFind.search(":") >= 0) ? x.getNamespace()
      .getPrefix()   ":" : '')   zElements[zIndex].getName();
    if (zCheckName == nameToFind) {
      var zOutput = zElements[zIndex].getValue()
      Logger.log(zOutput);
      return zOutput;
    }
  })
}

CodePudding user response:

In your situation, the structure of XML data has already been known. When this situation is used, how about the following modification?

Modified script:

From:

let document = XmlService.parse(url);

let root = document.getRootElement();

let ns = root.getNamespace();
let entries = root.getChildren("entry", ns);
let entry, ftpJobInfo, ftpJobInfoDetails;

let id, title, asciiFormat;
for (let counter = 0; counter < entries.length; counter  ) {
  entry = entries[counter];

  ftpJobInfo = entry.getContent(8);
  ftpJobInfoDetails = ftpJobInfo.getChildren('tes:ftpjob');
  
  asciiFormat = ftpJobInfoDetails.getContent(0).getValue();

  Logger.log("prayer hands");
}

To:

const search = "asciiformat"; // This is from your question.
const root = XmlService.parse(url).getRootElement();
const ns = XmlService.getNamespace("tes", "http://www.software.com");
const value = root.getChild("entry", root.getNamespace()).getChild("ftpjob", ns).getChild(search, ns).getValue();
console.log(value) // N is returned.
  • When url is your showing XML data, when you run this modified script, you can see the value of N in the log.

Note:

  • In your showing script, I think that ftpJobInfo = entry.getContent(8); has no value. By this, I think that this might be the reason for your issue. And, from your showing XML data, the tag of entry is only one. So, in this case, I thought that getChildren is not required to be used. And, from your XML data, it seems that the namespace of ftpjob is tes. This can be used for retrieving the tag of asciiformat. So, I proposed the above modification.

  • As another direction, when this benchmark is considered, I thought that the following sample script might be able to be also used.

      const res = url.match(/<tes:asciiformat>(. )<\/tes:asciiformat>/);
      console.log(res && res[1]); // N is returned.
    

Reference:

  • Related