From my understanding of XSLT I can't figure out what does it match.
<xsl:template match="*|/">
XPath matching is relative to a 'current node' (i.e. current position) in the XML being matched.
* matches all child elements of the current node, whereas / matches the root - and only the root - regardless of what the current node is.
Note also that the root is NOT the root element of the XML, it is actually a level above it - the root element is a child of the root.
If you have this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/|*">
<item name="{name()}">
<xsl:apply-templates/>
</item>
</xsl:template>
</xsl:stylesheet>
and you apply it to this XML:
<rootElement>
<subElement1/>
<subElement2/>
</rootElement>
you get this:
<item name="">
<item name="rootElement">
<item name="subElement1" />
<item name="subElement2" />
</item>
</item>
the first <item name=""> correspond to the match of / (that is not an element, and so does not have a name), the others are the matches of *.
Related
I read expressions like this
<xsl:variable name="myVar" select="$data[not(key('myKey',#myRef))]"/>
in legacy code. Most likeley it is code from experts ;-). I'm wondering what it does, how it works and how i could reeng it in order to make it more readable. Thank you.
Keys are an important aspect of XSLT. Instead of re-engineering them, it's better to learn the concept.
Keys can be understood as tables with nodes stored under specific keys. They are defined like this:
<xsl:key name="addressByStreet" match="address" use="street"/>
The name attribute is just a QName (similar to a variable name). The match attribute holds an XPath expression that works similarly to the match attribute of <xsl:template>. When the processor finds a node that matches the expression, it evaluates the XPath expression of the use attribute in the context of the matched element. If this expression returns values, they will be used to create new entries in the "key table" for the matched element.
To illustrate that: The above key creates a table with all the <address> elements in the processed document, keyed by the value of their <street> child. This means, if you have these elements:
<address>
<street>Main Street</street>
<number>123</number>
</address>
<address>
<street>Main Street</street>
<number>456</number>
</address>
<address>
<street>Country Road</street>
<street>Country Rd.</street>
<number>789</number>
</address>
… you could then use key('addressByStreet', 'Main Street') to retrieve all the listed addresses in Main Street.
You can use both key('addressByStreet', 'Country Road') and key('addressByStreet', 'Country Rd.') to retrieve the last address.
Why use keys here? The above expression could be re-implemented like //address[street='Main Street'], but now every time this expression is called, the XSLT processor likely goes through the entire document again. That's a problem if a template or loop is called often. Keys can have huge performance benefits (e.g. reduce complexity from O(n²) to O(n)) because the results are "cached".
There are many applications and patterns in which keys are used. For example if you have this XML:
<street-list>
<street>Main Street</street>
<street>Bumpy Road</street>
</street-list>
The expression street-list/street[not(key('addressByStreet', .))] will filter the list of streets and only return streets for which there is no address in the above list – i.e. only "Bumpy Road" in this case because for "Main Street", a key entry exists.
A typical application of keys in XSLT 1 is Muenchian grouping.
I've got the use case now. No, it is not legacy code. This is clear from context and definition of key and data.
If we have data like this:
<xsl:variable name="dict">
<ITEMS>
<ITEM id="1" content="it1">
<ITEM-REF ref="3"/>
</ITEM>
<ITEM id="2" content="it2">
<ITEM-REF ref="1"/>
</ITEM>
<ITEM id="3" content="it3">
<ITEM-REF ref="6"/>
</ITEM>
<ITEM id="4" content="it4">
<ITEM-REF ref="3"/>
</ITEM>
<ITEM id="5" content="it5">
<ITEM-REF ref="5"/>
</ITEM>
<ITEM id="6" content="it6">
<ITEM-REF ref="8"/>
</ITEM>
<ITEM id="7" content="it7">
<ITEM-REF ref="9"/>
</ITEM>
</ITEMS>
</xsl:variable>
And we want to get all ITEM-REF elements with #ref values where there is no ITEM with the same #id value (broken links) the expression can help out:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:variable name="dict">
<ITEMS>
<ITEM id="1" content="it1">
<ITEM-REF ref="3"/>
</ITEM>
<ITEM id="2" content="it2">
<ITEM-REF ref="1"/>
</ITEM>
<ITEM id="3" content="it3">
<ITEM-REF ref="6"/>
</ITEM>
<ITEM id="4" content="it4">
<ITEM-REF ref="3"/>
</ITEM>
<ITEM id="5" content="it5">
<ITEM-REF ref="5"/>
</ITEM>
<ITEM id="6" content="it6">
<ITEM-REF ref="8"/>
</ITEM>
<ITEM id="7" content="it7">
<ITEM-REF ref="9"/>
</ITEM>
</ITEMS>
</xsl:variable>
<xsl:key name="itemkey" match="ITEM" use="#id"/>
<xsl:template match="START">
<xsl:variable name="allItems" select="msxsl:node-set($dict)//ITEM"/>
<xsl:variable name="allItemRefs" select="msxsl:node-set($dict)//ITEM-REF"/>
<xsl:variable name="itemRefsNotReferencingOtherItems" select="$allItemRefs[not(key('itemkey',#ref))]"/>
<REFERENCED-NOT-EXISTING>
<xsl:for-each select="msxsl:node-set($itemRefsNotReferencingOtherItems)">
<ITEM>
<xsl:attribute name="id">
<xsl:value-of select="#ref"/>
</xsl:attribute>
</ITEM>
</xsl:for-each>
</REFERENCED-NOT-EXISTING>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>
<REFERENCED-NOT-EXISTING>
<ITEM id="8" />
<ITEM id="9" />
</REFERENCED-NOT-EXISTING>
Input file:
<?xml version="1.0" encoding="utf-8"?>
<START/>
I have an XML file with this structure:
<DetailTxt>
<Text>
<span>Some Text</span>
</Text>
<TextComplement Kind="Owner" MarkLbl="1">
<ComplCaption>
Caption 1
</ComplCaption>
<ComplBody>
Body 1
</ComplBody>
</TextComplement>
<Text>
<span>More Text</span>
</Text>
</DetailTxt>
Here is the part of the XSLT that is relevant here:
<xsl:template match="*[local-name() = 'DetailTxt']">
<xsl:apply-templates select="*[local-name() = 'Text']"/>
</xsl:template>
<xsl:template match="*[local-name() = 'Text']">
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl,']')" />
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
</xsl:template>
I expect the output to look like this:
<item name="Text">
<richtext>
<par>
<run><![CDATA[
<span>Some Text</span>
</p>]]></run>
</par>
</richtext>
</item>
<item name="Text">
<richtext>
<par>
<run><![CDATA[[Owner1]]]></run>
</par>
</richtext>
</item>
But the line using the TextComplement XPath looks like this:
<run><![CDATA[[]]]></run>
All values from TextComplement are missing. Whats wrong with the XPath here?
EDIT: I completely reworked my question and put in a CONCRETE question resulting from the first answer. That kind of invalidates the first answer but IMHO improves the question.
Not sure how the XSLT looks like but you can try adding the following template with the concat() function for getting the output.
<xsl:template match="Text">
<document version="9.0" form="Form1">
<item name="{local-name()}">
<xsl:copy-of select="span" />
</item>
<item name="{local-name()}">
<span>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl, ']')" />
</span>
</item>
</document>
</xsl:template>
This template is applied to the <Text> node and the ../ is used to go up one level and then access the attributes of <TextComplement> using the XPath.
The output of the template when applied to your XML will look like.
<document form="Form1" version="9.0">
<item name="Text">
<span>Some Text</span>
</item>
<item name="Text">
<span>[Owner1]</span>
</item>
</document>
The same template will also get applied to the <Text> node having More Text content and produce similar output.
I found a solution myself for the concrete question. I quess this is IBM Notes / LotusScript specific issue.
When using the selector
../TextComplement/#Kind
the parser returned an empty string. I changed to
../*[local-name() = 'TextComplement']/#Kind
and later (more concrete) to:
./following-sibling::*[local-name() = 'TextComplement']/#Kind
And that worked. I personally see no difference in these notations, but it seams that internally they are handled differently.
I am learning XSLT 2.0 and XPath. While creating the examples I came across to preceding axis available in XPath and created the below example.
Please find the below order.xml file used as input.
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Order id="12345">
<Item>
<ItemId>007</ItemId>
<ItemName>iPhone 5</ItemName>
<Price>500</Price>
<Quantity>1</Quantity>
</Item>
<Item>
<ItemId>456</ItemId>
<ItemName>Ipad</ItemName>
<Price>600</Price>
<Quantity>2</Quantity>
</Item>
<Item>
<ItemId>7864567</ItemId>
<ItemName>Personal Development Book</ItemName>
<Price>10</Price>
<Quantity>10</Quantity>
</Item>
<Item>
<ItemId>123</ItemId>
<ItemName>Java Book</ItemName>
<Price>20</Price>
<Quantity>12</Quantity>
</Item>
</Order>
Please find the below XSLT testaxis.xsl file used for transformation of the above XML.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="count(/Order/Item[ItemName='Ipad']/ItemName/preceding::*)" />
</xsl:template>
The output after transformation is
6
Here context node is below one if I am not wrong.
<ItemName>Ipad</ItemName>
If we count all the nodes which are before the context node then counts come to 5 .
Now coming to the question, why it is showing the count of the nodes as 6 in the output?
Please do let me know if I misunderstood anything
Thanks in advance.
You are correct about which node is the context node, and that node does have 6 preceding elements:
The first <Item> element.
The four elements inside that.
The <ItemId> element immediately before the context node.
That makes six. You can verify this by doing the following:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each select="/Order/Item[ItemName='Ipad']/ItemName/preceding::*">
<xsl:value-of select="concat(name(), '
')" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The preceding elements are the ones marked below with a "*"
<*Item>
<*ItemId>007</ItemId>
<*ItemName>iPhone 5</ItemName>
<*Price>500</Price>
<*Quantity>1</Quantity>
</Item>
<Item>
<*ItemId>456</ItemId>
You will see that there are six of them.
I have a xml need to group by category
I try to use
<xsl:for-each-group select="ItemList/Item" group-by="CategoryID">
How can I get the CategoryName, CategoryID or any information related to Category
during the transformation?
Original XML
<ItemList>
<Item>
<CategoryID>1</CategoryID>
<CategoryName>Book</CategoryName>
<ItemName>ASP.NET</ItemName>
<ItemDetails>ASP.NET12345</ItemDetails>
</Item>
<Item>
<CategoryID>1</CategoryID>
<CategoryName>Book</CategoryName>
<ItemName>PHP</ItemName>
<ItemDetails>PHP12345</ItemDetails>
</Item>
<Item>
<CategoryID>2</CategoryID>
<CategoryName>Tool</CategoryName>
<ItemName>ToolAbcde</ItemName>
<ItemDetails>sth details</ItemDetails>
</Item></ItemList>
New XML
<NewXML>
<Category>
<CategoryID>1</CategoryID>
<CategoryName>Book</CategoryName>
<ItemList>
<Item>
<ItemName>ASP.NET</ItemName>
<ItemDetails>ASP.NET12345</ItemDetails>
</Item>
<Item>
<ItemName>PHP</ItemName>
<ItemDetails>PHP12345</ItemDetails>
</Item>
<ItemName>PHP</ItemName>
</ItemList>
</Category>
<Category>
<CategoryID>2</CategoryID>
<CategoryName>Tool</CategoryName>
<ItemList>
<Item>
<ItemName>ToolAbcde</ItemName>
<ItemDetails>sth details</ItemDetails>
</Item>
</ItemList>
</Category></NewXML>
The xslt spec has an extensive doucmentation on grouping, complete with examples at http://www.w3.org/TR/xslt20/#xsl-for-each-group. You are probably looking for the current-group() and current-grouping-key() functions. Also
Within the sequence constructor, the context item is the initial item of the relevant group, the context position is the position of this item among the sequence of initial items (one item for each group) arranged in processing order of the groups, the context size is the number of groups, the current group is the group being processed, and the current grouping key is the grouping key for that group.
Meaning that these two expressions would be equivalent inside xsl:for-each-group: CategoryId and current-group()[1]/CategoryId. Since CategoryId is the grouping key this is also the same as current-grouping-key(). Here is a complete example for your use case:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" encoding="utf-8" />
<xsl:template match="/">
<NewXml>
<xsl:for-each-group select="ItemList/Item" group-by="CategoryID">
<Category>
<CategoryID><xsl:value-of select="CategoryID" /></CategoryID>
<CategoryName><xsl:value-of select="CategoryName" /></CategoryName>
<ItemList>
<xsl:for-each select="current-group()">
<Item>
<ItemName><xsl:value-of select="ItemName" /></ItemName>
<ItemDetails><xsl:value-of select="ItemDetails" /></ItemDetails>
</Item>
</xsl:for-each>
</ItemList>
</Category>
</xsl:for-each-group>
</NewXml>
</xsl:template>
</xsl:stylesheet>
given this xml:
<root>
<list>
<!-- foo's comment -->
<item name="foo" />
<item name="bar" />
<!-- another foo's comment -->
<item name="another foo" />
</list>
</root>
I'd like to use a XPath to select all item-nodes that have a comment immediately preceding them, that is I like to select the "foo" and "another foo" items, but not the "bar" item.
I already fiddled about the preceding-sibling axis and the comment() function but to no avail.
This seems to work:
//comment()/following-sibling::*[1]/self::item
It looks for immediately following siblings of comments which are also <item> elements. I don't know a better way to express the ::*[1]/self::item part, which is ugly; note that if it were written ::item[1] then it would also find <item>s not immediately proceded by a comment.
The currently selected solution:
//comment()/following-sibling::*[1]/self::item
doesn't work in the case where there is a procesing instruction (or a whole group of processing instructions) between the comment and the element -- as noticed in a comment by Martin Honnen.
The solution below doesn't have such a problem.
The following XPath expression selects only elements nodes that are either immediately preceded by a comment node, or are immediately preceded by a white-space-only text node, which is immediately preceded by a comment node:
(//comment()
/following-sibling::node()
[1]
[self::text()
and
not(normalize-space())
]
/following-sibling::node()
[1] [self::item]
)
|
(//comment()
/following-sibling::node()
[1]
[self::item]
)
Here is a complete test:
We use this XML document:
<root>
<list>
<!-- foo's comment -->
<item name="foo" />
<item name="bar" />
<!-- another foo's comment -->
<item name="another foo" />
<!-- comment 3 --><item name="immed.after 3"/>
<!-- comment 4 --><?PI ?><item name="after PI"/>
</list>
</root>
When the following transformation is applied on the above 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:template match="/">
<xsl:copy-of select=
"
(//comment()
/following-sibling::node()
[1]
[self::text()
and
not(normalize-space())
]
/following-sibling::node()
[1] [self::item]
)
|
(//comment()
/following-sibling::node()
[1]
[self::item]
)
"/>
</xsl:template>
</xsl:stylesheet>
the wanted, correct result is produced:
<item name="foo"/>
<item name="another foo"/>
<item name="immed.after 3"/>
As mentioned in this thread, introducing a test (<xsl:if test="..."></xsl:if>) like:
preceding-sibling::comment()
would only tests whether the node has a preceding sibling that's a comment.
If you want to know, of the preceding siblings that are elements or comments, whether the nearest one is a comment, you could try:
(preceding-sibling::*|preceding-sibling::comment())[1][self::comment()] # WRONG
BUT: that won't work, because though "[1]" means first in the backwards direction
for preceding-sibling, it doesn't mean that for a parenthesized expression - it
means first in document order
You can try:
(preceding-sibling::*|preceding-sibling::comment())[last()][self::comment()]
or
preceding-sibling::node()[self::*|self::comment()][1][self::comment()]
For instance:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output omit-xml-declaration="no" indent="no"/>
<xsl:template match="//item">
<xsl:if test="preceding-sibling::node()[self::*|self::comment()][1][self::comment()]">
<xsl:value-of select="./#name" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
would only display:
foo
another foo
when typing:
C:\Prog\xslt\preceding-sibling_comment>
java -cp ..\saxonhe9-2-0-6j\saxon9he.jar net.sf.saxon.Transform -s:test.xml -xsl:t.xslt -o:res.xml
with:
test.xml: your file displayed in your question
t.xslt: the xslt file above
res.xml: the resulting transformed file
Edit: since it doesn't take into account processing instructions, I left that answer as Community Wiki.