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

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.

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>

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 to check lookup table tags with documents tags

Halo,
I have this xml document:
<document>
<Line>
<Line-Item>
<ID>5</ID>
<Quantity>100</Quantity>
</Line-Item>
</Line>
<Line>
<Line-Item>
<ID>6</ID>
<Quantity>9</Quantity>
</Line-Item>
</Line>
<Line>
<Line-Item>
<ID>60</ID>
<Quantity>3020</Quantity>
</Line-Item>
</Line>
</document>
And lookup file with table:
<lookup>
<Code>
<LookupID>5</LookupID>
<LookupQuantity>25</LookupQuantity>
</Code>
<Code>
<LookupID>6</LookupID>
<LookupQuantity>3</LookupQuantity>
</Code>
<Code>
<LookupID>70</LookupID>
<LookupQuantity>3</LookupQuantity>
</Code>
</lookup>
I should check lookup tables field lookup/Code/LookupId with document Line/Line-Item/ID. if lookup/Code/LookupId=document/Line/Line-Item/ID then document/Line/Line-Item/Quantity=document/Line/Line-Item/Quantity div lookup/Code/LookupQuantity, otherwise document/Line/Line-Item/Quantity=document/Line/Line-Item/Quantity
Needed result:
<document>
<Line>
<Line-Item>
<ID>5</ID>
<Quantity>4</Quantity>
</Line-Item>
</Line>
<Line>
<Line-Item>
<ID>6</ID>
<Quantity>3</Quantity>
</Line-Item>
</Line>
<Line>
<Line-Item>
<ID>60</ID>
<Quantity>3020</Quantity>
</Line-Item>
</Line>
</document>
My xslt:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:date="http://exslt.org/dates-and-times" extension-element-prefixes="date">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:key name="skRez" match="LookupQuantity" use="../LookupID"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Line/Line-Item/Quantity">
<xsl:variable name="inputS" select="..//ID"/>
<xsl:variable name="inputQ" select="..//Quantity"/>
<OrderedQuantity>
<xsl:for-each select="document('lookup.xml')">
<xsl:for-each select="key('skRez',$inputS)">
<xsl:variable name="Quantity" select="."/>
<xsl:choose>
<xsl:when test="$Quantity"><xsl:value-of select="ceiling($inputQ div $Quantity)"/></xsl:when>
<xsl:otherwise><xsl:value-of select="$inputQ"/></xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
</OrderedQuantity>
</xsl:template>
</xsl:stylesheet>
To do this, you can define a variable to hold the look-up data
<xsl:variable name="lookup" select="document('Lookup.xml')/lookup"/>
And then you can look-up the quantity for a particular Line-Item like so (In this case, the XSLT is currently positioned on a Quantity element within a Line-Item)
<xsl:variable name="quantity"
select="$lookup//Code[LookupID = current()/../ID]/LookupQuantity"/>
If nothing was returned by this variable, then you know the element was not in the look-up
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="lookup" select="document('Lookup.xml')/lookup"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Line-Item/Quantity">
<xsl:variable name="quantity"
select="$lookup//Code[LookupID = current()/../ID]/LookupQuantity"/>
<Quantity>
<xsl:choose>
<xsl:when test="number($quantity) = number($quantity)">
<xsl:value-of select="number(.) div number($quantity)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</Quantity>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<document>
<Line>
<Line-Item>
<ID>5</ID>
<Quantity>4</Quantity>
</Line-Item>
</Line>
<Line>
<Line-Item>
<ID>6</ID>
<Quantity>3</Quantity>
</Line-Item>
</Line>
<Line>
<Line-Item>
<ID>60</ID>
<Quantity>3020</Quantity>
</Line-Item>
</Line>
</document>

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.

How to check tags in lookup table

I have, this xml lookup table:
<lookup>
<Codes>
<code>123<code>
</Codes>
<Codes>
<code>321<code>
</Codes>
</lookup>
and document:
<document>
<header>
<remarks>test</remarks>
</header>
<Line>
<Line-Item>
<code>123</code>
</Line-Item>
<Line-Item>
<code>444</code>
</Line-Item>
<Line-Item>
<code>321</code>
</Line-Item>
</Line>
</document>
There is possible, to check document codes with lookup table codes, and if document code=lookup code, delete Line-Item from document.
answer should be:
<document>
<header>
<remarks>test</remarks>
</header>
<Line>
<Line-Item>
<code>444</code>
</Line-Item>
</Line>
</document>
I'm stuck on xsl:stylesheet version="1.0".
Best regards :)
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:variable name="vCodes" select=
"document('file:///c:/temp/LookupDelete.xml')/*/*/code"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"Line-Item[code
= document('file:///c:/temp/LookupDelete.xml')
/*/*/code
]
"/>
<xsl:template match=
"Line[not(Line-Item/code
[not(. = document('file:///c:/temp/LookupDelete.xml')
/*/*/code
)
]
)
]
"/>
</xsl:stylesheet>
when applied on the provided XML document:
<document>
<header>
<remarks>test</remarks>
</header>
<Line>
<Line-Item>
<code>123</code>
</Line-Item>
<Line-Item>
<code>444</code>
</Line-Item>
<Line-Item>
<code>321</code>
</Line-Item>
</Line>
</document>
produces the wanted, correct result:
<document>
<header>
<remarks>test</remarks>
</header>
<Line>
<Line-Item>
<code>444</code>
</Line-Item>
</Line>
</document>
If the document is this:
<document>
<header>
<remarks>test</remarks>
</header>
<Line>
<Line-Item>
<code>123</code>
</Line-Item>
<Line-Item>
<code>321</code>
</Line-Item>
<Line-Item>
<code>321</code>
</Line-Item>
</Line>
</document>
then again the correct result is produced (note that no Line element is output at all):
<document>
<header>
<remarks>test</remarks>
</header>
</document>
<!-- codes of items to exclude from external document -->
<xsl:variable name="lookup-table" select="document('lookup-table.xml')/lookup/Codes/code"/>
<!-- exclude Line-Items with codes from lookup table -->
<xsl:template match="Line-Item[ code = $lookup-table ]"/>
<!--
exclude <Line> elements,
which contains only <Line-Item>'s which will be removed
-->
<xsl:template match="Line[ not(Line-Item[ code != $lookup-table ]) ]"/>
<!-- copy all other items -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl;apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
This stylesheet produces the desired result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lookup="lookup">
<lookup:lookup>
<Codes>
<code>123</code>
</Codes>
<Codes>
<code>321</code>
</Codes>
</lookup:lookup>
<xsl:variable name="lookup" select="document('')/*/*/Codes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Line">
<xsl:copy>
<xsl:apply-templates select="#* |
node()[not(self::Line-Item[code=$lookup/code])]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that the lookup table is included in the stylesheet. It could easily be included in a separate document if it's too large to include in the transformation.