Counting elements that are generated in XSLT1 - xslt

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

Related

calling XQuery from XSLT, building XSLT dynamically in XQuery?

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

Sum of Multiplied Values

I have a fairly convoluted XML file and I need to do a weighted average of a few values within it using XSL. I am able to complete a sum of the weights OR of the values, but I cannot get the multiplication to work. I get an error:
XPTY0004: A sequence of more than one item is not allowed as the first
operand of '*'
I am not able to share the XML, but I have simplified the XML to the following example (assume there are a large number of foos):
<group>
<fooList>
<foo>
<attributeList>
<Attribute ID="1" Weight="0.5">
<otherParams />
</Attribute>
</attributeList>
<Properties>
<PhysicalProperties>
<Volume Average="125" Unknown="50" />
</PhysicalProperties>
</Properties>
</foo>
</fooList>
</group>
My current attempt to get the weighted average is the following:
<xsl:variable name="WeightedVolume" select="sum(/group/fooList/foo[attributeList/Attribute/[#ID=$test_id]]/attributeList/Attribute/#Weight * /group/fooList/foo[attributeList/Attribute/[#ID=$test_id]]/Properties/PhysicalProperties/Volume/#Average)"/>
I know there are similar questions available - but most of them deal with something like summing and multiplying foo
<foo>
<Weight>0.5</Weight>
<VolumeAverage>125</VolumeAverage>
</foo>
The answer on this StackOverflow Question appeals to me, but I cannot seem to make it work.
I'm using Saxon-HE 9.5.1.1N from Saxonica, with Visual Studio 2013.
Edited
I was able to get something to work for XSL 2, but need to have a fall-back for XSL1.
<xsl:variable name="WeightedVolume" select="sum(for $i in /group/FooList/foo[attributeList/Attribute[#ID=$test_id] return $i/AttributeList/Attribute/#Weight * $i/Properties/PhysicalProperties/Volume/#Average)"/>
To follow the example in that question you linked to, you would use this in XSLT 2.0/XPath 2.0:
<xsl:variable name="FoosToCalculate"
select="/group/fooList/foo[attributeList/Attribute/#ID = $test_id]" />
<xsl:variable name="WeightedVolume"
select="sum($FoosToCalculate/(attributeList/Attribute/#Weight *
Properties/PhysicalProperties/Volume/#Average)
)"/>
Doing this summing in XSLT 1.0 is considerably more involved and typically involves either using recursive templates or some manifestation of the node-set() function. Here is an example of the latter:
<xsl:stylesheet version="1.0"
xmlns:ex="http://exslt.org/common"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<!-- determine $test_id however you need to -->
<xsl:variable name="products">
<xsl:for-each
select="/group/fooList/foo[attributeList/Attribute/#ID = $test_id]">
<product>
<xsl:value-of select="attributeList/Attribute/#Weight *
Properties/PhysicalProperties/Volume/#Average" />
</product>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(ex:node-set($products)/product)"/>
</xsl:template>
</xsl:stylesheet>
For completeness, if you want to sum over a computed quantity in XSLT 1.0, there are three ways of doing it:
(a) recursion: write a recursive template that processes the items in the sequence one by one, computing the total as it goes.
(b) create an XML tree in which the computed quantities are node values, and then process this tree using the sum() function. To do this in a single stylesheet you will need the exslt:node-set() extension function.
(c) use an extension function provided by the XSLT vendor, or user-written using the facilities provided by the vendor for calling external functions.
In XSLT 2.0, it can always be done using the construct
sum(for $x in node-set return f($x))
where f is a function that computes the quantity.

XSLT text to large integer

I'm attempting to create an XSLT mapping that properly converts a fairly large integer value coming through in a text field into the appropriate integer value. The problem is that since 1.0 only supports converting to type number, I get a value like 1.234567890E9 back for input of "1234567890"
I'm using Altova MapForce with XSLT1.0 as the coding platform. XSLT2.0 doesn't appear to be an option, as the XSLT has to be processed using a pre-existing routine that only supports XSLT1.0
By default Mapforce generates
<xsl:value-of select="string(floor(number(string(.))))"/>
and I've tried every combination of functions I can think of, but always get a float for large values.
Further testing shows the problem lies in Mapforce, which insists on using the number() function when mapping from text to int.
Let me try and move this forward by answering a question that you did not ask, but perhaps should have. Suppose you have the following input:
XML
<input>
<value>1234567890000000.9</value>
<value>9876543210000000</value>
</input>
and you want to make sure that the input values (which are all numbers, but some of them are not integers) are converted to integers at the output, you could apply the following transformation:
XSLT 1.0
<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="/">
<output>
<xsl:for-each select="input/value">
<value><xsl:value-of select="format-number(., '#')"/></value>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
to obtain the following output:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<value>1234567890000001</value>
<value>9876543210000000</value>
</output>
Note that the results here are rounded, not floored.
Are you sure that mapforce isn't using xslt-2.0?
If I do in XSLT-1.0 (with either saxon or Altova's processor):
<xsl:value-of select="number('1234567890')"/>
I get -> 1234567890
If I use XSLT-2.0 I get -> 1.23456789E9
So I think it is very strange that an XSLT 1 transformation supposedly returns you the floating point representation of the number.
Formatting the number with format-number(1.23456789E9,'#') will always give you 1234567890 in both XSLT-1.0 and 2.0. Edit: saxon will not convert 1.23456789E9 to number in xslt-1.0, altova's processor however will.
The problem lies within Mapforce, so I've decided to let mapforce generate it's code, then overwrite it for this one field that's causing all the trouble.
#Tobias #Michael Thanks to you both for your help. I've +1'ed both your answers and a few comments since your help led to the answer.

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