xslt 1 : check if node contains any value from list - xslt

I need to tranform a XML document from one format to a other, and that is all dandy. Now the hard part is that I need to check all lines for a status code, and need to return true/false depend if any code element have any of this codes.
<xml>
<line>
<id>1</id>
<code>109</code>
</line>
<line>
<id>2</id>
<code>065</code>
</line>
<line>
<id>3</id>
<code>405</code>
</line>
<line>
<id>4</id>
<code>101</code>
</line>
</xml>
The document I tranform to keep a copy of all line element, but have a extra field, where is set to true/false depend if the any code is in the list.
So I need to compare this list of data to every code and return true if just one of them is in the list
"101","102","103","104","105","106","107","108","109","110","111"
Is there any fix mode to make this so I don't need 11 compare stament ?
Ohh and the output look some thing like
<System>
<Route>true</Route> <!-- will be false if the <code> from the first document is not in the list of elements -->
<Status>
<ID>1</ID>
<Code>109</Code>
</Status>
<Status>
<ID>2</ID>
<Code>065</Code>
</Status>
<Status>
<ID>3</ID>
<Code>405</Code>
</Status>
<Status>
<ID>4</ID>
<Code>101</Code>
</Status>
<System>

This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pCodeList" select="'101,102,103,104,105,106,107,108,109,110,111'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<System>
<Route>
<xsl:value-of select=
"boolean(/*/*/code
[contains(concat(',', $pCodeList, ','), concat(',', ., ',')
)
]
)"/>
</Route>
<xsl:apply-templates/>
</System>
</xsl:template>
<xsl:template match="line">
<Status><xsl:apply-templates/></Status>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<xml>
<line>
<id>1</id>
<code>109</code>
</line>
<line>
<id>2</id>
<code>065</code>
</line>
<line>
<id>3</id>
<code>405</code>
</line>
<line>
<id>4</id>
<code>101</code>
</line>
</xml>
produces the wanted, correct result:
<System>
<Route>true</Route>
<Status>
<id>1</id>
<code>109</code>
</Status>
<Status>
<id>2</id>
<code>065</code>
</Status>
<Status>
<id>3</id>
<code>405</code>
</Status>
<Status>
<id>4</id>
<code>101</code>
</Status>
</System>
When the same transformation (above) is applied on this XML document:
<xml>
<line>
<id>1</id>
<code>509</code>
</line>
<line>
<id>2</id>
<code>065</code>
</line>
<line>
<id>3</id>
<code>405</code>
</line>
<line>
<id>4</id>
<code>501</code>
</line>
</xml>
it again produces the wanted, correct result:
<System>
<Route>false</Route>
<Status>
<id>1</id>
<code>509</code>
</Status>
<Status>
<id>2</id>
<code>065</code>
</Status>
<Status>
<id>3</id>
<code>405</code>
</Status>
<Status>
<id>4</id>
<code>501</code>
</Status>
</System>

Related

Group by at multiple node at once in xslt

Below is the input xml structure-
<root>
<Entry>
<id>id1</id>
<Line>
<tax>tax1</tax>
<amount>13.00</amount>
</Line>
<Line>
<tax>tax2</tax>
<amount>4.00</amount>
</Line>
<Line>
<tax>tax2</tax>
<amount>2.00</amount>
</Line>
<Line>
<tax>tax4</tax>
<amount>8.00</amount>
</Line>
</Entry>
<Entry>
<id>id1</id>
<Line>
<tax>tax2</tax>
<amount>15.00</amount>
</Line>
<Line>
<tax>tax3</tax>
<amount>35.00</amount>
</Line>
</Entry>
<Entry>
<id>id2</id>
<Line>
<tax>tax2</tax>
<amount>12.00</amount>
</Line>
<Line>
<tax>tax2</tax>
<amount>22.00</amount>
</Line>
<Line>
<tax>tax1</tax>
<amount>5.00</amount>
</Line>
</Entry>
</root>
I want to get the output as below structure-
id1, tax1, 13.00
id1, tax2, 21.00
id1, tax3, 35.00
id1, tax4, 8.00
id2, tax1, 5.00
id2, tax2, 34.00
I tried to implement the below logic. but it didn't work.
forEach Entry groupBy id
forEach distinct(id) groupBy tax
Can someone please help me with the xslt transformation?
The following XSLT 2.0 stylesheet uses xsl:for-each-group to group the amount by a composite key of their id and tax values concatenated, calculates the sum() of the grouped amount and uses format-number() to ensure two decimal places.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each-group select="/root/Entry/Line/amount" group-by="string-join((../../id, ../tax), '-')">
<xsl:sort select="current-grouping-key()"/>
<xsl:value-of select="../../id, ../tax, format-number(sum(current-group()), '#.00')" separator=", " />
<xsl:text>
</xsl:text>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

How to get last 2 nodes previous specific node?

This is my xml:
<Line>
<Item>
<Id>1</Id>
<Name>A</Name>
<Unit>AA</Unit>
<Value>5</Value>
</Item>
</Line>
<Line>
<Item>
<Id>2</Id>
<Name>B</Name>
<Unit>Test</Unit>
<Value>5</Value>
</Item>
</Line>
<Line>
<Item>
<Id>3</Id>
<Name>C</Name>
<Unit>AA</Unit>
<Value>5</Value>
</Item>
</Line>
<Line>
<Item>
<Id>4</Id>
<Name>D</Name>
<Unit>AA</Unit>
<Value>5</Value>
</Item>
</Line>
<Line>
<Item>
<Id>5</Id>
<Name>E</Name>
<Unit>AA</Unit>
<Value>5</Value>
</Item>
</Line>
How to get all nodes which are at first and second position after nodes with Unit= Test. In this case, node with Id= 2 have Unit = Test so I want to display nodes with Id = 3 and Id = 4.
Thanks
The expression you want is this...
<xsl:copy-of select="//Line[Item/Unit='Test']/following-sibling::Line[position() <= 2]" />
This will work regardless of what the current node is.
Alternatively, you could split it out in to templates. For example
<xsl:template match="/*">
<xsl:apply-templates select="//Line[Item/Unit='Test']" />
</xsl:template>
<xsl:template match="Line">
<xsl:copy-of select="following-sibling::Line[position() <= 2]" />
</xsl:template>
If you want to get all nodes except 3 and 4, try this expression instead
<xsl:copy-of select="//Line[not(preceding-sibling::Line[position() <= 2][Item/Unit = 'Test'])]" />

xslt grouping by calling template

I am new to xslt and trying to learn how to learn grouping using keys and using templates.
Can somebody help me on how can do the following in xslt.
I have to call a template from another template to do the transformation.
Thanks
here is my xml.
<Doc>
<ExOrder>121</ExOrder>
<Line>
<Ordernumber>225</Ordernumber>
<OrderID>1</OrderID>
<Quantity>5</Quantity>
</Line>
<Line>
<Ordernumber>225</Ordernumber>
<OrderID>5</OrderID>
<Quantity>5</Quantity>
</Line>
<Line>
<Ordernumber>226</Ordernumber>
<OrderID>2</OrderID>
<Quantity>5</Quantity>
</Line>
And here is how it should look like after.
<Doc>
<Order>
<Ordernumber>225</Ordernumber>
<Line>
<ID>1</ID>
<ID>5</ID>
</Line>
</Order>
<Order>
<Ordernumber>225</Ordernumber>
<Line>
<ID>1</ID>
<ID>5</ID>
</Line>
</Order>
</Doc>
I'm going to assume the output you actually want is:
<Doc>
<Order>
<Ordernumber>225</Ordernumber>
<Line>
<ID>1</ID>
<ID>5</ID>
</Line>
</Order>
<Order>
<Ordernumber>226</Ordernumber>
<Line>
<ID>2</ID>
</Line>
</Order>
</Doc>
Since the sample output you provided makes no sense. This XSLT will produce the output above when run on your sample input:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kOrder" match="Line" use="Ordernumber"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<Doc>
<xsl:apply-templates select="Line[generate-id() =
generate-id(key('kOrder', Ordernumber)[1])]" />
</Doc>
</xsl:template>
<xsl:template match="Line">
<Order>
<xsl:apply-templates select="Ordernumber" />
<Line>
<xsl:apply-templates select="key('kOrder', Ordernumber)/OrderID" />
</Line>
</Order>
</xsl:template>
<xsl:template match="OrderID">
<ID>
<xsl:value-of select="."/>
</ID>
</xsl:template>
</xsl:stylesheet>

How can I get first x occurrences from given xml using xsl

How can I get first x occurrences on line elements from the below xml using xslt
Below is a sample xml
<document>
<line>
<name>MAR111</name>
<value>1</value>
</line>
<line>
<name>MAR111</name>
<value>3</value>
</line>
<line>
<name>MEA111</name>
<value>1</value>
</line>
<line>
<name>MPR111</name>
<value>1</value>
</line>
<line>
<name>MEA111</name>
<value>4</value>
</line>
<line>
<name>MPR111</name>
<value>2</value>
</line>
</document>
For instance if I want first 3 occurrences the result should be
<document>
<line>
<name>MAR111</name>
<value>1</value>
</line>
<line>
<name>MAR111</name>
<value>3</value>
</line>
<line>
<name>MEA111</name>
<value>1</value>
</line>
</document>
Thanks in advance.
You can test the postition()...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="document">
<xsl:copy>
<xsl:apply-templates select="#*|line[3 >= position()]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Use:
/*/line[not(position() > $x)]
where $x must be a variable containing the wanted number of "first" elements to be selected.
Or, $x can be replaced with the desired number itself.
Do note:
This problem is a pure XPath problem and as such has a pure XPath solution -- XSLT isn't necessary at all.

how to get some documents from one xml

I would like to get distinct documents from my xml on multiple levels.
My xml file:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<line id="0">
<Info><![CDATA[Header]]></Info>
<documentNUM><![CDATA[DOC1]]></documentNUM>
<Code><![CDATA[AS22]]></Code>
</line>
<line id="1">
<Info><![CDATA[Line]]></Info>
<Position><![CDATA[1]]></Position>
<Number><![CDATA[7361]]></Number>
</line>
<line id="2">
<Info><![CDATA[Line]]></Info>
<Position><![CDATA[2]]></Position>
<Number><![CDATA[7362]]></Number>
</line>
<line id="3">
<Info><![CDATA[Header]]></Info>
<documentNUM><![CDATA[DOC2]]></documentNUM>
<Code><![CDATA[AS22]]></Code>
</line>
<line id="4">
<Info><![CDATA[Line]]></Info>
<Position><![CDATA[1]]></Position>
<Number><![CDATA[3623]]></Number>
</line>
<line id="5">
<Info><![CDATA[Header]]></Info>
<documentNUM><![CDATA[DOC1]]></documentNUM>
<Code><![CDATA[AS22]]></Code>
</line>
<line id="6">
<Info><![CDATA[Line]]></Info>
<Position><![CDATA[1]]></Position>
<Number><![CDATA[3623]]></Number>
</line>
</document>
From this xml I should get two documents,
for it I use key function:
<xsl:key name="kNext" match="line[starts-with(Info,'H')]" use="concat(starts-with(Info,'H'), '+', documentNUM)"/>
And for both documents 3 and 1 lines.
Needed result:
<result>
<Group>
<Message>
<document>
<documentNUM>DOC1</documentNUM>
<Lines>
<Line>
<LineNumber>1</LineNumber>
<Number>7361</Number>
<Code>AS22</Code>
</Line>
<Line>
<LineNumber>2</LineNumber>
<Number>7362</Number>
<Code>AS22</Code>
</Line>
<Line>
<LineNumber>3</LineNumber>
<Number>3623</Number>
<Code>AS22</Code>
</Line>
</Lines>
</document>
</Message>
</Group>
<Group>
<Message>
<document>
<documentNUM>DOC2</documentNUM>
</document>
<Line>
<LineNumber>1</LineNumber>
<Number>3623</Number>
<Code>AS22</Code>
</Line>
</Lines>
</Message>
</Group>
</result>
I'm stuck to finding lines. For both documents calculate the same lines. Please give me some advice how to get correct answer.
P.S. Please correct my question if it needed.
Here is a complete XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kFollowing" match="line[not(documentNUM)]"
use="generate-id(preceding-sibling::line
[documentNUM]
[1]
)"/>
<xsl:key name="kLineByDocNum" match="line"
use="documentNUM"/>
<xsl:template match=
"line
[documentNUM
and
generate-id()
=
generate-id(key('kLineByDocNum', documentNUM)[1])
]">
<Group>
<Message>
<document>
<documentNUM>
<xsl:value-of select="documentNUM"/>
</documentNUM>
<Lines>
<xsl:apply-templates mode="inGroup" select=
"key('kLineByDocNum', documentNUM)"/>
</Lines>
</document>
</Message>
</Group>
</xsl:template>
<xsl:template match="line" mode="inGroup">
<xsl:apply-templates mode="inGroup2"
select="key('kFollowing', generate-id())">
<xsl:with-param name="pCode" select="Code"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="line" mode="inGroup2">
<xsl:param name="pCode"/>
<xsl:variable name="vcurDocNum" select=
"preceding-sibling::line
[documentNUM][1]
/documentNUM
"/>
<xsl:variable name="vPos" select=
"count(preceding-sibling::line
[not(documentNUM)]
[preceding-sibling::line
[documentNUM][1]
/documentNUM
=
$vcurDocNum
]
) +1"/>
<Line>
<LineNumber><xsl:value-of select="$vPos"/></LineNumber>
<xsl:copy-of select="Number"/>
<Code><xsl:value-of select="$pCode"/></Code>
</Line>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<document>
<line id="0">
<Info>Header</Info>
<documentNUM>DOC1</documentNUM>
<Code>AS22</Code>
</line>
<line id="1">
<Info>Line</Info>
<Position>1</Position>
<Number>7361</Number>
</line>
<line id="2">
<Info>Line</Info>
<Position>2</Position>
<Number>7362</Number>
</line>
<line id="3">
<Info>Header</Info>
<documentNUM>DOC2</documentNUM>
<Code>AS22</Code>
</line>
<line id="4">
<Info>Line</Info>
<Position>1</Position>
<Number>3623</Number>
</line>
<line id="5">
<Info>Header</Info>
<documentNUM>DOC1</documentNUM>
<Code>AS22</Code>
</line>
<line id="6">
<Info>Line</Info>
<Position>1</Position>
<Number>3623</Number>
</line>
</document>
the wanted, correct result is produced:
<Group>
<Message>
<document>
<documentNUM>DOC1</documentNUM>
<Lines>
<Line>
<LineNumber>1</LineNumber>
<Number>7361</Number>
<Code>AS22</Code>
</Line>
<Line>
<LineNumber>2</LineNumber>
<Number>7362</Number>
<Code>AS22</Code>
</Line>
<Line>
<LineNumber>3</LineNumber>
<Number>3623</Number>
<Code>AS22</Code>
</Line>
</Lines>
</document>
</Message>
</Group>
<Group>
<Message>
<document>
<documentNUM>DOC2</documentNUM>
<Lines>
<Line>
<LineNumber>1</LineNumber>
<Number>3623</Number>
<Code>AS22</Code>
</Line>
</Lines>
</document>
</Message>
</Group>
This looks like a grouping problem. If you have XSLT 2.0 available, use for-each-group / group-starting-with. Then you don't need a key:
<result>
<xsl:for-each-group select="line"
group-starting-with="line[starts-with(Info,'H')]">
<Group>
<Message>
<document>
<documentNUM>
<xsl:value-of select="current-grouping-key()/documentNUM" />
</documentNUM>
</document>
<xsl:apply-templates select="current-group()
[not(starts-with(Info, 'H'))]" />
etc.
If you want more detail, let me know.