I was wondering how this predicate([1]), is hardcoded as 1 always in the muenchian grouping. The concept was not clear for me, after a lot of search. It is explained as the current node, is compared with the 1st group returned by the key.
Why does it always compare with the first one that a key is matched?
Also why are we giving contact[count(. | key('contacts-by-surname', surname)[1]) = 1], the =1 part? again 1 is hardcoded. I referred the below link
http://www.jenitennison.com/xslt/grouping/muenchian.html
Let's say we have a key definition <xsl:key name="contacts-by-surname" match="contact" use="surname"/>, then the expression key('contacts-by-surname', 'Doe') gives you a node set with all contact elements where the surname is Doe. The expression key('contacts-by-surname', 'Doe')[1] gives you the first contact in that "group".
Now when processing all contact elements with for-each or apply-templates we usually want a way to identify the first contact element in each group. This can be achieved with <xsl:for-each select="contact[count(. | key('contacts-by-surname', surname)[1]) = 1]"> or <xsl:for-each select="contact[generate-id() = generate-id(key('contacts-by-surname', surname)[1])]">.
If your requirement is different and you for instance wanted to identify the last item in each group then you could of course use a different predicate, as in <xsl:for-each select="contact[count(. | key('contacts-by-surname', surname)[last()]) = 1]"> or <xsl:for-each select="contact[generate-id() = generate-id(key('contacts-by-surname', surname)[last()])]">.
I was wondering how this predicate([1]), is hardcoded as 1 always in
the muenchian grouping.
This is simple:
The key() function produces all nodes for a given group, and we want to take just one node from any group.
It isn't guaranteed that all groups will have two or more nodes in them -- some might have just one node.
This is why it is safe and convenient to take the first (and possibly the only) node from each group.
We could equally well do the grouping taking the last node from each group (but this will be less efficient):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kNumByMod3" match="num"
use=". mod 3"/>
<xsl:template match=
"num[generate-id()
=
generate-id(key('kNumByMod3', . mod 3)[last()])
]
">
3k + <xsl:value-of select=". mod 3"/>:
<xsl:text/>
<xsl:copy-of select="key('kNumByMod3', . mod 3)"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on this XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
produces the wanted, correctly grouped result:
3k + 2:
<num>02</num>
<num>05</num>
<num>08</num>
3k + 0:
<num>03</num>
<num>06</num>
<num>09</num>
3k + 1:
<num>01</num>
<num>04</num>
<num>07</num>
<num>10</num>
The basic algorithm is that there are two nested loops. The outer loop selects one representative node from each group, and the inner loop selects all the nodes in that group (including the one chosen as representative). The easiest way of selecting one representative node from a group is to select the first, hence the predicate [1].
Related
I have the xpath query:
<xsl:variable name="fbids">
<xsl:sequence select="
feature_relationship[type_id/cvterm[name='molec_deletes' or name='deletes']]
/object_id/feature/feature_synonym/synonym_id/synonym/synonym_sgml"/>
</xsl:variable>
This query does not return a sequence of matching nodes as a sequence itself, it returns them as children of a single #documentfragment. Even if no elements satisfy this query, $fbids is still set to an empty #documentfragment.
This screws up my code whereas the following loop, instead of iterating once per matched element, runs once through for the #documentfragment and that's it. How can I force this to return a nice element()* type?
Thanks!
<xsl:variable name="fbids">
<xsl:sequence select="
feature_relationship[type_id/cvterm[name='molec_deletes' or name='deletes']]
/object_id/feature/feature_synonym/synonym_id/synonym/synonym_sgml"/>
</xsl:variable>
This query does not return a sequence of matching nodes as a sequence
itself, it returns them as children of a single #documentfragment.
Yes, because there is a sequence inside the body of the variable and the variable has no type specified. The default type in such case is: document-node().
Solution:
Specify the variable without a body and provide the XPath expression in the select attribute of xsl:variable:
<xsl:variable name="fbids" select="
feature_relationship[type_id/cvterm[name='molec_deletes' or name='deletes']]
/object_id/feature/feature_synonym/synonym_id/synonym/synonym_sgml"/>
It is still a good practice to specify the wanted type of the variable using the as attribute.
A complete answer also must cover the case when the value of the variable (a sequence of elements) is dynamically created inside its body.
Again specifying the type of the variable is key to the solution.
Here is a small and complete example:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:variable name="vMapped" as="element()*">
<xsl:for-each select="num">
<num><xsl:value-of select=".*2"/></num>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$vMapped">
<xsl:sequence select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the variable named $vMapped gets its dynamically created value inside its body. The rest of the code successfully uses this variable as a sequence of elements -- each of the elements in this sequence is accessed and copied to the output:
<num>2</num>
<num>4</num>
<num>6</num>
<num>8</num>
<num>10</num>
<num>12</num>
<num>14</num>
<num>16</num>
<num>18</num>
<num>20</num>
The default type for a variable in XSLT 2.0 is a documentfragment. If you want it to be a sequence of elements, then add the as attribute and specify the sequence type. You can also use the select attribute, rather than nested sequence element:
<xsl:variable name="fbids" as="element()*" select="
feature_relationship[type_id/cvterm[name='molec_deletes' or name='deletes']]
/object_id/feature/feature_synonym/synonym_id/synonym/synonym_sgml"/>
Is it possible in XSLT 2.0 to use node sets stored in global variables in xsl:template match patterns?
For instance:
<!-- GLOBAL PARAMETERS -->
<xsl:param name="tktDocRS" />
<xsl:variable name="tktDoc" select="saxon:parse($tktDocRS)" />
...
<xsl:template match="$tktDoc//someNodeInTktDoc">
...
</xsl:template>
XSLT 3.0 (unpublished!) makes the following legal:
<xsl:template match="$x//a">
which will match any a element that has $x as an ancestor.
In XSLT 2.0 you have to write this as:
<xsl:template match="a[ancestor::node() intersect $x]">
Is it possible in XSLT 2.0 to use node sets stored in global variables
in xsl:template match patterns.
Yes, but a variable reference can only occur in the predicate.
According to the eight syntax rules in the W3C XSLT 2.0 specification,
a pattern is a union of *relativePathPattern*s (that can eventually be pre-pended by '/' or '//') and each relativePathPattern consists of *patternStep*s, each of which has the following syntax:
[4] PatternStep ::= PatternAxis? NodeTest PredicateList
both the PatternAxis and the NodeTest cannot contain the $ character, which means that variable references are allowed only in the predicates.
Here is a very simple example:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vOdds" select="/*/*[. mod 2 = 1]"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="num[. = count($vOdds)]">
<num special="yes"><xsl:value-of select="."/></num>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is produced:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num special="yes">05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
I think you can't put the variable reference as a step in a pattern. And you don't have to, it should suffice to use
<xsl:template match="someNodeInTktDoc">...</xsl:template>
then you simply need to make sure you apply-templates on $tktDoc.
When iterating through the nodes in a node-set variable I want an XPATH 1.0 expression that returns all ancestors of a node (e.g., of $myVariable[7]) -- not the ancestors in the node-set variable, but ancestors in the original document.
I thought one of these would work, but neither does.
select="//*[generate-id()=generate-id($myVariable[7])]/ancestor::*"
select="id(generate-id($myVariable[7]))/ancestor::*"
Am I close?
Edit: It's not central to my question, but I had //ancestor; that extra slash is unnecessary.
Your expression:
//*[generate-id()=generate-id($myVariable[7])]/ancestor::*
is correct.
The reason it is "not working" may be due to the fact that $myVariable[7] doesn't contain what you are expecting.
Here is a simple complete example using the above expression and producing the expected. correct results:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="myVariable"
select="/*/*/*/*/num"/>
<xsl:template match="/">
<xsl:for-each select=
"//*[generate-id()
=
generate-id($myVariable[7])
]
/ancestor::*
">
<xsl:value-of select="name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<a>
<b>
<c>
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
</c>
</b>
</a>
the wanted, correct result (the names of all ancestors of $myVariable[7]) is produced:
a
b
c
nums
I have a variable in xslt that must contains several values which depends on the page.
I know that <xslt: variable> cannot do the job because variables cannot be changed.
Can xslt:param does the job and how can I change it value on the fly?
Roelof
Edit 1 : I try to explain what I want. I have a website which displays articles per month. What I really like is a script where I can filter the articles so only the articles of a particular month is displayed in pages. But I need to decide how many articles are displayed on each page. So the number of displayed articles is based on the month and the displayed page.
XSLT is a functional language, which, among other things, means that an xsl:variable or xsl:param, once specified cannot change its value.
This doesn't prevent the ability to express in XSLT the solution to any problem (XSLT is Turing-complete) and only requires one to change thinking based on the imperative programming paradigm.
Once you publish your specific problem, many readers will be able to provide complete XSLT solutions.
Here is a simple example:
We "accumulate" all wanted values in a variable, then output it:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vOddsAccumulator">
<xsl:apply-templates/>
</xsl:variable>
<xsl:template match="/">
<xsl:value-of select="$vOddsAccumulator"/>
</xsl:template>
<xsl:template match="*/*"/>
<xsl:template match="num[. mod 2 = 1]">
<xsl:value-of select="concat(., ' ')"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied to the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted result is produced:
01 03 05 07 09
I have some complex XSLT 2.0 transformations. I'm trying to find out if there is general purpose way to ensure that no empty tags are output. So... conceptually, a final stage of processing that recursively removes all empty tags. I understand this could be done by a separate XSLT that did nothing but filter out empty tags, but I need to have it all packaged together in a single one.
This XSLT 2.0 transformation illustrates how multi-pass (in this case 2-pass) processing can be done:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" mode="#all">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates select="$vPass1/*" mode="non-empty"/>
</xsl:template>
<xsl:template match="text()[xs:integer(.) mod 2 eq 0]"/>
<xsl:template match="*[not(node())]" mode="non-empty"/>
</xsl:stylesheet>
when applied on this XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
It creates a result document in the first pass (which is captured in the $vPass1 variable), in which all <num> elements with contents even integer are stripped off their content and are empty. Then, in the second pass, applied in a specific mode, all empty elements are removed.
The result of the transformation is:
<nums>
<num>01</num>
<num>03</num>
<num>05</num>
<num>07</num>
<num>09</num>
</nums>
Do note the use of modes, and the special modes #all and #current.
Update: The OP now wants in a comment to delete "recursively" "all nodes that have no non-empty descendant".
This can be implemented simpler using no explicit recursion. Just change:
<xsl:template match="*[not(node())]" mode="non-empty"/>
to:
<xsl:template match="*[not(descendant::text())]" mode="non-empty"/>