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>
Related
I am using Saxon-JS to convert an XML file to an XSL-fo. I follow the documentation and leverage the xslt3 command to first compile an XSL file to a sef file such as:
"compile": "xslt3 -xsl:./src/styles/sample.xsl -export:sef/sample.sef.json -t -nogo -ns:##html5"
However, this causes an issue that the server needs to reload and compile the stylesheet whenever there is a change in stylesheet.
My question is: how to prevent the server from reloading when a change is made? Thank you in advance.
Using SaxonJS.XPath.evaluate('transform(...)', ..., { ... }) it is also possible to run XSLT directly. Whether that works and performs for you is something you would need to test, I don't know how complex your XSLT is, whether it has includes or imports, how large the input XML and how large the resulting XSL-FO is. But if the target format is XSL-FO and not HTML in the browser (although that way I don't understand the use of -ns:##html5) you might not need the interactive Saxon extensions that the xslt3 compilation supports and adds.
Simple example:
const SaxonJS = require("saxon-js")
const xml = `<root>
<item>a</item>
<item>b</item>
</root>`;
const xslt = `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
exclude-result-prefixes="xs"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="/" name="xsl:initial-template">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="sample">
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="sample">
<fo:flow flow-name="xsl-region-body">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="root">
<fo:list-block>
<xsl:apply-templates/>
</fo:list-block>
</xsl:template>
<xsl:template match="item">
<fo:list-item>
<fo:list-item-label end-indent="label-end()">
<fo:block>
<xsl:number format="1."/>
</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="body-start()">
<fo:block>
<xsl:apply-templates/>
</fo:block>
</fo:list-item-body>
</fo:list-item>
</xsl:template>
</xsl:stylesheet>`;
const result = SaxonJS.XPath.evaluate(
`transform(
map {
'stylesheet-text' : $xslt,
'source-node' : parse-xml($xml),
'delivery-format' : 'serialized'
}
)?output`,
[],
{ params : { 'xml' : xml, 'xslt' : xslt } }
);
console.log(result);
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
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
Camel version 2.14 Smooks version 1.5.1
I got a message which i want to split and transform, but i need the id from the parent. So I thought about using Smooks, splitting the message, transforming and send each output to a queue. Which will be using freemarker template for the transform.
<!-- Message -->
<data>
<id>123</id> <!-- This is needed in both portal messages -->
<portals>
<portal id="1" />
<portal id="2" />
</portals
</data>
<!-- Msg 1 -->
<portal dataId="123">
<id>1</id>
<portal>
<!-- Msg 2 -->
<portal dataId="123">
<id>2</id>
<portal>
There are plenty of examples. But for example the camel examples does not work, due to "java.lang.ClassNotFoundException: org.apache.camel.component.ResourceBasedComponent" which is a known issue.
An alternative would be using groovy for transformation?
So, how could this easiest be solved?
I don't know about smooks, but you can combine the XSLT transformer with a XPATH splitter to do this.
First, transform the data into the blocks that should make up each message. Do it using XSLT, groovy or whatever you feel comfortable with. Here is a simple stylesheet, to be put into src/main/resources (or any classpath location).
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<portals>
<xsl:variable name="dataId" select="/data/id"/>
<xsl:for-each select="/data/portals/portal">
<portal dataId="$dataId">
<xsl:attribute name="dataId">
<xsl:value-of select="/data/id"/>
</xsl:attribute>
<id><xsl:value-of select="#id"/></id>
</portal>
</xsl:for-each>
</portals>
</xsl:template>
The Camel route: First the transform, then splitter. The "to" can be whatever, like a seda/direct for further processing or the target protocol.
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file:data"/>
<to uri="xslt:transform.xslt"/>
<split>
<xpath>portals/portal</xpath>
<to uri="log:foo.bar?level=INFO"/>
</split>
</route>
In groovy it can be done this way:
import groovy.util.XmlSlurper
import groovy.xml.MarkupBuilder
def xml = """
<data>
<id>123</id>
<portals>
<portal id="1" />
<portal id="2" />
</portals>
</data>
"""
def slurped = new XmlSlurper().parseText(xml)
def msgId = slurped.id
def portalIds = slurped.portals.portal.#id*.text()
def portalXmls = portalIds.collect { portalId ->
writer = new StringWriter()
portalXml = new MarkupBuilder(writer)
portalXml.doubleQuotes = true
portalXml.portal(dataId: msgId) {
id(portalId)
}
writer
}.each { println it.toString() }
null
I need to set Ship Date to current date, when I'm trying to confirm shipment. But I don't know where it to set. In documentation example don't have this attribute.
My XML code:
<?xml version="1.0" encoding="UTF-8"?>
<AmazonEnvelope xsi:noNamespaceSchemaLocation="amzn-envelope.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Header>
<DocumentVersion>1.02</DocumentVersion>
<MerchantIdentifier>XXXXXXXXXXXX</MerchantIdentifier>
</Header>
<MessageType>OrderFulfillment</MessageType>
<Message>
<MessageID>1</MessageID>
<OperationType>Update</OperationType>
<OrderFulfillment>
<AmazonOrderID>103-8280673-5282661</AmazonOrderID>
<FulfillmentDate>2015-01-22T06:30:00</FulfillmentDate>
<FulfillmentData>
<CarrierName>USPS</CarrierName>
<ShipperTrackingNumber>1234567890</ShipperTrackingNumber>
</FulfillmentData>
<Item>
<AmazonOrderItemCode>18531427322146</AmazonOrderItemCode>
<Quantity>1</Quantity>
</Item>
<Item>
<AmazonOrderItemCode>54419133385610</AmazonOrderItemCode>
<Quantity>1</Quantity>
</Item>
</OrderFulfillment>
</Message>
</AmazonEnvelope>
I'm trying to set Ship Date into <OrderFulfillment>, <FulfillmentData> - no result.
I'm trying to write it like <ShipDate>, <ShippingDate>, <ShippedDate> - no result.
Error returned:
<Result>
<MessageID>1</MessageID>
<ResultCode>Error</ResultCode>
<ResultMessageCode>25</ResultMessageCode>
<ResultDescription>We are unable to process the XML feed because one or more items are invalid. Please re-submit the feed.</ResultDescription>
</Result>
Maybe someone knows how to set Ship Date?
The above should fix it. And also, FulfillmentDate can also be the following format:
<FulfillmentDate>2015-01-22T06:30:00Z</FulfillmentDate>
And remove this tag:
<OperationType>Update</OperationType>
I am using same XML feed but without <OperationType>Update</OperationType>
and <item> tag because you don't need tag if you are shipping whole order.
and then I am doing following PHP code
$feed = trim($feed);
$marketplaceIdArray = array("Id" => array($MARKETPLACE_ID));
$feedHandle = #fopen('php://temp', 'rw+');
fwrite($feedHandle, $feed);
rewind($feedHandle); //Sets file pointer at beginning of file
$parameters = array(
'Merchant' => $MERCHANT_ID,
'MarketplaceIdList' => $marketplaceIdArray,
'FeedType' => '_POST_ORDER_FULFILLMENT_DATA_',
'FeedContent' => $feedHandle,
'PurgeAndReplace' => false, //Leave this PurgeAndReplace to false so that it want replace whole product in amazon inventory
'ContentMd5' => base64_encode(md5(stream_get_contents($feedHandle), true))
);
rewind($feedHandle);
$request = new MarketplaceWebService_Model_SubmitFeedRequest($parameters);
Not entirely positive on this but something that our feed contains is
<FulfillmentDate>2015-01-22T06:30:00+00:00</FulfillmentDate>
Instead of
<FulfillmentDate>2015-01-22T06:30:00</FulfillmentDate>
And that's the only thing that is not exactly the same as ours.
EDIT 1: I lied, there is a tag you have that I do not.
<OperationType>Update</OperationType>