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! :-)
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.
Please help with this xml:
<document>
<sheet id="0" name="Sheet1">
<line id="0">
<field id="0"><![CDATA[Calculate]]></field>
</line>
<line id="1">
<field id="0"><![CDATA[Quantity]]></field>
<field id="1"><![CDATA[Value]]></field>
</line>
<line id="2">
<field id="0"><![CDATA[3]]></field>
<field id="1"><![CDATA[2]]></field>
</line>
<line id="3">
<field id="0"><![CDATA[2]]></field>
<field id="1"><![CDATA[7]]></field>
</line>
<line id="4">
<field id="0"></field>
<field id="1"></field>
</line>
</sheet>
</document>
I need to get Sum of field[#id=1] before multiply this field with field[#id=0]. Line[#id=4] is empty so there should be condition to eliminate this line.
correct result should be:
<document>
<id>Calculate</id>
<line>
<sum>20</sum>
</line>
</document>
Here is an XSLT 2.0 solution that can be run with XSLT 2.0 processors like Saxon 9, XQSharp, AltovaXML:
<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="document">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="sheet">
<id><xsl:value-of select="line[#id = 0]/field"/></id>
<line>
<sum>
<xsl:value-of select="sum(line[field[#id = 1] castable as xs:double and field[#id = 0] castable as xs:double]/(field[#id = 1] * field[#id = 0]))"/>
</sum>
</line>
</xsl:template>
</xsl:stylesheet>
[edit] I am adding an XSLT 1.0 stylesheet with a named recursive template below:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="document">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="lines"/>
<xsl:param name="total" select="0"/>
<xsl:choose>
<xsl:when test="not($lines)">
<xsl:value-of select="$total"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="sum">
<xsl:with-param name="lines" select="$lines[position() > 1]"/>
<xsl:with-param name="total" select="$total + $lines[1]/field[#id = 1] * $lines[1]/field[#id = 0]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="sheet">
<id><xsl:value-of select="line[#id = 0]/field"/></id>
<line>
<sum>
<xsl:call-template name="sum">
<xsl:with-param name="lines"
select="line[number(field[#id = 1]) = number(field[#id = 1])
and number(field[#id = 0]) = number(field[#id = 0])]"/>
</xsl:call-template>
</sum>
</line>
</xsl:template>
</xsl:stylesheet>
I have an XML file:
<document>
<line id="0">
<field id="2">X111</field>
<field id="3">1</field>
<field id="4">222222222222</field>
</line>
<line id="1">
<field id="2">X111</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
<line id="2">
<field id="2">X222</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
<line id="3">
<field id="2">X222</field>
<field id="3">1></field>
<field id="4">111111111111</field>
</line>
<line id="4">
<field id="2">X333</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
</document>
From this xml file I should group field2 (after that field4 ), question will be not to how to group field 2 and get three documents but how to group field4 if they are the same?
Output:
<document>
<Result>
<Header>
<Field2>X111</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>222222222222</Field4>
<Sum>1<Sum>
<Position>2</Position>
<Field4>111111111111</Field4>
<Sum>1<Sum>
</Line>
</Result>
<Result>
<Header>
<Field2>X222</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>111111111111</Field4>
<Sum>2<Sum>
</Line>
</Result>
<Result>
<Header>
<Field2>X333</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>111111111111</Field4>
<Sum>1</Sum>
</Line>
</Result>
</document>
I'm stuck in grouping lines, I didn't know how to group the same and different fields which id= 4.
My program looks:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:key name="kLine" match="line" use="string(field[#id=2])"/>
<xsl:key name="bLine" match="line" use="field[#id=4]"/>
<xsl:template match="document">
<document>
<xsl:apply-templates select="line[count( . | key('kLine', string(field[#id='2']))[1]) = 1]"/>
</document>
</xsl:template>
<xsl:template match="line">
<xsl:variable name="field2" select="field[#id='2']"/>
<result>
<Header>
<xsl:value-of select="field[#id='2']"/>
</Header>
<Line>
<xsl:for-each select="//line[field[#id='2']=$field2]">
<Position>
<xsl:value-of select="position()"/>
</Position>
<Field4><xsl:value-of select="field[#id='4']"/></Field4>
<Sum><xsl:value-of select="sum(key('bLine', field[#id='4'])/field[#id='3'])"/></Sum>
</xsl:for-each>
</Line>
</result>
</xsl:template>
</xsl:stylesheet>
You're asking how to group at multiple levels. The following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="byField2" match="line" use="field[#id='2']" />
<xsl:key name="byField2AndField4" match="line"
use="concat(field[#id='2'], '|', field[#id='4'])" />
<xsl:template match="/">
<document><xsl:apply-templates /></document>
</xsl:template>
<xsl:template
match="line[generate-id() =
generate-id(key('byField2', field[#id='2'])[1])]">
<Result>
<Header>
<Field2><xsl:value-of select="field[#id='2']" /></Field2>
</Header>
<Line>
<xsl:apply-templates
select="key('byField2', field[#id='2'])
[generate-id() =
generate-id(key('byField2AndField4',
concat(field[#id='2'], '|', field[#id='4']))[1])]"
mode="field4" />
</Line>
</Result>
</xsl:template>
<xsl:template match="line" mode="field4">
<Position><xsl:value-of select="position()" /></Position>
<Field4><xsl:value-of select="field[#id='4']" /></Field4>
<Sum>
<xsl:value-of
select="sum(key('byField2AndField4',
concat(field[#id='2'], '|', field[#id='4']))/
field[#id='3'])" />
</Sum>
</xsl:template>
<xsl:template match="line" />
</xsl:stylesheet>
On this input:
<document>
<line id="0">
<field id="2">X111</field>
<field id="3">1</field>
<field id="4">222222222222</field>
</line>
<line id="1">
<field id="2">X111</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
<line id="2">
<field id="2">X222</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
<line id="3">
<field id="2">X222</field>
<field id="3">1></field>
<field id="4">111111111111</field>
</line>
<line id="4">
<field id="2">X333</field>
<field id="3">1</field>
<field id="4">111111111111</field>
</line>
</document>
Produces the desired result:
<document>
<Result>
<Header>
<Field2>X111</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>222222222222</Field4>
<Sum>1</Sum>
<Position>2</Position>
<Field4>111111111111</Field4>
<Sum>1</Sum>
</Line>
</Result>
<Result>
<Header>
<Field2>X222</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>111111111111</Field4>
<Sum>2</Sum>
</Line>
</Result>
<Result>
<Header>
<Field2>X333</Field2>
</Header>
<Line>
<Position>1</Position>
<Field4>111111111111</Field4>
<Sum>1</Sum>
</Line>
</Result>
</document>
We use two keys. The first groups only by field 2. The second groups by the concatenation of fields 2 and 4.
I have to transform xml file where i should check field id '0', field id '1' and sum field id '2'. For example I have:
<document>
<line id="0">
<field id="0">MAR</field>
<field id="1">doc1</field>
<field id="2">2</field>
</line>
<line id="1">
<field id="0">MAR</field>
<field id="1">doc2</field>
<field id="2">3</field>
</line>
<line id="2">
<field id="0">AAA></field>
<field id="1">doc4</field>
</line>
<line id="3">
<field id="0">MAR</field>
<field id="1">doc1</field>
<field id="2">4</field>
</line>
</document>
result should be:
<type-MAR>
<document>doc1</document>
<sum>6</sum>
</type-MAR>
<type-MAR>
<document>doc2</document>
<sum>3</sum>
</type-MAR>
there I should take all MAR lines, and show some results which are depends of field id '1'.
My idea was, first off all do cycle(for each) and use condition(when). Maybe somebody offer more omptimal decision.
I add new note, how to check if data comes like that:
<field id="0">MAR999</field>
<field id="1">doc1-1231</field>
First field i try to use function contains 'MAR', others substring-before '-'. but I stuck when I try it use on Yours program. maybe you can take some advice for it?
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:key name="kLineById0Id1" match="line[field[#id=2]]"
use="concat(field[#id=0],'+',field[#id=1])"/>
<xsl:template match=
"line[field[#id=2]
and
generate-id()
=
generate-id(key('kLineById0Id1',
concat(field[#id=0],
'+',field[#id=1])
)[1])
]
">
<xsl:element name="type-{field[#id=0]}">
<document>
<xsl:value-of select="field[#id=1]"/>
</document>
<sum>
<xsl:value-of select=
"sum(key('kLineById0Id1',
concat(field[#id=0],
'+',field[#id=1])
)
/field[#id=2]
)
"/>
</sum>
</xsl:element>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document:
<document>
<line id="0">
<field id="0">MAR</field>
<field id="1">doc1</field>
<field id="2">2</field>
</line>
<line id="1">
<field id="0">MAR</field>
<field id="1">doc2</field>
<field id="2">3</field>
</line>
<line id="2">
<field id="0">AAA></field>
<field id="1">doc4</field>
</line>
<line id="3">
<field id="0">MAR</field>
<field id="1">doc1</field>
<field id="2">4</field>
</line>
</document>
produces the wanted, correct result:
<type-MAR>
<document>doc1</document>
<sum>6</sum>
</type-MAR>
<type-MAR>
<document>doc2</document>
<sum>3</sum>
</type-MAR>
Explanation: The Muenchian method for grouping is used with the key defined as the concatenation of two elements.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="kLine" match="line" use="field[#id='1']"/>
<xsl:template match="/*">
<r>
<xsl:apply-templates select="line
[field[#id='0'] = 'MAR']
[count(
. | key('kLine', field[#id='1'])[1]
) = 1]
"/>
</r>
</xsl:template>
<xsl:template match="line">
<type-MAR>
<document>
<xsl:value-of select="field[#id='1']"/>
</document>
<sum>
<xsl:value-of select="
sum(
key('kLine', field[#id='1'])/
field[#id='2']
)"/>
</sum>
</type-MAR>
</xsl:template>
</xsl:stylesheet>
Correct against your sample will be:
<r>
<type-MAR>
<document>doc1</document>
<sum>6</sum>
</type-MAR>
<type-MAR>
<document>doc2</document>
<sum>3</sum>
</type-MAR>
</r>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kLineById0-Id1" match="line"
use="concat(field[#id='0'],'+',field[#id='1'])"/>
<xsl:param name="pId0" select="'MAR'"/>
<xsl:template match="document">
<result>
<xsl:apply-templates select="line[generate-id()=
generate-id(
key('kLineById0-Id1',
concat($pId0,
'+',
field[#id='1']
)
)[1]
)]"/>
</result>
</xsl:template>
<xsl:template match="line">
<xsl:element name="type-{$pId0}">
<document>
<xsl:value-of select="field[#id='1']"/>
</document>
<sum>
<xsl:value-of select="sum(key('kLineById0-Id1',
concat(field[#id='0'],
'+',
field[#id='1']
)
)/field[#id='2']
)"/>
</sum>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output:
<result>
<type-MAR>
<document>doc1</document>
<sum>6</sum>
</type-MAR>
<type-MAR>
<document>doc2</document>
<sum>3</sum>
</type-MAR>
</result>
Note: Grouping by both #id attributes, sum group, dynamic element name, parameterized first #id.
Thanks for answers, I use Flack decision and make some correction:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="kLine" match="line" use="substring(field[#id='1'],1,4)"/>
<xsl:template match="/*">
<document>
<xsl:apply-templates select="line[contains(field[#id='0'], 'MAR')][count(. | key('kLine', substring(field[#id='1'],1,4))[1]) = 1]"/>
</document>
</xsl:template>
<xsl:template match="line">
<type-MAR>
<document>
<xsl:value-of select="substring(field[#id='1'],1,4)"/>
</document>
<sum>
<xsl:value-of select="sum(key('kLine', substring(field[#id='1'],1,4))/field[#id='2'])"/>
</sum>
</type-MAR>
</xsl:template>
</xsl:stylesheet>
Dimitre and Alejandro decisions are also good and useful(maybe more professional). But Dimitre more concentrate on my task which I wrote, for example he use condition to check if we have the second field(I didn't wrote that not only MAR could have field2). Alejandro to check it use parameter, so for me it was a good lesson to search more information how to use it, because with xsl language I have less than one month experience. So for me was difficult to prepair Yours programs for my work. Flack text was more understandable for me as a beginner.