How to count created node XSL? - xslt

I was asking myself a question.
I'm on project where I've to transform a .XML file into an other one (after a treatment) but I've to do a numbered list. I know the count(//node) function but I don't think we can count the created nodes with.
For example this is what my .xsl looks like :
<xsl:template match="/">
<Type>
<List>
<xsl:apply-templates mode="PartOne" select="/Stuff/Info/TypeA"/>
<xsl:apply-templates mode="PartOneBis" select="/Stuff/Info/TypeB"/>
</List>
<AnotherList>
<xsl:apply-templates mode="PartTwo" select="/Stuff/Info/TypeB"/>
</AnotherList>
</Type>
</xsl:template>
<xsl:template mode="PartOne" match="/Stuff/Info/TypeA">
<PartOne indexlist="{position()-1}">
... treatment ...
</PartOne>
</xsl:template>
<xsl:template mode="PartOneBis" match="/Stuff/Info/TypeB">
<xsl:if test="TypeB_Indice != 'stuff'">
<PartOne indexlist="{count(//TypeA) + position()-1}">
... treatment ...
</PartOne>
</xsl:if>
</xsl:template>
<xsl:template mode="PartTwo" match="/Stuff/Info/TypeB">
<xsl:if test="TypeB_Indice = 'stuff'">
<PartTwo indexlist="{count(//TypeA) + position()-1}">
... treatment ...
</PartTwo>
</xsl:if>
</xsl:template>
And this is what my .xml looks like:
<Stuff>
<Info>
<TypeA>
<TypeA_Stuff/>
<TypeA_Indice>xxx</TypeA_Indice>
</TypeA>
<TypeB>
<TypeB_Stuff/>
<TypeB_Indice>xxx</TypeB_Indice>
</TypeB>
</Info>
</Stuff>
---------------------- edit ----------------------------
The conditions for the PartOneBis is more complacated than the one I put in this code, there's more like 6 differents factor that can change its state from ok to not ok.
I was thinking of a for-each with an if and an incrementation but this don't work because you can't overwrite a variable or may be I'm wrong in my method.
If there's a way to count the node create before your point without having to create two .xml or use a c++ function I'll like to know it.
Thanks.
I need to put the "partOne" type first and the "partTwo" second but in the xml I've got, there's some conditions that makes that the TypeB is a partOne otherwise the other cases'll be a partTwo.
TypeA -> partOne
TypeB -> if (something) partOne else partTwo
But the condition depend of several value which don't came from the same node but the wanted result is something like that
<PartOne indexlist="0">
SomeStuff
</PartOne>
<PartOne indexlist="1">
SomeStuff
</PartOne>
<PartTwo indexlist="2">
SomeStuff
</PartTwo>

I find it impossible to follow your example. Consider a simpler one:
XML
<input>
<item>red</item>
<item>red red</item>
<item>red blue</item>
<item>red green</item>
<item>blue</item>
<item>blue blue</item>
<item>blue green</item>
<item>blue red</item>
<item>green</item>
</input>
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="/input">
<xsl:variable name="red-items" select="item[contains(., 'red')]" />
<xsl:variable name="blue-items" select="item[contains(., 'blue')]" />
<output>
<red-list>
<xsl:apply-templates select="$red-items"/>
</red-list>
<blue-list>
<xsl:apply-templates select="$blue-items">
<xsl:with-param name="n" select="count($red-items)"/>
</xsl:apply-templates>
</blue-list>
</output>
</xsl:template>
<xsl:template match="item">
<xsl:param name="n" select="0"/>
<item i="{$n + position()}">
<xsl:value-of select="." />
</item>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<red-list>
<item i="1">red</item>
<item i="2">red red</item>
<item i="3">red blue</item>
<item i="4">red green</item>
<item i="5">blue red</item>
</red-list>
<blue-list>
<item i="6">red blue</item>
<item i="7">blue</item>
<item i="8">blue blue</item>
<item i="9">blue green</item>
<item i="10">blue red</item>
</blue-list>
</output>

Related

How to determine the value format is dd-mmm-yyyy in xslt

I have to determine the input value having date format of dd-mmm-yyyy. If I can find will set some attribute based on the attribute I can do the format in C# report processing class.
<td>
<xsl:if test="To write expression to match the value">
<r>
<xyz:value-of select="'Set Value'" />
</r>
</xsl:if>
</td>
Input value is "30-Jun-2019". If it matches I want to set .
Basically I have set of columns in the report. I have to identify the the values in the report if the value matches with the Date format of dd-mmm-yyy setting some attribute in the xslt and applying the same format in report parser code which is written in c#
As I said in comments, there is no regex support in XSLT 1.0, so this can get quite tedious.
Consider the following example:
XML
<input>
<item>21-Jan-1987</item>
<item>921-Jan-1987</item>
<item>15-Jul-2009</item>
<item>15-Jux-2009</item>
<item>03-Dec-2014</item>
<item>03-Dec-999</item>
</input>
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="/input">
<output>
<xsl:for-each select="item">
<item value="{.}">
<xsl:variable name="dd" select="substring-before(., '-')" />
<xsl:variable name="mmm" select="substring-before(substring-after(., '-'), '-')" />
<xsl:variable name="yyyy" select="substring-after(substring-after(., '-'), '-')" />
<xsl:if test="translate($dd, '123456789', '000000000') = '00' and translate($yyyy, '123456789', '000000000') = '0000' and ($mmm='Jan' or $mmm='Feb' or $mmm='Mar' or $mmm='Apr' or $mmm='May' or $mmm='Jun' or $mmm='Jul' or $mmm='Aug' or $mmm='Sep' or $mmm='Oct' or $mmm='Nov' or $mmm='Dec')">
<xsl:text>Is Date</xsl:text>
</xsl:if>
</item>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<item value="21-Jan-1987">Is Date</item>
<item value="921-Jan-1987"/>
<item value="15-Jul-2009">Is Date</item>
<item value="15-Jux-2009"/>
<item value="03-Dec-2014">Is Date</item>
<item value="03-Dec-999"/>
</output>
Note that this checks only that the input conforms to the pattern, not that the date itself is valid. Also keep in mind that XML is case-sensititve.
Added:
If you prefer, you could simplify the test to:
<xsl:if test="translate(translate(translate(., '123456789', '000000000'), 'JFMASOND', '########'), 'anebpryulgctov', '%%%%%%%%%%%%%%') = '00-#%%-0000'">
but then a value like 15-Jpt-2009 will pass as date.
In XSLT 2.0 this is fairly trivial: matches(., '[0-9]{2}-[A-Z][a-z]{2}-[0-9]
{4}')
In 1.0 it's considerably harder, and it depends a little bit how precise you want to be. But you could get close with translate(translate($input, 'ABC...abc...', 'AAAAAAAA....'), '0123456789', '9999999999') = '99-AAA-9999') where the '...' means you have to write out the rest of the alphabet.

How do I generate a XSLT stylesheet for the following TEI snipet?

i wanted to ask you for help. I am totally new to XSLT and i wanted to know if someone can show me the right XSLT stylesheet for the following TEI-snipet:
<div>
<head>Weitere Aufzählungen</head>
<list rend="numbered">
<item n="1">A</item>
<item n="2">B</item>
<item n="3">C<list rend="numbered">
<item n="3.1">a</item>
<item n="3.2">b</item>
</list>
</item>
</list>
</div>
The Output should look like that in the HTML-Document:
1. A
2. B
3. C
3.1 a
3.2 b
Thank you so much for helping me :)
If you want a text output, the following stylesheet with <xsl:output method="text" /> will do. It distinguishes the level of indentation by counting the item ancestor nodes and adds an extra . on level 0.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" />
<xsl:variable name="newLine" select="'
'" />
<xsl:template match="text()" />
<xsl:template match="/div/list">
<xsl:apply-templates select="item" />
</xsl:template>
<xsl:template match="item">
<xsl:for-each select="ancestor::item"><xsl:text> </xsl:text></xsl:for-each>
<xsl:value-of select="#n" />
<xsl:if test="not(ancestor::item)"><xsl:text>.</xsl:text></xsl:if>
<xsl:value-of select="concat(' ',text(),$newLine)" />
<xsl:apply-templates select="list" />
</xsl:template>
</xsl:stylesheet>
Output is:
1. A
2. B
3. C
3.1 a
3.2 b
BTW you may need to add an appropriate namespace declaration for the TEI namespace to the xsl:stylesheet element.

Process an element both through a generic and a targeted template

I'm new at XSL and still not sure of some of my terminology. I'm currently facing something I can't seem to crack. I'm attempting to
Search through all data nodes (leaf elements?) of input XML and replace text
Search through all attribute values in input XML and replace text
Copy over other nodes to output
Copy over processor instructions and comments to output
Match and process specific nodes in the input
The issues I'm facing are:
A. Not sure of the terminology (see the comments in files below) and the tack I'm taking in attacking this
B. The template (5) above, whenever it matches a node, seems to be preventing the other templates (1 and 2) from processing it
If it makes a difference, I'm running this on Windows, using Microsoft's processor and am using XSLT 1.0. I've included simplified versions of the input (Input.xml), XSLT (Transform.xslt) and the output (Output.xml) I'm getting.
I did try using "mode" to run the targeted template (5) from the generic search and replace templates (1 and 2) but, in that case, template 1 and 2 run but the targeted template (5) itself doesn't run.
I'd appreciate any comments and suggestions.
Input.xml
<?xml version="1.0" encoding="UTF-8"?>
<List>
<Item Name="Item1" Text="abcd"/>
<Item Name="Itembc" Text="qrst"/>
<Item Name="Special" Text="Hello, Worldbc"/>
<Item Name="Special" Text=""/>
</List>
Transform.xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:myjs="urn:custom-javascript" exclude-result-prefixes="msxsl myjs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<msxsl:script language="JavaScript" implements-prefix="myjs">
<![CDATA[
function EscapeRegExp(str)
{
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function StringReplace(strWhere, strWhat, strBy, strFlags)
{
return strWhere.replace ( new RegExp(EscapeRegExp(strWhat), strFlags), strBy);
}
]]>
</msxsl:script>
<!-- ********************************************************************************************************************** -->
<!-- -->
<!-- Because of the following 4 templates, the identity transform is not needed in this XSLT -->
<!-- -->
<!-- ********************************************************************************************************************** -->
<!-- 1 of 4: Copy all nodes from source XML to the final XML, searching and replacing -->
<!-- Modify (1 of 4) and (2 of 4) to support additional replacement -->
<!-- Search and Replace in attributes -->
<xsl:template match="#*">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:variable name="TempAttrValue" select="."/>
<xsl:value-of select="myjs:StringReplace(string($TempAttrValue), 'bc', '2', 'g')"/>
</xsl:attribute>
<!-- xsl:apply-templates mode="TargetedTemplate"/ -->
</xsl:template>
<!-- 2 of 4: Copy all nodes from source XML to the final XML, searching and replacing -->
<!-- Modify (1 of 4) and (2 of 4) to support additional replacement -->
<!-- Search and Replace in data nodes -->
<xsl:template match="text()">
<xsl:variable name="TempTextValue" select="."/>
<xsl:value-of select="myjs:StringReplace(string($TempTextValue), 'bc', '3', 'g')"/>
<!-- xsl:apply-templates mode="TargetedTemplate"/ -->
</xsl:template>
<!-- 3 of 4: Copy all nodes from source XML to the final XML, searching and replacing -->
<!-- Process element nodes but not attributes -->
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- 4 of 4: Copy all nodes from source XML to the final XML, searching and replacing -->
<!-- Leave the comment nodes and processing instruction nodes alone -->
<xsl:template match="comment() | processing-instruction()">
<xsl:copy/>
</xsl:template>
<!-- 5 : Process specific nodes -->
<!-- Assumes an item in the input called Special and fills it with (No Data) if it is empty -->
<!-- Seems to be interfering with 1-4 above. Changing the [#Name='Special'] to [#Name='SpecialA'] will let 1-4 above to work -->
<xsl:template match="Item[#Name='Special']/#Text">
<xsl:attribute name="Text">
<xsl:variable name="TempSpecialText" select="."/>
<xsl:choose>
<xsl:when test="($TempSpecialText = '')">(No Data)</xsl:when>
<xsl:otherwise><xsl:value-of select="$TempSpecialText"/></xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Output.xml
<?xml version="1.0" encoding="UTF-8"?>
<List>
<Item Name="Item1" Text="a2d">
</Item>
<Item Name="Item2" Text="qrst">
</Item>
<Item Name="Special" Text="Hello, Worldbc">
</Item>
<Item Name="Special" Text="(No Data)">
</Item>
</List>
Output - Desired.xml
<?xml version="1.0" encoding="UTF-8"?>
<List>
<Item Name="Item1" Text="a2d">
</Item>
<Item Name="Item2" Text="qrst">
</Item>
<Item Name="Special" Text="Hello, World2">
</Item>
<Item Name="Special" Text="(No Data)">
</Item>
</List>
I think the main problem is with your final template
<xsl:template match="Item[#Name='Special']/#Text">
<xsl:attribute name="Text">
<xsl:variable name="TempSpecialText" select="."/>
<xsl:choose>
<xsl:when test="($TempSpecialText = '')">(No Data)</xsl:when>
<xsl:otherwise><xsl:value-of select="$TempSpecialText"/></xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:template>
This will match your <Item Name="Special" Text="Hello, Worldbc"> element ahead of the template that just matches #*. However, the Text attribute is not empty, and your xsl:otherwise simply outputs the value again, and doesn't do the replace you want. The <xsl:apply-templates/> here is unnecessary because attributes do not have any child nodes to select.
What you can do is this...
<xsl:template match="Item[#Name='Special']/#Text">
<xsl:attribute name="Text">
<xsl:variable name="TempSpecialText" select="."/>
<xsl:choose>
<xsl:when test="($TempSpecialText = '')">(No Data)</xsl:when>
<xsl:otherwise>
<xsl:value-of select="myjs:StringReplace(string($TempSpecialText), 'bc', '2', 'g')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:template>
But this is duplication of code. A better solution would be to change the template only match empty attributes, like so:
<xsl:template match="Item[#Name='Special']/#Text[. = '']">
<xsl:attribute name="Text">(No Data)</xsl:attribute>
</xsl:template>
That way it will only match the <Item Name="Special" Text=""/> element, where as the <Item Name="Special" Text="Hello, Worldbc"/> will be matched by the generic #* template.
Be wary of using Microsoft specific extension functions, as this obviously limits you to running on Microsoft platforms. If you limited to XSLT 1.0, this means using a recursive template. (See Find and replace entity in xslt as an example). Alternatively, if you can switch to XSLT 2.0, the replace function comes as standard.
Would this work for you?
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:myjs="urn:custom-javascript"
exclude-result-prefixes="msxsl myjs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<msxsl:script language="JavaScript" implements-prefix="myjs">
<![CDATA[
function EscapeRegExp(str)
{
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function StringReplace(strWhere, strWhat, strBy, strFlags)
{
return strWhere.replace ( new RegExp(EscapeRegExp(strWhat), strFlags), strBy);
}
]]>
</msxsl:script>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*[contains(., 'bc')]">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="myjs:StringReplace(string(.), 'bc', '2', 'g')"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="#*[not(string())]">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:text>(No Data)</xsl:text>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>

call template twice with similar parameters

right now I have an xml file like this:
Basically all tags appear twice, but with prefixes sideA or sideB
<root>
<sideA_foo>abc</sideA_foo>
<sideA_bar>123</sideA_bar>
<sideA_foobar>xyz</sideA_foobar>
<!--many more sideA... tags -->
<sideB_foo>def</sideB_foo>
<sideB_bar>456</sideB_bar>
<sideB_foobar>uvw</sideB_foobar>
<!--many more sideB... tags -->
</root>
then I have a template like this
<xsl:template name="template1">
<xsl:param name = "foo"/>
<xsl:param name = "bar"/>
<xsl:param name = "foobar"/>
<!-- many more params -->
<!-- do anything here -->
</xsl:template>
Is there an elegant way to call this template twice with all of its params,
<xsl:with-param name = "foo" select = "sideA_foo"/> etc.
<xsl:with-param name = "foo" select = "sideB_foo"/> etc.
without wirting all of this very verbosely, which I hate?
Here's one way you could consider:
Given an example input:
<root>
<sideA_width>5</sideA_width>
<sideA_length>7</sideA_length>
<sideB_width>6</sideB_width>
<sideB_length>3</sideB_length>
</root>
the following stylesheet:
<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:template match="/root">
<output>
<xsl:call-template name="side-area">
<xsl:with-param name="side" select="'sideA'"/>
</xsl:call-template>
<xsl:call-template name="side-area">
<xsl:with-param name="side" select="'sideB'"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="side-area">
<xsl:param name="side"/>
<xsl:param name="width" select="*[name()=concat($side, '_width')]"/>
<xsl:param name="length" select="*[name()=concat($side, '_length')]"/>
<xsl:element name="{$side}_area">
<xsl:value-of select="$width * $length"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<sideA_area>35</sideA_area>
<sideB_area>18</sideB_area>
</output>
Note, however, that explicit naming of elements is more efficient - sometimes much more efficient. The really elegant solution would be to normalize your input before it gets to you, so that it looks more like (for example):
<root>
<rect id="X">
<width>5</width>
<length>7</length>
</rect>
<rect id="Y">
<width>6</width>
<length>3</length>
</rect>
</root>

msxsl:node-set() not recognized

I am trying to pull nodes out of a node set stored in a variable using the msxsl:node-set() function and am not getting anything. My xml looks like this:
<Root>
<Items olditemnumber="100" newitemnumber="200">
<Item ItemNumber="100" ItemAliasCode="1001" ItemCode="X" />
<Item ItemNumber="100" ItemAliasCode="1002" ItemCode="X" />
<Item ItemNumber="200" ItemAliasCode="2001" ItemCode="X" />
<Item ItemNumber="200" ItemAliasCode="2003" ItemCode="X" />
<Item ItemNumber="100" ItemAliasCode="1003" ItemCode="P" />
<Item ItemNumber="100" ItemAliasCode="1004" ItemCode="P" />
<Item ItemNumber="200" ItemAliasCode="2002" ItemCode="P" />
</Items>
</Root>
In my xslt I try to populate a variable with a subset of the nodes and then call them using the msxsl:node-set() function. This doesn't return anything however.
XSLT looks like this:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:template match="//Root">
<xsl:variable name="OldItemNumber" select="/Items/#olditemnumber"/>
<xsl:variable name="NewItemNumber" select="/Items/#newitemnumber"/>
<xsl:variable name="OldItems">
<xsl:value-of select="//Item[#ItemNumber = $OldItemNumber]"/>
</xsl:variable>
<xsl:variable name="NewItems">
<xsl:value-of select="//Item[#ItemNumber = $NewItemNumber]"/>
</xsl:variable>
<xsl:for-each select="msxsl:node-set($OldItems)/Item">
...work
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The XSLT skips over the for-each loop, though I see in the watch that the the Xpath query grabs the right nodes in assigning the variables. The watch also tells me that the msxsl:node-set() function is undefined. Any help would be appreciated. What am I missing?
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:template match="//Root">
<xsl:variable name="OldItemNumber" select="/Items/#olditemnumber"/>
<xsl:variable name="NewItemNumber" select="/Items/#newitemnumber"/>
<xsl:variable name="OldItems" select="//Item[#ItemNumber = $OldItemNumber]"/>
<xsl:variable name="NewItems" select="//Item[#ItemNumber = $NewItemNumber]"/>
<xsl:for-each select="$OldItems">
...work
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
msxsl:node-set is for converting a result tree fragment (a.k.a. RTF) to a node set, which is not needed on your case.
xsl:value-of is for creating text nodes, so don't use it for selecting nodes of the input tree that you want to further query/process.