Output Context Node (full path) in XSLT 1.0? - debugging

For debugging purposes it would be handy to output the full path of the context node from within a template, is there unabbreviated xpath or function to report this ?
Example Template:
<xsl:template match="#first">
<tr>
<td>
<xsl:value-of select="??WHAT TO PUT IN HERE??"/>
</td>
</tr>
</xsl:template>
Example (Abridged) input document:
<people>
<person>
<name first="alan">
...
The output from the template would be something like:
people / person / name / #first
Or something similar.

This transformation produces an XPath expression for the wanted node:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:variable name="vNode" select=
"/*/*[2]/*/#first"/>
<xsl:apply-templates select="$vNode" mode="path"/>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:value-of select="concat('/',name())"/>
<xsl:variable name="vnumPrecSiblings" select=
"count(preceding-sibling::*[name()=name(current())])"/>
<xsl:variable name="vnumFollSiblings" select=
"count(following-sibling::*[name()=name(current())])"/>
<xsl:if test="$vnumPrecSiblings or $vnumFollSiblings">
<xsl:value-of select=
"concat('[', $vnumPrecSiblings +1, ']')"/>
</xsl:if>
</xsl:template>
<xsl:template match="#*" mode="path">
<xsl:apply-templates select="ancestor::*" mode="path"/>
<xsl:value-of select="concat('/#', name())"/>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document:
<people>
<person>
<name first="betty" last="jones"/>
</person>
<person>
<name first="alan" last="smith"/>
</person>
</people>
the wanted, correct result is produced:
/people/person[2]/name/#first

Here's a stylesheet (of dubious value) that prints the path to every element and attribute in a document:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:strip-space elements="*" />
<xsl:template match="*">
<xsl:param name="pathToHere" select="''" />
<xsl:variable name="precSiblings"
select="count(preceding-sibling::*[name()=name(current())])" />
<xsl:variable name="follSiblings"
select="count(following-sibling::*[name()=name(current())])" />
<xsl:variable name="fullPath"
select="concat($pathToHere, '/', name(),
substring(concat('[', $precSiblings + 1, ']'),
1 div ($follSiblings or $precSiblings)))" />
<xsl:value-of select="concat($fullPath, '
')" />
<xsl:apply-templates select="#*|*">
<xsl:with-param name="pathToHere" select="$fullPath" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#*">
<xsl:param name="pathToHere" select="''" />
<xsl:value-of select="concat($pathToHere, '/#', name(), '
')" />
</xsl:template>
</xsl:stylesheet>
When applied to this input:
<people>
<person>
<name first="betty" last="jones" />
</person>
<person>
<name first="alan" last="smith" />
</person>
<singleElement />
</people>
Produces:
/people
/people/person[1]
/people/person[1]/name
/people/person[1]/name/#first
/people/person[1]/name/#last
/people/person[2]
/people/person[2]/name
/people/person[2]/name/#first
/people/person[2]/name/#last
/people/singleElement

Related

Split variables separated by a comma, and perform an action on each

I am using XSLT v1.0 and running it through Microsoft Visual Studio.
I have data (which will be different in every node) but is structured like this:
<ItemGroupData ItemGroupOID="DDG4">
<ItemDataString ItemOID="DDLOCC" AuditRecordID="AR.8452551">5,8,9,2,3</ItemDataString>
<ItemDataString ItemOID="DDLOCL" AuditRecordID="AR.8452551">1,7</ItemDataString>
<ItemDataString ItemOID="DDLOCR" AuditRecordID="AR.8452551">1</ItemDataString>
</ItemGroupData>
There can be any number of values separated by commas in each of the 3 fields.
I am trying to split the data so I can work with each individual integer, and have tried the method suggested by Dimitre Novatchev here: split function in xslt 1.0, but it keeps giving me the error:
"'template' is not a recognized extension element. An error occurred at (0,0).".
The new split/mark templates I created are inside my overall template, which is being used to convert my XML to a CSV file.
Can I have a template within a template? Or do I need to define it outside the main template? Bit of a N00b with XML so any help would be greatly appreciated.
My new templates (note the processedItem bit has been simplified for demonstration purposes):
<xsl:template match="mark">
<xsl:variable name="vrtfSplit">
<xsl:apply-templates/>
</xsl:variable>
<xsl:for-each select="ext:node-set($vrtfSplit)/*">
<processedItem>
<xsl:if test="$varLOCOID='DDLOCL'">
<xsl:value-of select="current() * 100"/>
</xsl:if>
<xsl:if test="$varLOCOID='DDLOCC'">
<xsl:value-of select="current() * 10"/>
</xsl:if>
<xsl:if test="$varLOCOID='DDLOCR'">
<xsl:value-of select="current() * 150"/>
</xsl:if>
</processedItem>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" name="split">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText) > 0">
<item>
<xsl:value-of select="substring-before(concat($pText, ','), ',')"/>
</item>
<xsl:call-template name="split">
<xsl:with-param name="pText" select="substring-after($pText, ',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Consider the following example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/ItemGroupData">
<output>
<xsl:apply-templates/>
</output>
</xsl:template>
<xsl:template match="ItemDataString">
<items>
<xsl:call-template name="tokenize-and-process">
<xsl:with-param name="text" select="."/>
<xsl:with-param name="factor">
<xsl:choose>
<xsl:when test="#ItemOID='DDLOCL'">100</xsl:when>
<xsl:when test="#ItemOID='DDLOCC'">10</xsl:when>
<xsl:when test="#ItemOID='DDLOCR'">150</xsl:when>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</items>
</xsl:template>
<xsl:template name="tokenize-and-process">
<xsl:param name="text"/>
<xsl:param name="factor" select="1"/>
<xsl:param name="delimiter" select="','"/>
<xsl:variable name="token" select="substring-before(concat($text, $delimiter), $delimiter)" />
<item>
<xsl:value-of select="$token * $factor"/>
</item>
<xsl:if test="contains($text, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="tokenize-and-process">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
<xsl:with-param name="factor" select="$factor"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Applied to your example input XML, the result will be:
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<items>
<item>50</item>
<item>80</item>
<item>90</item>
<item>20</item>
<item>30</item>
</items>
<items>
<item>100</item>
<item>700</item>
</items>
<items>
<item>150</item>
</items>
</output>
P.S. No, a template cannot be a child of another template.

XSLT IF evaluation rules

From the XML file :
<store >
<tools>
<tool IDT="T1">
<container>B1</container>
<container>B2</container>
</tool>
<tool IDT="T2">
<container>B1</container>
</tool>
<tool IDT="T3">
<container>B2</container>
</tool>
</tools>
<boxes>
<box IDB="B1" height="10" width="20" length="30" weight="4"/>
<box IDB="B2" height="5" width="40" length="30" weight="2"/>
</boxes>
</store>
I try to display for each box the list of tools that go into each box. For that, I wrote the following XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
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">
<xsl:output
method="html"
encoding="UTF-8"
doctype-public="-//W3C//DTD HTML 4.01//EN"
doctype-system="http://www.w3.org/TR/html4/strict.dtd"
indent="yes" />
<xsl:template match="/">
<html>
<head>
<title>Boxes contents</title>
<link type="text/css" rel="stylesheet" href="styles.css" />
</head>
<body>
<h1>Boxes contents</h1>
<ul>
<xsl:apply-templates select="/store/boxes/box" />
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="box" >
<li><xsl:text>Box </xsl:text>
<xsl:value-of select="#ID"/>
<xsl:text>contains the following tools : </xsl:text>
</li>
<xsl:call-template name="findTools" >
<xsl:with-param name="currentBOX" select="#IDB"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="findTools" >
<xsl:param name="currentBOX" />
<xsl:for-each select="/store/tools/tool/container" >
<xsl:if test="container = $currentBOX" >
<br><xsl:value-of select="#IDT"/></br>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When I do it, I never see the tools. In debug under OXYGEN, I see that the IF is never true. I do not understand why? I start in XPath and XSLT, thanks for your help
You already are at a <container> element inside the <xsl:for-each>. There are no children, so selecting another <container> inside the <xsl:if> won't return anything.
You mean to execute your check from the <tool> node.
<xsl:for-each select="/store/tools/tool">
<xsl:if test="container = $currentBOX">
<xsl:value-of select="#IDT"/><br />
</xsl:if>
</xsl:for-each>
which is easier written as
<xsl:for-each select="/store/tools/tool[container = $currentBOX]">
<xsl:value-of select="#IDT"/><br />
</xsl:for-each>
Overall a more straight-forward way to write the two templates would be this:
<xsl:template match="box">
<li>
<xsl:text>Box </xsl:text>
<xsl:value-of select="#ID"/>
<xsl:text>contains the following tools : </xsl:text>
</li>
<xsl:apply-templates select="/store/tools/tool[container = current()/#IDB]" />
</xsl:template>
<xsl:template match="tool">
<xsl:value-of select="#IDT"/><br />
</xsl:template>
And alternatively you can use an <xsl:key> to index <tool> elements by their <container> value:
<xsl:key name="kToolByContainer" match="/store/tools/tool" use="container" />
<xsl:template match="box">
<li>
<xsl:text>Box </xsl:text>
<xsl:value-of select="#ID"/>
<xsl:text>contains the following tools : </xsl:text>
</li>
<xsl:apply-templates select="key('kToolByContainer', #IDB)" />
</xsl:template>
<xsl:template match="tool">
<xsl:value-of select="#IDT"/><br />
</xsl:template>

XSLT for each list value for specified id

I try list value in deep level parameter, but only specified parameter.
I do for each test id for 706 number, when true I do for each and list value name. I need too semicolon between values but not at the beginning and at the end.
XML:
<parameters>
<section id="27" name="Parametry produktu"/>
<parameter id="23" name="kolor">
<value id="42" name="jasny róż"/>
</parameter>
<parameter id="25" name="skład">
<value id="43" name="97% bawełna, 3% poliamid"/>
</parameter>
<parameter id="706" name="rozmiar (ukryć)"">
<value id="717" name="46"/>
<value id="718" name="47"/>
<value id="719" name="48"/>
</parameter>
<parameter id="142" name="płeć (ukryć)">
<value id="746" name="ona"/>
</parameter>
</parameters>
XSLT:
<sizes3>
<xsl:for-each select="parameters">
<xsl:if test="parameter/#id = 706">
<xsl:for-each select="parameter">
<xsl:choose>
<xsl:when test="position() = 1">
<xsl:value-of select="value/#name" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('; ', value/#name)" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</sizes3>
Result is:
<sizes3>jasny róż; 97% bawełna, 3% poliamid;46; ona;</sizes3>
But i need:
<sizes3>46;47;48</sizes3>
Here's one way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/parameters">
<!-- other stuff ? -->
<sizes3>
<xsl:for-each select="parameter[#id=706]/value">
<xsl:value-of select="#name" />
<xsl:if test="position()!=last()">
<xsl:text>;</xsl:text>
</xsl:if>
</xsl:for-each>
</sizes3>
<!-- more stuff ? -->
</xsl:template>
</xsl:stylesheet>
Simpler, shorter, no <xsl:for-each>, no XSLT conditional operators:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<size3><xsl:apply-templates/></size3>
</xsl:template>
<xsl:template match="parameter[#id=706]/value">
<xsl:apply-templates select="#name"/>
</xsl:template>
<xsl:template match="parameter[#id=706]/value[position() > 1]" priority="2">
<xsl:text>;</xsl:text><xsl:apply-templates select="#name"/>
</xsl:template>
</xsl:stylesheet>

Sort and concatenate consecutive nodes

I have a XML file which looks roughly like this (actual file is much more complex, everything has been truncated in this example):
<?xml version="1.0" encoding="utf-8"?>
<root>
<element>
<tag1>1</tag1>
<tag2>stuff</tag2>
<type>String</type>
<tag3>stuff</tag3>
</element>
<element>
<tag1>2</tag1>
<tag2>stuff</tag2>
<type>String</type>
<type>Date</type>
<type>Float</type>
<tag3>stuff</tag3>
</element>
<element>
<tag1>3</tag1>
<tag2>stuff</tag2>
<type>DateTime</type>
<tag3>stuff</tag3>
</element>
<element>
<tag1>4</tag1>
<tag2>stuff</tag2>
<type>Float</type>
<type>String</type>
<type>Date</type>
<tag3>stuff</tag3>
</element>
</root>
I process it with the following XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="element">
<xsl:element name="xs:element">
<xsl:attribute name="type"><xsl:call-template name="type"/></xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template name="type">
<xsl:variable name="initialType" select="translate(type,' ','')"/>
<xsl:choose>
<xsl:when test="$initialType='String'">
<xsl:text>xs:string</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Date'">
<xsl:text>xs:date</xsl:text>
</xsl:when>
<xsl:when test="$initialType='DateTime'">
<xsl:text>xs:dateTime</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Float'">
<xsl:text>xs:float</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Integer'">
<xsl:text>xs:int</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$initialType"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
And I get this resulting file:
<?xml version="1.0" encoding="UTF-8"?>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:float"/>
My problem here is that only the first <type> tag is taken into account. What I would like is to concatenate all the type tag contents into the type tag of the output, preceded by a sign indicating that the tag is an agglomerate if applicable.
However, to avoid creating artificially numerous types, the content of the tags must be alphabetically sorted first. In this example, the <element> number 2 and 4 are both made of only Float, String, and Date, albeit in a different order. They need to have the same type in the output.
The following output would be acceptable:
<?xml version="1.0" encoding="UTF-8"?>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:datexs:floatxs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:datexs:floatxs:string"/>
I am very new to XLST, and I have not managed to get anywhere close to the desired output so far. The code I have tried is just below, and fails horribly, notably because I failed to understand how to get <xsl:sort> working:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="element">
<xsl:element name="xs:element">
<xsl:attribute name="type">
<xsl:apply-templates>
<xsl:sort select="."/>
</xsl:apply-templates>
<xsl:call-template name="type"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template name="type">
<xsl:choose>
<xsl:when test="following-sibling::type">
<xs:text>union</xs:text>
<xsl:for-each select="following-sibling::type">
<xs:text>translate(type,' ','')</xs:text>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="initialType" select="translate(type,' ','')"/>
<xsl:choose>
<xsl:when test="$initialType='String'">
<xsl:text>xs:string</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Date'">
<xsl:text>xs:date</xsl:text>
</xsl:when>
<xsl:when test="$initialType='DateTime'">
<xsl:text>xs:dateTime</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Float'">
<xsl:text>xs:float</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Integer'">
<xsl:text>xs:int</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$initialType"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Just a few adjustments on your existing code were needed.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="element">
<xsl:element name="xs:element">
<xsl:attribute name="type">
<xsl:variable name="sorted">
<xsl:for-each select="type">
<xsl:sort select="."/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:apply-templates select="$sorted/type"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="type">
<xsl:variable name="initialType" select="translate(., ' ', '')"/>
<xsl:if test="count(preceding-sibling::type) = 0 and count(following-sibling::type) > 0">
<xsl:text>union</xsl:text>
</xsl:if>
<!-- HINT remove if you dont want any seperator -->
<xsl:if test="count(preceding-sibling::type) > 0">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:choose>
<xsl:when test="$initialType='String'">
<xsl:text>xs:string</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Date'">
<xsl:text>xs:date</xsl:text>
</xsl:when>
<xsl:when test="$initialType='DateTime'">
<xsl:text>xs:dateTime</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Float'">
<xsl:text>xs:float</xsl:text>
</xsl:when>
<xsl:when test="$initialType='Integer'">
<xsl:text>xs:int</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$initialType"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Please verify, for me i get the output (see HINT inline in XSLT):
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:date xs:float xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:date xs:float xs:string"/>
I would do it like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="element">
<xsl:element name="xs:element">
<xsl:attribute name="type">
<xsl:apply-templates select="type">
<xsl:sort select="."/>
</xsl:apply-templates>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'String']">xs:string</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'Date']">xs:date</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'DateTime']">xs:dateTime</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'Float']">xs:float</xsl:template>
<xsl:template match="type[translate(., ' ', '') = 'Integer']">xs:int</xsl:template>
<xsl:template match="type">
<xsl:value-of select="translate(., ' ', '')"/>
</xsl:template>
</xsl:stylesheet>

Xpath Query Help. Selecting data from multiple paths as a single data set

I have an xml structure like the following :
<doc>
<line void="false">
<lineNumber>1</lineNumber>
<info1>ddddd</info1>
<info2>aaaaa</info2>
</line>
<line void="true">
<lineNumber>2</lineNumber>
<voidLineNumber>1</voidLineNumber>
<voidValue>2.00</voidValue>
</line>
</doc>
I need one single set of data. I would like to select all the lines where void = false as well as the voidLineNumber and voidValue data from the line where void = true and the voidLineNumber = lineNumber from the original line.
Is this possible? Any help would be appreciated. Thanks
As Michael Kay noted, XPath itself can only be used to select nodes, not to transform them. You can do what you want with XSLT:
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8" indent="yes" />
<xsl:template match="doc">
<xsl:apply-templates select="line" />
</xsl:template>
<xsl:template match="line">
<xsl:variable name="voidvalue"><xsl:value-of select="#void" /></xsl:variable>
<xsl:if test="$voidvalue='false'">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:if test="name(.)='lineNumber'">
<xsl:value-of select="."/>
</xsl:if>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Resources