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

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

Related

Issue while converting xml to json, and then back to xml

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);

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 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>

How to append element after particular element in nodejs

My .XMl is
<?xml version="1.0" encoding="utf-8"?>
<data>
<shell id="TX-alg1-title">Lesson 2.1</shell>
<options id="parent">
<option id="TX-alg1">TX16E_ISE_0005</option>
<option id="TX-alg1-CID">9780353053</option>
</options>
</data>
I wanted to add another shell tag after shell tag previously present in xml.
Expected O/p
<?xml version="1.0" encoding="utf-8"?>
<data>
<shell id="TX-alg1-title">Lesson 2.1</shell>
<shell id="CA-int1-title">Lesson 2.1</shell>
<options id="parent">
<option id="TX-alg1">TX16E_ISE_0005</option>
<option id="TX-alg1-CID">9780353053</option>
</options>
</data>
I am reading my .XML file in app.js as
fs.readFile( './dlo_custom_en-US.xml', function(err, data) {
});
Please advise how to append shell tag after already present shell tag in nodejs.
question is almost similar Regex match text between tags
node is noting except javascript at server side. almost all programming languages have regexp to find patterns. here is simplest solution but you should to study more about regexp
fs.readFile( './dlo_custom_en-US.xml', function(err, data) {
data.replace(/\<shell(.)*\<\/shell>/, "new replacement here")
});
***** updated *****
from JavaScript: How can I insert a string at a specific index
var fs =require('fs')
String.prototype.splice = function(idx, rem, str) {
return this.slice(0, idx) + str + this.slice(idx + Math.abs(rem));
};
fs.readFile( './dlo_custom_en-US.xml',"utf8", function(err, data) {
if(err) return
var index = data.lastIndexOf('</shell>')+8
console.log(index)
var data= data.splice(index,0, 'newwwwwwwwwww')
console.log(data)
fs.writeFile('./new.xml',data,(err, done)=>{
if(err) return
console.log(err, done)
})
});

How to get attribute value of node in xml using xpath in javascript

Hi I am trying to get the user profile properties from sharepoint on client side with javascript.But I am not getting the value of nodes in xml.
How to get them. the xml will look like as:
How to get attribute value of node in xml using xpath
Here I want to get the value which is between <name> tags <Name>AccountName</Name> and between Name tags
want to get the value = abc what will be the xpath expression
Please help
<?xml version="1.0" encoding="utf-8" ?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetUserProfileByNameResponse xmlns="http://microsoft.com/webservices/SharePointPortalServer/UserProfileService">
<GetUserProfileByNameResult>
<Pro pertyData>
<IsPrivacyChanged>false</IsPrivacyChanged>
<IsValueChanged>false</IsValueChanged>
<Name>UserProfile_GUID</N ame>
<Privacy>NotSet</Privacy>
<Values>
<ValueData>
<Value xmlns:q1="http://microsoft.com/wsdl/types/" xsi:type="q1:guid">8ed84415-7330-4857-a7d2- d797d71c439f
</Value>
</ValueData>
</Values>
</PropertyData>
<PropertyData>
<IsPrivacyChanged>false</IsPrivacyChanged>
<Is ValueChanged>false</IsValueChanged>
<Name>AccountName</Name>
<Privacy>NotSet</Privacy>
<Values>
<ValueData>
<Value xsi:type="xsd:string">abc
</Value>
</ValueData>
</Values>
</PropertyData>
</GetUserProfileByNameResult>
</GetUserProfileByNameResponse>
</ soap:Body>
</soap:Envelope>
Please help me in this.
var propertyData = $(responseXML).find("PropertyData").filter(function(e){
return $(this).find("Name").text() == "AccountName";
});
var value = propertyData.length > 0 ? propertyData.find('Value').text() : '';
Since you are trying to retrieve user profile via SharePoint Web Services I would recommend to utilize SPServices library, it hides (almost)all the intricacies when working with SharePoint Web Services from JavaScript. The following example demonstrates how to retrieve user profile using GetUserProfileByName method and process the results:
function getUserProfile(accountName,completeFn) {
var userInfo = {};
$().SPServices({
AccountName: accountName,
operation: 'GetUserProfileByName',
completefunc: function (xData, Status) {
$(xData.responseXML).SPFilterNode("PropertyData").each(function() {
userInfo[$(this).find("Name").text()] = $(this).find("Value").text();
});
completeFn(userInfo);
}
});
}
var loginName = 'i:0#.f|membership|username#contoso.onmicrosoft.com';
getUserProfile(loginName,function(info){
console.log(info);
});
You have to traverse through the xml nodes returned from the SPServices. I have written a function for getting the desired user profile property.
function getUPValue(x, p) {
var thisValue = $(x).SPFilterNode("PropertyData").filter(function() {
return $(this).find("Name").text() == p;
}).find("Values").text();
return thisValue;
}
Further to query the user property you just need to call like below,
getUPValue(xData.responseXML, "WorkEmail");
These article provides a detail overview of it over here

Resources