XSLT generate table out of child nodes in same parent node - xslt

I need to generate a table out of one parent node.
The XML snipped looks like this:
<persons>
<name>John</name>
<age>30</age>
<city>London</city>
<name>Jake</name>
<age>28</age>
<city>New York</city>
</persons>
But sometimes it can look like that:
<persons>
<name>John</name>
<age>30</age>
<name>Jake</name>
<age>28</age>
</persons>
The generated table should look the same for both cases, so like these:
<table>
<tbody>
<tr>
<th>Name</th>
<th>Age</th>
<th>City</th>
</tr>
<tr>
<td>John</td>
<td>30</td>
<td>London</td>
</tr>
<tr>
<td>Jake</td>
<td>28</td>
<td>New York</td>
</tr>
</tbody>
</table>
Or in the other case:
<table>
<tbody>
<tr>
<th>Name</th>
<th>Age</th>
<th>City</th>
</tr>
<tr>
<td>John</td>
<td>30</td>
<td></td>
</tr>
<tr>
<td>Jake</td>
<td>28</td>
<td></td>
</tr>
</tbody>
</table>
I know what tags can be in <persons>.
But I don't know how to do this in xslt.

As you are using XSLT 2.0, you can use the xsl:for-each-group construct for this, so you are grouping elements starting with the name element
Try this XSLT. This assumes name will always be present, and be the first element in the group. age and city need not be present though.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:template match="persons">
<table>
<tr>
<th>Name</th>
<th>Age</th>
<th>City</th>
</tr>
<xsl:for-each-group select="*" group-starting-with="name">
<tr>
<td><xsl:value-of select="." /></td>
<td><xsl:value-of select="current-group()[self::age]" /></td>
<td><xsl:value-of select="current-group()[self::city]" /></td>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>

Related

Using multiple regex in XSLT : how to reduce processing time

I am quite new to programming and XSLT: I try to improve the way I ask questions and explain problems, but I still have a long way to go. Sorry if there is something unclear.
I need to detect various alphabets in my XML document, which looks like this, with a lot more different language options.
<text>
<p>Some text. dise´mbər Some text. Some text.</p> <!-- text in International Phonetic Alphabet + English -->
<p>Some text. dise´mbər Some text. Издательство Академии Наук СССР Some text.</p> <!-- text in International Phonetic Alphabet + English + Cyrillic alphabet -->
<p>Some text. Издательство Академии Наук СССР dise´mbər Some text. Some text.</p>
<p>Some text. Some text. Издательство Академии Наук СССР Some text.</p> <!-- text in English + Cyrillic alphabet -->
</text>
What I started to do in XSLT is this:
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" indent="no" encoding="UTF-8" omit-xml-declaration="no" />
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:for-each select="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="processing-instruction()">
<xsl:processing-instruction name="{local-name()}"><xsl:apply-templates></xsl:apply-templates></xsl:processing-instruction>
</xsl:template>
<xsl:template name="IPA">
<xsl:variable name="text" ><xsl:copy-of select="."/></xsl:variable>
<xsl:analyze-string select="$text" regex="((\p{{IsIPAExtensions}}|\p{{IsPhoneticExtensions}})+)" >
<xsl:matching-substring>
<IPA><xsl:value-of select="regex-group(1)"/></IPA>
</xsl:matching-substring>
<xsl:non-matching-substring><xsl:copy-of select="."></xsl:copy-of></xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template name="Cyrillic">
<xsl:variable name="texte" ><xsl:call-template name="IPA"></xsl:call-template></xsl:variable>
<xsl:analyze-string select="$texte" regex="(\p{{IsCyrillic}}+)" >
<xsl:matching-substring>
<Cyrillic><xsl:apply-templates select="regex-group(1)"/></Cyrillic>
</xsl:matching-substring>
<xsl:non-matching-substring><xsl:call-template name="IPA"></xsl:call-template></xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template match="text()">
<xsl:call-template name="Cyrillic"></xsl:call-template>
</xsl:template>
</xsl:stylesheet>
So that I could get an XML like this:
<?xml version="1.0" encoding="UTF-8"?><text>
<p>Some text. dise´mb<IPA>ə</IPA>r Some text. Some text.</p>
<p>Some text. dise´mb<IPA>ə</IPA>r Some text. <Cyrillic>Издательство</Cyrillic> <Cyrillic>Академии</Cyrillic> <Cyrillic>Наук</Cyrillic> <Cyrillic>СССР</Cyrillic> Some text.</p>
<p>Some text. <Cyrillic>Издательство</Cyrillic> <Cyrillic>Академии</Cyrillic>
<Cyrillic>Наук</Cyrillic> <Cyrillic>СССР</Cyrillic> dise´mb<IPA>ə</IPA>r Some text. Some text.</p>
<p>Some text. Some text. <Cyrillic>Издательство</Cyrillic> <Cyrillic>Академии</Cyrillic>
<Cyrillic>Наук</Cyrillic> <Cyrillic>СССР</Cyrillic> Some text.</p>
</text>
This is what I needed, however, there is a ten or so regex blocks that I use and the processing time will be quite long if I use this method. What would you do instead? Do you think XSLT is appropriate for this?
Thank you !
Maria
(XSLT 2, Saxon-HE 9.8.0.8)
Edit: here's the profile:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Analysis of Stylesheet Execution Time</title>
</head>
<body>
<h1>Analysis of Stylesheet Execution Time</h1>
<p>Total time: 72128.065 milliseconds</p>
<h2>Time spent in each template, function or global variable:</h2>
<p>The table below is ordered by the total net time spent in the template, function
or global variable. Gross time means the time including called templates and functions
(recursive calls only count from the original entry); net time means time excluding
time spent in called templates and functions.
</p>
<table border="border" cellpadding="10">
<thead>
<tr>
<th>file</th>
<th>line</th>
<th>instruction</th>
<th>count</th>
<th>average time (gross/ms)</th>
<th>total time (gross/ms)</th>
<th>average time (net/ms)</th>
<th>total time (net/ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td> "*code/unicode.xsl" </td>
<td>21</td>
<td>template Greek</td>
<td align="right">2,755,968</td>
<td align="right">0.017</td>
<td align="right">46,854.785</td>
<td align="right">0.017</td>
<td align="right">46,854.785</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>32</td>
<td>template Hebrew</td>
<td align="right">1,329,696</td>
<td align="right">0.043</td>
<td align="right">57,529.163</td>
<td align="right">0.008</td>
<td align="right">10,674.378</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>54</td>
<td>template IPA</td>
<td align="right">333,984</td>
<td align="right">0.206</td>
<td align="right">68,964.076</td>
<td align="right">0.019</td>
<td align="right">6,381.186</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>43</td>
<td>template Cyrillic</td>
<td align="right">665,392</td>
<td align="right">0.094</td>
<td align="right">62,582.890</td>
<td align="right">0.008</td>
<td align="right">5,053.727</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>65</td>
<td>template Arabic</td>
<td align="right">167,068</td>
<td align="right">0.421</td>
<td align="right">70,284.800</td>
<td align="right">0.008</td>
<td align="right">1,320.724</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>76</td>
<td>template Arrows</td>
<td align="right">83,536</td>
<td align="right">0.849</td>
<td align="right">70,945.946</td>
<td align="right">0.008</td>
<td align="right">661.146</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>8</td>
<td>template *</td>
<td align="right">12,122</td>
<td align="right">5.959</td>
<td align="right">72,238.100</td>
<td align="right">0.034</td>
<td align="right">413.937</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>87</td>
<td>template Dingbats</td>
<td align="right">41,768</td>
<td align="right">1.708</td>
<td align="right">71,323.074</td>
<td align="right">0.009</td>
<td align="right">377.128</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>98</td>
<td>template Private</td>
<td align="right">20,884</td>
<td align="right">3.427</td>
<td align="right">71,576.916</td>
<td align="right">0.012</td>
<td align="right">253.842</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>18</td>
<td>template processing-instruction()</td>
<td align="right">6,907</td>
<td align="right">0.014</td>
<td align="right">98.490</td>
<td align="right">0.014</td>
<td align="right">98.490</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>121</td>
<td>template text()</td>
<td align="right">20,884</td>
<td align="right">3.429</td>
<td align="right">71,600.976</td>
<td align="right">0.001</td>
<td align="right">24.060</td>
</tr>
</tbody>
</table>
</body>
</html>
The profile of Martin Honnen's code:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Analysis of Stylesheet Execution Time</title>
</head>
<body>
<h1>Analysis of Stylesheet Execution Time</h1>
<p>Total time: 2900.594 milliseconds</p>
<h2>Time spent in each template, function or global variable:</h2>
<p>The table below is ordered by the total net time spent in the template, function
or global variable. Gross time means the time including called templates and functions
(recursive calls only count from the original entry); net time means time excluding
time spent in called templates and functions.
</p>
<table border="border" cellpadding="10">
<thead>
<tr>
<th>file</th>
<th>line</th>
<th>instruction</th>
<th>count</th>
<th>average time (gross/ms)</th>
<th>total time (gross/ms)</th>
<th>average time (net/ms)</th>
<th>total time (net/ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td> "*code/unicode.xsl" </td>
<td>44</td>
<td>template text()</td>
<td align="right">222,968</td>
<td align="right">0.009</td>
<td align="right">1,949.720</td>
<td align="right">0.009</td>
<td align="right">1,949.720</td>
</tr>
<tr>
<td> "*code/unicode.xsl" </td>
<td>26</td>
<td>template text()</td>
<td align="right">20,884</td>
<td align="right">0.135</td>
<td align="right">2,823.597</td>
<td align="right">0.042</td>
<td align="right">873.877</td>
</tr>
</tbody>
</table>
</body>
</html>
Regular expressions such as \p{IsIPAExtensions} should be reasonably efficient: most of the blocks are a single consecutive range of codepoints and testing a character should simply check whether it is in that range. The cost, I suspect, arises not so much from the cost of checking one character against one Unicode block, but from the number of characters and the number of blocks.
It might be worth getting a profile at the Java level to see where it is spending its time. I can guess, but a profile would reveal if my guess is right.
The thing that can kill performance with regular expressions is backtracking, but I don't immediately see any risk of backtracking with this code.
The only other approach that comes to mind is to generate an enormous translate() call that classifies characters into groups (so all latin characters become "1", all Cyrillic characters become "2", etc) and then to process the result using `<xsl:for-each-group select="string-to-codepoints(.)" group-adjacent=".">. But there's no guarantee that would perform any better, and it's a lot of work to do the experiments to find out.
In XSLT 3, I would consider the following approach:
<?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:fn="http://www.w3.org/2005/xpath-functions"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="scripts"
as="map(xs:string, xs:string)*"
select="map { 'Cyrillic' : '\p{IsCyrillic}+'},
map { 'IPA' : '[\p{IsIPAExtensions}\p{IsPhoneticExtensions}]+' }"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="text()">
<xsl:iterate select="$scripts">
<xsl:param name="input" select="."/>
<xsl:on-completion>
<xsl:sequence select="$input"/>
</xsl:on-completion>
<xsl:next-iteration>
<xsl:with-param name="input">
<xsl:apply-templates select="$input" mode="wrap">
<xsl:with-param name="script-map" tunnel="yes" select="."/>
</xsl:apply-templates>
</xsl:with-param>
</xsl:next-iteration>
</xsl:iterate>
</xsl:template>
<xsl:mode name="wrap" on-no-match="shallow-copy"/>
<xsl:template match="text()" mode="wrap">
<xsl:param name="script-map" tunnel="yes"/>
<xsl:analyze-string select="." regex="{$script-map?*}">
<xsl:matching-substring>
<xsl:element name="{map:keys($script-map)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
I haven't measured whether it performs better but for the regular expressions I would [\p{IsIPAExtensions}\p{IsPhoneticExtensions}]+ consider to be easier than (\p{IsIPAExtensions}|\p{IsPhoneticExtensions})+.
The other improvements are to rely on the xsl:mode based identity transformation and xsl:iterate.

How to get absolute document path of a file from collection

Please suggest to get the absolute path of each documents which are collected thru xslt collection.
Posted script is able to give the required absolute path, but I have used two collections (it may take unnecessary memory to store info of all articles twice, one collection for collecting info and other one to collect document-uri()s).
XMLs:
D:/DocumentPath/Project-01/2016/ABC/Test.xml
<article>
<title>First article</title>
<tag1>The tag 1</tag1>
<tag3>The tag 3</tag3>
</article>
D:/DocumentPath/Project-01/2016/DEF/Test.xml
<article>
<title>Second article</title>
<tag2>The tag 2</tag2>
<tag3>The tag 3</tag3>
</article>
and other XMLs....
XSLT 2.0:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="varDocuments">
<xsl:copy-of select="collection('file:///D:/DocumentPath/Project-01/2016/?select=*.xml;recurse=yes')
[matches(document-uri(.), '2016/([A-z]+)/.*?.xml')]"/>
</xsl:variable>
<xsl:variable name="varDocuments1">
<xsl:copy-of select="collection('file:///D:/DocumentPath/Project-01/2016/?select=*.xml;recurse=yes')
[matches(document-uri(.), '2016/([A-z]+)/.*?.xml')]/document-uri(.)"/>
</xsl:variable>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<xsl:template match="/">
<Table border="1">
<TR><TH>Position</TH><TH>Title</TH><TH>Tag1</TH><TH>Tag2</TH><TH>Tag3</TH><TH>Tag4</TH><TH>Path</TH></TR>
<xsl:for-each select="$varDocuments">
<xsl:for-each select="article">
<TR>
<xsl:variable name="varPos" select="position()"/>
<td><xsl:value-of select="position()"/></td>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="count(descendant::tag1)"/></td>
<td><xsl:value-of select="count(descendant::tag2)"/></td>
<td><xsl:value-of select="count(descendant::tag3)"/></td>
<td><xsl:value-of select="count(descendant::tag4)"/></td>
<td><xsl:value-of select="normalize-space(tokenize($varDocuments1, 'file:/')[position()=$varPos + 1])"/></td>
</TR>
</xsl:for-each>
</xsl:for-each>
</Table>
</xsl:template>
</xsl:stylesheet>
Required result:
<Table border="1">
<TR>
<TH>Position</TH>
<TH>Title</TH>
<TH>Tag1</TH>
<TH>Tag2</TH>
<TH>Tag3</TH>
<TH>Tag4</TH>
<TH>Path</TH>
</TR>
<TR>
<td>1</td>
<td>First article</td>
<td>1</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>D:/DocumentPath/Project-01/2016/ABC/Test.xml</td>
</TR>
<TR>
<td>2</td>
<td>Second article</td>
<td>0</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>D:/DocumentPath/Project-01/2016/DEF/Test.xml</td>
</TR>
<TR>
<td>3</td>
<td>Third article</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>2</td>
<td>D:/DocumentPath/Project-01/2016/GHI/Test.xml</td>
</TR>
</Table>
I would first suggest to change
<xsl:variable name="varDocuments">
<xsl:copy-of select="collection('file:///D:/DocumentPath/Project-01/2016/?select=*.xml;recurse=yes')
[matches(document-uri(.), '2016/([A-z]+)/.*?.xml')]"/>
</xsl:variable>
to at least
<xsl:variable name="varDocuments" select="collection('file:///D:/DocumentPath/Project-01/2016/?select=*.xml;recurse=yes')
[matches(document-uri(.), '2016/([A-z]+)/.*?.xml')]"/>
as there does not seem to be a need to pull in the documents with collection and then create an additional copy with copy-of.
With that correction, when you process each document with with <xsl:for-each select="$varDocuments">, you can simply there read out the document-uri(.) now, as you are processing the documents pulled in and not any copy assembled.

xsl counting and xpath syntax

I have a table of projects created in SharePoint 2010. I am trying to create reporting for management and need to get counts of various fields. I have used xsl to get the values of fields when viewing single items, so I am fairly familiar with the language. However, I cannot find a good explanation of the syntax for counting multiple items.
I have a table like this:
<table>
<tr>
<th class="ms-vb2">
Project Title
</th>
<th class="ms-vb2">
Project Leader
</th>
<th class="ms-vb2">
Project Status
</th>
</tr>
<tr>
<td class="ms-vb2">
Project Title 1
</td>
<td class="ms-vb2">
Project Leader 1
</td>
<td class="ms-vb2">
Completed
</td>
</tr>
<tr>
<td class="ms-vb2">
Project Title 2
</td>
<td class="ms-vb2">
Project Leader 1
</td>
<td class="ms-vb2">
Withdrawn
</td>
</tr>
<tr>
<td class="ms-vb2">
Project Title 3
</td>
<td class="ms-vb2">
Project Leader 2
</td>
<td class="ms-vb2">
Completed
</td>
</tr>
<!--About 100 more rows-->
</table>
There is a lot of nesting going on, so I am having difficulty targeting specific areas and I have very little control over the html due to this being generated by SharePoint.
Here is the reporting table I am trying to create using XSL:
<table id="FourBlockerHead" class="ClearBlockFloat">
<tr>
<th>Completed Count</th>
<th>Withdrawn Count</th>
<th>On Hold Count</th>
</tr>
<tr>
<td>
<xsl:value-of select="count(../td[#ms-vb2='Completed'])" /><!--Should be 2-->
</td>
<td>
<xsl:value-of select="count(../td[#ms-vb2='Withdrawn'])" /><!--Should be 1-->
</td>
<td>
<xsl:value-of select="count(../td[#ms-vb2='On Hold'])" /><!--Should be 0-->
</td>
</tr>
</table>
I know there is a problem with my XPATH syntax, but I cannot figure it out.
A complete XSLT would look like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" />
<xsl:template match="/table">
<html>
<body>
<table id="FourBlockerHead" class="ClearBlockFloat" border="1">
<tr>
<th>Completed Count</th>
<th>Withdrawn Count</th>
<th>On Hold Count</th>
</tr>
<xsl:for-each select="tr">
<tr>
<td>
<xsl:value-of select="count(td[#class='ms-vb2' and normalize-space(text())='Completed'])" /><!--Should be 2-->
</td>
<td>
<xsl:value-of select="count(td[#class='ms-vb2' and normalize-space(text())='Withdrawn'])" /><!--Should be 1-->
</td>
<td>
<xsl:value-of select="count(td[#class='ms-vb2' and normalize-space(text())='On Hold'])" /><!--Should be 0-->
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
You can include this in your source XML file with
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="source.xslt"?>
in your XML file to get a well formatted HTML output.
The output would look like this:
Instead of:
<xsl:value-of select="count(../td[#ms-vb2='Completed')" />
try:
<xsl:value-of select="count(//td[#class='ms-vb2'][normalize-space()='Completed'])" />
and likewise for the other two.
Notes:
You did not provide the context, so I have changed the path to an absolute one, that counts all nodes in the entire document;
You don't have an attribute named ms-vb2;
You need to trim the whitespace in the data cell before comparing ot to a string with no whitespace.
The following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="table">
<table id="FourBlockerHead" class="ClearBlockFloat">
<tr>
<th>Completed Count</th>
<th>Withdrawn Count</th>
<th>On Hold Count</th>
</tr>
<tr>
<td>
<xsl:value-of select="count(//td[#class='ms-vb2'][normalize-space()='Completed'])" />
</td>
<td>
<xsl:value-of select="count(//td[#class='ms-vb2'][normalize-space()='Withdrawn'])" />
</td>
<td>
<xsl:value-of select="count(//td[#class='ms-vb2'][normalize-space()='On Hold'])" />
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
applied to your input example, will return:
<table id="FourBlockerHead" class="ClearBlockFloat">
<tr>
<th>Completed Count</th>
<th>Withdrawn Count</th>
<th>On Hold Count</th>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>0</td>
</tr>
</table>

Iterate through siblings until a specific element type

I have a flat XML structure looking like this:
<root>
<header>First header</header>
<type1>Element 1:1</type1>
<type2>Element 1:2</type2>
<header>Second header</header>
<type1>Element 2:1</type1>
<type3>Element 3:1</type3>
<header>Third header</header>
<type1>Element 3:1</type1>
<type2>Element 3:2</type2>
<type1>Element 3:3</type1>
<type2>Element 3:4</type2>
</root>
Essentialy there is an unknown number of headers. Under each header there is an unknown number of elements (tree different types). There can be zero to many elements of each type under each header. I do not have control over this structure so I can't change/improve it.
What I'm trying to generate is this HTML:
<h2>First header</h2>
<table>
<tr>
<th>Type 1</th>
<td>Element 1:1</td>
</tr>
<tr>
<th>Type 2</th>
<td>Element 1:2</td>
</tr>
</table>
<h2>Second header</h2>
<table>
<tr>
<th>Type 1</th>
<td>Element 2:1</td>
</tr>
<tr>
<th>Type 3</th>
<td>Element 2:2</td>
</tr>
</table>
<h2>third header</h2>
<table>
<tr>
<th>Type 1</th>
<td>Element 3:1</td>
</tr>
<tr>
<th>Type 2</th>
<td>Element 3:2</td>
</tr>
<tr>
<th>Type 1</th>
<td>Element 3:3</td>
</tr>
<tr>
<th>Type 2</th>
<td>Element 3:4</td>
</tr>
</table>
Each header will be an HTML header (level 2) and then I want all the other elements until the next header to be shown in a table.
My first idea is to make a template that matches the header elements:
<xsl:template match="header">
<h2><xsl:value-of select="text()" /></h2>
<table>
???
</table>
</xsl:template>
I'm thinking I could replace "???" with code iterating through all the following siblings until the next header element and turn them into table rows.
Is that a good idea?
If it is, how do I do it?
If it isn't, what's a better solution?
I'm using XSLT 1.0.
One way to achieve this to use a key, to group the non-header elements by their first preceding header element
<xsl:key name="type" match="*[not(self::header)]" use="generate-id(preceding-sibling::header[1])" />
You would then start off by selecting just header elements
<xsl:apply-templates select="header" />
And within the template that matches this header element, you can then get all the type elements that correspond to the header by using the key
<xsl:for-each select="key('type', generate-id())">
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:key name="type" match="*[not(self::header)]" use="generate-id(preceding-sibling::header[1])" />
<xsl:template match="root">
<xsl:apply-templates select="header" />
</xsl:template>
<xsl:template match="header">
<h2><xsl:value-of select="." /></h2>
<table>
<xsl:for-each select="key('type', generate-id())">
<tr>
<th><xsl:value-of select="local-name()" /></th>
<th><xsl:value-of select="." /></th>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Note, this doesn't include the issue of converting the node name type1 to Type 1, but I will leave that exercise for you....

XSL Repeat Template Object containing List<Object>

I have a Library object that contains a collection of Books... The Library object has properties like Name, Address, Phone... While the Book object has properties like ISDN, Title, Author, and Price.
XML looks something like this...
<Library>
<Name>Metro Library</Name>
<Address>1 Post Rd. Brooklyn, NY 11218</Address>
<Phone>800 976-7070</Phone>
<Books>
<Book>
<ISDN>123456789</ISDN>
<Title>Fishing with Luke</Title>
<Author>Luke Miller</Author>
<Price>18.99</Price>
</Book>
<Book>
<ISDN>234567890</ISDN>
<Title>Hunting with Paul</Title>
<Author>Paul Worthington</Author>
<Price>28.99</Price>
</Book>
...
And more books
...
</Books>
</Library>
I have a template with space for only 10 per page for example. There can be hundreds of books in the list of Books... So I need to limit the number of books and repeat the template every 10 books.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<div>
<table>
<tr>
<td>NAME</td>
<td><xsl:value-of select="/Library/Name"/></td>
</tr>
<tr>
<td>ADDRESS</td>
<td><xsl:value-of select="/Library/Address"/></td>
</tr>
<tr>
<td>PHONE</td>
<td><xsl:value-of select="/Library/Phone"/></td>
</tr>
</table>
<table>
<xsl:for-each select="/Library/Books/Book">
<tr>
<td><xsl:value-of select="position()"/></td>
<td><xsl:value-of select="ISDN"/></td>
<td><xsl:value-of select="Title"/></td>
<td><xsl:value-of select="Author"/></td>
<td><xsl:value-of select="Price"/></td>
</tr>
</xsl:for-each>
</table>
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
How can I get the Library information to appear on all repeating pages and add 10 books per page?... First page has Library info with Books 1 thru 10, Second page has Library info with Books 11 thru 20, and so on??
Thanks
For starters, try not to use for-each, apply-templates allows the engine to optimise the order that events are processed.
It appears that you are calling this stylesheet from some other system, so the approach I've taken is to define a pagination param. In the host language, when you call this just change the root parameter. This then allows you to select the require pages in this line here:
Books/Book[($page - 1)*10 < position() and position() <= ($page)*10]
This should do the trick.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="page" select="1"/>
<xsl:template match="/Library">
<html>
<body>
<div>
<table>
<tr>
<td>NAME</td>
<td>
<xsl:value-of select="/Name"/>
</td>
</tr>
<tr>
<td>ADDRESS</td>
<td>
<xsl:value-of select="/Address"/>
</td>
</tr>
<tr>
<td>PHONE</td>
<td>
<xsl:value-of select="/Phone"/>
</td>
</tr>
</table>
<table>
<xsl:apply-templates select="Books/Book[($page - 1)*10 < position() and position() <= ($page)*10]"/>
</table>
</div>
</body>
</html>
</xsl:template>
<xsl:template match="/Book">
<tr>
<td>
<xsl:value-of select="position()"/>
</td>
<td>
<xsl:value-of select="ISDN"/>
</td>
<td>
<xsl:value-of select="Title"/>
</td>
<td>
<xsl:value-of select="Author"/>
</td>
<td>
<xsl:value-of select="Price"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>