XSLT - First Instance of Empty Node edit - xslt-1.0

I am trying to edit the first instance of an empty node(Status) in the following xml :
<?xml version='1.0' encoding='windows-1252'?>
<root>
<row>
<flowname>1</flowname>
<path>#[payload]</path>
<id>3</id>
<setMessage>4</setMessage>
<MockOne>5</MockOne>
<MockTwo>6</MockTwo>
<MockThree>7</MockThree>
<Assert></Assert>
<status>12</status>
</row>
<row>
<flowname>2</flowname>
<path>4</path>
<id>5</id>
<setMessage>6</setMessage>
<MockOne>7</MockOne>
<MockTwo>8</MockTwo>
<MockThree></MockThree>
<Assert></Assert>
<status></status>
</row>
<row>
<flowname>3</flowname>
<path>5</path>
<id>6</id>
<setMessage>7</setMessage>
<MockOne>8</MockOne>
<MockTwo>9</MockTwo>
<MockThree></MockThree>
<Assert>3</Assert>
<status></status>
</row>
</root>
What I want to achieve is for the xslt to find the first instance of the tag which is empty and edit it to say 123. I tried using the following XSLT, but it seems to be replacing every Empty Status tag and I need it only to do the first instance. Kindly suggest on what has to be changed
The XSLT as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row/status[1][not(text())][1]">
<status>123</status>
</xsl:template>
</xsl:stylesheet>
The output right now is as follows( Every empty Status tag is replaced instead of the first instance of an empty one)
<root>
<row>
<flowname>1</flowname>
<path>#[payload]</path>
<id>3</id>
<setmessage>4</setmessage>
<mockone>5</mockone>
<mocktwo>6</mocktwo>
<mockthree>7</mockthree>
<assert></assert>
<status>12</status>
</row>
<row>
<flowname>2</flowname>
<path>4</path>
<id>5</id>
<setmessage>6</setmessage>
<mockone>7</mockone>
<mocktwo>8</mocktwo>
<mockthree></mockthree>
<assert></assert>
<status>123</status>
</row>
<row>
<flowname>3</flowname>
<path>5</path>
<id>6</id>
<setmessage>7</setmessage>
<mockone>8</mockone>
<mocktwo>9</mocktwo>
<mockthree></mockthree>
<assert>3</assert>
<status>123</status>
</row>
</root>

Your Xpath expression is saying to update all row/status which is empty.
You need to change like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row[not(preceding-sibling::row[not(normalize-space(status))])]/status[1][not(text())][1]">
<status>123</status>
</xsl:template>
</xsl:stylesheet>

Related

Grouping and select distinct in XSLT 1.0

I have an XML, i am trying to do kind of group by with XLST 1.0.
Input XMl:
<Rowset>
<Row>
<col1>7:00</col1>
<name>Shell Test</name>
<passCount>1</passCount>
<failCount>0</failCount>
</Row>
<Row>
<col1>7:00</col1>
<name>Stroke Test</name>
<passCount>1</passCount>
<failCount>1</failCount>
</Row>
<Row>
<col1>7:00</col1>
<name>Shutoff Test</name>
<passCount>0</passCount>
<failCount>1</failCount>
</Row>
<Row>
<col1>8:00</col1>
<name>Shell Test</name>
<passCount>0</passCount>
<failCount>0</failCount>
</Row>
<Row>
<col1>8:00</col1>
<name>Stroke Test</name>
<passCount>0</passCount>
<failCount>0</failCount>
</Row>
<Row>
<col1>8:00</col1>
<name>Shutoff Test</name>
<passCount>0</passCount>
<failCount>0</failCount>
</Row>
<Row>
<col1>9:00</col1>
<name>Shell Test</name>
<passCount>0</passCount>
<failCount>0</failCount>
</Row>
<Row>
<col1>9:00</col1>
<name>Stroke Test</name>
<passCount>0</passCount>
<failCount>0</failCount>
</Row>
<Row>
<col1>9:00</col1>
<name>Shutoff Test</name>
<passCount>0</passCount>
<failCount>0</failCount>
</Row>
</Rowset>
outPutXMl:
<?xml version="1.0" encoding="UTF-8"?>
<Row>
<element>
<TestName>Shell Test</TestName>
</element>
<element>
<TestName>Stroke Test</TestName>
</element>
<element>
<TestName>Shutoff Test</TestName>
</element>
</Row>
<Row>
<element>
<Time>7:00</Time>
<Pass>1</Pass>
<Fail>0</Fail>
<Pass>1</Pass>
<Fail>1</Fail>
<Pass>0</Pass>
<Fail>1</Fail>
</element>
<element>
<Time>8:00</Time>
<Pass>0</Pass>
<Fail>0</Fail>
<Pass>0</Pass>
<Fail>0</Fail>
<Pass>0</Pass>
<Fail>0</Fail>
</element>
<element>
<Time>9:00</Time>
<Pass>0</Pass>
<Fail>0</Fail>
<Pass>0</Pass>
<Fail>0</Fail>
<Pass>0</Pass>
<Fail>0</Fail>
</element>
</Row>
i am facing problem in extracting all the values from group in second for-each-group
my xslt 1.0 is as below:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="col1name" match="Row" use="name" />
<xsl:key name="time" match="Row" use="col1" />
<xsl:template match="/Rowsets/Rowset">
<Row>
<xsl:for-each select="Row[generate-id() = generate-id(key('col1name', name)[1])]">
<TestName><xsl:value-of select="name"/></TestName>
</xsl:for-each>
</Row>
<xsl:for-each select="Row">
<Time><xsl:value-of select="col1"/></Time>
<xsl:apply-templates select="/Rowsets/Rowset"/>
</xsl:for-each>
</xsl:template>
<xsl:template match="/Rowsets/Rowset">
<xsl:for-each select="Row[generate-id() = generate-id(key('time', col1)[*])]">
<Pass><xsl:value-of select="passCount"/></Pass>
<Fail><xsl:value-of select="failCount"/></Fail>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
with XSLT 2.0 i could achieve this, but as per my requirement my application only supports XSLT 1.0
XSLT 2.0 Code:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/Rowsets/Rowset">
<Row>
<xsl:for-each-group select="Row" group-by="name">
<element>
<TestName>
<xsl:value-of select="name"/>
</TestName>
</element>
</xsl:for-each-group>
</Row>
<Row>
<xsl:for-each-group select="Row" group-by="col1">
<element>
<Time>
<xsl:value-of select="col1"/>
</Time>
<xsl:for-each select="current-group()">
<Pass>
<xsl:value-of select="passCount"/>
</Pass>
<Fail>
<xsl:value-of select="failCount"/>
</Fail>
</xsl:for-each>
</element>
</xsl:for-each-group>
</Row>
</xsl:template>
</xsl:stylesheet>
Can someone please help in replicating the output with XSLT 1.0
Use the two keys for Muenchian grouping:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="by-name" match="Row" use="name"/>
<xsl:key name="by-col1" match="Row" use="col1"/>
<xsl:template match="/Rowsets/Rowset">
<Row>
<xsl:for-each select="Row[generate-id() = generate-id(key('by-name', name)[1])]">
<element>
<TestName>
<xsl:value-of select="name"/>
</TestName>
</element>
</xsl:for-each>
</Row>
<Row>
<xsl:for-each select="Row[generate-id() = generate-id(key('by-col1', col1)[1])]">
<element>
<Time>
<xsl:value-of select="col1"/>
</Time>
<xsl:for-each select="key('by-col1', col1)">
<Pass>
<xsl:value-of select="passCount"/>
</Pass>
<Fail>
<xsl:value-of select="failCount"/>
</Fail>
</xsl:for-each>
</element>
</xsl:for-each>
</Row>
</xsl:template>
</xsl:stylesheet>

xslt 3.0 xsl:evaluate example

For the following xml document:
<company>
<employee>
<name>John Andrews</name>
<age>23</age>
<salary>4000</salary>
<division>Accounting</division>
</employee>
</company>
I have the xsl like
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of select="company/employee/name"/>
<xsl:variable name="test">
<xsl:text>company/employee/name</xsl:text>
</xsl:variable>
<xsl:evaluate xpath="$test"/>
</xsl:template>
</xsl:stylesheet>
How can I use $test variable in xsl:evaluate in order to get the same result as in the:
<xsl:value-of select="company/employee/name"/>
Is it possible?
You need to set the context item with e.g.
<xsl:evaluate xpath="$test" context-item="."/>

XSLT: Convert Name/Value pair and transform an XML

I need to convert a name value pair into XML. I'm able to generate an XML, but the element name should be grouped and it should not be duplicated. Please see below. The FieldValue element contains 2 OrderItem values in the Detail node. If the FieldValue with OrderItem repeats, then the result should be grouped into one OrderItem node. Please help.
Source XML:
<SC>
<Header>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderHeader</FieldValue>
</Record>
<Record>
<FieldName>Order</FieldName>
<FieldValue>1234</FieldValue>
</Record>
</Header>
<Detail>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderItem</FieldValue>
</Record>
<Record>
<FieldName>Item</FieldName>
<FieldValue>1</FieldValue>
</Record>
<Record>
<FieldName>Qty</FieldName>
<FieldValue>10</FieldValue>
</Record>
</Detail>
<Detail>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderItem</FieldValue>
</Record>
<Record>
<FieldName>Item</FieldName>
<FieldValue>2</FieldValue>
</Record>
<Record>
<FieldName>Qty</FieldName>
<FieldValue>20</FieldValue>
</Record>
</Detail>
</SC>
Target XML:
<Order>
<OrderItem>
<Item>
<Item>1</Item>
<Qty>10</Qty>
</Item>
<Item>
<Item>2</Item>
<Qty>20</Qty>
</Item>
</OrderItem>
</Order>
XSLT:
<xsl:template match="#*|node()">
<Order>
<xsl:for-each select="Detail">
<Item>
<xsl:apply-templates select="Record[position()>1]"/>
</Item>
</xsl:for-each>
</Order>
</xsl:template>
<xsl:template match="Record">
<xsl:element name="{FieldName}">
<xsl:value-of select="FieldValue"/>
</xsl:element>
</xsl:template>
The grouping can be done as follows:
<?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 indent="yes"/>
<xsl:template match="SC">
<Order>
<xsl:for-each-group select="Detail" group-by="Record[1]/FieldValue">
<xsl:element name="{current-grouping-key()}">
<xsl:apply-templates select="current-group()"/>
</xsl:element>
</xsl:for-each-group>
</Order>
</xsl:template>
<xsl:template match="Detail">
<Item>
<xsl:apply-templates select="Record[position() gt 1]"/>
</Item>
</xsl:template>
<xsl:template match="Record">
<xsl:element name="{FieldName}">
<xsl:value-of select="FieldValue"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It appears that you are trying to define two XSLT templates, when one should be sufficient. You want to match on the root and then that you want to iterate over each SC/Detail.
Then, you want to take the FieldValue of the sibling of the FieldName node that is 'Item' (for item value) and 'Qty' (for quantity value), but only those listed under 'Record'.
Note: You have specified a doubly-nested <Item> in your transformed output and this solution reflects that requirement.
This XSLT should do what you are requesting:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:for-each select="SC/Detail">
<Order>
<OrderItem>
<Item>
<Item>
<xsl:value-of select="Record[FieldName[text()='Item']]/FieldValue" />
</Item>
<Qty>
<xsl:value-of select="Record[FieldName[text()='Qty']]/FieldValue" />
</Qty>
</Item>
</OrderItem>
</Order>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Filter xml in .Net with param

I am trying to filter an xml file in a .Net application I am developing. Some sample xml below, obviously not the proper xml, but near enough :-)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Row A1="1" A2="AMS">
<Name>Ashley</Name>
<Team>Team B</Team>
<Date>3/25/2012</Date>
<Value>511681.15</Value>
</Row>
<Row A1="2" A2="AMS">
<Name>Kylie</Name>
<Team>Team A</Team>
<Date>9/28/2010</Date>
<Value>408438.47</Value>
</Row>
<Row A1="3" A2="AMS">
<Name>Gianna</Name>
<Team>Team B</Team>
<Date>40004</Date>
<Value>109709.22</Value>
</Row>
<Row A1="4" A2="AMS">
<Name>Chase</Name>
<Team>Team F</Team>
<Date>40152</Date>
<Value>279018.79</Value>
</Row>
The stylesheet has a param that is set by XsltArgumentList in the application. The param is passed into the stylesheet, but does not filter the xml. I have tried using the ms node-set and exsl node-set but only get the top level root returned. Stylesheet below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes" encoding="UTF-8" />
<xsl:decimal-format name="NN" NaN="0" />
<xsl:param name="Filter" />
<xsl:template match="/">
<Root>
<xsl:for-each select="exsl:node-set($Filter)">
<Row>
<xsl:attribute name="A1">
<xsl:value-of select="#A1" />
</xsl:attribute>
<xsl:attribute name="A2">
<xsl:value-of select="#A2" />
</xsl:attribute>
<Team>
<xsl:value-of select="Team"/>
</Team>
</Row>
</xsl:for-each>
</Root>
</xsl:template>
The filter i am trying to pass could be using any of the combination of the xml elements. The filter is am currently attempting is below
<xsl:param name="Filter" select="//Row[Team='Team A']" />
But this is just returns
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Row A1="" A2="">
<Team></Team>
</Row>
</Root>
Any help or pointers would be appreciated!
Thanks
This will not woke like you tried it, because you can't have a xpath expression in an XLST parameter or variable. You can use variable or parameter in please of const values.
Therefore you can use something like
<xsl:param name="teamFilter" select="'Team A'" />
...
<xsl:for-each select="//Row[Team='$teamFilter']">
Or if you have to filter for different nodes you may try:
<xsl:param name="filterValue" select="'Team A'" />
<xsl:param name="filterNode" select="'Team'" />
...
<xsl:for-each select="//Row[*[name()= $filterNode and . =$filterValue]]">
Other alternatives would be:
Patch the XSLT before loading it
Use xpath iterator with XPathExpression from .NET.

XSLT get Node by Index

i am beginner in XSLT and i am using it to transform XML to XML
This is the source XML i receive
Source XML:
<Response>
<Pax>
<Id>1</Id>
</Pax>
<Pax>
<Id>2</Id>
</Pax>
<Travelers>
<Traveler>
<Name>ABC</Name>
</Traveler>
<Traveler>
<Name>XYZ</Name>
</Traveler>
</Travelers>
</Response>
I have written below XSLT
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="Response">
<xsl:element name="Root">
<xsl:apply-templates select="Travelers/Traveler"/>
</xsl:element>
</xsl:template>
<xsl:template match="Traveler">
<xsl:element name="Person">
<xsl:element name="PId">
<xsl:value-of select="//Pax/Id[position()]" />
</xsl:element>
<xsl:element name="Name">
<xsl:value-of select="Name" />
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output:
<Root>
<Person>
<PId>1</PId>
<Name>ABC</Name>
</Person>
<Person>
<PId>1</PId>
<Name>XYZ</Name>
</Person>
</Root>
I would like to generate below XML output
Expected Output:
<Root>
<Person>
<PId>1</PId>
<Name>ABC</Name>
</Person>
<Person>
<PId>2</PId>
<Name>XYZ</Name>
</Person>
</Root>
As shown in above XML the only issue is with PId, it should have value 2.
Please help. Thanks.
Here's a relatively simple solution.
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<Root>
<xsl:apply-templates select="Pax" />
</Root>
</xsl:template>
<xsl:template match="Pax">
<xsl:variable name="vPosition" select="position()" />
<Person>
<PId>
<xsl:value-of select="Id" />
</PId>
<Name>
<xsl:value-of select="/*/Travelers/*[$vPosition]/Name" />
</Name>
</Person>
</xsl:template>
</xsl:stylesheet>
...is applied to the original XML:
<Response>
<Pax>
<Id>1</Id>
</Pax>
<Pax>
<Id>2</Id>
</Pax>
<Travelers>
<Traveler>
<Name>ABC</Name>
</Traveler>
<Traveler>
<Name>XYZ</Name>
</Traveler>
</Travelers>
</Response>
...the wanted result is produced:
<Root>
<Person>
<PId>1</PId>
<Name>ABC</Name>
</Person>
<Person>
<PId>2</PId>
<Name>XYZ</Name>
</Person>
</Root>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="Response">
<Root>
<xsl:for-each select="Travelers/Traveler">
<Person>
<xsl:variable name="index" select="position()" />
<Pid><xsl:value-of select="//Pax[$index]/Id"/></Pid>
<Name><xsl:value-of select="Name"/></Name>
</Person>
</xsl:for-each>
</Root>
</xsl:template>
</xsl:stylesheet>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml"/>
<xsl:template match="/Response">
<Root>
<xsl:for-each select="Pax">
<xsl:variable name="pos" select="position()"/>
<Person>
<PId>
<xsl:value-of select="Id"/>
</PId>
<xsl:apply-templates select="//Travelers">
<xsl:with-param name="pos" select="$pos"/>
</xsl:apply-templates>
</Person>
</xsl:for-each>
</Root>
</xsl:template>
<xsl:template match="Travelers">
<xsl:param name="pos"/>
<xsl:for-each select="//Name">
<xsl:if test="position()=$pos">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>