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
Related
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>
Please tell me how to split document by fields id. For example doc number are saved in line [#id=0]/field[#id=1] and line[#id=0]/field[#id=2], in line[#id>0] are saved codes and values.
XML example:
<document>
<sheet id="0" name="Sheet1">
<line id="0">
<field id="0"><![CDATA[Code]]></field>
<field id="1"><![CDATA[01]]></field>
<field id="2"><![CDATA[02]]></field>
</line>
<line id="1">
<field id="0"><![CDATA[9772]]></field>
<field id="1"><![CDATA[9.0]]></field>
<field id="2"><![CDATA[5.0]]></field>
</line>
<line id="5">
<field id="0"><![CDATA[9771]]></field>
<field id="1"><![CDATA[1.0]]></field>
<field id="2"/>
</line>
<line id="1">
<field id="0"><![CDATA[9773]]></field>
<field id="1"><![CDATA[8.0]]></field>
<field id="2"><![CDATA[4.0]]></field>
</line>
</sheet>
</document>
if needed result:
<documents>
<document>
<header>
<number>01</number>
</heder>
<line>
<line-item>
<lineNumber>1</lineNumber>
<Code>9772</Code>
<value>9.0</value>
</line-item>
<line-item>
<lineNumber>2</lineNumber>
<Code>9771</Code>
<value>1.0</value>
</line-item>
<line-item>
<lineNumber>3</lineNumber>
<Code>9773</Code>
<value>8.0</value>
</line-item>
</line>
</document>
<document>
<header>
<number>02</number>
</heder>
<line>
<line-item>
<lineNumber>1</lineNumber>
<Code>9772</Code>
<value>5.0</value>
</line-item>
<line-item>
<lineNumber>2</lineNumber>
<Code>9773</Code>
<value>4.0</value>
</line-item>
</line>
</document>
</documents>
Transformation should work on xsl:stylesheet version="1.0"
Try this ...
Solution 1: Procedural
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*" />
<xsl:template match="/">
<documents>
<xsl:apply-templates select="document/sheet/line[#id='0']/field[#id!='0']" />
</documents>
</xsl:template>
<xsl:template match="field[#id!='0']">
<document>
<header>
<number><xsl:value-of select="." /></number>
</header>
<line>
<xsl:call-template name="line-item-template" >
<xsl:with-param name="value-id" select="#id" />
<xsl:with-param name="lines" select="../../line[#id!='0']"/>
</xsl:call-template>
</line>
</document>
</xsl:template>
<xsl:template name="line-item-template">
<xsl:param name="value-id" />
<xsl:param name="lines" />
<xsl:for-each select="$lines[field[#id=$value-id]!='']" >
<line-item>
<lineNumber><xsl:value-of select="format-number(position(),'00')" /></lineNumber>
<Code><xsl:value-of select="field[#id='0']" /></Code>
<value><xsl:value-of select="field[#id=$value-id]" /></value>
</line-item>
</xsl:for-each >
</xsl:template>
</xsl:stylesheet>
.. or try this ...
Solution 2: Template-o-phile
This solution hooks into the node that relates to the output node.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*" />
<xsl:template match="/">
<documents>
<xsl:apply-templates select="document/sheet/line[#id='0']/field[#id!='0']" />
</documents>
</xsl:template>
<xsl:template match="field[parent::line[#id='0']][#id!='0']">
<document>
<header>
<number><xsl:value-of select="." /></number>
</header>
<line>
<xsl:variable name="value-index" select="#id" />
<xsl:apply-templates select="../../line[#id!='0']/field[#id=$value-index]" />
</line>
</document>
</xsl:template>
<xsl:template match="field[parent::line[#id!='0']][.!='']">
<xsl:variable name="current" select="." />
<xsl:variable name="value-index" select="#id" />
<line-item>
<lineNumber><xsl:value-of select="format-number( count(
preceding::line[field[#id=$value-index]!='']
[..=$current/../..]
),'00')" /></lineNumber>
<Code><xsl:value-of select="../field[#id='0']" /></Code>
<value><xsl:value-of select="." /></value>
</line-item>
</xsl:template>
</xsl:stylesheet>
Now imagine solving this problem for XSLT 2.0. Now that would be fun! :-)
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>
I want to ask. there is posible to add condition which will be checking xml data with lookup table, and if we didnt't have value in lookup table add const 8 to output?
xslt Code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="Department" match="Department" use="../Collection"/>
<xsl:template match="/">
<document>
<xsl:apply-templates/>
</document>
</xsl:template>
<xsl:template match="line">
<xsl:variable name="inputDep" select="field[#id='3']"/>
<Department>
<xsl:for-each select="document('lookup.xml')">
<xsl:for-each select="key('Deparment',$inputDep)">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:for-each>
</Department>
</xsl:template>
</xsl:stylesheet>
lookup table:
<document>
<line-item>
<Collection>1</Collection>
<Department>3</Department>
</line-item>
<line-item>
<Collection>2</Collection>
<Department>1</Department>
</line-item>
<line-item>
<Collection>3</Collection>
<Department>2</Department>
</line-item>
</document>
xml file:
<document>
<line id="0">
<field id="3"><![CDATA[1]]></field>
</line>
<line id="1">
<field id="3"/>
</line>
<line id="2">
<field id="3"/><![CDATA[4]]></field>
</line>
</document>
result:
<Department>3<Department>
<Department>8<Department>
<Department>8<Department>
You could assign the looked-up value to a variable and choose what to output based on whether anything was found.
Edit 2: A full demonstration stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="Department" match="Department" use="../Collection"/>
<xsl:template match="/">
<document>
<xsl:apply-templates/>
</document>
</xsl:template>
<xsl:template match="line">
<xsl:variable name="inputDep" select="field[#id='3']"/>
<Department>
<xsl:for-each select="document('lookup.xml')">
<xsl:variable name="value" select="key('Department',$inputDep)"/>
<xsl:choose>
<xsl:when test="$value">
<xsl:value-of select="$value"/> <!-- see note -->
</xsl:when>
<xsl:otherwise>8</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Department>
</xsl:template>
</xsl:stylesheet>
Note: Replaced the xsl:for-each loop in the original stylesheet with a simple xsl:value-of, assuming that the looping the values was not intentional. If it actually was, you can replace this with a for-each loop.
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.