XSLT Generating incremental values - xslt

I have the following xml:
<Details>
<Head>
<pageid>123</pageid> <!-- Needs to be sequential starting with 0000000001 -->
</Head>
<Start>
<pageid>124</pageid>
<value>Details of Minerals</value>
</Start>
<Item>
<pageid>12</pageid>
<name>Coal</name>
</Item>
<Quantity>
<pageid>45</pageid>
<value>3</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<Item>
<pageid>459</pageid>
<name>MICA</name>
</Item>
<Quantity>
<pageid>65</pageid>
<value>2</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<END>
<pageid>78</pageid>
</END>
</Details>
I want to the value pageid to be incremental with 10 digits.
Sample o/p
<Details>
<Head>
<pageid>0000000001</pageid>
</Head>
<Start>
<pageid>0000000002</pageid>
<value>Details of Minerals</value>
</Start>
<Item>
<pageid>0000000003</pageid>
<name>Coal</name>
</Item>
<Quantity>
<pageid>0000000004</pageid>
<value>3</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<Item>
<pageid>0000000005</pageid>
<name>MICA</name>
</Item>
<Quantity>
<pageid>0000000006</pageid>
<value>2</value>
<comments>NONE MENTIONED</comments>
</Quantity>
<END>
<pageid>0000000007</pageid>
</END>
</Details>
I tried using the following construct:
<xsl:variable name="counter" select="0000000000" saxon:assignable="yes"/>
<xsl:template match="//*[local-name()='pageid']">
<saxon:assign name="counter" select="$counter+0000000001"/>
<imp1:Line_id>
<xsl:value-of select="$counter"></xsl:value-of>
</imp1:Line_id>
But this wasnt helpful. Can u suggest a easier way to do it?

Instead of trying to use a variable counter, you could just make use of the xsl:number element here:
<xsl:template match="//*[local-name()='pageid']">
<imp1:Line_id>
<xsl:number level="any" format="0000000000" />
</imp1:Line_id>
</xsl:template>

Related

Usage of the Variable inside the select-value clause to traverse through the XML path

I am trying to fetch the XML value based on a condition, if the variable value matches the value of the XML path mentioned then to obtain the value of its own sub elements.
The Input XML looks like below
<ns1:productSpecificationFullDTO xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full" xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple">
<ns1:product>
<ns1:name>Test Component 1</ns1:name>
<ns1:parent>false</ns1:parent>
</ns1:product>
<ns1:product>
<ns1:name>Test Component 2</ns1:name>
<ns1:parent>false</ns1:parent>
</ns1:product>
<ns1:specification>
<ns1:name>Test Component 1</ns1:name>
<ns1:parent>false</ns1:parent>
<ns1:Labeling>
<ns1:mainProductTitle>Test1</ns1:ns1:mainProductTitle>
</ns1:Labeling>
</ns1:specification>
<ns1:specification>
<ns1:name>Test Component 2</ns1:name>
<ns1:parent>false</ns1:parent>
<ns1:Labeling>
<ns1:mainProductTitle>Test2</ns1:ns1:mainProductTitle>
</ns1:Labeling>
</ns1:specification>
My XSLT Definition is below
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full" xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple" exclude-result-prefixes="ns1 ns1">
<xsl:template match="/">
<ItemDetails>
<Items>
<!-- Food section start here -->
<xsl:for-each select="/ns1:productSpecificationFullDTO/ns1:product/ns1:parent[text() != 'true']/../ns1:name[text() != 'Parent']/..">
<xsl:variable name="subItem" select="ns1:name/text()"/>
<Item>
<name>
<xsl:value-of select="$subItem"/>
</name>
<LongDescription>
<xsl:value-of select="normalize-space(ns1:productSpecificationFullDTO/ns1:specification/ns1:parent[text() != 'true']/../ns1:name[text() = '''$subItem''']/../ns1:Labeling/ns1:mainProductTitle/text())"/>
</LongDescription>
</Item>
</xsl:for-each>
</Items>
</ItemDetails>
</xsl:template>
The output is as below
<Items>
<Item>
<name>Test Component 1</name>
<LongDescription/>
</Item>
<Item>
<name>Test Component 2</name>
<LongDescription/>
</Item>
Desired Output is
<Items>
<Item>
<name>Test Component 1</name>
<LongDescription>Test1<LongDescription/>
</Item>
<Item>
<name>Test Component 2</name>
<LongDescription>Test2<LongDescription/>
</Item>
As Seen above i'm unable to fetch the value of that variable's sub element.
Please advise, Thanks
I think this solves what you are trying to accomplish, simplifying your XPath expressions and using a key to get to the linked descriptions.
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://www.micros.com/creations/core/domain/dto/v1p0/full"
xmlns:ns2="http://www.micros.com/creations/core/domain/dto/v1p0/simple"
exclude-result-prefixes="ns1 ns2">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="keySpec" match="ns1:specification" use="ns1:name"/>
<xsl:template match="/">
<ItemDetails>
<Items>
<!-- Food section start here -->
<xsl:for-each select="/ns1:productSpecificationFullDTO/ns1:product[not(ns1:parent='true') and not(ns1:name='Parent')]">
<Item>
<name>
<xsl:value-of select="ns1:name"/>
</name>
<LongDescription>
<xsl:value-of select="key('keySpec',ns1:name)/ns1:Labeling/ns1:mainProductTitle"/>
</LongDescription>
</Item>
</xsl:for-each>
</Items>
</ItemDetails>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/6qjt5Sw/1

XSLT: Trying to combine two nodes based on the value of an element of those two nodes

I am struggling with XSLT and have been for days. I'm in the process of modifying a previous coworkers python code that transforms many different JSON files into xml and finally into kml. I'm within a hairs breadth of wrapping this up and, of course, the one part I can't get my head around is what's left.
I have an xml file with this structure:
<?xml version="1.0" ?>
<document xmlns="http://ws.wso2.org/dataservice">
<group>
<display_name>Housing</display_name>
<id>Housing</id>
<item>
<id>5063</id>
<image_url>images/5063.jpg</image_url>
<latitude>40.354007</latitude>
<longitude>-74.666675</longitude>
<name>Stanworth Apartments</name>
</item>
.
. (Many items omitted)
.
</group>
<group>
<display_name>Buildings</display_name>
<id>Building</id>
<item>
<id>5025</id>
<image_url>images/5025.jpg</image_url>
<latitude>40.350066</latitude>
<longitude>-74.603464</longitude>
<name>Lyman Spitzer Building</name>
<name_alt>LSB</name_alt>
<organization_id>ORG418</organization_id>
</item>
.
. (Many items omitted)
.
</group>
<group>
.
. (Many groups omitted)
.
</group>
<group>
<display_name>Accessible Features</display_name>
<id>Entryway</id>
<item>
<description>Accessible entryway</description>
<id>E028</id>
<latitude>40.349159</latitude>
<longitude>-74.658629</longitude>
<name>E028</name>
</item>
<item>
<description>Accessible entryway</description>
<id>E029</id>
<latitude>40.349398</latitude>
<longitude>-74.658517</longitude>
<name>E029</name>
</item>
</group>
<group>
<display_name>Accessible Features</display_name>
<id>Route</id>
<item>
<description>Accessible pathway</description>
<id>R054</id>
<name>R054</name>
<steps>-74.66032495749012,40.3489269473544</steps>
<steps>-74.6602836233495,40.34888813533125</steps>
</item>
<item>
<description>Accessible pathway</description>
<id>R055</id>
<name>R055</name>
<steps>-74.66023036637355,40.34884827131961</steps>
<steps>-74.66018651597699,40.34881015960344</steps>
</item>
<item>
<description>Accessible pathway</description>
<id>R072</id>
<name>R072</name>
<steps>-74.66101885775542,40.34737535360176</steps>
<steps>-74.6610915120654,40.34740600913134</steps>
<steps>-74.66187000551304,40.34717392492537</steps>
</item>
</group>
</document>
Each "group" is transformed into a Folder in the final KML file.
<Folder id="Housing">
<name>Housing</name>
<Placemark id="_0288">
.
. (Many lines omitted)
.
The goal is to create one Folder "id='Accessible" with the contents of two groups. The group with id='Entryway' and the group with id='Route. The desired output would be:
<Folder id="Accessible">
<name>Accessible Features</name>
<Placemark id="_E001">
<name>E001</name>
<description><![CDATA[<div><p>Accessible entryway</p></div>]]></description>
<styleUrl>#entryway</styleUrl>
<Point>
<coordinates>-74.663266, 40.348289,0</coordinates>
</Point>
</Placemark>
<Placemark id="_E002">
<name>E002</name>
<description><![CDATA[<div><p>Accessible entryway</p></div>]]></description>
<styleUrl>#entryway</styleUrl>
<Point>
<coordinates>-74.662252, 40.348057,0</coordinates>
</Point>
</Placemark>
.
. then have the items from the group with id='Route'
.
<Placemark id="_R002">
<name>Accessible Routes</name>
<description><![CDATA[<div><p>Accessible pathway</p></div>]]></description>
<styleUrl/>
<Style>
<LineStyle>
<color>FFFF0000</color>
<width>4</width>
</LineStyle>
</Style>
<LineString>
<coordinates>
-74.65135187837255,40.34699608960065
-74.65134698312161,40.34698651192196
</coordinates>
</LineString>
</Placemark>
<Placemark id="_R003">
<name>Accessible Routes</name>
<description><![CDATA[<div><p>Accessible pathway</p></div>]]></description>
<styleUrl/>
<Style>
<LineStyle>
<color>FFFF0000</color>
<width>4</width>
</LineStyle>
</Style>
<LineString>
<coordinates>
-74.65135184561255,40.34699603789065
-74.65134698312256 44.34698634192100
</coordinates>
</LineString>
</Placemark>
.
. more than 66,000 lines omitted
.
</Folder>
I've written the XSLT to transform the XML into these KML Folders and the only thing left to do is get them under the same folder.
What I've been trying to do is move all of the items from the group with id='Route' into the group with id='Entryway.
In my xslt file is an apply-templates at the group nodes.
<xsl:apply-templates select="ds:group">
<xsl:sort select="ds:display_name"/>
</xsl:apply-templates>
This is picked up by the template match for each group.
<xsl:template match="ds:group">
At which point I'm lost. I'll post my code but it is only going to confuse and depress you.
<xsl:template match="ds:group">
<xsl:choose>
<xsl:when test="not(ds:id = 'Route') and not(ds:id = 'Entryway')">
<Folder id="{ds:id}">
<name>
<xsl:value-of select="ds:display_name"/>
</name>
<xsl:apply-templates select="ds:item">
<xsl:sort select="ds:name"/>
</xsl:apply-templates>
</Folder>
</xsl:when>
<xsl:when test="ds:id = 'Entryway'">
<Folder id='Accessible'>
<name>
<xsl:value-of select="ds:display_name"/>
</name>
<xsl:apply-templates select="ds:item">
<xsl:sort select="ds:name"/>
</xsl:apply-templates>
</Folder>
</xsl:when>
<xsl:when test="ds:id = 'Route'">
<!-- Copy all of current node to Entryway node -->
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I don't think I'm going about this the right way. By the time the XSLT process gets to the group with id='Route' the KML Folder for Entryway has already been written. I'm at a dead end. Can I union two groups together based on the value of "id"? Conceptually the idea would be: <xsl:template match="ds:id='Route' | ds:id='Entryway'"> But that doesn't even compile.
Can I copy all of the elements of the group (id='Route') to the group (id='Entryway') after the first group has been processed?
Thank you in advance for your time and attention.
George
I think you need to "step" in at the document level and do e.g.
<xsl:template match="ds:document">
<Folder id='Accessible'>
<xsl:apply-templates select="ds:group[ds:id = 'Entryway' or ds:id = 'Route']"/>
</Folder>
<!-- process other ds:group here as well e.g.
<xsl:apply-templates select="ds:group[not(ds:id = 'Entryway' or ds:id = 'Route')]"/>
-->
</xsl:template>
AFAICT you want to do something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://ws.wso2.org/dataservice"
exclude-result-prefixes="ns0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/ns0:document">
<Folders>
<xsl:variable name="accessible" select="ns0:group[ns0:id='Entryway' or ns0:id ='Route']" />
<xsl:if test="$accessible">
<Folder id="Accessible">
<name>Accessible Features</name>
<xsl:apply-templates select="$accessible/ns0:item"/>
</Folder>
</xsl:if>
<xsl:apply-templates select="ns0:group[not(ns0:id='Entryway' or ns0:id ='Route')]"/>
</Folders>
</xsl:template>
<xsl:template match="ns0:group">
<Folder id="{ns0:id}">
<name>
<xsl:value-of select="display_name"/>
</name>
<xsl:apply-templates select="ns0:item"/>
</Folder>
</xsl:template>
<xsl:template match="ns0:item">
<Placemark id="{ns0:id}">
<name>
<xsl:value-of select="ns0:name"/>
</name>
<!-- more here -->
</Placemark>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="/ds:document">
<kml>
<Document>
<name>Campus Map</name>
<xsl:apply-templates select="ds:group[ds:id != 'Entryway' and ds:id != 'Route']">
<xsl:sort select="ds:display_name"/>
</xsl:apply-templates>
<Folder id='Accessible'>
<name>Accessible Feature</name>
<xsl:apply-templates select="ds:group[ds:id = 'Entryway' or ds:id = 'Route']"/>
</Folder>
</Document>
</kml>
</xsl:template>
<xsl:template match="ds:group[ds:id = 'Entryway' or ds:id = 'Route']">
<xsl:apply-templates select="ds:item"/>
</xsl:template>
<xsl:template match="ds:group[ds:id != 'Entryway' and ds:id != 'Route']">
<Folder id="{ds:id}">
<name>
<xsl:value-of select="ds:display_name"/>
</name>
<xsl:apply-templates select="ds:item">
<xsl:sort select="ds:name"/>
</xsl:apply-templates>
</Folder>
</xsl:template>

Copy single value in XML to two different places in other XML

I have an XML file with this structure:
<DetailTxt>
<Text>
<span>Some Text</span>
</Text>
<TextComplement Kind="Owner" MarkLbl="1">
<ComplCaption>
Caption 1
</ComplCaption>
<ComplBody>
Body 1
</ComplBody>
</TextComplement>
<Text>
<span>More Text</span>
</Text>
</DetailTxt>
Here is the part of the XSLT that is relevant here:
<xsl:template match="*[local-name() = 'DetailTxt']">
<xsl:apply-templates select="*[local-name() = 'Text']"/>
</xsl:template>
<xsl:template match="*[local-name() = 'Text']">
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl,']')" />
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
</xsl:template>
I expect the output to look like this:
<item name="Text">
<richtext>
<par>
<run><![CDATA[
<span>Some Text</span>
</p>]]></run>
</par>
</richtext>
</item>
<item name="Text">
<richtext>
<par>
<run><![CDATA[[Owner1]]]></run>
</par>
</richtext>
</item>
But the line using the TextComplement XPath looks like this:
<run><![CDATA[[]]]></run>
All values from TextComplement are missing. Whats wrong with the XPath here?
EDIT: I completely reworked my question and put in a CONCRETE question resulting from the first answer. That kind of invalidates the first answer but IMHO improves the question.
Not sure how the XSLT looks like but you can try adding the following template with the concat() function for getting the output.
<xsl:template match="Text">
<document version="9.0" form="Form1">
<item name="{local-name()}">
<xsl:copy-of select="span" />
</item>
<item name="{local-name()}">
<span>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl, ']')" />
</span>
</item>
</document>
</xsl:template>
This template is applied to the <Text> node and the ../ is used to go up one level and then access the attributes of <TextComplement> using the XPath.
The output of the template when applied to your XML will look like.
<document form="Form1" version="9.0">
<item name="Text">
<span>Some Text</span>
</item>
<item name="Text">
<span>[Owner1]</span>
</item>
</document>
The same template will also get applied to the <Text> node having More Text content and produce similar output.
I found a solution myself for the concrete question. I quess this is IBM Notes / LotusScript specific issue.
When using the selector
../TextComplement/#Kind
the parser returned an empty string. I changed to
../*[local-name() = 'TextComplement']/#Kind
and later (more concrete) to:
./following-sibling::*[local-name() = 'TextComplement']/#Kind
And that worked. I personally see no difference in these notations, but it seams that internally they are handled differently.

Setting XSLT variables within for-each select

Good Day,
I have an XSLT template I'm assembling that look like:
<xsl:for-each select="CarParts/Items">
<div class="columns"><xsl:value-of select="Quantity"/></div>
<div class="columns"><xsl:value-of select="Amount"/></div>
<div class="columns">[SUBTOTAL]</div><br />
</xsl:for-each>
I know that I can define an XSLT variable like this:
<xsl:variable name="totalAmount" select="sum(CarParts/Items/Amount)" />
But I want my XSLT variable to be [SUBTOTAL] which is equal to Quantity * Amount within the for-each select loop. Is this possible? If this was SQL, this would be the equivalent of a computed column.
Any suggestions?
TIA,
coson
What you want to do is cast the value to a number, then you can multiply it as desired:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<results>
<xsl:for-each select="CarParts/Items">
<Item id="{position()}">
<q><xsl:value-of select="Quantity"/></q>
<a><xsl:value-of select="Amount"/></a>
<st><xsl:value-of select="number(Quantity)*number(Amount)"/></st>
</Item>
</xsl:for-each>
</results>
</xsl:template>
</xsl:stylesheet>
I changed the formatting a little since there was no provided input/CSS, but you should see what I was going for. Running it on my sample input of
<CarParts>
<Items>
<Quantity>1</Quantity>
<Amount>100.00</Amount>
</Items>
<Items>
<Quantity>2</Quantity>
<Amount>25.00</Amount>
</Items>
<Items>
<Quantity>3</Quantity>
<Amount>6</Amount>
</Items>
</CarParts>
I get the result of
<?xml version="1.0" encoding="utf-8"?>
<results>
<Item id="1">
<q>1</q>
<a>100.00</a>
<st>100</st>
</Item>
<Item id="2">
<q>2</q>
<a>25.00</a>
<st>50</st>
</Item>
<Item id="3">
<q>3</q>
<a>6</a>
<st>18</st>
</Item>
</results>

How do i render a limited number of elements in XSLT?

I need to render a specified number of elements from an XML source, where the elements "DueDate" is not exeeded. Here is an example of the xml:
<Items>
<Item>
<Title>Title 1</Title>
<DueDate>01-02-2008</DueDate>
</Item>
<Item>
<Title>Title 2</Title>
<DueDate>01-02-2009</DueDate>
</Item>
<Item>
<Title>Title 3</Title>
<DueDate>01-02-2010</DueDate>
</Item>
<Item>
<Title>Title 4</Title>
<DueDate>01-02-2011</DueDate>
</Item>
<Item>
<Title>Title 5</Title>
<DueDate>01-02-2012</DueDate>
</Item>
<Item>
<Title>Title 6</Title>
<DueDate>01-02-2013</DueDate>
</Item>
</Items>
The number of elements to display and the current date are passed to the XSLT as paramaters.
Is it possible to count the number of rendered elements in a for-each loop in Xslt? Or is there a better approach?
An example could be that the limit was set to 3 elements. In this example I would expect to see the following results: "Title 3", "Title 4" and "Title 5".
You can do something like this. Make it into a template you can call with paramters:
<xsl:template match="/">
<xsl:variable name="count" select="3"/>
<xsl:for-each select="Items/Item">
<xsl:if test="position() < $count">
<xsl:value-of select="Title"/> - <xsl:value-of select="DueDate"/>
</xsl:if>
</xsl:for-each>
Try nesting this inside a standard xsl for-each where n in your case is 3:
<xsl:if test="position() < n">
But if you also want to check the date then you will need nest another if and create dates in the format yyyyMMdd which can be numerically compared like this:
<xsl:variable name="secondDate" select="concat(substring(submissionDeadline, 1,4),substring(submissionDeadline, 6,2),substring(submissionDeadline, 9,2))"/>
<xsl:if test="$firstDate > $secondDate">
The main problem is that your input XML does not conform to the useful 'yyyy-mm-dd' format. This makes sorting/filtering these items a pain in the ass behind.
If I understand you correctly, you need to
filter on due date first
apply a maximum to the output after that
The XPath for selecting all <Item>s up to a certain maximum due date would go like this:
Item[
substring-before(DueDate, '-')
<=
substring-before($MaxDueDate, '-')
and
substring-before(substring-after(DueDate, '-'), '-')
<=
substring-before(substring-after($MaxDueDate, '-'), '-')
and
substring-after(substring-after(DueDate, '-'), '-')
<=
substring-after(substring-after($MaxDueDate, '-'), '-')
][
position() <= $MaxCount
]
Now compare that to the trivial way, if you had 'yyyy-mm-dd' dates:
Item[
DueDate <= $MaxDueDate
][
position() <= $MaxCount
]
So, to copy only these elements, you would go:
<xsl:template match="Items">
<xsl:copy>
<xsl:copy-of select="
{ the expression I gave above }
" />
</xsl:copy>
</xsl:template>
For parameter values of '01-02-2012' and 4, respectively, I get:
<Items>
<Item>
<Title>Title 1</Title>
<DueDate>01-02-2008</DueDate>
</Item>
<Item>
<Title>Title 2</Title>
<DueDate>01-02-2009</DueDate>
</Item>
<Item>
<Title>Title 3</Title>
<DueDate>01-02-2010</DueDate>
</Item>
<Item>
<Title>Title 4</Title>
<DueDate>01-02-2011</DueDate>
</Item>
</Items>