How to group at more than one level? - xslt

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.

Related

How to split document by fields id?

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! :-)

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 find start(min) and end(max) date in xml file?

I have an XML file. I should find for same field2 start(min) date and End(max) date from field3. Maybe xsl have some function to find it.Because I try do it trying to find min month and min day and max month, max day.
XML:
<document>
<line id="0">
<field id="2">X111</field>
<field id="3">2011-03-31</field>
</line>
<line id="1">
<field id="2">X111</field>
<field id="3">2011-04-04</field>
</line>
<line id="2">
<field id="2">X111</field>
<field id="3">2011-04-02</field>
</line>
<line id="3">
<field id="2">X222</field>
<field id="3">2011-04-04</field>
</line>
<line id="4">
<field id="2">X222</field>
<field id="3">2011-04-01</field>
</line>
<line id="4">
<field id="2">X333</field>
<field id="3">2011-04-01</field>
</line>
</document>
Output:
<document>
<Message>
<ID>X111</ID>
<dateStart>2011-03-31</dateStart>
<dateEnd>2011-04-04</dateEnd>
</Message>
<Message>
<ID>X222</ID>
<dateStart>2011-04-01</dateStart>
<dateEnd>2011-04-04</dateEnd>
</Message>
<Message>
<ID>X333</ID>
<dateStart>2011-04-01</dateStart>
<dateEnd>2011-04-01</dateEnd>
</Message>
</document>
Please help to solve it. I'm working with stylesheet version="1.0".
This can probably be optimized but it returns the requested results:
<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="test" match="line" use="field[#id=2]"/>
<xsl:template match="/">
<document>
<xsl:for-each select="//line[generate-id()=generate-id(key('test',field[#id=2]))]">
<xsl:sort select="field[#id=2]"/>
<Message>
<ID>
<xsl:value-of select="field[#id=2]"/>
</ID>
<xsl:apply-templates select="key('test',field[#id=2])">
<xsl:sort select="field[#id=3]"/>
</xsl:apply-templates>
</Message>
</xsl:for-each>
</document>
</xsl:template>
<xsl:template match="line">
<xsl:if test="position()=1">
<dateStart>
<xsl:value-of select="field[#id=3]"/>
</dateStart>
</xsl:if>
<xsl:if test="position()=last()">
<dateEnd>
<xsl:value-of select="field[#id=3]"/>
</dateEnd>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I upvoted #mousio's answer, but I'd prefer to see the first of each line type handled in its own template. So, in the spirit of TMTOWTDI, here's how I would have done it:
<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="byField2" match="line" use="field[#id=2]" />
<xsl:template match="/">
<document>
<xsl:apply-templates select="document/line" />
</document>
</xsl:template>
<xsl:template match="line[count(.|key('byField2', field[#id=2])[1])=1]">
<Message>
<ID><xsl:value-of select="field[#id=2]" /></ID>
<xsl:apply-templates select="key('byField2', field[#id=2])" mode="m">
<xsl:sort select="field[#id=3]" />
</xsl:apply-templates>
</Message>
</xsl:template>
<xsl:template match="line" mode="m">
<xsl:if test="position()=1">
<dateStart><xsl:value-of select="field[#id=3]" /></dateStart>
</xsl:if>
<xsl:if test="position()=last()">
<dateEnd><xsl:value-of select="field[#id=3]" /></dateEnd>
</xsl:if>
</xsl:template>
<xsl:template match="line" />
</xsl:stylesheet>
I think this is easier to read (and probably more efficient on large documents, since it doesn't abuse //).
This transformation shows how to find the wanted minimum and maximum:
<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:template match="/*">
<xsl:apply-templates select="line">
<xsl:sort select="field[#id=3]"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="line">
<xsl:if test="position()=1">
Earliest:
<xsl:copy-of select="."/>
</xsl:if>
<xsl:if test="position()=last()">
Latest:
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<document>
<line id="0">
<field id="2">X111</field>
<field id="3">2011-03-31</field>
</line>
<line id="1">
<field id="2">X111</field>
<field id="3">2011-04-04</field>
</line>
<line id="2">
<field id="2">X111</field>
<field id="3">2011-04-02</field>
</line>
<line id="3">
<field id="2">X222</field>
<field id="3">2011-04-04</field>
</line>
<line id="4">
<field id="2">X222</field>
<field id="3">2011-04-01</field>
</line>
<line id="4">
<field id="2">X333</field>
<field id="3">2011-04-01</field>
</line>
</document>
produces:
Earliest:
<line id="0">
<field id="2">X111</field>
<field id="3">2011-03-31</field>
</line>
Latest:
<line id="3">
<field id="2">X222</field>
<field id="3">2011-04-04</field>
</line>
Explanation:
Choosing the first and last element from the sorted node-list. The dates are in a "good" format, so they are sorted just as strings.

how to use ELEMENT: xsl:key?

HI all,
I should to generate this xml file:
<document>
<line id="0">
<field id="0"><![CDATA[MAR5555]]></field>
<field id="1"><![CDATA[something]]></field>
<field id="2"><![CDATA[something]]></field>
<field id="3"><![CDATA[something12123]]></field>
<field id="4"/>
<field id="5"/>
<field id="6"/>
<field id="7"/>
<field id="8"/>
<field id="9"/>
<field id="10"/>
<field id="11"/>
<field id="12"/>
<field id="13"/>
<field id="14"/>
<field id="15"/>
<field id="16"/>
<field id="17"><![CDATA[0072972+1313113123123]]></field>
<field id="18"><![CDATA[5353]]></field>
<field id="19"><![CDATA[444432323]]></field>
<field id="20"/>
<field id="21"/>
</line>
<line id="1">
<field id="0"><![CDATA[MAR6435]]></field>
<field id="1"><![CDATA[car123]]></field>
<field id="2"><![CDATA[sds]]></field>
<field id="3"><![CDATA[fest]]></field>
<field id="4"/>
<field id="5"/>
<field id="6"/>
<field id="7"/>
<field id="8"/>
<field id="9"/>
<field id="10"/>
<field id="11"/>
<field id="12"/>
<field id="13"/>
<field id="14"/>
<field id="15"><![CDATA[0000062+0dadasd]]></field>
<field id="16"><![CDATA[032]]></field>
<field id="17"><![CDATA[23242442]]></field>
<field id="18"/>
<field id="19"><![CDATA[000000]]></field>
<field id="20"/>
<field id="21"/>
</line>
<line id="2">
<field id="0"><![CDATA[MAR6435]]></field>
<field id="1"><![CDATA[sss]]></field>
<field id="2"><![CDATA[Something111]]></field>
<field id="3"><![CDATA[something111]]></field>
<field id="4"/>
<field id="5"/>
<field id="6"/>
<field id="7"/>
<field id="8"/>
<field id="9"/>
<field id="10"/>
<field id="11"/>
<field id="12"/>
<field id="13"/>
<field id="14"/>
<field id="15"><![CDATA[0000062+0sdsd]]></field>
<field id="16"><![CDATA[022]]></field>
<field id="17"><![CDATA[23444444]]></field>
<field id="18"/>
<field id="19"><![CDATA[000000]]></field>
<field id="20"/>
<field id="21"/>
</line>
</document>
My programs looks:
<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-before(field[contains(., '+')],'+')"/>
<xsl:template match="/*">
<document>
<xsl:apply-templates select="line[contains(field[#id='0'], 'MAR')][count( . | key('kLine',substring-before(field[contains(., '+')],'+'))[1]) = 1]"/>
</document>
</xsl:template>
<xsl:template match="line">
<type-MAR>
<document>
<xsl:value-of select="substring-before(field[contains(., '+')],'+')"/>
</document>
<!-- For each document-->
<line>
<LineNumber>
<xsl:value-of select="position()"/>
</LineNumber>
<LineItem>
<xsl:value-of select="'should be fielled number of after two fields which contain symbol + '"/>
</LineItem>
</line>
<!-- For each document-->
</type-MAR>
</xsl:template>
</xsl:stylesheet>
I'm stuck, taking LineItem number after field id which contains symbol '+'. for example if I know that 'field id = 17' has + symbol, when after two rows I will have LineItem and do calculating I mean if 'field id = 17' when 'field id = 17+2', if 'field id = 16' then 'field id = 18' and so on. but now when field with symbol '+' are not defining, I can't to do it. Also if I use 'key', i want to ask how to use correct cycle "for each", to calculate number of lines. Result should be:
<document>
<type-MAR>
<document>0072972</document>
<line>
<LineNumber>1</LineNumber>
<LineItem>444432323</LineItem>
</line>
</type-MAR>
<type-MAR>
<document>0000062</document>
<line>
<LineNumber>1</LineNumber>
<LineItem>23242442</LineItem>
<LineNumber>2</LineNumber>
<LineItem>23444444</LineItem>
</line>
</type-MAR>
</document>
I solve it by self. maybe my question was not clearly.
first off all to found path to field after document number I use Following sibling. to select all needed LineItem I use "variable name", finally program looks:
<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-before(field[contains(., '+')],'+')"/>
<xsl:template match="/*">
<document>
<xsl:apply-templates select="line[contains(field[#id='0'], 'MAR')][count( . | key('kLine',substring-before(field[contains(., '+')],'+'))[1]) = 1]"/>
</document>
</xsl:template>
<xsl:template match="line">
<xsl:variable name="doc_code" select="substring-before(field[contains(., '+')],'+')"/>
<type-MAR>
<document>
<xsl:value-of select="substring-before(field[contains(., '+')],'+')"/>
</document>
<xsl:for-each select="//line[substring-before(field[contains(., '+')],'+')=$doc_code]">
<line>
<LineNumber>
<xsl:value-of select="position()"/>
</LineNumber>
<LineItem>
<xsl:value-of select="field[contains(., '+')]/following-sibling::field[2]"/>
</LineItem>
</line>
</xsl:for-each>
</type-MAR>
</xsl:template>
</xsl:stylesheet>
Best regards

Retrieve data from a different fields, where a landmark is a symbol

i have data, which looks like:
<line id="1">
<field id="1">324</field id="1">
<field id="2">abc</field id="2">
.................................
.................................
.................................
<field id="11">324321</field id="11">
<field id="12"
><![CDATA[6256812+0000140000000990000000000009900000000004058002C]]></field>
</line id="1">
<line id="2">
<field id="1">324</field id="1">
<field id="2">abc</field id="2">
.................................
.................................
.................................
<field id="8">324321</field id="11">
<field id="9"
><![CDATA[6256813+0000040000000890000000000008900000000003648002C]]></field>
</line id="2">
<line id="3">
<field id="1">324fsf</field id="1">
<field id="2">abcdf</field id="2">
.................................
.................................
.................................
<field id="12">32432s1</field id="11">
<field id="13"
><![CDATA[6256812+0000060000000750000000000007500000000003074002C]]></field>
</line id="3">
<line id="4">
<field id="1">3fsfa24</field id="1">
<field id="2">abasc</field id="2">
.................................
.................................
.................................
<field id="18">32fasf4321</field id="11">
<field id="19"
><![CDATA[6256837+0000010000000650000000000006500000000002664003C]]></field>
</line id="5">
I need to take number before landmark '+'.
<xsl:choose>
<xsl:when test="string(field[#id='9'])">
<xsl:value-of select="number(substring(field[#id='10'], 1,7))"/>
</xsl:when>
<xsl:when test="string(field[#id='11'])">
<xsl:value-of select="number(substring(field[#id='12'], 1,7))"/>
</xsl:when>
<xsl:when test="string(field[#id='17'])">
<xsl:value-of select="number(substring(field[#id='17'], 1,7))"/>
</xsl:when>
</xsl:choose>
maybe there is more right decision?
The proper XSLT way of doing this (push style processing):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="field[contains('|9|13|19|', concat('|',#id, '|'))]">
<xsl:value-of select="substring-before(.,'+')"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when this transformation is applied on the provided XML document (corrected from being severely malformed):
<t>
<line id="1">
<field id="12"><![CDATA[6256812+0000140000000990000000000009900000000004058002C]]></field>
</line>
<line id="2">
<field id="9"><![CDATA[6256813+0000040000000890000000000008900000000003648002C]]></field>
</line>
<line id="3">
<field id="13"><![CDATA[6256812+0000060000000750000000000007500000000003074002C]]></field>
</line>
<line id="4">
<field id="19"><![CDATA[6256837+0000010000000650000000000006500000000002664003C]]></field>
</line>
</t>
the wanted, correct result is produced:
6256813
6256812
6256837
Do note: There is not even a single conditional xslt instruction in the transformation.
Use the substring-before function. You can use it to get the text before the first +
I'd also recommend using a separate template for each condition:
<xsl:template match="field[#id='9']">
<xsl:value-of select="substring-before(../field[#id='10'],'+')" />
</xsl:template>
Or something similar depending on the context.
Looks like a bad, brittle design to me. If you're going to rely on positional logic to parse the record, why not abandon XML and use EDI or another record-based format?
You're not using XML for what it's good for here. Tags are metadata that describe the stream. You lost it when you went to this design.
I'd recommend either using XML in a better way or going all the way to a position-based record scheme.