Support #5188
closed
Asynchronous JavaScript Function Call with Map Return
0%
Description
I am prototyping a transform using SaxonJS 2.3.0 under NodeJS. The intent is to have the XSL issue a MongoDB query and receive the JSON result as an XDM map that can be used in the XSL like $map(‘name’) or map:get($map, ‘name’). I have been around and around exploring different techniques to get javascript (js) to return a usable map to the transform. I have been successful in passing JSON to the transform with:
const jsonResult = { id: 1, name: 'name1' }
const jsonMap = SaxonJS.getResource({ text: JSON.stringify(jsonResult), type: 'json' }).then( doc => { return doc; }, rej => { debugger; } );
Promise.all([…, jsonMap]).then((values) => { var […, jsonMap] = values; var htmlOutput = SaxonJS.transform({ stylesheetParams: { … jsonMap: jsonMap },
But I can’t figure out how to access the promise(d) result in the transform with:
class class1 { constructor() { } getJasonMap() {
var json = {
id: 1,
name: 'name1'
}
var jsonMap = SaxonJS.getResource({
text: JSON.stringify(json),
type: 'json'
}).then(
map => { return map; },
rej => { debugger; }
);
return jsonMap;
} // getJasonMap
} // class1
var instance = new class1();
var htmlOutput = SaxonJS.transform({ stylesheetParams: { … instance: instance },
<xsl:variable name="resultMap" as="map(*)" select="ixsl:call($instance, 'getJasonMap', [])" />
If I skip the SaxonJS.getResource() and just return the straight json, it sort of works, but things get sketchy where $map(‘id’) works in the XSL and map:get($map, ‘id’) does not work or visa versa. If I am making choices about what technique to use, it needs to be something solid that seems to be supported rather than something that works one way and not the other.
I have also toyed with creating my own map in the js using “var map = new SaxonJS.XDMMAP()” and adding items with:
var key1 = new SaxonJS.XdmAtomicValue('id1'); var value1 = new SaxonJS.XdmAtomicValue('value1'); map.put(key1, value1);
--- or ---
map.inSituPut(key1, value1);
but even though the $map does not generate any errors when used in the transform, I cannot get any values back (i.e. $map(‘id’) returns nothing).
Where should I go with this attempt to get maps returned from js functions in a reliable and support way?
BTW, the SEF was generated in the test program with SaxonJS.compile()
Updated by Michael Kay over 1 year ago
There seem to be two separate questions here: one is related to the representation of maps, the other to handling of asynchronous callbacks that return a Promise.
Debbie Lockett can probably provide a better answer than I can on the question of map representations. A Javascript object, as you've observed, can be treated in some ways a bit like a map, but it doesn't have all the functionality of XDM maps, and returning a "real" XDM map would be cleaner.
As regards asynchrony, we're aware that the product needs a major revamp in this area. I don't know if you've seen my Balisage paper sketching out ideas (not yet implemented, sadly) for how to tackle this: see https://www.balisage.net/Proceedings/vol25/html/Kay01/BalisageVol25-Kay01.html
In the meantime the only real way of tackling asychronous access to external resource such as a Mongo database is to package up the request as one of the things we support in ixsl:schedule-action
, typically an HTTP request. I don't know if Mongo already supports an HTTP interface or whether you would need to craft your own, but I would think an HTTP interface where the request and response are both JSON-encoded would be the way to go. This might also answer the question about returning maps, since if you return JSON as a string, the application can then turn it into an XDM map using fn:parse-json()
.
Updated by Debbie Lockett over 1 year ago
Do actually mean that you want to call the MongoDB query from the XSLT transform? Or are you actually calling it from JavaScript, and you just want to pass the JSON result to XSLT for processing? Your examples suggest that it is the latter; but if it is the former, then I agree with Mike's suggestion to use the HTTP client with ixsl:schedule-action
.
Assuming you do indeed intend the former, then to answer your questions about supplying JSON to the transform, and then accessing it as an XDM map in the XSLT stylesheet; there are a number of options available. (It looks like your attempts are overcomplicating things; unless I'm missing something.)
- You can in fact supply JSON text as the source for the transform. e.g.:
SaxonJS.transform({sourceText: JSON.stringify(jsonResult), sourceType: "json", ...})
then the XDM result (e.g. XDM map or array) of parsing the supplied JSON (following the rules for the fn:json-doc
function) becomes the global context item for the transformation. So you could just access it with:
<xsl:variable name="resultMap" select="." as="map(*)"/>
- Alternatively you could pass that JSON text as a stylesheet parameter:
SaxonJS.transform({stylesheetParams: {jsonText :JSON.stringify(jsonResult)}, ...})
And as Mike mentioned, use fn:parse-json
in your stylesheet to convert the JSON string to an XDM map:
<xsl:parameter name="jsonText" as="xs:string"/>
<xsl:variable name="resultMap" select="parse-json($jsonText)" as="map(*)"/>
- I'm not quite sure if you can use
SaxonJS.getResource()
withtype:"json"
, andSaxonJS.transform({stylesheetParams: {jsonMap :jsonMap}, ...})
with the stylesheet parameter declared as<xsl:parameter name="jsonMap" as="map(*)"/>
. Or ifSaxonJS.getResource()
withtype:"json"
just works better with thetextResourcePool
option forSaxonJS.transform
(see https://www.saxonica.com/saxon-js/documentation/index.html#!api/getResource).
One other note: if you're using promises and asynchronous calls, then you probably want an asynchronous transform too: i.e. SaxonJS.transform({...}, "async") Is that relevant to the issues with getting promises to work?
Updated by David Camps over 1 year ago
Michael & Debbie, thanks for the great and fast response to my question. I am a first time SaxonJS user and find the support just as robust and fast as the software…
From the replies I can see using <ixsl:call to get JS promised results is a dead end. Thanks for the insights as to other solutions. One of my design goals was to package the html to be transformed with the db query parameters. Since I can’t do it from within the transform (because of the mongoDB async promises), like you said, do it before and pass the results as <xsl:param. That way I can use the proven SaxonJS.getResource to convert the JSON query result to an XdmMap and deal with the promises before starting the transform.
I will still try to package the query parameters in the html, just imbed something like <dls:query query=”something” /> in the html, pre-parse the html, pull the <dls:query from the DOM, do the queries, deal with promises and pass the html DOM and query results to the transform. I will consider this issue closed.
Updated by Michael Kay about 1 year ago
- Status changed from New to Closed
Closing this as the questions have been answered.
Please register to edit this issue
Also available in: Atom PDF Tracking page