Project

Profile

Help

Support #5188

closed

Asynchronous JavaScript Function Call with Map Return

Added by David Camps 8 months ago. Updated 4 months ago.

Status:
Closed
Priority:
Low
Assignee:
-
Category:
-
Sprint/Milestone:
-
Start date:
2021-12-21
Due date:
% Done:

0%

Estimated time:
Applies to JS Branch:
Fix Committed on JS Branch:
Fixed in JS Release:
SEF Generated with:
Platforms:
Company:
-
Contact person:
-
Additional contact persons:
-

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

Actions #1

Updated by Michael Kay 8 months 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().

Actions #2

Updated by Debbie Lockett 8 months 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.)

  1. 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(*)"/>
  1. 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(*)"/>
  1. I'm not quite sure if you can use SaxonJS.getResource() with type:"json", and SaxonJS.transform({stylesheetParams: {jsonMap :jsonMap}, ...}) with the stylesheet parameter declared as <xsl:parameter name="jsonMap" as="map(*)"/>. Or if SaxonJS.getResource() with type:"json" just works better with the textResourcePool option for SaxonJS.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?

Actions #3

Updated by David Camps 8 months 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.

Actions #4

Updated by Michael Kay 4 months 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