calling XQuery from XSLT, building XSLT dynamically in XQuery? - xslt

Environment: eXist-db 4.2.1 , XQuery 3.1, XSLT 2.0
I am required to perform an XSLT transformation within eXist-DB using XQuery. At one point the XSLT needs to search across hundreds of documents for matches on a node attribute value. Calling collection() from XSLT in eXist-DB seems to not work .
I've done some searching on other ways to solve this problem, and having failed to find anything, I'm posting two questions here:
Is is possible to dynamically write and transform XSLT from XQuery, thus allowing me to dynamically inject values from XQuery itself (parameters on xquery transform:transform() don't suffice here)
Is it possible to call/retrieve results from an (eXist) XQuery document/function from XSLT in any way?
Thanks for any opinions and references.

As XSLT is XML and with XQuery you can construct XML you can of course construct XSLT on the fly and inject data you gathered elsewhere in XQuery, the following is a silly example obviously but it constructs some data in XQuery, creates an XSLT stylesheet on the fly injecting some of that data directly inline as a parameter value and then runs the XSLT:
declare namespace xsl = "http://www.w3.org/1999/XSL/Transform";
let $elements := (1 to 3)!<root><data>{.}</data></root>,
$stylesheet :=
<xsl:stylesheet version="2.0">
<xsl:param name="data-elements" as="element()*">{$elements!data}</xsl:param>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="foo[. = $data-elements]"/>
</xsl:stylesheet>,
$input := <root><list><foo>a</foo><foo>2</foo><foo>10</foo><foo>1</foo></list></root>
return transform:transform($input, $stylesheet, ())

Related

merge multiple XML files using STAX parser

I have multiple XML files.all nodes are similar. Please provide an example how to merge XML files using STAX Parser and apply a stylesheet on it.
If you want to apply XSLT to several XML documents then (with pure XSLT, I don't know about Stax) you can simply use the document function (XSLT 1.0 and 2.0) or the collection function (with XSLT 2.0) e.g.
<xsl:template match="/">
<root>
<xsl:apply-templates select="document('file1.xml')/* | document('file2.xml')/* | document('file3.xml')/*"/>
</root>
</xsl:template>
then add templates matching the element names in the documents you want process.

Counting elements that are generated in XSLT1

I'm trying to count the elements my transformation generates (must use XLST1). For example, my transformation creates:
<Parent>
<ElementX Att1="2"/>
<ElementY Att1="1"/>
<ElementZ Att1="6"/>
</Parent>
I need to print 3 within the same transformation, because there are 3 child elements.
Can this be done?
Thanks.
It would help a lot if you provide some extract of your XSLT.
I cn't give you a XSLT code without it. I'll try to give some "way" to the answer :
One solution could be to store the output into a nodeset (use the XSLT 1.0 extension which provides the nodeset() function) and apply the XPath count() function on this variable. After that just output your variable with xsl:value-of, and your count result the same way.
Here is a demo how to do this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="count(ext:node-set($vrtfPass1)/*/*)"/>
</xsl:template>
<xsl:template match="/*">
<Parent>
<ElementX Att1="2"/>
<ElementY Att1="1"/>
<ElementZ Att1="6"/>
</Parent>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used in this Demo), the wanted, correct result is produced:
3
Explanation:
A general way to process the result of the transformation (in a single transformation), is to organize it in two passes where we save the result of the first pass in a variable.
In the second pass we access the result and do the additional processing.
Do note that in XSLT 1.0 if the variable that captures the result of the first pass is of the infamous RTF (Result Tree Fragment) type and needs to be converted to a regular tree in order of any nodes inside this tree to be accessible (xsl:copy-of and string() are still allowed on an RTF).
This conversion to a regular tree is done by an extension function, which most often has the name node-set and always belongs to a vendor-defined namespace. In this demo we are using the node-set() extension function that belongs to the EXSLT namespace -- because most XSLT 1.0 processors implement EXSLT.
For more information on multi-pass processing, see this: Two phase processing: Do not output empty tags from phase-1 XSLT 2.0 processing

Indirect variable/parameter reference (name in another property / another variable)

Is it possible using XSL to access a variable (or a parameter) whose name is stored in another variable (or parameter)? If no, why?
I am new to xsl, coming from other languages, where this functionality is accessible, like bash, ant. Maybe I was wrong even looking for an answer to this question. But since I didn't find it on SO, I think there should be one.
Two examples. I have parameters p1, p2, p3. Then I have a parameter pname whose value is a string p2. I would like to read the value of p2 using pname, something like $$pname or ${$pname}. Or in a more complicated way. If pnumber is equal to 2, then I would like to read the value of the parameter with name concat('p', $pnumber), something I would code asparam-value(concat('p', $pnumber)).
This is possible whenthe XSLT stylesheet accesses itself as a regular XML document:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="p1" select="'P1-Value'"/>
<xsl:param name="p2" select="'P2-Value'"/>
<xsl:param name="p3" select="'P3-Value'"/>
<xsl:param name="pName" select="'p3'"/>
<xsl:param name="pNumber" select="2"/>
<xsl:variable name="vDoc" select="document('')"/>
<xsl:template match="/">
<xsl:value-of select=
"concat('Param with name ',
$pName,
' has value: ',
$vDoc/*/xsl:param[#name = $pName]/#select
)"/>
<xsl:text>
</xsl:text>
<xsl:variable name="vParam" select=
"$vDoc/*/xsl:param[#name = concat('p', $pNumber)]"/>
<xsl:value-of select=
"concat('Param with name p',
$pNumber,
' has value: ',
$vParam/#select
)"/>
</xsl:template>
</xsl:stylesheet>
produces the wanted result:
Param with name p3 has value: 'P3-Value'
Param with name p2 has value: 'P2-Value'
Explanation:
The expression document('') selects the document node of the current XSLT stylesheet.
A limitation is that the current XSLT stylesheet must have (be accessible via) a URI (such as residing at a given file and accessible by its filename) -- the above code doesn't produce a correct result if the stylesheet is dynamically generated (a string in memory).
In libxslt the thing is possible through dyn:evaluate extension. Here is the description. There is total of 3 processors mentioned which are said to support this function:
Xalan-J from Apache (version 2.4.1) and
4XSLT, from 4Suite. (version 0.12.0a3)
libxslt from Daniel Veillard et al. (version 1.0.19)
A portable workaround. If you control both the application and the stylesheet, you should pass the parameters as an xml document. Most processors give the option to make parameter a node-set. For example in MSXML I did it using:
xslProc.addParameter("params", xmlParams)
where xslProc is of processor type, created from "Msxml2.XSLTemplate.6.0" using createProcessor method and xmlParams is DomDocument. Inside the stylesheet I was accesing my parameters using something like that:
<xsl:variable name="value">
<xsl:value-of select="$params//*[name() = concat('p', $pnumber)]" />
</xsl:variable>
If the processor does not support node-set external parameters, one may always combine the parameters with the data in one xml document. This works well in memory. If access to external files is possible, one may use document('params.xml') syntax to access the parameters stored in a separate file.
I was also looking for a possibility to parse xml string and have a node-set of it, but it is seems to be available only as an extension in some xslt 2.0 parsers. I wanted 1.0 solution.

XSLT: How to remove synonymous namespaces

I have a large collection of XML files which I need to transform using XSLT. The problem is that many of these files were hand-written by different people and they do not use consistent names to refer to the schemas. For example, one file might use:
xmlns:itemType="http://example.com/ItemType/XSD"
where another might use the prefix "it" instead of "itemType":
xmlns:it="http://example.com/ItemType/XSD"
If that's not bad enough, there are several files which use two or three synonyms for the same thing!
<?xml version="1.0"?>
<Document
xmlns:it="http://example.com/ItemType/XSD"
xmlns:itemType="http://example.com/ItemType/XSD"
xmlns:ItemType="http://example.com/ItemType/XSD"
...
(there's clearly been a lot of cutting and pasting going on)
Now, because the pattern matching in the XSLT file appears to work on the namespace prefix (as opposed to the schema it relates to) the pattern only matches one of the variants. So if I write something like:
<xsl:template match="SomeNode[#xsi:type='itemType:SomeType']">
...
</xsl:template>
Then it only matches a subset of the cases that I want it to.
Question 1: Is there any way to get the XSLT to match all the variants?
Question 2: Is there any way to remove the duplicates so all the output files use consistent naming?
I naïvely tried using "namespace-alias" but I guess I've misunderstood what that does because I can't get it to do anything at all - either match all the variants or affect the output XML.
<?xsl:stylesheet
version="1.0"
...
xmlns:it="http://example.com/ItemType/XSD"
xmlns:itemType="http://example.com/ItemType/XSD"
xmlns:ItemType="http://example.com/ItemType/XSD"
...
<xsl:output method="xml" indent="yes"/>
<xsl:namespace-alias stylesheet-prefix="it" result-prefix="ItemType"/>
<xsl:namespace-alias stylesheet-prefix="itemType" result-prefix="ItemType"/>
Attribute values or text nodes won't be cast to QName unless you explicitly say so. Although this is only posible in XSLT/XPath 2.0
In XSLT/XPath 1.0 you must do this "manually":
<xsl:template match="SomeNode">
<xsl:variable name="vPrefix" select="substring-before(#xsi:type,':')"/>
<xsl:variable name="vNCName"
select="translate(substring-after(#xsi:type,$vPrefix),':','')"/>
<xsl:if test="namespace::*[
name()=$vPrefix
] = 'http://example.com/ItemType/XSD'
and
$vNCName = 'SomeType'">
<!-- Content Template -->
<xsl:if>
</xsl:template>
Edit: All in one pattern (less readable, maybe):
<xsl:template match="SomeNode[
namespace::*[
name()=substring-before(../#xsi:type,':')
] = 'http://example.com/ItemType/XSD'
and
substring(
concat(':',#xsi:type),
string-length(#xsi:type) - 7
) = ':SomeType'
]">
<!-- Content Template -->
</xsl:template>
In XSLT 2.0 (whether or not you use schema-awareness) you can write the predicate as [#xsi:type=xs:QName('it:SomeType')] where "it" is the prefix declared in the stylesheet for this namespace. It doesn't have to be the same as the prefix used in the source document.
Of course matching of element and attribute names (as distinct from QName-valued content) uses namespace URIs rather than prefixes in both XSLT 1.0 and XSLT 2.0.

How to concatenate two node-sets such that order is respected?

My understanding has been that, despite the fact that XSLT's "node-sets" are called "sets", they are, in fact, ordered lists of nodes (which is why each node is associated with an index). I've therefore been trying to use the "|" operator to concatenate node-sets such that the order of the nodes is respected.
What I am attempting to accomplish is something like the following JavaScript code:
[o1,o2,o3].concat([o4,o5,o6])
Which yields:
[o1,o2,o3,o4,o5,o6]
But, consider the following reduced example:
testFlatten.xsl
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:variable name="parentTransition" select="//*[#id='parentTransition']"/>
<xsl:variable name="childTransition" select="//*[#id='childTransition']"/>
<xsl:variable name="parentThenChildTransitions" select="$parentTransition | $childTransition"/>
<xsl:variable name="childThenParentTransitions" select="$childTransition | $parentTransition"/>
<return>
<parentThenChildTransitions>
<xsl:copy-of select="$parentThenChildTransitions"/>
</parentThenChildTransitions>
<childThenParentTransitions>
<xsl:copy-of select="$childThenParentTransitions"/>
</childThenParentTransitions>
</return>
</xsl:template>
</xsl:stylesheet>
Given the following input:
<?xml version="1.0"?>
<root>
<element id="parentTransition"/>
<element id="childTransition"/>
</root>
Which yields (with xsltproc):
<?xml version="1.0"?>
<return>
<parentThenChildTransitions>
<element id="parentTransition"/><element id="childTransition"/>
</parentThenChildTransitions>
<childThenParentTransitions>
<element id="parentTransition"/><element id="childTransition"/>
</childThenParentTransitions>
</return>
So the "|" operator in fact does not respect the order of the node-set operands. Is there a way I can concatenate node-sets such that order is respected?
This is actually not an XSLT but an XPath question.
In XPath 1.0 there isn't anything similar to a "list" datatype. A node-set is a set and it has no order.
In XPath 2.0 there is the sequence data type. Any items in a sequence are ordered. This has nothing to do with document order. Also, the same item (or node) can appear more than once in a sequence.
So, in XSLT 2.0 one just uses the XPath 2.0 sequence concatenation operator ,:
//*[#id='parentTransition'] , //*[#id='childTransition']
and this evaluates to the sequence of all elements in the document with id attribute 'parentTransition' followed by all elements in the document with id attribute 'childTransition'
In XSLT it is still possible to access and process nodes not in document order: for example using the <xsl:sort> instruction -- however the set of nodes that are processed as result of <xsl:apply-templates> or <xsl:for-each> is a node-list -- not a node-set.
Another example of evaluating nodes not in document order is the position() function within <xsl:apply-templates> or <xsl:for-each> that have a <xsl:sort> child or within a predicate of a location step (of an XPath expression) in which a reverse axis is used (such as ancesstor:: or preceeding::)
In XSLT 1.0, you can process nodes in a selected order (for example by use of xsl:sort), but you can't hold a list of nodes in a variable. The only thing you can hold in a variable (or pass to a template, etc) is a node-set; node-sets have no intrinsic order, but when you process them, they are always processed in document order unless you use xsl:sort to request a different processing order.
You might be able to solve your problem by copying the nodes:
<xsl:variable name="temp">
<xsl:copy-of select="$ns0"/>
<xsl:copy-of select="$ns1"/>
</xsl:variable>
...
<xsl:apply-templates select="exslt:node-set($temp/*)"/>
but this depends on your use-case.
Switch to XSLT 2.0 if you can!
The "|" operator will retain nodes in document order. In XSLT 1.0 you will need to have sequential copy or for-each operations.
<xsl:copy-of select="$parentTransition"/>
<xsl:copy-of select="$childTransition"/>