Please advise, noob to XSLT transformation.
What am I missing from the following XSLvalue-of? I have the following XML data. Then in my transformation listed below I am referencing these elements but I get empty fields in my output. I must be using the incorrect syntax on the <xsl:value-of select="./userInfo/addressMap/entry[2]/firstName"/>
<userInfo>
<addressMap>
<entry>
<key>2</key>
<value>
<addressField1>21941 Main Drive</addressField1>
<addressField2>Apt XYZ</addressField2>
<addressType>0</addressType>
<city>Lake Forest</city>
<emailId>krystal#bogus.com</emailId>
<firstName>Krystal M</firstName>
<lastName>Obama</lastName>
<phoneNo>9495551212</phoneNo>
<state>CA</state>
<zipCode>92630</zipCode>
</value>
</entry>
</addressMap>
</userInfo>
<table border="0" width="600" cellpadding="5" cellspacing="5">
<tr bgcolor="#cccccc" style="font-size:14px; font-weight:bold;">
<td align="left">SHIPPING INFO</td>
<td align="left">BILLING INFO</td>
</tr>
<tr bgcolor="#ffffff" style="font-size:12px;">
<td align="left"><table border="0">
<tr>
<td><xsl:value-of select="./userInfo/addressMap/entry[2]/firstName"/>
<xsl:value-of select="./userInfo/addressMap/entry[2]/lastName"/></td>
</tr>
<tr>
<td><xsl:value-of select="./userInfo/addressMap/entry[2]/addressField1"/></td>
</tr>
<xsl:choose>
<xsl:when test="./userInfo/addressMap/entry[2]/addressField2 and ./userInfo/addressMap/entry[2]/addressField2 != ''">
<tr>
<td><xsl:value-of select="./userInfo/addressMap/entry[2]/addressField2"/></td>
</tr>
</xsl:when>
<xsl:otherwise>
<tr>
<td> </td>
</tr>
</xsl:otherwise>
</xsl:choose>
<tr>
<td><xsl:value-of select="./userInfo/addressMap/entry[2]/city"/>,
<xsl:value-of select="./userInfo/addressMap/entry[2]/state"/>
<xsl:value-of select="./userInfo/addressMap/entry[2]/zipCode"/> USA </td>
</tr>
<tr>
<td><xsl:value-of select="./userInfo/addressMap/entry[2]/phoneNo"/></td>
</tr>
</table>
Here is my entire xml data.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cart>
<basketQuantity>0</basketQuantity>
<cartTotals>
<amtDueToBePaid>35.4</amtDueToBePaid>
<discountList>Employee Discount</discountList>
<giftWrapping>0.0</giftWrapping>
<preferredMemberAmountCharged>0.0</preferredMemberAmountCharged>
<preferredMemberSavings>0.0</preferredMemberSavings>
<promoCodeSavings>0.0</promoCodeSavings>
<promoShipping>0.0</promoShipping>
<regularMerchandise>59.0</regularMerchandise>
<regularShipping>0.0</regularShipping>
<saleMerchandise>59.0</saleMerchandise>
<savings>23.6</savings>
<shippingSavings>0.0</shippingSavings>
<subTotal>35.4</subTotal>
<tax>0.0</tax>
<taxableMerchandise>35.4</taxableMerchandise>
<total>35.4</total>
</cartTotals>
<errorAndWarnings/>
<itemsList>
<discountMap>
<entry>
<key>Employee Discount</key>
<value>23.6</value>
</entry>
</discountMap>
<itemName>Rosette Smocked Top</itemName>
<productId>45711923</productId>
<promoPrice>0.0</promoPrice>
<quantity>1</quantity>
<regularPrice>59.0</regularPrice>
<returnValue>35.4</returnValue>
<salePrice>59.0</salePrice>
<savings>23.6</savings>
<taxCode>0.0</taxCode>
<unitTax>0.0</unitTax>
<upc>457119500004</upc>
<uuid>b18ffa87c0a86f6f14618000479d92c9</uuid>
</itemsList>
<orderPlaced>false</orderPlaced>
<pipelineSessionId>abcxyz</pipelineSessionId>
<shipping>
<availableShippingMap>
<entry>
<key>2</key>
<value>
<actualShippingCost>7.95</actualShippingCost>
<deliveryDays>0</deliveryDays>
<savings>0.0</savings>
<shippingDiscount>0.0</shippingDiscount>
<shippingMethodName>2nd Day</shippingMethodName>
</value>
</entry>
<entry>
<key>1</key>
<value>
<actualShippingCost>0.0</actualShippingCost>
<deliveryDays>0</deliveryDays>
<savings>0.0</savings>
<shippingDiscount>0.0</shippingDiscount>
<shippingMethodName>Standard Shipping</shippingMethodName>
</value>
</entry>
<entry>
<key>3</key>
<value>
<actualShippingCost>19.95</actualShippingCost>
<deliveryDays>0</deliveryDays>
<savings>0.0</savings>
<shippingDiscount>0.0</shippingDiscount>
<shippingMethodName>Overnight Delivery</shippingMethodName>
</value>
</entry>
</availableShippingMap>
<savings>0.0</savings>
<selectedShippingMethodId>1</selectedShippingMethodId>
<shippingCost>0.0</shippingCost>
<shippingPromo>0.0</shippingPromo>
</shipping>
<userInfo>
<addressMap>
<entry>
<key>2</key>
<value>
<addressField1>21941 Main Drive</addressField1>
<addressField2>Apt XYZ</addressField2>
<addressType>0</addressType>
<city>Lake Forest</city>
<emailId>krystal#bogus.com</emailId>
<firstName>Krystal M</firstName>
<lastName>Obama</lastName>
<phoneNo>9495551212</phoneNo>
<state>CA</state>
<zipCode>92630</zipCode>
</value>
</entry>
</addressMap>
</userInfo>
</cart>
The problem is the "entry[2]", it's not selecting the entry with key 2 as you intend it to.
Off the top of my head, that should be something like "./userInfo/addressMap/entry[string(key) = 2]/value/firstName".
Related
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.
Hi SO,
New to XSLT and have been debugging this code for a while
The current variable is always returning 0
I need to find the sum of all (X) with the same value of (D) through each (Row)
V and W are related, not sure how to "connect" them
Example:
Row (AAA123)[SomeDesc1] = 1.00 +
Row (BBB456)[SomeDesc1] = 3.00
SumOfSomeDesc1 = 4.00
XSLT 1.0 only
XML:
<Root>
<Row>
<ID>AAA123</ID>
<V>
<X>1.00</X>
</V>
<V>
<X>2.00</X>
</V>
<MultipleFieldsInBetween />
<W>
<D>SomeDesc1</D>
</W>
<W>
<D>SomeDesc2</D>
</W>
</Row>
<Row>
<ID>BBB456</ID>
<V>
<X>3.00</X>
</V>
<V>
<X>4.00</X>
</V>
<MultipleFieldsInBetween />
<W>
<D>SomeDesc1</D>
</W>
<W>
<D>SomeDesc2</D>
</W>
</Row>
</Root>
XSLT Sum (Current):
<xsl:variable name="SumOfX" select="sum(//Row[ID/text()=$ID]/V[D/text()
=$Description])" />
I would tackle it as a grouping problem, first identifying unique descriptions, then finding Rows by the description and finally summing up the elements in the same position:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:key name="desc-group" match="Row/W/D" use="."/>
<xsl:variable name="desc-groups" select="//Row/W/D[generate-id() = generate-id(key('desc-group', .)[1])]"/>
<xsl:key name="row-group" match="Row" use="W/D"/>
<xsl:template match="/Root">
<html>
<body>
<table>
<thead>
<tr>
<th>Description</th>
<th>Sum</th>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="$desc-groups"/>
</tbody>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="Row/W/D">
<tr>
<td>
<xsl:value-of select="."/>
</td>
<td>
<xsl:variable name="pos" select="count(.. | ../preceding-sibling::W)"/>
<xsl:value-of select="sum(key('row-group', .)/V[position() = $pos]/X)"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
Result is
<html>
<body>
<table>
<thead>
<tr>
<th>Description</th>
<th>Sum</th>
</tr>
</thead>
<tbody>
<tr>
<td>SomeDesc1</td>
<td>4</td>
</tr>
<tr>
<td>SomeDesc2</td>
<td>6</td>
</tr>
</tbody>
</table>
</body>
</html>
I have some xslt script to parse the xml input and return the html file. So far it works fine. But now I need to parse the xml input to get this complex table with merging headers. What I want the output is below.
| V1 || V2 |
| version diff || version diff |
==========================================
| || |
| CONTENT || CONTENT |
| || |
As the Category1, Category2, Col 1, Col2, Col3, Col4 are all the table header. but Cateogry1, Cateogry2 are merged to make the table more clear.
My xml input like this,
<?xml version="1.0" encoding="utf-8"?>
<Index>
<entry name="Entry1">
<entry name="V1.baseversion">1.0.0.77</entry>
<entry name="V1.sizedifferencescount">0</entry>
<entry name="V2.sizedifferencescount">0</entry>
<entry name="V2.baseversion">1.0.0.75</entry>
</entry>
<entry name="Entry2">
<entry name="V1.baseversion">3.0.0.12</entry>
<entry name="V1.sizedifferencescount">0</entry>
<entry name="V2.sizedifferencescount">0</entry>
<entry name="V2.baseversion">3.0.0.13</entry>
</entry>
</Index>
Below is what I have tried the xslt so far, I intend to use this to parse the header.
<xsl:when test="position()=1">
<xsl:for-each select="*" >
<xsl:sort select="#name"/>
<xsl:if test=" ( (#name='V1.baseversion') or (#name='V1.sizedifferencescount') or (#name='V2.baseversion') or (#name='V2.sizedifferencescount'))">
<tr>
<th>
<xsl:attribute name="colspan">
<xsl:value-of select="2" />
</xsl:attribute>
<!--need to print Version 1 here-->
</th>
<th>
<xsl:attribute name="colspan">
<xsl:value-of select="2" />
</xsl:attribute>
<!--need to print Version 2 here-->
</th>
</tr>
<tr>
<th>
<!--need to print V1.baseversion here-->
</th>
<th>
<!--need to print V1.sizedifferencescount here-->
</th>
<th>
<!--need to print V2.baseversion here-->
</th>
<th>
<!--need to print V2.sizedifferencescount here-->
</th>
</tr>
</xsl:if>
</xsl:for-each>
</xsl:when>
And in the output html, each entry in the xml will be one row in the table. The output html should be like this.
<table>
<tr>
<th colspan=2>V1</th>
<th colspan=2>V2</th>
</tr>
<tr>
<th >Version</th>
<th >Diff</th>
<th >Version</th>
<th >Diff</th>
</tr>
<tr>
<td>XXXX</td><!--Display value for Entry1.V1.baseversion-->
<td>XXXX</td><!--Display value for Entry1.V1.sizedifferencescount-->
<td>XXXX</td><!--Display value for Entry1.V2.baseversion-->
<td>XXXX</td><!--Display value for Entry1.V2.sizedifferencescount-->
</tr>
<tr>
<td>XXXX</td><!--Display value for Entry2.V1.baseversion-->
<td>XXXX</td><!--Display value for Entry2.V1.sizedifferencescount-->
<td>XXXX</td><!--Display value for Entry2.V2.baseversion-->
<td>XXXX</td><!--Display value for Entry2.V2.sizedifferencescount-->
</tr>
</table>
Thank you for your information and help.
Alex
I still think there is something missing in your description, but in the interest of moving this forward, how about:
XSLT 1.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<table>
<tr>
<th colspan="2">V1</th>
<th colspan="2">V2</th>
</tr>
<tr>
<th>Version</th>
<th>Diff</th>
<th>Version</th>
<th>Diff</th>
</tr>
<xsl:for-each select="Index/entry">
<tr>
<td><xsl:value-of select="entry[#name='V1.baseversion']"/></td>
<td><xsl:value-of select="entry[#name='V1.sizedifferencescount']"/></td>
<td><xsl:value-of select="entry[#name='V2.baseversion']"/></td>
<td><xsl:value-of select="entry[#name='V2.sizedifferencescount']"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
My XML code & XSLT code
explanation :
I'm trying to move the string '2' from the elements with
(tr[#type='detail'] and td[#column='1'])
to the category header
(tr [#type='categoryhead' and level='2'])
Any help on this is greatly appreciated
Thanks a ton
<!--=============My XML=============-->
<tbody xmlns="http://mynamespace.com">
<tr layoutcode="" type="categoryhead" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">Bonds</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41164">
<td colname="1">Security_1(1,2)</td>
<td colname="2">500</td>`enter code here`
<td colname="3">330</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41167">
<td colname="1">Security_4(1,2,3,4)</td>
<td colname="2">10</td>
<td colname="3">265</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categorytotal" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">TOTAL Bonds</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categoryhead" level="1" categorykey="2936" hierarchykey="4921">
<td colname="1">Options</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,1)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,2)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
</tbody>
XSLT where I'm trying to move the string '2' from the elements with (tr[#type='detail'] and td[#column='1'])to the category header (tr [#type='categoryhead' and level='2'])
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://mynamespace.com" version="2.0">
<!-- Global Variable -->
<xsl:variable name="arg1" select="'2'"></xsl:variable>
<!-- This identity template copies the document -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #* "/>
</xsl:copy>
</xsl:template>
<xsl:template match="a:tbody/a:tr[#type='categoryhead' and #level='2']/a:td">
<xsl:for-each select="//a:tbody/a:tr[#type='detail']/a:td[#colname='1'][contains(.,$arg1)]">
<xsl:variable name="IsFooted" select="contains(.,$arg1)"></xsl:variable>
<xsl:value-of select="count(//a:tbody/a:tr[#type='detail']/a:td[#colname='1'][contains(.,$arg1)])"/>
<xsl:choose>
<xsl:when test="$IsFooted='true'">
<xsl:value-of select="."/>
<xsl:value-of select="concat('(',concat($arg1,')'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Desired XML Output:
<tbody xmlns="http://mynamespace.com">
<tr layoutcode="" type="categoryhead" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">Bonds</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages (2)</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41164">
<td colname="1">Security_1(1)</td>
<td colname="2">500</td>
<td colname="3">330</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41167">
<td colname="1">Security_4(1,3,4)</td>
<td colname="2">10</td>
<td colname="3">265</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3255" hierarchykey="4922">
<td colname="1">Beverages</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categorytotal" level="1" categorykey="2789" hierarchykey="4921">
<td colname="1">TOTAL Bonds</td>
<td colname="2">530</td>
<td colname="3">1,045</td>
</tr>
<tr layoutcode="" type="categoryhead" level="1" categorykey="2936" hierarchykey="4921">
<td colname="1">Options</td>
</tr>
<tr layoutcode="" type="categoryhead" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,1)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="detail" level="3" securitymasterkey="41168">
<td colname="1">Security_5(#,2)</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
<tr layoutcode="" type="categorytotal" level="2" categorykey="3248" hierarchykey="4922">
<td colname="1">Agriculture</td>
<td colname="2">10</td>
<td colname="3">890</td>
</tr>
</tbody>
The terminology is not quite clear here, as looking at the output sample, all that is happening is a "(2)" is being appended to a particular table cell, so it is not really moving anything.
Also, your question title of the question mentions about child elements, but looking at the XML structure, the "detail" rows are actually siblings of the "categoryhead" rows. This is probably where the problem lies; how do you correlate the "detail" rows with the associated "categoryheader". One way to do this could be using a key
<xsl:key name="row"
match="a:tr[#level != '1']"
use="generate-id(preceding-sibling::a:tr[#level=current()/#level - 1][1])" />
This gets the rows (other than at level 1) and groups them by the first preceding row with a lowel #level attribute.
Now, for your template match, because you are changing "td" elements, I would change the template to match such an element, rather than the "tr" element
<xsl:template match="a:tbody/a:tr[#type='categoryhead' and #level='2']/a:td[#colname='1']">
You can then use the key to get the 'child' elements, but instead of thinking in terms of if all child elements contain a '2', reverse the logic and check whether any child eleent doesn't contain a '2'
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://composition.bowne.com/2010/v4" version="1.0">
<!-- Global Variable -->
<xsl:variable name="arg1" select="'2'"></xsl:variable>
<xsl:key name="row" match="a:tr[#level != '1']" use="generate-id(preceding-sibling::a:tr[#level=current()/#level - 1][1])" />
<!-- This identity template copies the document -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a:tbody/a:tr[#type='categoryhead' and #level='2']/a:td[#colname='1']">
<xsl:variable name="IsMissing" select="key('row', generate-id(..))/a:td[#colname='1'][not(contains(text(), $arg1))]" />
<xsl:choose>
<xsl:when test="not($IsMissing)">
<xsl:value-of select="."/>
<xsl:value-of select="concat('(',concat($arg1,')'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
As an aside, the namespace in your XSLT does not match the namespace in your XML. You would need these to match for this to work, but I assume this is a typo.
EDIT: To remove the '2' from the 'detail' rows, try adding the following template. The 'isMissing' variable is a bit messier, because it has to find the associated 'categoryhead' first. Also note it uses 'replace' which is only available in XSLT 2.0.
<xsl:template match="a:tbody/a:tr[#type='detail']/a:td[#colname='1']">
<xsl:variable name="parentLevel" select="../#level - 1" />
<xsl:variable name="IsMissing" select="key('row', generate-id(../preceding-sibling::a:tr[#level=$parentLevel][1]))/a:td[#colname='1'][not(contains(text(), $arg1))]" />
<xsl:choose>
<xsl:when test="not($IsMissing)">
<xsl:value-of select="replace(., concat(',', $arg1), '')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I have the following xml:
input
<page>
<group category="cat1">
<item fileunder="#">.45 colt</item>
<item fileunder="#">8 queens</item>
<item fileunder="#">9 lives</item>
<item fileunder="#">99 bottles of beer</item>
<item fileunder="A">An innocent man</item>
<item fileunder="A">Academy awards</item>
<item fileunder="B">Before the dawn</item>
</group>
<group category="cat2">
<item fileunder="R">Rows of houses</item>
</group>
</page>
The input items are already sorted.
desired output
I want to produce a 3-column HTML table for every group, with a subheading (a 3-column spanning cell) for each distinct fileunder, optimally presented in a top-down, then-next-column (the items are already sorted):
<table>
<tr><td colspan="3">#</td></tr>
<tr><td>.45 colt</td><td>9 lives</td><td>99 bottles of beer</td></tr>
<tr><td>8 queens</td></tr>
<tr><td colspan="3">A</td></tr>
<tr><td>An innocent man</td><td>Academy awards</td></tr>
<tr><td colspan="3">B</td></tr>
<tr><td>Before the dawn</td></tr>
</table>
<table>
<tr><td colspan="3">R</td></tr>
<tr><td>Rows of houses</td></tr>
</table>
I can live if the items are presented as left-to-right, then-next-row.
What I have so far is:
current xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="itm_grp" match="/page/group/item" use="concat(../#category,':',#fileunder)"/>
<xsl:template match="page/group">
<table>
<xsl:for-each select="item[.=key('itm_grp',concat(../#category,':',#fileunder))[1]]">
<tr><td colspan="3"><xsl:value-of select="#fileunder"/></td></tr>
<xsl:variable name="nodeset" select="key('itm_grp',concat(../#category,':',#fileunder))"/>
<xsl:for-each select="$nodeset[position() mod 3=1]">
<tr>
<td><xsl:value-of select="."/></td>
<td><xsl:value-of select="following-sibling::item[1]"/></td>
<td><xsl:value-of select="following-sibling::item[2]"/></td>
</tr>
</xsl:for-each>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
which produces a left-to-right, then-next-row output (non-optimal); however, the following-sibling selects produce a “bleed-through” effect:
#
.45 colt 8 queens 9 lives
99 bottles of beer An innocent man Academy awards
A
An innocent man Academy awards Before the dawn
B
Before the dawn
R
Rows of houses
As you can see, fileunder # has two A items, and fileunder A has one B item.
So, my question is:
How can I produce the desired output (column-wise)?
If I can't do that, how can I have the row-wise output avoiding the “bleeding”?
Please note that I have very little experience with XSLT, so if my code is blatantly inefficient/idiotic/whatever, please feel free to educate me by replacing all of it!
NB: XSLT version 1, so apparently no index-of function is available.
There is a slight contradiction between your narrative and your listed expected output. You have asked for top-down, then left-right column fill order, which you have so in the listing for the non-empty values, but not for the empties. This spatial order implies that a whole column must be filled out before the next column can begin. I have assumed that your listing was a mistake and what your really want in output is ...
<table>
<tr>
<td colspan="3">#</td>
</tr>
<tr>
<td>.45 colt</td>
<td>9 lives</td>
<td>&npsp;</td>
</tr>
<tr>
<td>8 queens</td>
<td>99 bottles of beer</td>
<td>&npsp;</td>
</tr>
<tr>
<td colspan="3">A</td>
</tr>
<tr>
<td>An innocent man</td>
<td>Academy awards</td>
<td>&npsp;</td>
</tr>
<tr>
<td colspan="3">B</td>
</tr>
<tr>
<td>Before the dawn</td>
<td>&npsp;</td>
<td>&npsp;</td>
</tr>
</table>
<table>
<tr>
<td colspan="3">R</td>
</tr>
<tr>
<td>Rows of houses</td>
<td>&npsp;</td>
<td>&npsp;</td>
</tr>
</table>
... which is consistent top-down, then left-right column fill order.
This XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="kItemByFile" match="item" use="concat(../#category,':',#fileunder)"/>
<xsl:template match="/">
<html lang="en">
<head><title>Songs</title></head>
<body>
<xsl:apply-templates select="*/group" />
</body>
</html>
</xsl:template>
<xsl:template match="group">
<xsl:variable name="cat" select="concat(#category,':')" />
<table>
<xsl:apply-templates select="item[
generate-id() = generate-id(key('kItemByFile',concat($cat,#fileunder))[1])]"
mode="group-head" />
</table>
</xsl:template>
<xsl:template match="item" mode="group-head">
<xsl:variable name="items"
select="key('kItemByFile',concat(../#category,':',#fileunder))" />
<xsl:variable name="row-count" select="ceiling( count($items) div 3)" />
<tr><td colspan="3"><xsl:value-of select="#fileunder" /></td></tr>
<xsl:for-each select="$items[position() <= $row-count]">
<xsl:variable name="pos" select="position()" />
<xsl:apply-templates select="." mode="row">
<xsl:with-param name="items" select="$items" />
<xsl:with-param name="row" select="$pos" />
<xsl:with-param name="row-count" select="$row-count" />
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="item" mode="row">
<xsl:param name="items" select="/.." />
<xsl:param name="row" select="1" />
<xsl:param name="row-count" select="1" />
<tr>
<xsl:apply-templates select="
$items[(position() mod $row-count) = ($row mod $row-count)]" mode="td" />
<xsl:variable name="full-cols" select="floor((count($items) div $row-count))" />
<xsl:variable name="part-col" select="number($row <
((count($items) mod $row-count) + 1))" />
<xsl:variable name="empties" select="3 - ($full-cols + $part-col)" />
<xsl:for-each select="(document('')/*/*)[position() <= $empties]">
<xsl:call-template name="empty-cell" />
</xsl:for-each>
</tr>
</xsl:template>
<xsl:template match="item" mode="td">
<td><xsl:value-of select="." /></td>
</xsl:template>
<xsl:template name="empty-cell">
<td> </td>
</xsl:template>
</xsl:stylesheet>
...when applied to this input...
<page>
<group category="cat1">
<item fileunder="#">.45 colt</item>
<item fileunder="#">8 queens</item>
<item fileunder="#">9 lives</item>
<item fileunder="#">99 bottles of beer</item>
<item fileunder="A">An innocent man</item>
<item fileunder="A">Academy awards</item>
<item fileunder="B">Before the dawn</item>
</group>
<group category="cat2">
<item fileunder="R">Rows of houses</item>
</group>
</page>
...yields...
<!DOCTYPE html SYSTEM "about:legacy-compat">
<html lang="en">
<head>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Songs</title>
</head>
<body>
<table>
<tr>
<td colspan="3">#</td>
</tr>
<tr>
<td>.45 colt</td>
<td>9 lives</td>
<td> </td>
</tr>
<tr>
<td>8 queens</td>
<td>99 bottles of beer</td>
<td> </td>
</tr>
<tr>
<td colspan="3">A</td>
</tr>
<tr>
<td>An innocent man</td>
<td>Academy awards</td>
<td> </td>
</tr>
<tr>
<td colspan="3">B</td>
</tr>
<tr>
<td>Before the dawn</td>
<td> </td>
<td> </td>
</tr>
</table>
<table>
<tr>
<td colspan="3">R</td>
</tr>
<tr>
<td>Rows of houses</td>
<td> </td>
<td> </td>
</tr>
</table>
</body>
</html>
Note
For the empty cells in the output, when viewing the lexical HTML, you will get either or the literal white space equivalent. It is XSLT processor implementation dependant, but should not cause you any concern because it is model-equivalent.
Easiest way to fix that:
<xsl:variable name="header" select="#fileunder"/>
...
<xsl:value-of select="following-sibling::item[#fileunder=$header][1]"/>
<xsl:value-of select="following-sibling::item[#fileunder=$header][2]"/>