'Looking to create an XML feed from a Google Sheet Table

Looking for some help please, I've been tinkering with the code now most of the day and im stuck, and this seems about the best solution I have found so far.

I'm trying to create a script that will create an XML file from a Google Sheet table.

The example sheet is like this >> https://docs.google.com/spreadsheets/d/1tSWMiXBRyhFcAmwpH5tEJiyHCB5vjlGwuHFv865-85A/edit?usp=sharing

I came across this code example Google Script Export Spreadsheet to XML File and it's 90% what I need and I got it working via publishing as a web app here >> https://script.google.com/macros/s/AKfycbxVcUi6dXw0D1CWfZTlwf94gAT9QjqpG__-SaCIHVFVPzftndU/exec?id=1tSWMiXBRyhFcAmwpH5tEJiyHCB5vjlGwuHFv865-85A

I'm now stuck on getting it to loop over the headers and values as the XML needs to be formatted.

Im also encountering some of the values have attributes, so im finding it tricky to add the xml:lang="x-default" in the below example 10AM : 6PM

Here is an example of what im trying to create

<store store-id="F123">
    <name>Store One</name>
    <address1>123 Street</address1>
    <address2></address2>
    <city>London</city>
    <postal-code>L67 9JF</postal-code>
    <phone>123 456</phone>
    <store-hours xml:lang="x-default">10AM | 6PM</store-hours>
    <custom-attribute attribute-id="freeTextTitle" xml:lang="x-default">Store Description Title</custom-attribute>
    <custom-attribute attribute-id="v3_store_open_hours_0" xml:lang="x-default">11 AM|7 PM</custom-attribute>
</store>
<store store-id="G456">
    <name>Store Two</name>
    <address1>123 Street</address1>
    <address2></address2>
    <city>Manchester</city>
    <postal-code>L67 9DS</postal-code>
    <phone>123 456</phone>
    <store-hours xml:lang="x-default">10AM | 6PM</store-hours>
    <custom-attribute attribute-id="freeTextTitle" xml:lang="x-default">Store Description Title</custom-attribute>
    <custom-attribute attribute-id="v3_store_open_hours_0" xml:lang="x-default">11 AM|7 PM</custom-attribute>
</store>

Many thanks

** Added more context

Thanks, Both, I'm actually stuck on the JavaScript map() function in the doIt function trying to map the headers & attributes

function doGet(e) {
  var content;
  try {
    content = doIt(e);
  } catch(err) {
    content = '<error>' + (err.message || err) + '</error>';
  }
  return ContentService.createTextOutput(content).setMimeType(ContentService.MimeType.XML);
}

function doIt(e) {
  if (!e) throw 'you cannot run/debug this directly\nyou have to either call the url or mock a call';
  if (!e.parameter.id) throw '"id" parameter not informed. Please provide a spreadsheet id.';

  var values = SpreadsheetApp.openById(e.parameter.id).getSheets()[0].getRange('A1:J4').getValues();
  return '<sheet>' + values.map(function(row, i) {
    return '<row>' + row.map(function(v) {
      return '<cell>' + v + '</cell>';
    }).join('') + '</row>';
  }).join('') + '</sheet>';
}

values grabs all of the values in the range, but I'm lost a little trying to break values down.

I did some reading on the map() function so ill have another go



Solution 1:[1]

As a simple modification, how about the following modification?

In your script, <sheet>, <row> and <cell> tags are used. But it seems that these are not included in your expected result. When you want to use the header row of 1st row as the each tag, it is required to use them in the script. When your script is modified it becomes as follows.

Modified script:

In this modification, your doIt() was modified.

function doIt(e) {
  if (!e) throw 'you cannot run/debug this directly\nyou have to either call the url or mock a call';
  if (!e.parameter.id) throw '"id" parameter not informed. Please provide a spreadsheet id.';
  var values = SpreadsheetApp.openById(e.parameter.id).getSheets()[0].getRange('A1:J4').getValues();

  // I modified below script.
  var header = values.shift();
  return values.reduce((s, r) => {
    r.forEach((c, j, a) => {
      s += j == 0 ? `<${header[j]}="${c}">` : `<${header[j]}>${c}<\/${header[j].split(" ")[0]}>`;
      if (j == a.length - 1) s += `<\/${header[0].split(" ")[0]}>`;
    });
    return s;
  }, "");
}

Result:

Whe above modified script is run, the following result is obtained.

<store store-id="F123">
   <name>Store One</name>
   <address1>123 Street</address1>
   <address2 />
   <city>London</city>
   <postal-code>L67 9JF</postal-code>
   <phone>123 456</phone>
   <store-hours xml:lang="x-default">10AM | 6PM</store-hours>
   <custom-attribute attribute-id="freeTextTitle" xml:lang="x-default">Store Description Title</custom-attribute>
   <custom-attribute attribute-id="v3_store_open_hours_0" xml:lang="x-default">11 AM|7 PM</custom-attribute>
</store>
<store store-id="G456">
   <name>Store Two</name>
   <address1>124 Street</address1>
   <address2 />
   <city>Manchester</city>
   <postal-code>L67 9DS</postal-code>
   <phone>124 111</phone>
   <store-hours xml:lang="x-default">9AM | 5PM</store-hours>
   <custom-attribute attribute-id="freeTextTitle" xml:lang="x-default">Store Description Title</custom-attribute>
   <custom-attribute attribute-id="v3_store_open_hours_0" xml:lang="x-default">12 AM|7 PM</custom-attribute>
</store>
<store store-id="J542">
   <name>Store Three</name>
   <address1>777 High Street</address1>
   <address2 />
   <city>Leeds</city>
   <postal-code>L7 9GG</postal-code>
   <phone>555 222</phone>
   <store-hours xml:lang="x-default">10AM | 6PM</store-hours>
   <custom-attribute attribute-id="freeTextTitle" xml:lang="x-default">Store Description Title</custom-attribute>
   <custom-attribute attribute-id="v3_store_open_hours_0" xml:lang="x-default">12 AM|7 PM</custom-attribute>
</store>

Note:

  • When you use above result as the xml data, for example, I think that it is required to enclose like <contents>{above results}</contents>. Please be careful this. So if you want to export the valid XML data, please use the following script. In this case, <contents> is a sample tag.

      function doIt(e) {
        if (!e) throw 'you cannot run/debug this directly\nyou have to either call the url or mock a call';
        if (!e.parameter.id) throw '"id" parameter not informed. Please provide a spreadsheet id.';
        var values = SpreadsheetApp.openById(e.parameter.id).getSheets()[0].getRange('A1:J4').getValues();
    
        // I modified below script.
        var header = values.shift();
        var data = values.reduce((s, r) => {
          r.forEach((c, j, a) => {
            s += j == 0 ? `<${header[j]}="${c}">` : `<${header[j]}>${c}<\/${header[j].split(" ")[0]}>`;
            if (j == a.length - 1) s += `<\/${header[0].split(" ")[0]}>`;
          });
          return s;
        }, "");
    
        return XmlService.getPrettyFormat().format(XmlService.parse(`<contents>${data}$</contents>`));
      }
    
  • When you modified the script of Web Apps, please redeploy the Web Apps as new version. By this, the latest script is reflected to the Web Apps. Please be careful this.

  • Please use this script with enabling V8.

References:

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1