XSLT a little confused - xslt

Having a little trouble with XSLT.. I think I may be going about it completely the wrong way..
Trying to display the customer name with the SKU of items with special 1 status in a line...
then the customer with the special 2 items etc. then part 2(which I haven't started) the items without a status by themselves
so for this XML file the output would be
Joe prod1 //special1
Joe prod3 //special2
Joe prod2 //no status
Joe prod4 //no status
Joe prod5 //no status
John Smith prod6 prod8 //special1
John Smith prod7 //no status
John Smith prod9 //no status
John Smith prod10 //no status
It kind of works at the moment but the problem is that if there is no special1 or special2 I can't figure out how to make it not print the Customer name..
and I'm not sure how to display the ones with no status afterwards either - any help would be much appreciated!
XML:
<customer>
<name>Joe</name>
<order>
<item>
<SKU>prod1</SKU>
<status>special1</status>
</item>
<item>
<SKU>prod2</SKU>
</item>
<item>
<SKU>prod3</SKU>
<status>special2</status>
</item>
<item>
<SKU>prod4</SKU>
</item>
<item>
<SKU>prod5</SKU>
</item>
</order>
</customer>
<customer
<name>John Smith</name>
<order>
<item>
<SKU>prod6</SKU>
<status>special1</status>
</item>
<item>
<SKU>prod7</SKU>
</item>
<item>
<SKU>prod8</SKU>
<status>special1</status>
</item>
<item>
<SKU>prod9</SKU>
</item>
<item>
<SKU>prod10</SKU>
</item>
</order>
XSLT:
<!DOCTYPE xsl:stylesheet[ <!ENTITY nl "
"> ]>
<xsl:template match="customer">
<xsl:value-of select="name" /><xsl:apply-templates select="order/item[status='special1']" /><xsl:text>&nl;</xsl:text>
<xsl:value-of select="name" /><xsl:apply-templates select="order/item[status='special2']" /><xsl:text>&nl;</xsl:text>
</xsl:template>
<xsl:template match="item[status='special1']"><xsl:text> </xsl:text><xsl:value-of select="SKU" /></xsl:template>
<xsl:template match="item[status=special2']"><xsl:text> </xsl:text><xsl:value-of select="SKU" /></xsl:template>
<xsl:template match="text()"/>

Your simplest option is just an xsl:if
<xsl:template match="customer">
<xsl:if test="order/item[status='special1']">
<xsl:value-of select="name" /><xsl:apply-templates select="order/item[status='special1']" /><xsl:text>&nl;</xsl:text>
</xsl:if>
<xsl:if test="order/item[status='special2']">
<xsl:value-of select="name" /><xsl:apply-templates select="order/item[status='special2']" /><xsl:text>&nl;</xsl:text>
</xsl:if>
</xsl:template>

I am assuming that you do not know a priori the set of different status. So, if you wan't your XML to be maintainable (without having to change it each time that you add a different status) you could use the following solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<!-- Use the following key for grouping -->
<xsl:key name="status-key"
match="item"
use="status" />
<!-- Cache the following operation to avoid doing it several times in the future.
You can improve performance by changing // to a fixed path in your XML where
all the items are (e.g. /customers/customer/order/item) -->
<xsl:variable name="item-group"
select="//item[generate-id(.) = generate-id(key('status-key', status)[1])]" />
<xsl:template match="customer">
<!-- Obtain the following values before losing the current context -->
<xsl:variable name="current-id" select="generate-id(.)" />
<xsl:variable name="current-name" select="name" />
<!-- Display the products with a status defined -->
<xsl:for-each select="$item-group">
<!-- Obtain list of status for this costumer -->
<xsl:variable name="customer-status"
select="key('status-key', status)[generate-id(../..) = $current-id]" />
<!-- Print the status information if the costumer has at least one status -->
<xsl:if test="$customer-status">
<!-- Display the name of the costumer -->
<xsl:value-of select="$current-name" />
<!-- Group the product by status -->
<xsl:for-each select="$customer-status">
<xsl:value-of select="concat(' ', SKU)" />
</xsl:for-each>
<!-- Output the status -->
<xsl:value-of select="concat(' //', status, '
')" />
</xsl:if>
</xsl:for-each>
<!-- Display the prodcuts without status -->
<xsl:for-each select="order/item[not(status)]">
<xsl:value-of select="concat($current-name, ' ', SKU, ' //no-status
')" />
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>

This is an example of a 'grouping' problem. You are trying to group items by a combination of customer and status. The approach you take to solve this differs by whether you are using XSLT 1.0 or XSLT 2.0. In XSLT 1.0 you would use a technique called Muenchian Grouping. You start off by defing a key to hold the nodes you are grouping. In this case, you are grouping by customer and item
<xsl:key name="items" match="item" use="concat(generate-id(../..), '|', status)" />
Then, for each customer element you match, you would get the distinct status elements of each item like so:
<xsl:apply-templates select="order/item
[status != '']
[generate-id() = generate-id(key('items', concat(generate-id(../..), '|', status))[1])]" />
Essentially what this is doing is looking at each item for the customer and selecting the one that occurs first in the key you defined for the status element.
Then, in the template that matches the items with a status, you can then get the individual items with the same status like so:
<xsl:apply-templates select="key('items', concat(generate-id(../..), '|', status))/SKU" />
Selecting items with no status though is much easier
<xsl:apply-templates select="order/item[not(status != '')]" />
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="items" match="item" use="concat(generate-id(../..), '|', status)" />
<xsl:template match="customer">
<xsl:apply-templates select="order/item[status != ''][generate-id() = generate-id(key('items', concat(generate-id(../..), '|', status))[1])]" />
<xsl:apply-templates select="order/item[not(status != '')]" />
</xsl:template>
<xsl:template match="item[status !='']">
<xsl:value-of select="../../name" />
<xsl:apply-templates select="key('items', concat(generate-id(../..), '|', status))/SKU" />
<xsl:value-of select="concat(' //', status, '
')" />
</xsl:template>
<xsl:template match="item">
<xsl:value-of select="concat(../../name, ' ', SKU, ' // no status
')" />
</xsl:template>
<xsl:template match="SKU">
<xsl:value-of select="concat(' ', .)" />
</xsl:template>
</xsl:stylesheet>
When applied to you XML, the following is output:
Joe prod1 //special1
Joe prod3 //special2
Joe prod2 // no status
Joe prod4 // no status
Joe prod5 // no status
John Smith prod6 prod8 //special1
John Smith prod7 // no status
John Smith prod9 // no status
John Smith prod10 // no status
If you were using XSLT2.0 it becomes a bit simpler to follow, because you can make us of xsl:for-each-group to handle the grouping:
<xsl:for-each-group select="order/item[status != '']" group-by="status">
And then to get the items with the group, you use the current-group() function
<xsl:apply-templates select="current-group()/SKU" />
Here is the full XSLT2.0 stylesheet which should also output the same results:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="customer">
<xsl:for-each-group select="order/item[status != '']" group-by="status">
<xsl:value-of select="../../name" />
<xsl:apply-templates select="current-group()/SKU" />
<xsl:value-of select="concat(' //', status, '
')" />
</xsl:for-each-group>
<xsl:apply-templates select="order/item[not(status != '')]" />
</xsl:template>
<xsl:template match="item">
<xsl:value-of select="concat(../../name, ' ', SKU, ' // no status
')" />
</xsl:template>
<xsl:template match="SKU">
<xsl:value-of select="concat(' ', .)" />
</xsl:template>
</xsl:stylesheet>

Related

How to select range's between IDs

Please suggest for how to select the range's between IDs. Example if range is 5-8, then 6,7 are required ids. If figs <link href="fig3">-<link href="fig7">, then fig4 fig5 fig6 are required IDs.
XML:
<root>
<p id="p1">This <link href="#fig-0001 #fig-0002"/>, <link href="#fig-0003"/>-<link href="#fig-0006"/></p>
<figure xml_id="fig-0001"><label>Fig. 1</label><caption><p>One</p></caption></figure>
<figure xml_id="fig-0002"><label>Fig. 2</label><caption><p>Two</p></caption></figure>
<figure xml_id="fig-0003"><label>Fig. 3</label><caption><p>Three</p></caption></figure>
<figure xml_id="fig-0004"><label>Fig. 4</label><caption><p>Four</p></caption></figure>
<figure xml_id="fig-0005"><label>Fig. 5</label><caption><p>Five</p></caption></figure>
<figure xml_id="fig-0006"><label>Fig. 6</label><caption><p>Six</p></caption></figure>
</root>
XSLT2:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy>
</xsl:template>
<xsl:key name="kFloat" match="*" use="#xml_id"/>
<xsl:template match="link[ancestor::p/following-sibling::*[1][matches(name(), '^(figure)$')]][matches(key('kFloat', if(contains(#href, ' ')) then substring-after(substring-before(#href, ' '), '#') else substring-after(#href, '#'))/name(), '^(figure)$')]">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy><!-- link element retaining-->
<!--Range between IDs selection -->
<xsl:if test="matches(preceding-sibling::node()[1][self::text()], '^(&#x2013;|&#x02013;|–|-)$')">
<xsl:variable name="varRangeFirst" select="substring-after(preceding-sibling::node()[2][name()='link']/#href, '#')"/>
<xsl:variable name="varRangeLast" select="substring-after(#href, '#')"/>
<xsl:variable name="varRangeBetweenIDs1">
<!--xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure return $i/#xml_id"/--><!-- this will select all preceding figures, but it should between 3 and 6 -->
<xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure[for $k in preceding-sibling::figure return contains($k/#xml_id, $varRangeFirst)] return $i/#xml_id"/><!-- here getting error--><!-- please suggest to select range's between IDs from this -->
<!--xsl:if test="matches(key('kFloat', $varRangeLast)/name(), '^(figure)$')">
<xsl:for-each select="key('kFloat', $varRangeLast)/preceding-sibling::figure">
<a><xsl:value-of select="#xml_id"/></a>
</xsl:for-each>
</xsl:if-->
</xsl:variable>
<xsl:for-each select="$varRangeBetweenIDs1/a">
<xsl:variable name="var2"><xsl:value-of select="preceding-sibling::a"/></xsl:variable>
<xsl:if test="contains($var2, $varRangeFirst)">
<xsl:element name="float"><xsl:attribute name="id" select="."/></xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
<xsl:for-each select="tokenize(#href, ' ')"><!--for each link's individual hrefs will have respective float element -->
<float id="{substring-after(., '#')}"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Required Result:
<root>
<p id="p1">This <link href="#fig-0001 #fig-0002"/><float id="fig-0001"/><float id="fig-0002"/>, <link href="#fig-0003"/><float id="fig-0003"/>-<link href="#fig-0006"/><float id="fig-0004"/><float id="fig-0005"/><float id="fig-0006"/></p>
<figure xml_id="fig-0001"><label>Fig. 1</label><caption><p>One</p></caption></figure>
<figure xml_id="fig-0002"><label>Fig. 2</label><caption><p>Two</p></caption></figure>
<figure xml_id="fig-0003"><label>Fig. 3</label><caption><p>Three</p></caption></figure>
<figure xml_id="fig-0004"><label>Fig. 4</label><caption><p>Four</p></caption></figure>
<figure xml_id="fig-0005"><label>Fig. 5</label><caption><p>Five</p></caption></figure>
<figure xml_id="fig-0006"><label>Fig. 6</label><caption><p>Six</p></caption></figure>
</root>
I couldn't quite work out your logic from your current XSLT, so I would consider a different approach, using templates to match the various types of link element you required. Specifically, have separate ones for link elements that precede or follow a text node with a hyphen in.
Try this XSLT. This makes use of the intersect function to get the range of elements you require in the case of 003-006.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kFloat" match="figure" use="#xml_id"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy>
</xsl:template>
<xsl:template match="p/link[following-sibling::node()[1][self::text()][. = '-']][following-sibling::node()[2][self::link]]" priority="3">
<xsl:call-template name="identity" />
<xsl:apply-templates select="key('kFloat', substring-after(#href, '#'))" mode="float" />
</xsl:template>
<xsl:template match="p/link[preceding-sibling::node()[1][self::text()][. = '-']][preceding-sibling::node()[2][self::link]]" priority="2">
<xsl:call-template name="identity" />
<xsl:variable name="firstLink" select="preceding-sibling::node()[2]" />
<xsl:apply-templates select="key('kFloat', substring-after($firstLink/#href, '#'))/following-sibling::figure intersect key('kFloat', substring-after(#href, '#'))/preceding-sibling::figure" mode="float" />
<xsl:apply-templates select="key('kFloat', substring-after(#href, '#'))" mode="float" />
</xsl:template>
<xsl:template match="p/link[#href]">
<xsl:next-match />
<xsl:variable name="doc" select="/" />
<xsl:for-each select="for $ref in tokenize(#href, ' ') return substring-after($ref, '#')">
<xsl:apply-templates select="key('kFloat', ., $doc)" mode="float" />
</xsl:for-each>
</xsl:template>
<xsl:template match="figure" mode="float">
<float id="{#xml_id}"/>
</xsl:template>
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/ncdD7nu
With the help from Tim Sir's suggestion, I modified my answer as below.
<xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure
[some $k in preceding-sibling::figure satisfies
contains($k/#xml_id, $varRangeFirst)] return $i/#xml_id"/>
XSLT2:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy>
</xsl:template>
<xsl:key name="kFloat" match="*" use="#xml_id"/>
<xsl:template match="link[ancestor::p/following-sibling::*[1][matches(name(), '^(figure)$')]][matches(key('kFloat', if(contains(#href, ' ')) then substring-after(substring-before(#href, ' '), '#') else substring-after(#href, '#'))/name(), '^(figure)$')]">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy><!-- link element retaining-->
<!--Range between IDs selection -->
<xsl:if test="matches(preceding-sibling::node()[1][self::text()], '^(&#x2013;|&#x02013;|–|-)$')">
<xsl:variable name="varRangeFirst" select="substring-after(preceding-sibling::node()[2][name()='link']/#href, '#')"/>
<xsl:variable name="varRangeLast" select="substring-after(#href, '#')"/>
<xsl:variable name="varRangeBetweenIDs1">
<xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure[some $k in preceding-sibling::figure satisfies contains($k/#xml_id, $varRangeFirst)] return $i/#xml_id"/>
</xsl:variable>
<xsl:for-each select="tokenize($varRangeBetweenIDs1, ' ')">
<xsl:element name="float"><xsl:attribute name="id" select="."/></xsl:element>
</xsl:for-each>
</xsl:if>
<xsl:for-each select="tokenize(#href, ' ')"><!--for each link's individual hrefs will have respective float element -->
<float id="{substring-after(., '#')}"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Getting value in between keys using XSLT

I Need to get the value in the XML file in between each key. For example, I have a list of keys to be used, and each key there's a corresponding output element. The keys can be placed anywhere, there is no proper order in where the key is needed to place. I need to do this in XSLT 2.0, and I don't have any idea on how will I do this.
Keys: Element:
/OPDH/ - ROOT/ELEMENT1/ABCD
/EKPH/ - ROOT/ELEMENT2/POIU
/SGDE/ - ROOT/ELEMENT3/WXYZ
...some other keys...
NOTE: Keys: is in BOLD, and Element is in ITALIC BOLD.
If I have a sample input like this:
1.)
<DATA>/OPDH/FLOWING SOLUTION/SGDE/Number0983713/EKPH/Sample test/some other keys/</DATA>
OR it can be:
2.)
<DATA>/some other keys/afdsf/SGDE/Number0983713/some other keys/PIHSAGA/OPDH/FLOWING SOLUTION/some other keys/No exception/EKPH/Sample test/some other keys/</DATA>
The expected output should look like this:
1.
<ROOT>
<ELEMENT1>
<ABCD>FLOWING SOLUTION</ABCD>
</ELEMENT1>
<ELEMENT2>
<POIU>Sample test</POIU>
</ELEMENT2>
<ELEMENT3>
<SGDE>Number0983713</SGDE>
</ELEMENT3>
...some other keys...
</ROOT>
2.
<ROOT>
...some other keys...
<ELEMENT3>
<SGDE>Number0983713</SGDE>
</ELEMENT3>
...some other keys...
<ELEMENT1>
<ABCD>FLOWING SOLUTION</ABCD>
</ELEMENT1>
...some other keys...
<ELEMENT2>
<POIU>Sample test</POIU>
</ELEMENT2>
...some other keys...
</ROOT>
Thank you.
Here is a partial suggestion that uses analyze-string:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf" version="2.0">
<xsl:param name="keys">
<element key="/OPDH/">ROOT/ELEMENT1/ABCD</element>
<element key="/EKPH/">ROOT/ELEMENT2/POIU</element>
<element key="/SGDE/">ROOT/ELEMENT3/WXYZ</element>
<element key="/some other keys/">ROOT/FOO/BAR</element>
</xsl:param>
<xsl:output indent="yes"/>
<xsl:variable name="pattern" as="xs:string"
select="concat('(', string-join($keys/element/#key, '|'), ')', '(.*?)', '(', string-join($keys/element/#key, '|'), ')')"/>
<xsl:key name="ref" match="element" use="#key"/>
<xsl:function name="mf:extract" as="element()*">
<xsl:param name="input" as="xs:string"/>
<xsl:analyze-string select="$input" regex="{$pattern}">
<xsl:matching-substring>
<xsl:if test="position() eq 1">
<element path="{key('ref', regex-group(1), $keys)}">
<xsl:value-of select="regex-group(2)"/>
</element>
<xsl:sequence
select="mf:extract(substring($input, string-length(concat(regex-group(1), regex-group(2))) + 1))"
/>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:function>
<xsl:template match="DATA">
<xsl:copy>
<xsl:sequence select="mf:extract(.)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This transforms the input
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<DATA>/OPDH/FLOWING SOLUTION/SGDE/Number0983713/EKPH/Sample test/some other keys/</DATA>
<DATA>/some other keys/afdsf/SGDE/Number0983713/some other keys/PIHSAGA/OPDH/FLOWING SOLUTION/some other keys/No exception/EKPH/Sample test/some other keys/</DATA>
</Root>
into list of elements with the extracted data and the path to build:
<DATA>
<element path="ROOT/ELEMENT1/ABCD">FLOWING SOLUTION</element>
<element path="ROOT/ELEMENT3/WXYZ">Number0983713</element>
<element path="ROOT/ELEMENT2/POIU">Sample test</element>
</DATA>
<DATA>
<element path="ROOT/FOO/BAR">afdsf</element>
<element path="ROOT/ELEMENT3/WXYZ">Number0983713</element>
<element path="ROOT/FOO/BAR">PIHSAGA</element>
<element path="ROOT/ELEMENT1/ABCD">FLOWING SOLUTION</element>
<element path="ROOT/FOO/BAR">No exception</element>
<element path="ROOT/ELEMENT2/POIU">Sample test</element>
</DATA>
I am not quite sure whether that is doing the right job as I am not sure what determines the order and contents of the two samples you have provided and what e.g. /some other keys/ is meant to express. Tell us whether the result has the data you want or clarify your question and the samples you have shown. It should be easy to generate the XML from the above intermediary results once we have established that the right data is extracted.
You wrote very little about keys, so I assume that:
Your input file contains both:
key list (in KEYS tag),
actual source (in DATA tag).
Both these tags are children of the source ROOT tag.
KEYS tag contains in each row a pair of key value and output path, where
respective content for this key shoud be placed.
Assume that your full input is:
<?xml version="1.0" encoding="UTF-8"?>
<ROOT>
<KEYS>
/OPDH/ - ROOT/ELEMENT1/ABCD
/EKPH/ - ROOT/ELEMENT2/POIU
/SGDE/ - ROOT/ELEMENT3/SGDE
</KEYS>
<DATA>/OPDH/FLOWING SOLUTION/SGDE/Number0983713/EKPH/Sample test/</DATA>
</ROOT>
Then you can write the XSLT as follows:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="ROOT">
<xsl:copy>
<!-- Divide KEYS into rows -->
<xsl:variable name="keys_1" select="tokenize(KEYS, '
')"/>
<!-- # of rows -->
<xsl:variable name="nn" select="count($keys_1)"/>
<!-- Drop 1st and last (empty) row -->
<xsl:variable name="keys" select="subsequence($keys_1, 2, $nn - 2)"/>
<!-- Divide DATA into tokens -->
<xsl:variable name="data_1" select="tokenize(DATA, '/')"/>
<!-- # of tokens -->
<xsl:variable name="nn" select="count($data_1)"/>
<!-- Drop 1st and last (empty) token -->
<xsl:variable name="data" select="subsequence($data_1, 2, $nn - 2)"/>
<!-- Generate output data for each row from keys -->
<xsl:for-each select="$keys">
<!-- Divide the keys row into tokens -->
<xsl:variable name="parts" select="tokenize(., '/')"/>
<!-- # of tokens -->
<xsl:variable name="nn" select="count($parts)"/>
<!-- Source key - token No 2 (after the 1st '/') -->
<xsl:variable name="srcKey" select="$parts[2]"/>
<!-- path - tokens after 'ROOT' -->
<xsl:variable name="path" select="subsequence($parts, 4)"/>
<!-- Open tags given in path -->
<xsl:for-each select="$path">
<xsl:text>
</xsl:text>
<!-- Spacing -->
<xsl:variable name="nn" select="position()"/>
<xsl:value-of select=
"string-join((for $i in 1 to $nn return ' '), '')"/>
<!-- Print opening tag -->
<xsl:value-of select="concat('<', ., '>')"
disable-output-escaping="yes"/>
</xsl:for-each>
<!-- Find position of the source key in data -->
<xsl:variable name="ind" select="index-of($data, $srcKey)[1]"/>
<!-- Get data from the next token -->
<xsl:value-of select="$data[$ind + 1]"/>
<!-- Close tags given in path -->
<xsl:for-each select="reverse($path)">
<xsl:variable name="nn" select="position()"/>
<!-- Spacing and NewLine - but not for the most inner tag -->
<xsl:if test="$nn > 1">
<xsl:text>
</xsl:text>
<xsl:value-of select=
"string-join((for $i in 1 to last() - $nn + 1 return ' '), '')"/>
</xsl:if>
<!-- Print closing tag -->
<xsl:value-of select="concat('</', ., '>')"
disable-output-escaping="yes"/>
</xsl:for-each>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>

How to get max valued xref's 'rid' except from particular section

Please suggest for how get the maximum 'rid' value from all xrefs except from the 'Online' sections. By identify the max valued 'rid', then need to insert the attribute to those references which are higher to maximum value. Please see required result text.
XML:
<article>
<body>
<sec><title>Sections</title>
<p>The test <xref rid="b1">1</xref>, <xref rid="b2">2</xref>, <xref rid="b3 b4 b5">3-5</xref></p></sec>
<sec><title>Online</title><!--This section's xrefs no need to consider-->
<p>The test <xref rid="b6">6</xref></p>
<sec><title>Other</title>
<p><xref rid="b1">1</xref>, <xref rid="b7 b8">7-8</xref></p>
</sec>
</sec><!--This section's xrefs no need to consider-->
<sec>
<p>Final test test</p>
<sec><title>Third title</title><p>Last text</p></sec>
</sec>
</body>
<bm>
<ref id="b1">The ref01</ref>
<ref id="b2">The ref02</ref>
<ref id="b3">The ref03</ref>
<ref id="b4">The ref04</ref>
<ref id="b5">The ref05</ref>
<ref id="b6">The ref06</ref>
<ref id="b7">The ref07</ref>
<ref id="b8">The ref08</ref>
</bm>
</article>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="var1"><!--Variable to get the all 'rid's except sec/title contains 'Online' -->
<xsl:for-each select="//xref[not(. is ancestor::sec[title[contains(., 'Online')]]/descendant-or-self)]/#rid">
<!--xsl:for-each select="//xref/#rid[not(contains(ancestor::sec/title, 'Online'))]"--><!--for this xpath, error is : "XPTY0004: A sequence of more than one item is not allowed as the first argument" -->
<!--xsl:for-each select="//xref/#rid[not(contains(ancestor::sec[1]/title, 'Online')) and not(contains(ancestor::sec[2]/title, 'Online'))]"--><!--for this xpath we are getting the required result, but there may be several nesting of 'sec's -->
<xsl:choose>
<xsl:when test="contains(., ' ')">
<xsl:for-each select="tokenize(., ' ')">
<a><xsl:value-of select="."/></a>
</xsl:for-each>
</xsl:when>
<xsl:otherwise><a><xsl:value-of select="."/></a></xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="varMax1">
<xsl:for-each select="$var1/a">
<xsl:sort select="substring-after(., 'b')" order="descending" data-type="number"/>
<a><xsl:value-of select="."/></a>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="varMax"><!--Variable to get max valued RID -->
<xsl:value-of select="substring-after($varMax1/a[1], 'b')"/>
</xsl:variable>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="varID"><xsl:value-of select="substring-after(#id, 'b')"/></xsl:variable>
<xsl:choose>
<xsl:when test="number($varMax) lt number($varID)">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="MoveRef">yes</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Required result:
<article>
<body>
<sec><title>Sections</title>
<p>The test <xref rid="b1">1</xref>, <xref rid="b2">2</xref>, <xref rid="b3 b4 b5">3-5</xref></p></sec>
<sec><title>Online</title><!--This section's xrefs no need to consider-->
<p>The test <xref rid="b6">6</xref></p>
<sec><title>Other</title>
<p><xref rid="b1">1</xref>, <xref rid="b7">7</xref>, <xref rid="b8">8</xref></p>
</sec>
</sec><!--This section's xrefs no need to consider-->
<sec>
<p>Final test test</p>
<sec><title>Third title</title><p>Last text</p></sec>
</sec>
</body>
<bm>
<ref id="b1">The ref01</ref>
<ref id="b2">The ref02</ref>
<ref id="b3">The ref03</ref>
<ref id="b4">The ref04</ref>
<ref id="b5">The ref05</ref>
<ref id="b6" MoveRef="yes">The ref06</ref>
<ref id="b7" MoveRef="yes">The ref07</ref>
<ref id="b8" MoveRef="yes">The ref08</ref>
</bm>
</article>
Here consider number 5 for 'b5' rid, 6 for 'b6'.... (Because alphanumeric)
Perhaps you can take a different approach rather than trying to find the maximum rid attribute that is not in an "online" section. Not least because it is not entirely clear what the maximum is when you are dealing with an alphanumeric string.
Instead, you could define a key to look up elements in the "online" section by their name
<xsl:key name="online" match="sec[title = 'Online']//*" use="name()" />
And then, another key, to look up the xref elements that occur in other sections
<xsl:key name="other" match="xref[not(ancestor::sec/title = 'Online')]" use="name()" />
Then, you can write a template to math the ref elements, and use an xsl:if to determine whether to add MoveRef attribute to it:
<xsl:variable name="id" select="#id" />
<xsl:if test="key('online', 'xref')[tokenize(#rid, ' ')[. = $id]] and not(key('other', 'xref')[tokenize(#rid, ' ')[. = $id]])">
Try this much shorter XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="online" match="sec[title = 'Online']//*" use="name()" />
<xsl:key name="other" match="xref[not(ancestor::sec/title = 'Online')]" use="name()" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<ref>
<xsl:variable name="id" select="#id" />
<xsl:if test="key('online', 'xref')[tokenize(#rid, ' ')[. = $id]] and not(key('other', 'xref')[tokenize(#rid, ' ')[. = $id]])">
<xsl:attribute name="MoveRef" select="'Yes'" />
</xsl:if>
<xsl:apply-templates select="#*|node()"/>
</ref>
</xsl:template>
</xsl:stylesheet>
You can actually amend the ref template to put the condition in the template match, if you wanted...
<xsl:template match="ref[key('online', 'xref')[tokenize(#rid, ' ')[. = current()/#id]] and not(key('other', 'xref')[tokenize(#rid, ' ')[. = current()/#id]])]">
<ref MoveRef="Yes">
<xsl:apply-templates select="#*|node()"/>
</ref>
</xsl:template>

Extract Xpaths of all nodes and then their attributes

I am struggling with xslt from the past 2 days, owing to my starter status.My requirement is that given any input XML file ,I want the output to be a list of all the XPaths of all the tags in order in which they appear in the original XML document(parent, then parent,parents Attributes list/child, parent/child/childOFchild and so forth). THe XSLT should not be specific to any single XMl schema. It should work for any XML file, which is a valid one.
Ex:
If the Input XML Is :
<v1:Root>
<v1:UserID>test</v1:UserID>
<v1:Destination>test</v1:Destination>
<v1:entity name="entiTyName">
<v11:attribute name="entiTyName"/>
<v11:attribute name="entiTyName"/>
<v11:attribute name="entiTyName"/>
<v11:filter type="entiTyName">
<v11:condition attribute="entiTyName" operator="eq" value="{FB8D669E-D090-E011-8F43-0050568E222C}"/>
<v11:condition attribute="entiTyName" operator="eq" value="1"/>
</v11:filter>
<v11:filter type="or">
<v11:filter type="or">
<v11:filter type="and">
<v11:filter type="and">
<v11:condition attribute="cir_customerissuecode" operator="not-like" value="03%"/>
</v11:filter>
</v11:filter>
</v11:filter>
</v11:filter>
</v1:entity>
</v1:Root>
I want my output to be :
/v1:Root/v1:UserID
/v1:Root/v1:Destination
/v1:Root/v1:entity/#name
/v1:Root/v1:entity/v11:attribute
/v1:Root/v1:entity/v11:attribute/#name
/v1:Root/v1:entity/v11:attribute[2]
/v1:Root/v1:entity/v11:attribute[2]/#name
/v1:Root/v1:entity/v11:attribute[3]
/v1:Root/v1:entity/v11:attribute[3]/#name
/v1:Root/v1:entity/v11:filter/#type
/v1:Root/v1:entity/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter/v11:condition/#attribute
/v1:Root/v1:entity/v11:filter/v11:condition/#operator
/v1:Root/v1:entity/v11:filter/v11:condition/#value
/v1:Root/v1:entity/v11:filter/v11:condition[2]
/v1:Root/v1:entity/v11:filter/v11:condition[2]/#attribute
/v1:Root/v1:entity/v11:filter/v11:condition[2]/#operator
/v1:Root/v1:entity/v11:filter/v11:condition[2]/#value
/v1:Root/v1:entity/v11:filter[2]/v11:filter/#type
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/#type
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/#type
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition/#attribute
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition/#operator
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition/#value
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/#type
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition/#attribute
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition/#operator
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition/#value
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]/#attribute
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]/#operator
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]/#value
So, it is basically all the XPath of each element ,then the Xpath of the elements Attributes.
I have an XSLT with me, which is like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no" />
<xsl:template match="*[not(child::*)]">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/', name())" />
<xsl:if test="count(preceding-sibling::*[name() = name(current())]) != 0">
<xsl:value-of
select="concat('[', count(preceding-sibling::*[name() = name(current())]) + 1, ']')" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="*" />
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="*" />
</xsl:template>
</xsl:stylesheet>
THe output which gets Produced does not cater to complex tags and also the tag's attributes in the resulting Xpath list :(.
Kindly help me in fixing this xslt to produce the output as mentioned above.
THe present output from the above XSLT is like this :
/v1:Root/v1:UserID
/v1:Root/v1:Destination
/v1:Root/v1:entity/v11:attribute
/v1:Root/v1:entity/v11:attribute[2]
/v1:Root/v1:entity/v11:attribute[3]
/v1:Root/v1:entity/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter/v11:condition[2]
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]
/v1:Root/v1:entity/v11:filter[2]/v11:filter[2]/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter[2]/v11:filter[2]/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter[2]/v11:filter[2]/v11:condition[2]
/v1:Root/v1:entity/v11:filter[2]/v11:filter[2]/v11:filter[2]/v11:condition[3]
I think there's a discrepancy between your sample input and output, in that the output describes a filter element with two conditions that's not in the source XML. At any rate, I believe this works:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no" />
<!-- Handle attributes -->
<xsl:template match="#*">
<xsl:apply-templates select="ancestor-or-self::*" mode="buildPath" />
<xsl:value-of select="concat('/#', name())"/>
<xsl:text>
</xsl:text>
</xsl:template>
<!-- Handle non-leaf elements (just pass processing downwards) -->
<xsl:template match="*[#* and *]">
<xsl:apply-templates select="#* | *" />
</xsl:template>
<!-- Handle leaf elements -->
<xsl:template match="*[not(*)]">
<xsl:apply-templates select="ancestor-or-self::*" mode="buildPath" />
<xsl:text>
</xsl:text>
<xsl:apply-templates select="#*" />
</xsl:template>
<!-- Outputs a path segment for the matched element: '/' + name() + [ordinalPredicate > 1] -->
<xsl:template match="*" mode="buildPath">
<xsl:value-of select="concat('/', name())" />
<xsl:variable name="sameNameSiblings" select="preceding-sibling::*[name() = name(current())]" />
<xsl:if test="$sameNameSiblings">
<xsl:value-of select="concat('[', count($sameNameSiblings) + 1, ']')" />
</xsl:if>
</xsl:template>
<!-- Ignore text -->
<xsl:template match="text()" />
</xsl:stylesheet>

How to apply an alphanumeric sort in XSLT

Based on the following XML, what is the best way to achieve an alphanumeric sort in XSL?
Edit: to clarify, the XML below is just a simple sample the real XML would contain much more variant values.
<colors>
<item>
<label>Yellow 100</label>
</item>
<item>
<label>Blue 12</label>
</item>
<item>
<label>Orange 3</label>
</item>
<item>
<label>Yellow 10</label>
</item>
<item>
<label>Orange 26</label>
</item>
<item>
<label>Blue 117</label>
</item>
</colors>
E.g. I want a final outcome in this order:
Blue 12, Blue 117, Orange 3, Orange 26, Yellow 10, Yellow 100
This is "effectively" what I would want.
<xsl:apply-templates select="colors/item">
<xsl:sort select="label" data-type="text" order="ascending"/><!--1st sort-->
<xsl:sort select="label" data-type="number" order="ascending"/><!--2nd sort-->
</xsl:apply-templates>
<xsl:template match="item">
<xsl:value-of select="label"/>
<xsl:if test="position() != last()">,</xsl:if>
</xsl:template>
Splitting the label text into text and number part using substring-before and substring-after will do in your example (however, this is not a general approach, but you get the idea):
<xsl:template match="/">
<xsl:apply-templates select="colors/item">
<xsl:sort select="substring-before(label, ' ')" data-type="text" order="ascending"/>
<!--1st sort-->
<xsl:sort select="substring-after(label, ' ')" data-type="number" order="ascending"/>
<!--2nd sort-->
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item">
<xsl:value-of select="label"/>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:template>
This gives the following output:
Blue 12, Blue 117, Orange 3, Orange 26, Yellow 10, Yellow 100
Update
A more generic way to solve your sorting problem would be to have the select attribute of the xls:sort element contain a string which is sortable according to the sort rules that you expect. E.g. in this string all numbers could be padded with leading 0's so that lexicgraphically sorting them as data-type="text" will result in the correct alphanumeric order.
If you are using the XSLT engine of .NET you could use a simple extension function in C# to pad numbers with leading 0's:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:myExt="urn:myExtension"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl myExt">
<xsl:output method="xml" indent="yes" />
<msxsl:script language="C#" implements-prefix="myExt">
<![CDATA[
private static string PadMatch(Match match)
{
// pad numbers with zeros to a maximum length of the largest int value
int maxLength = int.MaxValue.ToString().Length;
return match.Value.PadLeft(maxLength, '0');
}
public string padNumbers(string text)
{
return System.Text.RegularExpressions.Regex.Replace(text, "[0-9]+", new System.Text.RegularExpressions.MatchEvaluator(PadMatch));
}
]]>
</msxsl:script>
<xsl:template match="/">
<sorted>
<xsl:apply-templates select="colors/item">
<xsl:sort select="myExt:padNumbers(label)" data-type="text" order="ascending"/>
</xsl:apply-templates>
</sorted>
</xsl:template>
<xsl:template match="item">
<xsl:value-of select="label"/>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:template>
</xsl:stylesheet>