1. | Grouping, two levels. |
I think that the reason you're running into difficulties is because this is actually a two-level grouping problem. The first level of grouping is grouping all the technology elements by their value (e.g. tech1, tech2, tech3). The second level of grouping is only required to get rid of the duplicates - you need to group the technology elements for each particular technology by their domain attribute. In XSLT 2.0 terms, the grouping would look like: <xsl:for-each-group select="product/technology" group-by="."> <xsl:sort select="." /> <xsl:value-of select="." /> <xsl:for-each-group select="current-group()" group-by="@domain"> domain = <xsl:value-of select="@domain" /> </xsl:for-each-group> </xsl:for-each-group> or, alternatively: <xsl:for-each-group select="product/technology" group-by="."> <xsl:sort select="." /> <xsl:value-of select="." /> <xsl:for-each select="distinct-values(current-group()/@domain)"> domain = <xsl:value-of select="@domain" /> </xsl:for-each> </xsl:for-each-group> | |
2. | Grouping |
You'll find that this is a specific example showing how to use the new grouping facilities in http://www.w3.org/TR/xslt20 - we included it in our list of cases studies specifically because it's hard to to in XSLT 1.0. Using Saxon 7.0, you can do <xsl:for-each-group group-adjacent="@bullet"> <xsl:choose> <xsl:when test="@bullet=0"> <xsl:apply-templates select="current-group()" mode="p"/> </xsl:when> <xsl:when test="@bullet=1"> <ul> <xsl:apply-templates select="current-group()" mode="li"/> </ul> </xsl:choose> </xsl:for-each-group> | |
3. | Sorting and Grouping |
This resolves to <xsl:template match="a"> <a> <xsl:for-each-group select="b" group-by="." /> <xsl:sort select="." /> <xsl:variable name="rank" select="position()" /> <xsl:for-each select="current-group()"> <b rank="{$rank}"> <xsl:value-of select="." /> </b> </xsl:for-each> </xsl:for-each-group> </a> </xsl:template> | |
4. | every expression |
While the magical expression you want cannot be expressed in XPath 1.0, it can easily be expressed in XPath 2.0: document('2.xml')/outsidedata/b [every $att in @* satisfies current()/@* [.=$att and node-name(.)=node-name($att)]] The "every" expression is called a universal quantifier[1] and enables you to test that a condition applies for *every* node in a given sequence. This is something that could not be done in general in XPath 1.0. The node-name() function[2] returns an "expanded QName" type that consists of a local/URI pair that can in turn be compared with other expanded names. This ensures that your code is robust in the face of namespace-qualified attributes without having to write [local-name(.)=local-name($att) and namespace-uri(.)=namespace-uri($att)]. This is a welcome shorthand indeed! Except for the node-name() function, I'm happy to report that a stylesheet with this expression (using name() instead) is executed as expected by Saxon 7.1. Below is the stylesheet I ran against your two files: <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="/"> <xsl:apply-templates select="/mydata/a"/> </xsl:template> <xsl:template match="/mydata/a"> <xsl:for-each select="document('2.xml')/outsidedata/b [every $att in @* satisfies current()/@* [.=$att and name(.)=name($att)]]"> <xsl:text>[</xsl:text> <xsl:value-of select="."/> <xsl:text>]</xsl:text> <xsl:if test="position() != last()"> <xsl:text> </xsl:text> </xsl:if> </xsl:for-each> </xsl:template> </xsl:stylesheet> Invocation and output: $ java net.sf.saxon.Transform 1.xml test.xsl With your (corrected) reformulation of the original problem, I believe the expression only needs slight modification (@* trades places with current()/@*): document('2.xml')/outsidedata/b [every $att in current()/@* satisfies @* [.=$att and node-name(.)=node-name($att)]] Hope you found this helpful, or at least interesting! | |
5. | Grouping in XSLT2 |
Within <xsl:for-each-group> there is a context node, this node is the first e1 element in the current group. So name="{.}" outputs the string value of this first e1 element: "x" for the first group, "y" for the second. Since you were grouping by ".", this is actually the value that all members of the group have in common, so any element in the group would have been OK, but "." actually selects the first one.
That's right. The first time round the outer loop, current-group() selects the three "x" element, the second time round, it selects the "y" element. The inner xsl:for-each-group could equally have been written: <xsl:for-each select="distinct-values(current-group()/@att)"> since you only want to select the distinct values of the attribute, not the group of nodes associated with each distinct value.
| |
6. | Grouping in XSLT2 |
This is a kind of grouping problem which I call "positional grouping". In XSLT 2.0 you can do <xsl:for-each-group select="*" group-starting-with="a"> <a> <xsl:copy-of select="current-group() except ."/> </a> </xsl:for-each-group> | |
7. | Grouping in xslt 2 |
I think that what you need here is a nested set of for-each-groups, one for each level of the output tree. In this case it is quite deeply nested: <xsl:template match="Document" <xsl:for-each-group select="Paragraph" group-starting-with="*[@StyleName='PART']"> <Part Category="PART"> <xsl:copy-of select="child::node()"/> <xsl:for-each-group select="current-group() except ." group-starting-with="*[@StyleName='DIVISION']"> <Part Category="DIVISION"> <xsl:copy-of select="child::node()"/> <xsl:for-each-group select="current-group() except ." group-starting-with="*[@StyleName='SUBDIVISION']"> and so on. It might be possible to make the code neater by making it table-driven: <xsl:template match="Document" <xsl:for-each-group select="Paragraph" group-starting-with="*[@StyleName='PART']"> <xsl:apply-templates select="."/> </xsl:template> <xsl:template match="Paragraph"> <Part Category="{@StyleName}"> <xsl:copy-of select="child::node()"/> <xsl:for-each-group select="current-group() except ." group-starting-with="*[@StyleName=f:child(@StyleName)]"> <xsl:apply-templates select="."/> </ </ </ <xsl:function name="f:child" as="xs:string"> <xsl:param name="level" as="xs:string"> <xsl:choose> <xsl:when test="$level='PART'">DIVISION</xsl:when> <xsl:when test="$level='DIVISION'">SUBDIVISION</xsl:when> etc </ === Source XML Document ======================================== <Document> <Paragraph StyleName="PART">..................</Paragraph> <Paragraph StyleName="DIVISION">..............</Paragraph> <Paragraph StyleName="SUBDIVISION">...........</Paragraph> <Paragraph StyleName="REGULATION">............</Paragraph> <Paragraph StyleName="SUB-REGULATION">........</Paragraph> <Paragraph StyleName="PARAGRAPH">.............</Paragraph> <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph> <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph> <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph> <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph> <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph> <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph> <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph> <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph> <Paragraph StyleName="PARAGRAPH">.............</Paragraph> <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph> <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph> <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph> <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph> <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph> <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph> <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph> <Paragraph StyleName="SUB-SUB-PARAGRAPH">.....</Paragraph> <Paragraph StyleName="SUB-PARAGRAPH">.........</Paragraph> <Paragraph StyleName="SUB-REGULATION">........</Paragraph> <Paragraph StyleName="SUB-REGULATION">........</Paragraph> <Paragraph StyleName="SUB-REGULATION">........</Paragraph> <Paragraph StyleName="SUB-REGULATION">........</Paragraph> <Paragraph StyleName="NOTE">..................</Paragraph> </Document> > === Required Output ====================== > <Regulation> > <Part Category="PART"> > <Part Category = "DIVISION"> > <Part Category = "SUBDIVISION"> > <Article> > <Sub-Article> > <Paragraph> > <Sub-Paragraph/> > <Sub-Paragraph> > <Sub-Sub-Paragraph> > <Sub-Sub-Paragraph> > <Sub-Paragraph/> > <Sub-Paragraph/> > <Sub-Paragraph> > <Sub-Sub-Paragraph/> > <Sub-Sub-Paragraph/> > </Sub-Paragraph> > </Paragraph> > </Sub-Article> > </Article> > </Part> > </Part> > </Part> > </Regulation> > | |
8. | Grouping; group-starting-with |
Jeni responds In XSLT 2.0, you use group-starting-with to say that you want to group the child elements of <x> in the order that they appear, with each group starting with the <y> element. The contents of the group can be found using current-group() -- the first element in the resulting sequence will be the <y> element, and the rest will be <b> elements. <xsl:for-each-group select="/x/*" group-starting-with="y"> <y id="{@id}"> <xsl:copy-of select="current-group()[self::b]" /> </y> </xsl:for-each-group> | |
9. | Grouping by value |
range.xml <a> <r><x>1</x><x>3</x><x>5</x><x>7</x></r> <r><x>1</x><x>2</x><x>3</x><x>4</x></r> <r><x>1</x><x>2</x><x>3</x><x>8</x></r> </a> range.xsl <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <xsl:template match="r"> <xsl:text> [</xsl:text> <xsl:for-each-group select="x" group-adjacent=" if (position()=1 or .=preceding-sibling::x[1]+1) then -1 else position() "> <xsl:value-of select="."/> <xsl:if test="current-group()[2]"> <xsl:text>-</xsl:text> <xsl:value-of select="current-group()[last()]"/> </xsl:if> <xsl:if test="position()!=last()">,</xsl:if> </xsl:for-each-group> <xsl:text>]</xsl:text> </xsl:template> </xsl:stylesheet> $ java -jar ../saxon8/saxon8.jar range.xml range.xsl <?xml version="1.0" encoding="UTF-8"?>
| |
10. | Grouping |
Given this XML file: <?xml version="1.0" encoding="UTF-8"?> <tables> <table name="table1"> <column name="col1"/> <column name="col2"/> <column name="col3"/> <column name="col4"/> </table> <table name="table2"> <column name="col1"/> <column name="col2"/> <column name="col5"/> <column name="col6"/> </table> <table name="table3"> <column name="col1"/> <column name="col2"/> <column name="col7"/> <column name="col8"/> </table> </tables> Find the columns shared by all tables, by column name. The solution (in XSL 2.0): <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <commonColumns> <xsl:apply-templates/> </commonColumns> </xsl:template> <xsl:template match="tables"> <xsl:for-each-group select="table/column" group-by="@name"> <xsl:variable name="name" select="@name"/> <xsl:if test="count(/tables/table/column[@name=$name])=count(/tables/table)"> <columnName><xsl:value-of select="@name"/></columnName> </xsl:if> </xsl:for-each-group> </xsl:template> </xsl:stylesheet> Produces the following output with Saxon 8: <?xml version="1.0" encoding="UTF-8"?> <commonColumns> <columnName>col1</columnName> <columnName>col2</columnName> </commonColumns> Jay Bryant My suggestion would be to replace this: <xsl:for-each-group select="table/column" group-by="@name"> > <xsl:variable name="name" select="@name"/> > <xsl:if > test="count(/tables/table/column[@name=$name])=count(/tables/table)"> > <columnName><xsl:value-of select="@name"/></columnName> > </xsl:if> > </xsl:for-each-group> by this: <xsl:for-each-group select="table/column" group-by="@name"> <xsl:if test="count(current-group())=count(/tables/table)"> <columnName><xsl:value-of select="current-grouping-key()"/></columnName> </xsl:if> </xsl:for-each-group> You could also move count(/tables/table) outside the loop into a variable. Note that the algorithm only works if a column name can appear in a table at most once. Michael Kay
For your particular application the source data obviously satisfies this constraint, but the constraint needs to be stated in case someone tries to apply the same algorithm to a different situation where the constraint doesn't hold. It's not hard to generalize the solution: instead of count(current-group()) = $number-of-tables you want something like count(current-group()/parent::table) = $number-of-tables relying on the fact that "/" eliminates duplicates.
Thanks Mike. Now I see it. xslt 2.0 stylesheet below demonstrates it. regards DaveP <xsl:variable name="table-count" select="count(/tables/table)"/> <xsl:template match="tables"> <xsl:for-each-group select="table/column" group-by="@name"> <xsl:if test="count(current-group())=$table-count"> <columnName><xsl:value-of select="current-grouping-key()"/></columnName> </xsl:if> <p><i>count(current-group())<xsl:value-of select="count(current-group()) "/></i></p> <p><i>count(current-group())/parent::table <xsl:value-of select="count(current-group()/parent::table) "/></i></p> </xsl:for-each-group> </xsl:template> | |
11. | How to find the deepest node? |
<xsl:for-each-group select="//title" group-by="count(ancestor::*)"> <xsl:sort select="current-grouping-key()"> <xsl:if test="position()=last()"> <xsl:copy-of select="current-group()"/> </ </ | |
12. | Grouping |
using Dimitre's test file (which has sorted input) here's a simplish pure xslt1 solution, no node set or other extensions. <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output indent="yes"/> <xsl:template match="A"> <result> <xsl:apply-templates select="B[1]"/> </result> </xsl:template> <xsl:template match="B"> <xsl:param name="b" select="@period_begin"/> <xsl:param name="e" select="@period_end"/> <xsl:param name="g" select="/.."/> <xsl:variable name="e2" select="@period_end[. > $e]|$e[. >= current()/@period_end]"/> <xsl:choose> <xsl:when test="../B[@period_begin <=$e2 and @period_end > $e2]"> <xsl:apply-templates select="following-sibling::B[1]"> <xsl:with-param name="b" select="$b"/> <xsl:with-param name="e" select="$e2"/> <xsl:with-param name="g" select="$g|."/> </xsl:apply-templates> </xsl:when> <xsl:otherwise> <period begins="{$b}" ends="{$e2}"> <xsl:copy-of select="$g|."/> </period> <xsl:apply-templates select="following-sibling::B[1]"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> With input <A> <B period_begin="1" period_end="5"/> <B period_begin="2" period_end="7"/> <B period_begin="3" period_end="10"/> <B period_begin="4" period_end="12"/> <B period_begin="14" period_end="16"/> <B period_begin="16" period_end="20"/> <B period_begin="16" period_end="30"/> <B period_begin="32" period_end="33"/> <B period_begin="33" period_end="38"/> </A> Produces this output <?xml version="1.0" encoding="utf-8"?> <result> <period begins="1" ends="12"> <B period_begin="1" period_end="5"/> <B period_begin="2" period_end="7"/> <B period_begin="3" period_end="10"/> <B period_begin="4" period_end="12"/> </period> <period begins="14" ends="30"> <B period_begin="14" period_end="16"/> <B period_begin="16" period_end="20"/> <B period_begin="16" period_end="30"/> </period> <period begins="32" ends="38"> <B period_begin="32" period_end="33"/> <B period_begin="33" period_end="38"/> </period> </result> and from Dimitre An XSLT 2.0 solution, but using f:foldl(.) This can be re-written 1:1 in XSLT 1.0 + FXSL for XSLT 1.0. The reason I'm posting this is because it resembles very much the "functional tokenizer" (see for example: xslt archive) and your problem can be called something like: "interval tokenization". I still cannot fully assimilate the meaning and implications of this striking similarity but it seems to prove that there's law, order and elegance in the world of functional programming. Here's the transformation: <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:foldl-func="foldl-func" xmlns:f="http://fxsl.sf.net/" exclude-result-prefixes="f foldl-func" > <xsl:import href="../f/func-foldl.xsl"/> <xsl:output omit-xml-declaration="yes" indent="yes"/> <!-- This transformation must be applied to: ../data/periods.xml --> <xsl:variable name="vFoldlFun" as="element()"> <foldl-func:foldl-func/> </xsl:variable> <xsl:variable name="vA0" as="element()+"> <period start="0" end="0"/> </xsl:variable> <xsl:template match="/"> <xsl:sequence select="f:foldl($vFoldlFun, $vA0, /*/* )[position() > 1]"/> </xsl:template> <xsl:template match="foldl-func:*" as="element()+" mode="f:FXSL"> <xsl:param name="arg1"/> <xsl:param name="arg2"/> <xsl:variable name="vLastPeriod" select="$arg1[last()]"/> <xsl:choose> <xsl:when test= "number($arg2/@period_begin) > number($vLastPeriod/@end)"> <xsl:sequence select="$arg1"/> <period start="{$arg2/@period_begin}" end="{$arg2/@period_end}"/> </xsl:when> <xsl:otherwise> <xsl:sequence select="$arg1[not(. is $vLastPeriod)]"/> <xsl:choose> <xsl:when test="number($arg2/@period_end) > number($vLastPeriod/@end)"> <period start="{$vLastPeriod/@start}" end="{$arg2/@period_end}"/> </xsl:when> <xsl:otherwise> <xsl:sequence select="$vLastPeriod"/> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> When applied on the same source xml document: <A> <B period_begin="1" period_end="5"/> <B period_begin="2" period_end="7"/> <B period_begin="3" period_end="10"/> <B period_begin="4" period_end="12"/> <B period_begin="14" period_end="16"/> <B period_begin="16" period_end="20"/> <B period_begin="16" period_end="30"/> <B period_begin="32" period_end="33"/> <B period_begin="33" period_end="38"/> </A> it produces the wanted result: <period start="1" end="12"/> <period start="14" end="30"/> <period start="32" end="38"/> | |
13. | for-each Grouping |
This is a classic grouping problem which is best tackled using xsl:for-each-group in XSLT 2.0, <xsl:for-each-group select="//entry" group-adjacent="country"> <h2><xsl:value-of select="current-grouping-key()"/></h2> <table> <thead>...</thead> <tbody> <xsl:for-each select="current-group()"> <tr> <td><xsl:value-of select="../@type"/></td> <td><xsl:value-of select="name"/></td> <td><xsl:value-of select="id"/></td> David C comments Others have given solutions but let me just observe (given a recent thread about mutable variables) that the solutions follow the natural human language description rather than a solution that you'd program in an imperative programming language. If you were describing the problem to a person you'd say that you want to group the items by country and put a country heading at the top of each group. You wouldn't (or at least I wouldn't) introduce the notion of state and some temporary variable holding the last seen country and say that for each item you need to compare the current country with the value of the variable stored at the last item. The human-oriented description does not introduce any variables at all, and the XSLT solutions typically don't use any variables either. This is almost always the case when someone more familiar with imperative programming languages has problems with the fact that xsl variables "don't change". Usually it's not that you don't need to change the value of a variable, it's that you don't need a variable at all. | |
14. | Selective Grouping |
The key to this is to do a depth-first recursive traversal of the tree by starting with the root, and for each node processing its children using xsl:apply-templates, but with one key difference: you need to select the logical root and the logical children, rather than the XML root and the XML children. I'm not sure how you identify the logical root in your structure. The logical children of a node N are those nodes that have a child element equal (in name and value) to every child element of N: that is in XSLT 2.0: <xsl:template match="document"> <xsl:variable name="this" select="."/> <xsl:apply-templates select="//document[ every $c in $this/* satisfies (some $d in ./* satisfies deep-equals($c, $d))]"/> DC writes This is a grouping problem, in XSLT2 I think you just want <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";> <xsl:strip-space elements="*"/> <xsl:output indent="yes"/> <xsl:template match="x" name="group"> <xsl:param name="level" select="1"/> <xsl:param name="items" select="document"/> <xsl:for-each-group select="$items" group-by="*[name()=concat('tag',$level)]"> <node id="{current-grouping-key()}" name="{*[name()=concat('tag',$level,'a')]}"> <xsl:call-template name="group"> <xsl:with-param name="level" select="$level+1"/> <xsl:with-param name="items" select="current-group()"/> </xsl:call-template> </node> </xsl:for-each-group> </xsl:template> </xsl:stylesheet> <x> <document> <tag1>3</tag1> <tag1a>Social Sciences</tag1a> </document> <document> <tag1>3</tag1> <tag1a>Social Sciences</tag1a> <tag2>32</tag2> <tag2a>Politics</tag2a> </document> <document> <tag1>3</tag1> <tag1a>Social Sciences</tag1a> <tag2>32</tag2> <tag2a>Politics</tag2a> <tag3>326</tag3> <tag3a>Slavery</tag3a> </document> <document> <tag1>3</tag1> <tag1a>Social Sciences</tag1a> <tag2>33</tag2> <tag2a>Economics</tag2a> </document> </x> $ saxon8 grp2.xml grp2.xsl <?xml version="1.0" encoding="UTF-8"?> <node id="3" name="Social Sciences"> <node id="32" name="Politics"> <node id="326" name="Slavery"/> </node> <node id="33" name="Economics"/> </node> | |
15. | Grouping. |
How do I wrap the scenes and characters.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";> <xsl:output method="xml" indent="yes" /> <xsl:template match="/play"> <play> <xsl:apply-templates select="scene" /> </play> </xsl:template> <xsl:template match="scene"> <scene name="{.}"> <xsl:apply-templates select="following-sibling::character[1]"/> </scene> </xsl:template> <xsl:template match="character"> <character name="{.}"> <xsl:apply-templates select="following-sibling::*[1][self::line]"/> </character> <xsl:apply-templates select="following-sibling::*[self::character|self::scene][1][self::character ]"/> </xsl:template> <xsl:template match="line"> <xsl:copy-of select="."/> <xsl:apply-templates select="following-sibling::*[1][self::line]"/> </xsl:template> </xsl:stylesheet> I call the technique "sibling recursion". It's more concise than most recursive code, because it's self-terminating: you don't need to test explicitly whether there are more items to process, because apply-templates does that automatically for you. | |
16. | Grouping, xslt 2.0 |
I think you want <xsl:for-each-group select="*" group-adjacent="starts-with(local-name(),'b')"> <xsl:choose> <xsl:when test="current-grouping-key()"> <b> <xsl:copy-of select="current-group()"/> </b> <xsl:otherwise> <xsl:copy-of select="current-group()"/> </xsl:otherwise> </xsl:choose> </xsl:for-each-group> | |
17. | Grouping, a novel use of grouping-key |
Answer > <!-- how to match g, h and i as well within the same > "for-each-group"? --> Two ways. I used a grouping key of exists(self::b|self::c) so it only has two states to group or not to group so like that you can only group one thing at a time, but that's OK as you can have more than one pass. <xsl:template match="flatsequence"> <hierarchy> <xsl:variable name="p1"> <xsl:for-each-group select="*" group-adjacent="exists(self::b|self::c)"> ... </xsl:variable> <xsl:for-each-group select="$p1/*" group-adjacent="exists(self::h|self::h|self::i)"> ... </xsl:for-each-group> </hierarchy> </xsl:template> Or you do it in one pass with a grouping key that has three states (and an xsl:choose with three branches. <xsl:template match="flatsequence"> <hierarchy> <xsl:for-each-group select="*" group-adjacent="if (exists(self::b|self::c)) then 1 if (exists(self::h|self::i|self::i)) then 2 else 0"> <xsl:choose> <xsl:when test="current-grouping-key()=1"> <group1><xsl:copy-of select="current-group()"/>... <xsl:when test="current-grouping-key()=2"> <group2><xsl:copy-of select="current-group()"/>... <xsl:otherwise> <xsl:copy-of select="current-group()"/>.. | |
18. | Grouping by depth |
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="2.0"> <xsl:output indent="yes"/> <xsl:template name=" group" match="data"> <xsl:param name="r" select="row"/> <xsl:param name="d" select="1"/> <xsl:for-each-group select="$r" group-starting-with="*[section_depth=$d]"> <section> <title><xsl:value-of select="section_title"/></title> <xsl:call-template name="group"> <xsl:with-param name="r" select="current-group()[section_depth>$d]"/> <xsl:with-param name="d" select="$d+1"/> </xsl:call-template> </section> </xsl:for-each-group> </xsl:template> </xsl:stylesheet> $ saxon8 dep.xml dep.xsl <?xml version="1.0" encoding="UTF-8"?> <section> <title>Home</title> <section> <title>Events</title> <section> <title>Event 1</title> </section> <section> <title>Event 2</title> </section> </section> <section> <title>Equipment</title> </section> <section> <title>News</title> <section> <title>News 1</title> </section> <section> <title>News 2</title> </section> </section> </section> | |
19. | group adjacent |
(using copy-of rather than apply-templates for simplicity) <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";> <xsl:output indent="yes"/> <xsl:template match="x"> <x> <xsl:for-each-group select="*" group-adjacent="exists(self::xyz|self::abc)"> <xsl:choose> <xsl:when test="current-grouping-key()"> <wrap> <xsl:copy-of select="current-group()"/> </wrap> </xsl:when> <xsl:otherwise> <xsl:copy-of select="current-group()"/> </xsl:otherwise> </xsl:choose> </xsl:for-each-group> </x> </xsl:template> </xsl:stylesheet> $ saxon8 wrap.xml wrap.xsl <?xml version="1.0" encoding="UTF-8"?> <x> <wrap> <abc>1</abc> </wrap> <foo>bbb</foo> <wrap> <abc>b</abc> <xyz>b</xyz> <xyz>c</xyz> </wrap> <foo>bbb</foo> <zzz/> <hhh/> <wrap> <xyz>d</xyz> </wrap> </x> | |
20. | Grouping by alpha key |
The solution is as easy as this: <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; > <xsl:output method="text"/> <xsl:template match="/"> <xsl:for-each-group select="*/author" group-by="substring(name/@file-as,1,1)" > <xsl:text>

</xsl:text> <xsl:value-of select="current-grouping-key()"/> <xsl:text>
</xsl:text> <xsl:for-each select="current-group()"> <xsl:value-of select="name/@file-as"/> <xsl:text>
</xsl:text> </xsl:for-each> </xsl:for-each-group> </xsl:template> </xsl:stylesheet> and when applied on the provided xml document it produces the wanted result: A Ken Holman offers the xslt 1.0 solution <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="1.0"> <xsl:output method="text"/> <xsl:key name="alet" match="author" use="substring(name/@file-as,1,1)"/> <xsl:template match="/"> <xsl:for-each select="*/author[generate-id(.)= generate-id(key('alet',substring(name/@file-as,1,1))[1])]"> <xsl:text>

</xsl:text> <xsl:value-of select="substring(name/@file-as,1,1)"/> <xsl:text>
</xsl:text> <xsl:for-each select="key('alet',substring(name/@file-as,1,1))"> <xsl:value-of select="name/@file-as"/> <xsl:text>
</xsl:text> </xsl:for-each> </xsl:for-each> </xsl:template> </xsl:stylesheet> | |
21. | Grouping in 2.0 |
The value of group-starting-with is a pattern that must match a node in the population. Since the population consists of Item elements, none of them match the pattern RepresentationTerm[.='Code']. Use group-starting-with="Item[RepresentationTerm='Code']" A bit of a gotcha - only this morning I found someone doing select="para" group-by="para/title". Oh for a context-free language! | |
22. | for-each-group, counting ancestor paths |
This for-each-group <xsl:for-each-group select="//*/string-join(ancestor-or-self::*/name(), '/')" group-by="."> <xsl:sequence select="concat('
', ., ' - ', count(current-group()))"/> </xsl:for-each-group> give this result: a - 1 a/b - 2 a/b/x - 3 a/b/x/w - 3 a/b/y - 1 a/c - 1 a/c/w - 2 | |
23. | group-adjacent use |
do you mean there may be other elements in the same sequence, but you just want to group related-article? In which case (if there are no sibling text nodes) something like <xsl:for-each-group select="*" group-adjacent="exists(self::related-article)"> <xsl:choose> <xsl:when test="current-grouping-key()"> <related-article><xsl:value-of select="current-group()"/></related-article> </xsl:when> <xsl:otherwise> <xsl:copy-of select="current-group()"/> </xsl:otherwise> </xsl:choose> </xsl:for-each-group> | |
24. | Working with pairs of siblings |
It seems to me that you can either write code that assumes you're working with a sequence of siblings, or you can write code that works with an arbitrary sequence. For sibling elements, the following works just fine: <xsl:for-each select="*[position() mod 2 = 1]"> <pair> <xsl:copy-of select="., following-sibling::*[1]" /> </pair> </xsl:for-each> If you want to work with an arbitrary sequence $SEQ (not necessarily siblings), try <xsl:for-each select="1 to count($SEQ)[. mod 2 = 1]"> <xsl:variable name="p" select="."/> <pair> <xsl:copy-of select="$SEQ[$p], $SEQ[$p+1]" /> </pair> </xsl:for-each> Alternatively of course there is <xsl:for-each-group select="$SEQ" group-adjacent="(position()-1) idiv 2"> <pair> <xsl:copy-of select="current-group()"/> </pair> </xsl:for-each-group> | |
25. | Group by element name |
I think I would tackle it by first flattening the structure into something like this: <Book rdf:ID="boo2"> <address rdf:dataytpe="aaa" path="">Almogavers 56</address> <month rdf:dataytpe="bbb" path="event/Inbook">July</month> <year rdf:dataytpe="ccc" path="event/Inbook">2006</year> <year rdf:dataytpe="ddd" path="proceedings/Proceedings">2008</year> <month rdf:dataytpe="ddd" path="proceedings/Proceedings">May</month> etc. That part can be done by <xsl:for-each select="//*[not(*)]"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:attribute name="path" select="string-join(ancestor::*/name(), '/')"/> </xsl:copy> </xsl:for-each> Then you can construct the output with <xsl:template match="/"> <xsl:variable name='flattened'> <xsl:for-each select="//*[not(*)]"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:attribute name="path" select="string-join(ancestor::*[not(position() = last())]/name(), '/')"/> </xsl:copy> </xsl:for-each> </xsl:variable> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <xsl:for-each-group select="$flattened/*" group-by="@path"> <xsl:sequence select="f:nest(tokenize(@path, '/'), current-group())"/> </xsl:for-each-group> </rdf:RDF> </xsl:template> <xsl:function name="f:nest" as="element()*"> <xsl:param name="path" as="xs:string*"/> <xsl:param name="content" as="element()*"/> <xsl:choose> <xsl:when test="exists($path)"> <xsl:element name="{$path[1]}"> <xsl:sequence select="f:nest(remove($path, 1), $content)"/> </xsl:element> </xsl:when> <xsl:otherwise> <xsl:sequence select="$content"/> </xsl:otherwise> </xsl:choose> </xsl:function> <xsl:template match="*"> <xsl:message> *****<xsl:value-of select="name(..)"/>/{<xsl:value-of select="namespace-uri()"/>}<xsl:value-of select="name()"/>****** </xsl:message> </xsl:template> Needs refinement if namespaces are involved. | |
26. | List all unique element and attribute names |
In XSLT 2.0 it's simply distinct-values(//*/name()) distinct-values(//@*/name()) If you really need to do it with XSLT 1.0, eliminating duplicates is essentially the same problem as grouping, and you can use the Muenchian grouping approach. The preceding-sibling grouping technique isn't going to work (a) because your nodes are not siblings of each other, and (b) because it only works where the grouping key is the string-value of the node, not where it is some other function of the node (here, it's name). Muenchian grouping works for any string-valued function of a node. If you are stuck with 1.0 then you need to use a key: <xsl:key name="names" match="*|@*" use="name()"/> with <xsl:for-each select="//*|//*/@*"> <xsl:if test="generate-id(.) = generate-id(key('names', name(.))[1])"> <xsl:value-of select="local-name()"/> In 2.0 you can use distinct-values() or xsl:for-each-group, but it always good to learn this technique. | |
27. | If i have a node like <node>1, 2, 3, 5-8, 9, 11, 15</node> How can I make it as: <node>1-3, 5-9, 11, 15</node> |
First parse the string and convert to a sequence of integers, Then do grouping using a method first suggested by David Carlisle on this list:
|