Issue while converting xml to json, and then back to xml - node.js

I'm facing an issue when converting XML to JSON and then JSON to XML.
I'm using this xml-js npm library.
data.xml
<?xml version="1.0" encoding="UTF-8"?>
<property name="DESCRIPTION" value="Group 1 & , " , <"/>
test.js
const convert = require('xml-js');
const fs = require('fs');
const xml = fs.readFileSync('data.xml',).toString();
const result = convert.xml2json(xml, {compact: false, spaces: 2});
const output = convert.json2xml(result,{compact: false, spaces: 2});
fs.writeFileSync('output.xml', output);
output.xml
<?xml version="1.0" encoding="UTF-8"?>
<property name="DESCRIPTION" value="Group 1 & , " , <"/>
I was expecting to get the below result in output.xml
expected output.xml
<?xml version="1.0" encoding="UTF-8"?>
<property name="DESCRIPTION" value="Group 1 & , " , <"/>
Any idea why I'm getting that data on converting from JSON to XML.

I was able to get the problem fixed by using attributeValueFn.
const convert = require('xml-js');
const fs = require('fs');
const xml = fs.readFileSync('data.xml',).toString();
const result = convert.xml2json(xml, {compact: false, spaces: 2,
attributeValueFn: function(value) {
return value.replace(/&/g,'AMPERSAND_CHARACTER');
}
});
console.log('JSON : ' + result);
const output = convert.json2xml(result,{compact: false, spaces: 2,
attributeValueFn: function(value) {
return value.replace(/AMPERSAND_CHARACTER/g, '&')
.replace(/</g, '<');
}
});
fs.writeFileSync('output.xml', output);

Related

cheeriojs select tags that are not inside another specified tag

consider the following code
const cheerio = require('cheerio');
const xml = `<foo id="1" a="blah"><updateHistory><foo id="1" a="blah"/><foo id="1" a="blah"/><foo id="1" a="blah"/><foo id="1" a="blah"/></updateHistory>Figs. 1-9</foo>`;
const $ = cheerio.load(xml, { normalizeWhitespace: true, xmlMode: true }, false);
const elements = $('foo');
const num = elements.length;
if (num) console.log(num); // prints 5 because there are 5 'foo' tags
But I don't want anything from inside <updateHistory></updateHistory> tags. In other words, I want element to contain only the first <foo> tag and num to be 1. How can I do that?
update: so, turns out I can do something like this
for (let i = 0, j = elements.length; i < j; i++) {
if (elements[i].parent.name !== 'updateHistory') {
// this is the tag I want
}
}
is there a better way?

how to convert json to xml with saxonjs?

I need to convert a json to xml with saxonjs, I don't know how to match keys to xml nodes, I'm looking for some examples for none of them work for me, this is my code
const issue = {
id: 1,
details: {
type: 'urgent',
description: 'Description of issue comes here',
date: '2021-12-12',
}
};
saxonJS.transform({
stylesheetLocation: './issue.sef.json',
sourceType: 'json',
sourceText: issue,
destination: 'serialized',
}, 'async').then(data => {
fs.open('output.xml', 'w', function(err, fd) {
fs.write(fd, data.principalResult, (err2, bytes) => {
if(err2) {
console.log(err2);
}
});
});
res.status(200).send('Ok');
})
.catch(err => {
console.log(err);
res.status(500).send('error');
});
And this is the output I'm trying to achieve
<xml>
<issue id="1">
<description>
<![CDATA[
Description of issue comes here
]]>
</description>
<type>urgent</type>
<date>2021-12-12</date>
</issue>
</xml>
Can you please help me with the xslt template?
Your shown input is a JavaScript object, it is not JSON in the strict syntax rules of the JSON spec.
So I would think it is better to use JSON.stringify to create JSON and pass that to the XPath 3.1 function parse-json to create JSON or to make use of the Saxon-JS 2.3 feature to take JSON text, just make sure you have correctly JSON.stringifyed that object.
As for a sample XSLT, that looks easy, for readability of the XSLT the below sample just uses a JavaScript string with the XSLT source code and runs it through the Saxon API:
const SaxonJS = require("saxon-js");
const issue = {
id: 1,
details: {
type: 'urgent',
description: 'Description of issue comes here',
date: '2021-12-12',
}
};
const xslt = `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" expand-text="yes">
<xsl:output indent="yes" cdata-section-elements="description"/>
<xsl:template match=".">
<xml>
<issue id="{?id}">
<description>{?details?description}</description>
<type>{?details?type}</type>
<date>{?details?date}</date>
</issue>
</xml>
</xsl:template>
</xsl:stylesheet>`;
const result = SaxonJS.XPath.evaluate(`transform(map {
'stylesheet-text' : $xslt,
'initial-match-selection' : parse-json($json),
'delivery-format' : 'serialized'
})?output`,
[],
{ params :
{
json : JSON.stringify(issue),
xslt : xslt
}
});
Of course, in the end you can first compile the XSLT to SEF/JSON and then run it as you tried.
To give you an example XSLT that uses two different templates and apply-templates, the following, instead of processing the nested object/map with inline code pushes processing it to a different template:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" expand-text="yes" xmlns:map="http://www.w3.org/2005/xpath-functions/map" exclude-result-prefixes="#all">
<xsl:output indent="yes" cdata-section-elements="description"/>
<xsl:template match=".[. instance of map(*) and map:contains(., 'id')]">
<xml>
<issue id="{?id}">
<xsl:apply-templates select="?details"/>
</issue>
</xml>
</xsl:template>
<xsl:template match=".[. instance of map(*) and map:contains(., 'description')]">
<description>{?description}</description>
<type>{?type}</type>
<date>{?date}</date>
</xsl:template>
</xsl:stylesheet>

How do I use Promises in Node.js and interacting with Smartsheet

Pls be gentle!. Newbe having a go. I am using node.js (for the first time)
I have a large (10,000 line) XML file that I need to rip through and generate about 900 rows across 50 columns in smartsheets. I need to retain the order that comes from the XML file (important).
I have the code working so that it reads the xml file, and can write to the rows/columns but obviously I am generating the update inputs faster than smartsheet can handle, so I tried promises. I just can't seem to crack it and would appreciate any help you can give. The system generates all of the records but chokes as it tries to write them to smasrtsheet AND the order is all stuffed up.
Happy to be told I am barking up the wrong tree if you can suggest a better way.
Thanks in advance.
Jerji.
3 blocks of code:
testing.js file (the script itself)
testing.xml (a VERY cut down version of the xml file)
testing.xsd (the xml schema file).
testing.js
// Initialize the client
var client = require('smartsheet');
var smartsheetClient = client.createClient({
accessToken: '<insert your own access token before you run>', // use your access code
logLevel: 'info'
});
var fs= require('fs'),
xml2js = require('xml2js');
const parser = new xml2js.Parser();
const xpath = require('xpath'),
dom = require('xmldom').DOMParser;
fs.readFile('testing.xml',function(err , data){
var doc = new dom().parseFromString(data.toString(), 'text/xml');
var select = xpath.useNamespaces('testing.xsd');
if(err){
//display error
}
else{
for (var i=0; i < 10 ; i++ ){
var rowcounter=i+1;
var identifier = select('//Identifier/text()', doc)[i].nodeValue;
var revision = select('//Revision/text()', doc)[i].nodeValue;
var updated = select('//Updated/text()', doc)[i].nodeValue;
var description = select('//Description/text()', doc)[i].nodeValue;
var row = [{
"toBottom": true,
"cells": [
// Note that the column Ids here are samples. Adjust as required.
{"columnId": 2461535086897028, "value": rowcounter, "strict": false},
{"columnId": 6965134714267524, "value": identifier, "strict": false},
{"columnId": 1335635180054404, "value": description, "strict": false},
{"columnId": 7457715923511172, "value": revision, "strict": false},
{"columnId": 1828216389298052, "value": updated, "strict": false},
{"columnId": 7176240946800516, "value": 'Marker', "strict": false},
]
}];
writeRow(row);
sleep();
}
}
);
// DUMMY SLEEP FUNCTION
var sleep = function () {
let now = Date.now(), end = now + 3000;
while (now < end) { now = Date.now(); }
};
// Function to write row to sheet.
function writeRow(row) {
var options = {
sheetId: <insert your own sheet ID here>, //note real sheet id needed
body: row
};
return new Promise((resolve, reject) => {
try {
let obj = smartsheetClient.sheets.addRows(options);
resolve(obj);
} catch (err) {
reject(err);
};
});
let myPromise = smartsheetClient.sheets.addRows(options);
myPromise.then(data => {
console.log("Line Written :", data);
}).catch(err => {
console.log(err);
});
}
testing.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- June 2021 -->
<DOCUMENT>
<ITEM>
<Identifier>2021-91</Identifier>
<Revision>5</Revision>
<Updated>Oct-20</Updated>
<Description>Item Description 1.</Description>
</ITEM>
<ITEM>
<Identifier>2021-97</Identifier>
<Revision>1</Revision>
<Updated>Oct-20</Updated>
<Description>Item description 2.</Description>
</ITEM>
<ITEM>
<Identifier>2020-14</Identifier>
<Revision>0</Revision>
<Updated>Oct-20</Updated>
<Description>Item description 3.</Description>
</ITEM>
<ITEM>
<Identifier>2019-44</Identifier>
<Revision>2</Revision>
<Updated>Oct-20</Updated>
<Description>Item description 4.</Description>
</ITEM>
<ITEM>
<Identifier>2021-06</Identifier>
<Revision>2</Revision>
<Updated>Oct-20</Updated>
<Description>Item description 5.</Description>
</ITEM>
<ITEM>
<Identifier>2019-13</Identifier>
<Revision>2</Revision>
<Updated>Oct-20</Updated>
<Description>Item description 6.</Description>
</ITEM>
<ITEM>
<Identifier>2020-03</Identifier>
<Revision>2</Revision>
<Updated>Oct-20</Updated>
<Description>Item description 7.</Description>
</ITEM>
<ITEM>
<Identifier>2021-19</Identifier>
<Revision>2</Revision>
<Updated>Oct-20</Updated>
<Description>Item description 8.</Description>
</ITEM>
<ITEM>
<Identifier>2019-56</Identifier>
<Revision>0</Revision>
<Updated>Oct-20</Updated>
<Description>Item description 9.</Description>
</ITEM>
<ITEM>
<Identifier>2020-15</Identifier>
<Revision>3</Revision>
<Updated>Oct-20</Updated>
<Description>Item description 10.</Description>
</ITEM>
</DOCUMENT><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
testing.xsd
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="DOCUMENT">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="ITEM">
<xs:complexType>
<xs:sequence>
<xs:element name="Identifier" type="xs:unsignedShort" />
<xs:element name="Revision" type="xs:unsignedByte" />
<xs:element name="Updated" type="xs:string" />
<xs:element name="Description" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Any advice would be most appreciated.
Problem
Promise is asynchronous API which has it's own independant context.
It has 3 statuses, which are pending, resolve and reject.
When the Promise is resolved, it calls .then(cb) and in case it failed, which is reject, it calls .catch(cb) internally.
first of all, you didn't wait for Promise to be done either way that it be resolved or rejected.
You should use .then() and .catch() to wait for it be done. Maybe async/await API will be even better for doing that.
Solution
Standard way to implement delay function
const delay = ms => new Promise(resolve => setTimeout(() => resolve(), ms)
writeRow()
function writeRow(row) {//Return your promise and let it be controlled outside of function
var options = {
sheetId: < insert your own sheet ID here > , //note real sheet id needed
body: row
};
return new Promise((resolve, reject) => {
try {
let obj = smartsheetClient.sheets.addRows(options);
resolve(obj);
} catch (err) {
reject(err);
};
});
}
runner : wrap your logic into async function to use await API.
(or you can just use traditional way to control promise with .then(), .catch())
async function runner(){
...your scripts...
for (var i = 0; i < 10; i++) {
...
await writeRow(row)
await sleep(3000)//write row and wait for 3 seconds
...
}
}
...your scripts...
}
runner()
You have to know that
Smartsheet API doesn't support simultaneous calls for a given token.
Smartsheet API
can insert/update multiple rows with a single call like 100 rows
with a single call.
So change the strucure of your solution :
Read your source and build the list of smartsheet rows to insert/update.
Split that list of rows per 100 rows blocks.
Insert/update per 100 rows

How to modify and XML on the fly?

I am reading a XML file and send it to a REST API. However before sending it I would like to modify some values.
This is how I send the data:
data = await readFile(path.resolve(__dirname, file), 'utf8');
const config = {
headers: {
'Content-Type': 'text/plain',
'Content-Length': data.length,
},
};
result = await axios.post(
'https://someRestapi.com/',
data, config,
);
And I want to change the name Simon to Zimon in the author name for example.
<?xml version="1.0" encoding="UTF-8"?>
<Document schemaVersion="12"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Author>
<Name>
<First>Simon</First>
<Second>SomeName</Second>
</Name>
</Author>
</Document>
Is there an easy solution to do this?
Had to do something similar recently and ended up using fast-xml-parser. Applied to your case, you could do:
const xmlString = `<?xml version="1.0" encoding="UTF-8"?>
<Document schemaVersion="12"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Author>
<Name>
<First>Simon</First>
<Second>SomeName</Second>
</Name>
</Author>
</Document>`;
const xmlToJsonParser = require('fast-xml-parser');
const J2xParser = require("fast-xml-parser").j2xParser;
const tObj = xmlToJsonParser.getTraversalObj(xmlString,{ignoreAttributes :false});
const jsonObj = xmlToJsonParser.convertToJson(tObj,{ignoreAttributes :false});
jsonObj.Document.Author.Name.First = "Zimon";
let result = new J2xParser({format:true, ignoreAttributes :false}).parse(jsonObj);
result = `<?xml version="1.0" encoding="UTF-8"?>\n${result}`;
console.log(result);
This will print:
<?xml version="1.0" encoding="UTF-8"?>
<Document schemaVersion="12" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Author>
<Name>
<First>Zimon</First>
<Second>SomeName</Second>
</Name>
</Author>
</Document>

XML malformed fix using nodejs

I have an xml like this but i want to convert the xml into proper xml by node.js can anyone help me in this
<?xml version = 1.0 encoding = utf-8?>
<!-- xslplane.1.xml -->
<?xml-stylesheet type = text/xsl href = xslplane.1.xsl ?>
<plane>
<year> 1977 </year>
<make> Cessna </make>
<model> Skyhawk </model>
<color> Light blue and white </color>
</plane>
I want XML like this with all the possible quotations
<?xml version = "1.0" encoding = "utf-8"?>
<!-- xslplane.1.xml -->
<?xml-stylesheet type = "text/xsl" href = "xslplane.1.xsl" ?>
<plane>
<year> 1977 </year>
<make> Cessna </make>
<model> Skyhawk </model>
<color> Light blue and white </color>
</plane>
You can try using xml2js to convert your malformed xml to JSON and use it again to convert to xml. Try the following code.
let xml2js = require('xml2js');
var xml = "<hi>Hello xml2js!</hi>";
xml2js.parseString(xml, function (err, result) {
console.dir(result);
var builder = new xml2js.Builder();
var xml = builder.buildObject(result);
console.log(xml)
});
Update : Direct File read
var parser = new xml2js.Parser();
fs.readFile(__dirname + '/foo.xml', function(err, data) {
parser.parseString(data, function (err, result) {
console.dir(result);
console.log('Done');
var builder = new xml2js.Builder();
var xml = builder.buildObject(result);
console.log(xml)
});
});

Resources