xslt grouping by calling template - templates

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>

Related

XSLT sort and group child segments and duplicate parent

I have an xml with this structure:
<Order>
<Top>
</Top>
<Body>
<Line>
<Ref>
<PO>1</PO>
</Ref>
</Line>
<Line>
<Ref>
<PO>1</PO>
</Ref>
</Line>
<Line>
<Ref>
<PO>3</PO>
</Ref>
</Line>
<Line>
<Ref>
<PO>3</PO>
</Ref>
</Line>
<Line>
<Ref>
<PO>2</PO>
</Ref>
</Line>
<Line>
<Ref>
<PO>2</PO>
</Ref>
</Line>
</Body>
</Order>
I need to sort and group Line by Ref/PO, and then duplicate the parent nodes for each group of Line, like in the example below.
<Order>
<Top>
</Top>
<Body>
<Line>
<Ref>
<PO>1</PO>
</Ref>
</Line>
<Line>
<Ref>
<PO>1</PO>
</Ref>
</Line>
</Body>
</Order>
<Order>
<Top>
</Top>
<Body>
<Line>
<Ref>
<PO>2</PO>
</Ref>
</Line>
<Line>
<Ref>
<PO>2</PO>
</Ref>
</Line>
</Body>
</Order>
<Order>
<Top>
</Top>
<Body>
<Line>
<Ref>
<PO>3</PO>
</Ref>
</Line>
<Line>
<Ref>
<PO>3</PO>
</Ref>
</Line>
</Body>
</Order>
How can this be done? I have looked for similar questions without finding any exact match, so apologize if I have overlooked something. Appreciate the help.
Assuming at least XSLT 2 where you have for-each-group you can use it to group and sort and then you simply have to reconstruct the strucuture
<xsl:template match="Body">
<xsl:for-each-group select="Line" group-by="Ref/PO">
<xsl:sort select="xs:integer(current-grouping-key())"/>
<xsl:copy select="../..">
<xsl:copy-of select="* except Body"/>
<Body>
<xsl:copy-of select="current-group()"/>
</Body>
</xsl:copy>
</xsl:for-each-group>
</xsl:template>
https://xsltfiddle.liberty-development.net/bnnZVK/1 has full working example
<?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="#all"
version="3.0">
<xsl:mode on-no-match="shallow-skip"/>
<xsl:output indent="yes"/>
<xsl:template match="Body">
<xsl:for-each-group select="Line" group-by="Ref/PO">
<xsl:sort select="xs:integer(current-grouping-key())"/>
<xsl:copy select="../..">
<xsl:copy-of select="* except Body"/>
<Body>
<xsl:copy-of select="current-group()"/>
</Body>
</xsl:copy>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
using XSLT 3 (but the use of xsl:copy select unique to XSLT 3 could be avoided for XSLT 2 with xsl:element), for instance
<xsl:template match="Body">
<xsl:for-each-group select="Line" group-by="Ref/PO">
<xsl:sort select="xs:integer(current-grouping-key())"/>
<xsl:element name="{name(../..)}" namespace="{namespace-uri(../..)}">
<xsl:copy-of select="../../(* except Body)"/>
<Body>
<xsl:copy-of select="current-group()"/>
</Body>
</xsl:element>
</xsl:for-each-group>
</xsl:template>
http://xsltransform.net/pNEhB31
<xsl:template match="Body">
<xsl:for-each-group select="Line" group-by="Ref/PO">
<xsl:sort select="current-grouping-key()"/>
<xsl:element name="order">
<xsl:element name="top"></xsl:element>
<Body>
<xsl:copy-of select="current-group()"/>
</Body>
</xsl:element>
</xsl:for-each-group>
xslt 2.0

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 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.