In XSLT nested foreach loop always return first element - xslt

XLST receiving the date from apache-camel in below formate.
data format
<list>
<linked-hash-map>
<entry key="NAME">test1</entry>
</linked-hash-map>
<linked-hash-map>
<entry key="NAME">test2</entry>
</linked-hash-map>
</list>
My XSLT:
<xsl:stylesheet>
<xsl:template match="*">
<xsl:for-each select="//*[local-name()='linked-hash-map']">
<tag1>
<xsl:value-of select="string(//*[local-name()='entry'][#key='NAME'])"/>
</tag1t>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
OUTPUT always returns the first element.
<tag1>test1<tag1>
<tag1>test1<tag1>
What is wrong in above xslt and help generate xml with all elements.

Because path expressions starting with "//" select from the root of the document tree, you are selecting the same nodes every time in your xsl:value-of; and in XSLT 1.0, if you select multiple nodes, only the first one gets displayed.
Methinks you're using "//" because you've seen it in example code and don't actually understand what it means...
Within xsl:for-each, you normally want a relative path that selects from the node currently being processed by the for-each.
You've also probably picked up this *[local-name()='linked-hash-map'] habit from other people's code. With no namespaces involved, you can safely replace it with linked-hash-map.

Related

Is there a way to count the elements generated by an XSL within the same XSL?

If I have an XSL that creates output like this simple/rough example:
<Parent1>
<ABC><xsl:value-of select="SomeValue1"/></ABC>
<DEF><xsl:value-of select="SomeValue2"/></DEF>
<GHI><xsl:value-of select="SomeValue3"/></GHI>
...
<YZ><xsl:value-of select="SomeValue9"/></YZ>
</Parent1>
... within this same XSL, how can I count how many children the XSL will produce?
You can generate your content into a variable, count the children in the variable, and then emit the content of the variable:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<xsl:variable name="temp-results">
<Parent1>
<ABC><xsl:value-of select="SomeValue1"/></ABC>
<DEF><xsl:value-of select="SomeValue2"/></DEF>
<GHI><xsl:value-of select="SomeValue3"/></GHI>
...
<YZ><xsl:value-of select="SomeValue9"/></YZ>
</Parent1>
</xsl:variable>
<xsl:text>Number of children:</xsl:text>
<xsl:value-of select="count($temp-results/Parent1/*)"/>
<xsl:sequence select="$temp-results"/>
</xsl:template>
</xsl:stylesheet>
One possibility is wrapping the whole output process in a variable and then count its descendants.
So, for example, you can use the following XSLT code
<xsl:template match="/Parent1">
<xsl:variable name="output">
<ABC><xsl:value-of select="SomeValue1"/><ZZZ>Some Grandchild</ZZZ></ABC>
<DEF><xsl:value-of select="SomeValue2"/></DEF>
<GHI><xsl:value-of select="SomeValue3"/></GHI>
...
<YZ><xsl:value-of select="SomeValue9"/></YZ>
</xsl:variable>
<xsl:value-of select="concat('Outputting ', count($output/descendant::*), ' elements.
')" />
<xsl:copy-of select="$output" />
</xsl:template>
Its output is
Outputting 5 elements.
<ABC>
<ZZZ>Some Grandchild</ZZZ>
</ABC>
<DEF/>
<GHI/>
...
<YZ/>
This code accomplishes three things:
First it generates the result and puts it into the variable
It counts all the
children (child::* axis) or
descendants (descendant::* axis) as in the example above
of the elements in the variable
It copies the variable to the output stream
This approach can even be nested - meaning that it can be applied several times, one after another.
Your choices are:
(a) find a way of computing the result as a function of the input
(b) capture the output in a variable and run a second phase of processing against that variable.
(c) a blend of the above: compute some intermediate result in a variable, and use that variable as input to both processes.
In the example you've given, the first approach works perfectly well; but I guess your real problem is more complex than that, otherwise you wouldn't be asking.

Constructing, not selecting, XSL node set variable

I wish to construct an XSL node set variable using a contained for-each loop. It is important that the constructed node set is the original (a selected) node set, not a copy.
Here is a much simplified version of my problem (which could of course be solved with a select, but that's not the point of the question). I've used the <name> node to test that the constructed node set variable is in fact in the original tree and not a copy.
XSL version 1.0, processor is msxsl.
Non-working XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="entryNodes" select="msxsl:node-set($entries)"/>
<xsl:for-each select="$entryNodes">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<name>X</name>
<entry>1</entry>
<entry>2</entry>
</root>
Wanted output:
X1X2
Actual output:
12
Of course the (or a) problem is the copy-of, but I can't work out a way around this.
There isn't a "way around it" in XSLT 1.0 - it's exactly how this is supposed to work. When you have a variable that is declared with content rather than with a select then that content is a result tree fragment consisting of newly-created nodes (even if those nodes are a copy of nodes from the original tree). If you want to refer to the original nodes attached to the original tree then you must declare the variable using select. A better question would be to detail the actual problem and ask how you could write a suitable select expression to find the nodes you want without needing to use for-each - most uses of xsl:if or xsl:choose can be replaced with suitably constructed predicates, maybe involving judicious use of xsl:key, etc.
In XSLT 2.0 it's much more flexible. There's no distinction between node sets and result tree fragments, and the content of an xsl:variable is treated as a generic "sequence constructor" which can give you new nodes if you construct or copy them:
<xsl:variable name="example" as="node()*">
<xsl:copy-of select="//entry" />
</xsl:variable>
or the original nodes if you use xsl:sequence:
<xsl:variable name="example" as="node()*">
<xsl:sequence select="//entry" />
</xsl:variable>
I wish to construct an XSL node set variable using a contained
for-each loop.
I have no idea what that means.
It is important that the constructed node set is the original (a
selected) node set, not a copy.
This part I think I understand a little better. It seems you need to replace:
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
with:
<xsl:variable name="entries" select="//entry"/>
or, preferably:
<xsl:variable name="entries" select="root/entry"/>
The resulting variable is a node-set of the original entry nodes, so you can do simply:
<xsl:for-each select="$entries">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
to get your expected result.
Of course, you could do the same thing by operating directly on the original nodes, in their original context - without requiring the variable.
In response to the comments you've made:
We obviously need a better example here, but I think I am getting a vague idea of where you want to go with this. But there are a few things you must understand first:
1.
In order to construct a variable which contains a node-set of nodes in their original context, you must use select. This does not place any limits whatsoever on what you can select. You can do your selection all at once, or in stages, or even in a loop (here I mean a real loop). You can combine the intermediate selections you have made in any way sets can be combined: union, intersection, or difference. But you must use select in all these steps, otherwise you will end up with a set of new nodes, no longer having the context they did in the source tree.
IOW, the only difference between using copy and select is that the former creates new nodes, which is precisely what you wish to avoid.
2.
xsl:for-each is not a loop. It has no hierarchy or chronology. All the nodes are processed in parallel, and there is no way to use the result of previous iteration in the current one - because no iteration is "previous" to another.
If you try to use xsl:for-each in order to add each of n processed nodes to a pre-existing node-set, you will end up with n results, each containing the pre-existing node-set joined with one of the processed nodes.
3.
I think you'll find the XPath language is quite powerful, and allows you to select the nodes you want without having to go through the complicated loops you hint at.
It might help if you showed us a problem that can't be trivially solved in XSLT 1.0. You can't solve your problem the way you are asking for: there is no equivalent of xsl:sequence in XSLT 1.0. But the problem you have shown us can be solved without such a construct. So please explain why you need what you are asking for.

XSLT: for-each loop with key not returning all nodes

I am a novice XSLT developer. I have been asked to fix an issue on a project where the original developer is no longer with us. In the XSLT, there is a for-each loop using a key and a count
<xsl:for-each select="ns0:BOM[count(. | key('subsat', ns0:BomText01)[1]) = 1][ns0:BomText01]">
...
This is the key:
<xsl:key name="subsat" match="ns0:Parts/ns0:BOM[ns0:FindNum!='0']" use="ns0:BomText01" />
In the XML file being transformed, there are two sibling nodes that represent sub-parts:
<ns0:BOM referentId="10000:65091335:65359080">
<ns0:BomText01>3069260-303-SUB0027</ns0:BomText01>
<ns0:ItemNumber>My_part_1</ns0:ItemNumber>
<ns0:ItemType>Part</ns0:ItemType>
<ns0:Qty>67</ns0:Qty>
</ns0:BOM>
<ns0:BOM referentId="10000:65102551:86713230">
<ns0:BomText01>3069260-303-SUB0027</ns0:BomText01>
<ns0:ItemNumber>My_part_2</ns0:ItemNumber>
<ns0:ItemType>Part</ns0:ItemType>
<ns0:Qty>67</ns0:Qty>
</ns0:BOM>
However, the loop is only picking up the first node (My_part_1). I suspect it's because of the count=1 but I really don't know. And I don't know how to modify it. Ideas? If I need to include more data, let me know.
Assuming that the relevant part of your XSLT looks something like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="ns0" version="1.0">
<xsl:key name="subsat" match="ns0:BOM[ns0:FindNum!='0']" use="ns0:BomText01"/>
<xsl:template match="ns0:Parts">
<xsl:for-each
select="ns0:BOM[count(. | key('subsat', ns0:BomText01)[1]) = 1][ns0:BomText01]">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It will only print the first of the elements because it is selecting the BOM elements which have an unique BomText01 value. That's the expected result.
If the BomText01 is an ID field (as it seems it is) and you expected to get both result (perhaps, because their ItemNumber contains different values), the error is possibly in your source (which assigned equal IDs when it should not have done so).
If you change one of those values in the source, you should be able to select both and verify this.

Change text of elements identified by dynamic XPath

I have an XML with 2 XML fragments, 1st one is a fragment where the new values must be applied (which can have pretty complex elements) like
... some static parents
<a:element1>
<a:subelement tag="someString">
<a:s1>a</a:s1>
</a:subelement>
</a:element1>
<a:element2>b</a:element2>
<a:element3>c</a:element3>
... lots of other elements like the above ones
and 2nd fragment that has XPaths generated from the first XML and a new value, like
<field>
<xpath>/Parent/element1/subelement[#tag="someString"]/s1</xpath>
<newValue>1</newValue>
</field>
<field>
<xpath>/Parent/element2</xpath>
<newValue>2</newValue>
</field>
We might not have new values to apply for all the elements in the first fragment.
I'm struggling to make an XSLT transformation that should apply the new values to the places indicated by the XPaths.
The output should be:
... some static parents
<a:element1>
<a:subelement tag="someString">
<a:s1>1</a:s1>
</a:subelement>
</a:element1>
<a:element2>2</a:element2>
... lots of other elements like the above ones
I have access to xalan:evaluate to evaluate the dynamic xpath. I'm trying different solutions, I will write them here when they will start to make sense.
Any ideas of approaches are well received. Thanks
Oki, I found out how, and I will write the answer here maybe someone sometime will need this:
<xsl:template match="/">
<!-- static parents -->
<a:Root>
<xsl:apply-templates select="/a:Root/a:Parent" />
</a:Root>
</xsl:template>
<xsl:template match="#*|*|text()">
<xsl:variable name="x" select="generate-id(../.)" />
<xsl:variable name="y" select="//field[generate-id(xalan:evaluate(xpath)) = $x]" />
<xsl:choose>
<xsl:when test="$y">
<xsl:value-of select="$y/newValue" />
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|*|text()" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
And to explain the transformation:
I'm writing down part that is static and then call apply-templates on the fragment I'm interested in, that has a liquid structure.
Then I'm using a slightly modified identity transformation that copies everything from source to target (starting from the /a:Root/a:Parent fragment), except when we position ourselves on the text I'm interested in changing.
The text() I'm interested in will have as parent (../.) the element referred by an xpath string found in the second fragment. Variable x means, in the context of the when, this element.
Variable y finds a field element that has as child an xpath element that if evaluated using xalan will refer to the same element that the x variable relates to.
Now I used generate-id() in order to compare the physical elements, otherwise it would have compared by the toString of the element (which is wrong). If variable y doesn't exist, it means that I have no xpath element for this element that could have changed, and I'm leaving it alone. If the y variable exists, I can get from it the newValue and I'm currently positioned on the element which text I want to update.

XSLT number only counts instances in current file of a multi-file document

I've been tasked with putting together a book (using XSL FO) from a number of XML-files, now I'm trying to number the figures in this book (simple numbering, no resets at chapters or whatever), my naive approach was to do this
<xsl:template match="figure">
<fo:block xsl:use-attribute-sets="figure">
.. stuff to deal with images ..
<fo:block xsl:use-attribute-sets="figure-caption">
Figure <xsl:number level="any"/>: <xsl:apply-templates/>
</fo:block>
<fo:block xsl:use-attribute-sets="figure-caption">
</xsl:template>
I have an aggregate XML file which selects the files to use using the document() function like so:
<xsl:template match="include">
<xsl:apply-templates select="document(#src)"/>
</xsl:template>
Now, my problem is that number seems to always only count the instances in the current file, which is not what I want (currently, there's only one or two images per file, resulting in all figures being 'Figure 1' or 'Figure 2').
I've considered two approaches, both being essentially two-pass XSLT. First, the straightforward approach, generate an intermediary XML containing the entire book using an identity transform, which I'm reluctant to do for other reasons.
Second, using node-set() extension, which I tried like this
<xsl:template match="include">
<xsl:apply-templates select="ext:node-set(document(#src))"/>
</xsl:template>
but this produced the same result.
Any ideas? Perhaps something which isn't a two-pass transformation? Any help would be greatly appreciated.
The two -pass approach is the more logical and robust one.
One-pass approach is very challenging. One can provide an expression in the value attribute of <xsl:number> and this can be used to sum the "local number" with the maximum accumulated number so far from all previous documents.
However, this requires sequencing the documents (which is something bad in a functional language) and this only works for a flat numbering scheme. In case hierarchical numbering is used (3.4.2), I don't see an easy way to continue from the max number of a previous document.
Due to this considerations, I would definitely merge all documents into one before numbering.
I will also use a two phase transformation. But just for fun, with one include level and no repetition, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="vIncludes" select="//include"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="include">
<xsl:apply-templates select="document(#src)"/>
</xsl:template>
<xsl:template match="picture">
<xsl:variable name="vRoot" select="generate-id(/)"/>
<xsl:variable name="vInclude"
select="$vIncludes[
$vRoot = generate-id(document(#src))
]"/>
<xsl:copy>
<xsl:value-of
select="count(
document(
(.|$vInclude)/preceding::include/#src
)//picture |
(.|$vInclude)/preceding::picture
) + 1"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With this input:
<master>
<include src="child3.xml"/>
<block>
<include src="child1.xml"/>
<picture/>
</block>
<include src="child2.xml"/>
<picture/>
</master>
And 'child1.xml'
<child1>
<picture/>
</child1>
And 'child2.xml'
<child2>
<picture/>
</child2>
And 'child3.xml'
<child3>
<picture/>
</child3>
Output:
<master>
<child3>
<picture>1</picture>
</child3>
<block>
<child1>
<picture>2</picture>
</child1>
<picture>3</picture>
</block>
<child2>
<picture>4</picture>
</child2>
<picture>5</picture>
</master>
You could use an ancillary XML documentto keep track of the last figure number and load that file as a document from your stylesheet. Or, if you do not want to manage two output files from the same stylesheet (the "real" FOP output and the figure counter) you can simply load the previous chapter's FOP file and look for the MAX of the figure-caption.
Or you could pass the last figure number as a parameter with default zero and pass the parameter on the command line. The value of this parameter resulting from the parsing of the previous one in the ascending resulting document order.
All these alternatives suppose you are running the transformations in sequence in source document ascending order.
A more structured and robust solution would be to manage transverse document sections such as indices, table of contents and table of figures in as many separate FO documents that would be generated in a "second pass" run with their own XSLT.
I think I would do a prepass which outputs summary information about all the documents in a single XML file, and then use this as a secondary input to the number calculation. The summary information in your case might just be a count of how many figures each document contains, but in many cases it can be useful to hold other information as well such as the IDs of sections that will act as the target of hyperlinks.