A sample structure of my input xml looks as below:
<Products>
<Product>
<ID>Product1</ID>
<Extra1></Extra1>
<Extra2></Extra2>
<Img1>val1</Img1>
<Img2>val2</Img2>
<Img3>val2</Img3>
<Img4>val1</Img4>
</Product>
<Product>
<ID>Product2</ID>
<Extra1></Extra1>
<Extra2></Extra2>
<Img1>val1</Img1>
<Img2>val2</Img2>
<Img3>val2</Img3>
<Img4>val1</Img4>
</Product>
</Products>
I am parsing each product element at a time and grouping the Img tags of each product by its value. I am using <xsl:key name="keyImg" match="Product/*[contains(local-name(), 'Img')]"
use="."/>. But the same key is used over and over again. Will it be an issue if two product contains same value for some Img tag? I am not sure if there will be conflicts in such cases. Please guide. Thanks in advance.
Use a two-part key, like this:
<xsl:key
name="keyImg"
match="Product/*[starts-with(name(), 'Img')]"
use="concat(generate-id(..), '-', .)"
/>
and
<xsl:template match="Product">
<xsl:variable name="productID" select="generate-id()" />
<xsl:for-each select="*[starts-with(name(), 'Img')][
generate-id() = generate-id(key(concat($productID, '-', .)))
]">
<!-- ... --->
</xsl:for-each>
</xsl:template>
On a general note it's not ideal if nodes with the same semantic value (Img) have different names (Img1, Img2, etc). If you can do anything about it, just name them Img.
Q. But the same key is used over and over again. Will it be an issue if two product contains same value for some Img tag? I am not sure if there will be conflicts in such cases.
Whether there is a "conflict" or not depends of the wanted result. Your key holds all Img* items with the same value in a list. With this key you can generate an unique list of all Img* values.
If the key should be unique for Img* values within one Product you may add the generate-id() or the "Product/ID" to the key.
Something like this:
<xsl:key name="keyImg" match="Product/*[starts-with(local-name(), 'Img')]"
use="concat(../ID, '|', .)"/>
If the name of Img* tages is well known you may also try this:
<xsl:key name="keyImg" match="Img1 | Img2 | Img2 | Img4"
use="concat(../ID, '|', .)"/>
Related
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.
Here is my xslt.
<xsl:template match="/ns0:students/ns0:assignment[ns0:assigmentId[not(generate-id() = generate-id(key('assigmentId', #id)[1]))]]"/>
<xsl:key name="assigmentId" match="/ns0:students/ns0:assignment/ns0:assigmentId" use="#id"/>
Here is my input:
<ns0:students> <ns0:assignment >
<ns0:assigmentId Id="5326" />
</ns0:assignment>
<ns0:assignment >
<ns0:assigmentId id="5326" />
</ns0:assignment>
<ns0:assignment >
<ns0:assigmentId id="5326">
<ns0:assignmentDetails>
</ns0:assignmentDetails>
</ns0:assigmentId>
</ns0:assignment></ns0:students>
So, what I want to archive is I want to remove those duplicates ID's but I want to keep the "assignmentId" with child nodes.
The actual implementation is working smoothly (Muenchian algorithm) but it's not keeping the one with the child's nodes.
Thanks in advance.
If you know that each group of ns0:assignment elements having the same ns0:assigmentId/#id has exactly one ns0:assignment element where the ns0:assigmentId element has one or more child element, then you could do:
<xsl:key name="assigmentId" match="ns0:assignment" use="ns0:assigmentId/#id"/>
<xsl:template match="/ns0:students">
<xsl:copy>
<xsl:for-each select="ns0:assignment[generate-id() = generate-id(key('assigmentId', ns0:assigmentId/#id)[1])]">
<xsl:copy-of select="key('assigmentId', ns0:assigmentId/#id)[ns0:assigmentId/*]"/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
to copy only those ns0:assignment elements.
Is there any possibility to check if it exists a duplicate node in an xml file, then check the contains of this node and if possible differenciate the both node by an Id number using xslt?
I have for example this xml file
<Racine>
<el1>
<fils1>context1</fils1>
<fils2>test1</fils2>
<fils1>context1</fils1>
</el1>
<el2>
<fils1>context2</fils1>
<fils2>test2</fils2>
<fils1>context2</fils1>
</el2>
<el3>...........<el3>
</Racine>
In this case for example, the node "fils1" appears twice in the node "el1" and the contains of each of them is the same. I need in this case to transform my xml file so that this node will become
<fils1 id=1>context1</fils1>.
I add automatically an Id-number to differenciate the both node with the same name and same contain. And when the node got the same name with different contains( also a space in the contain of the node has to be considered as a difference), then lets the node as they are.
How can i make this possible using XSLT? Could someone here help me to transform that?
Thanks a lot for your help.
Franky
Thanks for yours remarks. Here is the expected output:
<Racine>
<el1>
<fils1 id=1>context1</fils1>
<fils2>test1</fils2>
<fils1 id=2>context1</fils1>
</el1>
<el2>
<fils1 id=1>context2</fils1>
<fils2>test2</fils2>
<fils1 id=2>context2</fils1>
</el2>
<el3>...........
<fils1 id=3>context1</fils1>
<el3>
</Racine>
Conerning the node "fils2" in "el1" and "el2", the contain is different, then i want to keep them unchange and when not, i want to add an id number to make the difference.
This XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="kCommon" match="*[starts-with(name(),'fils')]"
use="concat(name(),'|',.)" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'fils')]
[count(key('kCommon',concat(name(),'|',.))) >= 2]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:variable name="this-key" select="concat(name(),'|',.)" />
<xsl:attribute name="id"><xsl:value-of select="
count(preceding::*
[starts-with(name(),'fils')]
[count(.|key('kCommon',$this-key)) =
count( key('kCommon',$this-key)) ]
) + 1" /></xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
...will transform this input...
<Racine>
<el1>
<fils1>context1</fils1>
<fils2>test1</fils2>
<fils1>context1</fils1>
</el1>
<el2>
<fils1>context2</fils1>
<fils2>test2</fils2>
<fils1>context2</fils1>
</el2>
<el3>
<fils1>context1</fils1>
</el3>
</Racine>
...into...
<Racine>
<el1>
<fils1 id="1">context1</fils1>
<fils2>test1</fils2>
<fils1 id="2">context1</fils1>
</el1>
<el2>
<fils1 id="1">context2</fils1>
<fils2>test2</fils2>
<fils1 id="2">context2</fils1>
</el2>
<el3>
<fils1 id="3">context1</fils1>
</el3>
</Racine>
Explanation
We build a key ('kCommon') of the fils elements that have both common name and common text content. We look for elements which have membership in thier key group of at least two - in other words filX type elements that have a name and text in common with at least one other filX element. This is our last template.
For each such common element, we build two sets:
The set of all preceding fil elements in the common group. This set is given by...
Set 1
preceding::*[starts-with(name(),'fils')]
And this node's common name+content group, which is...
Set 2
key('kCommon',concat(name(),'|',.))
But because we will reference concat(name(),'|',.) quiet a few times, and in places where we don't have direct access to this context node, we just compute the concat() and substitute into Set 2 like this. Set 2 is now quick to compute.
Set 2
key('kCommon',$this-key)
And then we take a set intersection using the Kaysian method.
$set1[count(.|$set2)=count($set2)]
This intersection is the set of all like-minded element to this one that preceded it. We just count them up and add one, and this gives us an ordinal for the id property.
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.
Is there a way to walk-through a key and output all the values it contains?
<xsl:key name="kElement" match="Element/Element[#idref]" use="#idref" />
I though of it this way:
<xsl:for-each select="key('kElement', '.')">
<li><xsl:value-of select="." /></li>
</xsl:for-each>
However, this does not work. I simply want to list all the values in a key for testing purposes.
The question is simply: how can this be done?
You can't. That's not what keys are for.
You can loop through every element in a key using a single call to key() if and only if the key of each element is the same.
If you need to loop over everything the key is defined over, you can use the expression in the match="..." attribute of your <key> element.
So if you had a file like this:
<root>
<element name="Bill"/>
<element name="Francis"/>
<element name="Louis"/>
<element name="Zoey"/>
</root>
And a key defined like this:
<xsl:key name="survivors" match="element" use="#name"/>
You can loop through what the key uses by using the contents of its match attribute:
<xsl:for-each select="element">
<!-- stuff -->
</xsl:for-each>
Alternatively, if each element had something in common:
<root>
<element name="Bill" class="survivor"/>
<element name="Francis" class="survivor"/>
<element name="Louis" class="survivor"/>
<element name="Zoey" class="survivor"/>
</root>
Then you could define your key like this:
<xsl:key name="survivors" match="element" use="#class"/>
And iterate over all elements like this:
<xsl:for-each select="key('survivors', 'survivor')">
<!-- stuff -->
</xsl:for-each>
Because each element shares the value "survivor" for the class attribute.
In your case, your key is
<xsl:key name="kElement" match="Element/Element[#idref]" use="#idref" />
So you can loop through everything it has like this:
<xsl:for-each select="Element/Element[#idref]">
<!-- stuff -->
</xsl:for-each>
You CAN create a key to use for looping - if you simply specify a constant in the use attribute of the key element:
<xsl:key name="survivors" match="element" use="'all'"/>
Then you can loop over all elements in the following way:
<xsl:for-each select="key('survivors','all')">
...
</xsl:for-each>
Or count them:
<xsl:value-of select="count(key('survivors','all'))"/>
Note that the constant can be any string or even a number - but 'all' reads well.
However, you cannot use this key to lookup information about the individual entries (because they all have the same key).
In other words there are two types of possible keys:
"lookup keys" = standard keys with varying indexes in the use attribute
"looping keys" = keys with a constant in the use attribute
I do not know how efficient this method is to execute, it does however make the maintenance of the XSL more efficient by avoiding repetition of the same (potentially very complex) XPath expression throughout the XSL code.
Rather than think of the XSL keys in programming language terms, think of them as record sets of SQL. That will give a better understanding. For a given key index created as
<xsl:key name="paths" match="path" use="keygenerator()">
it can be "iterated"/"walk-through" as below
<xsl:for-each select="//path[generate-id()=generate-id(key('paths',keygenerator())[1])]">
To understand this magic number [1], let s go through the below example :
Consider this XML snippet
<root>
<Person>
<name>Johny</name>
<date>Jan10</date>
<cost itemID="1">34</cost>
<cost itemID="1">35</cost>
<cost itemID="2">12</cost>
<cost itemID="3">09</cost>
</Person>
<Person>
<name>Johny</name>
<date>Jan09</date>
<cost itemID="1">21</cost>
<cost itemID="1">41</cost>
<cost itemID="2">11</cost>
<cost itemID="2">14</cost>
</Person>
</root>
transformed using this XSL.
<xsl:for-each select="*/Person">
<personrecords>
<xsl:value-of select="generate-id(.)" />--
<xsl:value-of select="name"/>--
<xsl:value-of select="date"/>--
</personrecords>
</xsl:for-each>
<xsl:for-each select="*/*/cost">
<costrecords>
<xsl:value-of select="generate-id(.)" />--
<xsl:value-of select="../name"/>--
<xsl:value-of select="../date"/>--
<xsl:value-of select="#itemID"/>--
<xsl:value-of select="text()"/>
</costrecords>
</xsl:for-each>
The above XSL transformation lists the unique id of the Person nodes and the cost nodes in the form of idpxxxxxxx as the result below shows.
1. <personrecords>idp2661952--Johny--Jan10-- </personrecords>
2. <personrecords>idp4012736--Johny--Jan09-- </personrecords>
3. <costrecords>idp2805696--Johny-- Jan10-- 1-- 34</costrecords>
4. <costrecords>idp4013568--Johny-- Jan10-- 1-- 35</costrecords>
5. <costrecords>idp2808192--Johny-- Jan10-- 2-- 12</costrecords>
6. <costrecords>idp2808640--Johny-- Jan10-- 3-- 09</costrecords>
7. <costrecords>idp2609728--Johny-- Jan09-- 1-- 21</costrecords>
8. <costrecords>idp4011648--Johny-- Jan09-- 1-- 41</costrecords>
9. <costrecords>idp2612224--Johny-- Jan09-- 2-- 11</costrecords>
10.<costrecords>idp2610432--Johny-- Jan09-- 2-- 14</costrecords>
Let us create a key on the cost records using a combination of name and itemID values.
<xsl:key name="keyByNameItem" match="cost" use="concat(../name, '+', #itemID)"/>
Manually looking at the XML, the number of unique keys for the above would be three : Johny+1, Johny+2 and Johny+3.
Now lets test out this key by using the snippet below.
<xsl:for-each select="*/*/cost">
<costkeygroup>
<xsl:value-of select="generate-id(.)" />--
(1)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', #itemID) )[1] ) " />--
(2)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', #itemID) )[2] ) " />--
(3)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', #itemID) )[3] ) " />--
(4)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', #itemID) )[4] ) " />
</costkeygroup>
</xsl:for-each>
And here is the result:
1. <costkeygroup>idp2805696-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
2. <costkeygroup>idp4013568-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
3. <costkeygroup>idp2808192-- (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)</costkeygroup>
4. <costkeygroup>idp2808640-- (1)idp2808640-- (2)-- (3)-- (4)</costkeygroup>
5. <costkeygroup>idp2609728-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
6. <costkeygroup>idp4011648-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
7. <costkeygroup>idp2612224-- (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)</costkeygroup>
8. <costkeygroup>idp2610432-- (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)</costkeygroup>
Our interest is in trying to understand the importance of [1],[2], [3],[4]. In our case, the keygenerator is concat(../name, '+', #itemID).
For a given key, [1] refers to the first occurence of a node that satisfies the keygenerator. Similarly [2] refers to the second occurence of a node that satisfies the keygenerator. Thus [2], [3],[4], etc. are all nodes that satisfy the same key, and thus can be considered duplicates for the given key. The number of duplicates depends on the input XML. Thus:
Key Johny+1 satisfies 4 nodes (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648
Key Johny+2 satisfies 3 nodes (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)
Key Johny+3 satisfies 1 node (1)idp2808640-- (2)-- (3)-- (4)
Thus we see that ALL 8 cost nodes of the XML can be accessed through the key.
Here is a image that combines the transformation results to help better understand.
The red squares indicate the matching nodes for Johny+1. The green squares indicate the matching nodes for Johny+3. Match the idpxxxxxxx values in <costkeygroup> to the values in <costrecords>. The <costrecords> help map the idpxxxxxxx values to the source XML.
The takeaway is that,
an XSL key does not filter or eliminate nodes. All nodes including duplicates can be accessed through the key. Thus when we say "walk through" of the key, there is no concept of a resultant subset of nodes from the original set of nodes made available to the key for processing.
To "walk through" only unique nodes of the key in the above example, use
<xsl:for-each select="*/*/workTime[generate-id()=generate-id(key('keyByNameItem', concat(../name, '+', #itemID) )[1] ) ] ">
[1] signifies that the first record for a given key value is denoted as the unique record. [1] is almost always used because there will exist at least one node that satisfies a given key value. If we are sure that there will be a minimum of 2 records to satisfy each key value in the key, we can go ahead and use [2] to identify the second record in the record set as the unique record.
P.S The words nodes / records / elements are used interchangeably.
There is no way to walk-through the keys, although we can output all the values it contains. In XSLT2 it is quite easier than in XSLT1 (e.g., using fn:generate-id according to the previous answer).
Using fn:distinct-values
<xsl:variable name="e" select="."/>
<xsl:for-each select="distinct-values(Element/Element[#idref]/#idref)">
<li key="{.}"><xsl:value-of select="key('kElement', ., $e )" /></li>
</xsl:for-each>
Using xsl:for-each-group
<xsl:for-each-group select="Element/Element[#idref]" group-by="#idref">
<li key="{current-grouping-key()}"><xsl:value-of select="current-group()" /></li>
</xsl:for-each-group>