how to get some documents from one xml - xslt

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.

Related

xslt 1 : check if node contains any value from list

I need to tranform a XML document from one format to a other, and that is all dandy. Now the hard part is that I need to check all lines for a status code, and need to return true/false depend if any code element have any of this codes.
<xml>
<line>
<id>1</id>
<code>109</code>
</line>
<line>
<id>2</id>
<code>065</code>
</line>
<line>
<id>3</id>
<code>405</code>
</line>
<line>
<id>4</id>
<code>101</code>
</line>
</xml>
The document I tranform to keep a copy of all line element, but have a extra field, where is set to true/false depend if the any code is in the list.
So I need to compare this list of data to every code and return true if just one of them is in the list
"101","102","103","104","105","106","107","108","109","110","111"
Is there any fix mode to make this so I don't need 11 compare stament ?
Ohh and the output look some thing like
<System>
<Route>true</Route> <!-- will be false if the <code> from the first document is not in the list of elements -->
<Status>
<ID>1</ID>
<Code>109</Code>
</Status>
<Status>
<ID>2</ID>
<Code>065</Code>
</Status>
<Status>
<ID>3</ID>
<Code>405</Code>
</Status>
<Status>
<ID>4</ID>
<Code>101</Code>
</Status>
<System>
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:param name="pCodeList" select="'101,102,103,104,105,106,107,108,109,110,111'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<System>
<Route>
<xsl:value-of select=
"boolean(/*/*/code
[contains(concat(',', $pCodeList, ','), concat(',', ., ',')
)
]
)"/>
</Route>
<xsl:apply-templates/>
</System>
</xsl:template>
<xsl:template match="line">
<Status><xsl:apply-templates/></Status>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<xml>
<line>
<id>1</id>
<code>109</code>
</line>
<line>
<id>2</id>
<code>065</code>
</line>
<line>
<id>3</id>
<code>405</code>
</line>
<line>
<id>4</id>
<code>101</code>
</line>
</xml>
produces the wanted, correct result:
<System>
<Route>true</Route>
<Status>
<id>1</id>
<code>109</code>
</Status>
<Status>
<id>2</id>
<code>065</code>
</Status>
<Status>
<id>3</id>
<code>405</code>
</Status>
<Status>
<id>4</id>
<code>101</code>
</Status>
</System>
When the same transformation (above) is applied on this XML document:
<xml>
<line>
<id>1</id>
<code>509</code>
</line>
<line>
<id>2</id>
<code>065</code>
</line>
<line>
<id>3</id>
<code>405</code>
</line>
<line>
<id>4</id>
<code>501</code>
</line>
</xml>
it again produces the wanted, correct result:
<System>
<Route>false</Route>
<Status>
<id>1</id>
<code>509</code>
</Status>
<Status>
<id>2</id>
<code>065</code>
</Status>
<Status>
<id>3</id>
<code>405</code>
</Status>
<Status>
<id>4</id>
<code>501</code>
</Status>
</System>

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 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 group at more than one level?

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.

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.