Multiple lxml trees in one document - lxml

I understand each XML tree is created by parsing a document that starts with:
<?xml version="1.0" encoding="UTF-8"?>
In some of XML files I encounter, there are multiple chunks sitting within the same .xml file, like this:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<country name="Liechtenstein" xmlns="aaa:bbb:ccc:liechtenstein:eee">
<rank updated="yes">2</rank>
<holidays>
<christmas>Yes</christmas>
</holidays>
</country>
<country name="Singapore" xmlns="aaa:bbb:ccc:singapore:eee">
<continent>Asia</continent>
<holidays>
<christmas>Yes</christmas>
</holidays>
</country>
<country name="Panama" xmlns="aaa:bbb:ccc:panama:eee">
<rank updated="yes">69</rank>
<year>2011</year>
<gdppc>13600</gdppc>
</country>
<ethnicity xmlns="aaa:bbb:ccc:ethnicity:eee">
</ethnicity>
</data>
<?xml version="1.0" encoding="UTF-8"?>
<data>
<country name="Liechtenstein" xmlns="aaa:bbb:ccc:liechtenstein:eee">
<rank updated="yes">2</rank>
<holidays>
<christmas>Yes</christmas>
</holidays>
</country>
<country name="Singapore" xmlns="aaa:bbb:ccc:singapore:eee">
<continent>Asia</continent>
<holidays>
<christmas>Yes</christmas>
</holidays>
</country>
<country name="Panama" xmlns="aaa:bbb:ccc:panama:eee">
<rank updated="yes">69</rank>
<year>2011</year>
<gdppc>13600</gdppc>
</country>
<ethnicity xmlns="aaa:bbb:ccc:ethnicity:eee">
</ethnicity>
</data>
<?xml version="1.0" encoding="UTF-8"?>
<data>
<country name="Liechtenstein" xmlns="aaa:bbb:ccc:liechtenstein:eee">
<rank updated="yes">2</rank>
<holidays>
<christmas>Yes</christmas>
</holidays>
</country>
<country name="Singapore" xmlns="aaa:bbb:ccc:singapore:eee">
<continent>Asia</continent>
<holidays>
<christmas>Yes</christmas>
</holidays>
</country>
<country name="Panama" xmlns="aaa:bbb:ccc:panama:eee">
<rank updated="yes">69</rank>
<year>2011</year>
<gdppc>13600</gdppc>
</country>
<ethnicity xmlns="aaa:bbb:ccc:ethnicity:eee">
</ethnicity>
</data>
In this example, there are 3 chunks, which are typically 3 lxml trees. But for big files with unknown number of chunks, how to parse in this situation and what would be the root node? The intention is to deal with each chunk as a separate tree.

Related

How to group two siblings with consecutive days in XSLT

I have a requirement to group consecutive days into 1 for the matching ID and am not able to figure out the solution, can someone please guide me on how to approach it.
Here are my input and expected output.
<?xml version='1.0' encoding='UTF-8'?>
<Member>
<Data>
<Id>X0001</Id>
<Date>2022-01-01</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-02</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-03</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-04</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-05</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-08</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-12</Date>
</Data>
<Data>
<Id>X0004</Id>
<Date>2022-01-09</Date>
</Data>
<Data>
<Id>X0005</Id>
<Date>2022-01-10</Date>
</Data>
<Data>
<Id>X0005</Id>
<Date>2022-01-11</Date>
</Data>
</Member>
OUTPUT
----
Out>
<Member>
<Id>X0001</Id
<SDate>2022-01-01</SDate>
<EDate>2022-01-02</EDate
</Member>
<Member>
<Id>X0001</Id
<SDate>2022-01-03</SDate>
<EDate>2022-01-04</EDate
</Member>
<Member>
<Id>X0001</Id
<SDate>2022-01-05</SDate>
<EDate>2022-01-05</EDate
</Member>
<Member>
<Id>X0001</Id
<SDate>2022-01-08</SDate>
<EDate>2022-01-08</EDate
</Member>
<Member>
<Id>X0001</Id
<SDate>2022-01-12</SDate>
<EDate>2022-01-12</EDate
</Member>
<Member>
<Id>X0004</Id
<SDate>2022-01-09</SDate>
<EDate>2022-01-09</EDate
</Member>
<Member>
<Id>X0005</Id
<SDate>2022-01-10</SDate>
<EDate>2022-01-11</EDate
</Member>
</Out>
I need to group if days are consecutive for each ID, if there is no following day then day should be left as is.
Here is one way you could look at it, I think:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Member">
<Out>
<!-- group by id -->
<xsl:for-each-group select="Data" group-by="Id">
<!-- group contiguous ranges -->
<xsl:for-each-group select="current-group()" group-starting-with="Data[not(xs:date(Date) = xs:date(preceding-sibling::Data[1]/Date) + xs:dayTimeDuration('P1D'))]">
<!-- divide into pairs -->
<xsl:for-each-group select="current-group()" group-by="(position()-1) idiv 2">
<Member>
<xsl:copy-of select="Id"/>
<SDate>
<xsl:value-of select="Date"/>
</SDate>
<EDate>
<xsl:value-of select="current-group()[last()]/Date"/>
</EDate>
</Member>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:for-each-group>
</Out>
</xsl:template>
</xsl:stylesheet>
Note that this assumes that within each Id records are already sorted chronologically.

xslt 1.0 combinate two segments

how to combinate two or more segments to one segment at xslt 1.0?
I have two cases.
Case1:
If "QUALIFIER" at GRP/TXT is the same (for example: AAA) combinate this to one.
Correct:
QUALIFIER: AAA
TEXT: Test AAA rtetertertret
Case2:
Same should be at GRP/ITEM/TXT (for example: LIN)
Correct:
QUALIFIER: LIN
TEXT: Test LIN sdfsdfsfsf
<?xml version="1.0"?>
<SEEDELFOR>
<Test/>
<CNT>
<TRANSMISSION_DATE></TRANSMISSION_DATE>
<TRANSMISSION_TIME></TRANSMISSION_TIME>
<INTERCHANGE_CONTROL_NUMBER></INTERCHANGE_CONTROL_NUMBER>
<SENDER></SENDER>
<SENDER_QUALIFIER></SENDER_QUALIFIER>
<RECEIVER></RECEIVER>
<RECEIVER_QUALIFIER></RECEIVER_QUALIFIER>
<SYNTAX_IDENTIFIER></SYNTAX_IDENTIFIER>
<SYNTAX_VERSION></SYNTAX_VERSION>
<BGM></BGM>
<GRP>
<IDENTIFIER_BY></IDENTIFIER_BY>
<IDENTIFIER_SU></IDENTIFIER_SU>
<DATE_4></DATE_4>
<REF_ON></REF_ON>
<ADD>
<QUALIFIER></QUALIFIER>
<IDENTIFIER></IDENTIFIER>
<AGENCY_CODE></AGENCY_CODE>
<CONTACT>
<QUALIFIER></QUALIFIER>
<NUMBER></NUMBER>
</CONTACT>
<CONTACT>
<QUALIFIER></QUALIFIER>
<NUMBER></NUMBER>
</CONTACT>
<CONTACT>
<QUALIFIER></QUALIFIER>
<NUMBER></NUMBER>
</CONTACT>
</ADD>
<ADD>
<QUALIFIER></QUALIFIER>
<IDENTIFIER></IDENTIFIER>
<AGENCY_CODE></AGENCY_CODE>
</ADD>
<TXT>
<QUALIFIER>AAA</QUALIFIER>
<TEXT>Test AAA</TEXT>
</TXT>
<TXT>
<QUALIFIER>AAA</QUALIFIER>
<TEXT>rtetertertret</TEXT>
</TXT>
<TRANSPORT_DETAILS>
<ADDITIONAL_DETAILS>
<QUALIFIER></QUALIFIER>
<DETAILS></DETAILS>
</ADDITIONAL_DETAILS>
</TRANSPORT_DETAILS>
<TRANSPORT_DETAILS>
<ADDITIONAL_DETAILS>
<QUALIFIER></QUALIFIER>
<DETAILS></DETAILS>
</ADDITIONAL_DETAILS>
</TRANSPORT_DETAILS>
<TRANSPORT_DETAILS>
<ADDITIONAL_DETAILS>
<QUALIFIER></QUALIFIER>
<DETAILS></DETAILS>
</ADDITIONAL_DETAILS>
</TRANSPORT_DETAILS>
<ITEM>
<ITEM_NUMBER_SA></ITEM_NUMBER_SA>
<QUANTITY></QUANTITY>
<QUANTITY_UNIT></QUANTITY_UNIT>
<LINE_ITEM_NUMBER>2</LINE_ITEM_NUMBER>
<TXT>
<QUALIFIER>LIN</QUALIFIER>
<TEXT>Test LIN</TEXT>
</TXT>
</ITEM>
<ITEM>
<ITEM_NUMBER_SA></ITEM_NUMBER_SA>
<QUANTITY></QUANTITY>
<QUANTITY_UNIT></QUANTITY_UNIT>
<LINE_ITEM_NUMBER>1</LINE_ITEM_NUMBER>
<TXT>
<QUALIFIER>LIN</QUALIFIER>
<TEXT>Test LIN</TEXT>
</TXT>
<TXT>
<QUALIFIER>LIN</QUALIFIER>
<TEXT>sdfsdfsfsf</TEXT>
</TXT>
</ITEM>
</GRP>
</CNT>
</SEEDELFOR>
Correct output should be:
<?xml version="1.0"?>
<SEEDELFOR>
<Test/>
<CNT>
<TRANSMISSION_DATE></TRANSMISSION_DATE>
<TRANSMISSION_TIME></TRANSMISSION_TIME>
<INTERCHANGE_CONTROL_NUMBER></INTERCHANGE_CONTROL_NUMBER>
<SENDER></SENDER>
<SENDER_QUALIFIER></SENDER_QUALIFIER>
<RECEIVER></RECEIVER>
<RECEIVER_QUALIFIER></RECEIVER_QUALIFIER>
<SYNTAX_IDENTIFIER></SYNTAX_IDENTIFIER>
<SYNTAX_VERSION></SYNTAX_VERSION>
<BGM></BGM>
<GRP>
<IDENTIFIER_BY></IDENTIFIER_BY>
<IDENTIFIER_SU></IDENTIFIER_SU>
<DATE_4></DATE_4>
<REF_ON></REF_ON>
<ADD>
<QUALIFIER></QUALIFIER>
<IDENTIFIER></IDENTIFIER>
<AGENCY_CODE></AGENCY_CODE>
<CONTACT>
<QUALIFIER></QUALIFIER>
<NUMBER></NUMBER>
</CONTACT>
<CONTACT>
<QUALIFIER></QUALIFIER>
<NUMBER></NUMBER>
</CONTACT>
<CONTACT>
<QUALIFIER></QUALIFIER>
<NUMBER></NUMBER>
</CONTACT>
</ADD>
<ADD>
<QUALIFIER></QUALIFIER>
<IDENTIFIER></IDENTIFIER>
<AGENCY_CODE></AGENCY_CODE>
</ADD>
<TXT>
<QUALIFIER>AAA</QUALIFIER>
<TEXT>Test AAA rtetertertret</TEXT>
</TXT>
<TRANSPORT_DETAILS>
<ADDITIONAL_DETAILS>
<QUALIFIER></QUALIFIER>
<DETAILS></DETAILS>
</ADDITIONAL_DETAILS>
</TRANSPORT_DETAILS>
<TRANSPORT_DETAILS>
<ADDITIONAL_DETAILS>
<QUALIFIER></QUALIFIER>
<DETAILS></DETAILS>
</ADDITIONAL_DETAILS>
</TRANSPORT_DETAILS>
<TRANSPORT_DETAILS>
<ADDITIONAL_DETAILS>
<QUALIFIER></QUALIFIER>
<DETAILS></DETAILS>
</ADDITIONAL_DETAILS>
</TRANSPORT_DETAILS>
<ITEM>
<ITEM_NUMBER_SA></ITEM_NUMBER_SA>
<QUANTITY></QUANTITY>
<QUANTITY_UNIT></QUANTITY_UNIT>
<LINE_ITEM_NUMBER>2</LINE_ITEM_NUMBER>
<TXT>
<QUALIFIER>LIN</QUALIFIER>
<TEXT>Test LIN</TEXT>
</TXT>
</ITEM>
<ITEM>
<ITEM_NUMBER_SA></ITEM_NUMBER_SA>
<QUANTITY></QUANTITY>
<QUANTITY_UNIT></QUANTITY_UNIT>
<LINE_ITEM_NUMBER>1</LINE_ITEM_NUMBER>
<TXT>
<QUALIFIER>LIN</QUALIFIER>
<TEXT>Test LIN sdfsdfsfsf</TEXT>
</TXT>
</ITEM>
</GRP>
</CNT>
</SEEDELFOR>
Best regards
Julian
It can be achievable as following in XSLT 1.0:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="qualifierKey" match="/SEEDELFOR/CNT/GRP//TXT" use="QUALIFIER" />
<xsl:key name="qualifierTextKey" match="/SEEDELFOR/CNT/GRP//TXT" use="concat(QUALIFIER, '|', TEXT)" />
<xsl:key name="itemTxtKey" match="/SEEDELFOR/CNT/GRP/ITEM/TXT" use="concat(generate-id(parent::*), QUALIFIER, '|', TEXT)" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<xsl:template match="TXT[position() > 1]" />
<xsl:template match="/SEEDELFOR/CNT/GRP/ITEM/TXT[following-sibling::*[1]][generate-id(.) = generate-id(key('itemTxtKey', concat(generate-id(parent::*), QUALIFIER, '|', TEXT))[1])]
| /SEEDELFOR/CNT/GRP/TXT[following-sibling::*[1]][generate-id() = generate-id(key('qualifierKey',QUALIFIER)[1])]">
<TXT>
<QUALIFIER>
<xsl:value-of select="normalize-space(QUALIFIER)" />
</QUALIFIER>
<TEXT>
<xsl:variable name="count" select="count(key('qualifierKey',QUALIFIER)[generate-id() = generate-id(key('qualifierTextKey', concat(QUALIFIER, '|', TEXT))[1])])" />
<xsl:for-each select="key('qualifierKey',QUALIFIER)[generate-id() = generate-id(key('qualifierTextKey', concat(QUALIFIER, '|', TEXT))[1])]">
<xsl:value-of select="normalize-space(TEXT)" />
<xsl:if test="$count != position()"><xsl:value-of select="' '"></xsl:value-of></xsl:if>
</xsl:for-each>
</TEXT>
</TXT>
</xsl:template>
</xsl:stylesheet>
See output here: http://xsltfiddle.liberty-development.net/jyRYYib/2

Use values of the elements as XPath expression to extract another elements using XSLT 1.0

Suppose there is such a document:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<paths>
<item>
<name>username</name>
<path>user/name</path>
</item>
<item>
<name>useremail</name>
<path>concat(user/name,': ',user/email)</path>
</item>
</paths>
<user>
<name>John</name>
<email>john#gmail.com</email>
</user>
</root>
the output required to obtain such a document:
<?xml version="1.0" encoding="utf-8"?>
<results>
<item name="username" value="John"/>
<item name="useremail" value="John: john#gmail.com"/>
</results>
The point is that the number of path elements can vary, and their values can refer to different elements of the source document (including using functions).
Is there a way to use the values of some elements as an expression of the search for other?

How to return a list of objects from soap webservice with rails

I'm using Savon to access a soap webservice, but I can only ever return a single result, when what I want is an array of results.
Here is my call:
response = client.call(:get_events, message: { username: "xxxx", password: "xxxxxxxx", company_code: "12TCE" })
I want 'response' to return all records with the company_code of "12TCE" and for me to be able to output them all doing something like:
response.to_hash[:get_events_response].each do |a|
a[:return][:item][:name]
end
How can I return all records and output as desired?
UPDATE 1:
This is the link to the wsdl: http://www.brrmedia.co.uk/webservices/event/index.php?wsdl
This is the response I get:
HTTPI POST request to www.brrmedia.co.uk (httpclient)
SOAP response (status 200)
<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://www.brrmedia.co.uk/webservices/event" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:getEventsResponse xmlns:ns1="http://www.brrmedia.co.uk/webservices/event">
<return xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="tns:objEvent[1]">
<item xsi:type="tns:objEvent">
<id xsi:type="xsd:int">119466</id>
<name xsi:type="xsd:string">blur Group - 2000 projects milestone</name>
<summary xsi:type="xsd:string"/>
<location xsi:type="xsd:string"/>
<date xsi:type="xsd:string">2013-12-17 11:30</date>
<link xsi:type="xsd:string">http://www.brrmedia.co.uk/event/119466/partner/brrsoap</link>
<company xsi:type="tns:objCompany">
<name xsi:type="xsd:string">blur Group</name>
<codes xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="tns:objCompanyCode[1]">
<item xsi:type="tns:objCompanyCode">
<code xsi:type="xsd:string">BLUR</code>
<collection xsi:type="xsd:string">London Stock Exchange (AIM)</collection>
</item>
</codes>
<website xsi:type="xsd:string">http://www.blurgroup.com/</website>
<category xsi:type="xsd:string">Technology</category>
</company>
<presenter xsi:type="tns:objPresenter">
<name xsi:type="xsd:string"> Philip Letts</name>
<image xsi:type="xsd:string">http://www.brrmedia.co.uk/getimage/id/31215</image>
</presenter>
<media xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="tns:objMediaItem[3]">
<item xsi:type="tns:objMediaItem">
<src xsi:type="xsd:string">http://s3-us-west-2.amazonaws.com/brr-streamguys/files/BLUR/blur20131217.pdf</src>
<duration xsi:type="xsd:string">00:00:00</duration>
<filesize xsi:type="xsd:string">380</filesize>
<media_type xsi:type="xsd:string">pdf</media_type>
</item>
<item xsi:type="tns:objMediaItem">
<src xsi:type="xsd:string">http://s3-us-west-2.amazonaws.com/brr-streamguys/files/BLUR/BLUR20131217</src>
<duration xsi:nil="true" xsi:type="xsd:string"/>
<filesize xsi:nil="true" xsi:type="xsd:string"/>
<media_type xsi:type="xsd:string">presimages</media_type>
</item>
<item xsi:type="tns:objMediaItem">
<src xsi:type="xsd:string">http://s3-us-west-2.amazonaws.com/brr-streamguys/files/BLUR/BLUR20131217editv1.mp3</src>
<duration xsi:type="xsd:string">00:07:54</duration>
<filesize xsi:type="xsd:string">5557</filesize>
<media_type xsi:type="xsd:string">audio</media_type>
</item>
</media>
<type xsi:type="xsd:string">audio</type>
<height xsi:type="xsd:int">900</height>
<width xsi:type="xsd:int">680</width>
</item>
</return>
</ns1:getEventsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Dynamic parameters for XSLT 2.0 group-by

I got this input
<?xml version="1.0" encoding="UTF-8"?>
<result>
<datapoint poiid="2492" period="2004" value="1240"/>
<datapoint poiid="2492" period="2005" value="1290"/>
<datapoint poiid="2492" period="2006" value="1280"/>
<datapoint poiid="2492" period="2007" value="1320"/>
<datapoint poiid="2492" period="2008" value="1330"/>
<datapoint poiid="2492" period="2009" value="1340"/>
<datapoint poiid="2492" period="2010" value="1340"/>
<datapoint poiid="2492" period="2011" value="1335"/>
<datapoint poiid="2493" period="2004" value="1120"/>
<datapoint poiid="2493" period="2005" value="1120"/>
<datapoint poiid="2493" period="2006" value="1100"/>
<datapoint poiid="2493" period="2007" value="1100"/>
<datapoint poiid="2493" period="2008" value="1100"/>
<datapoint poiid="2493" period="2009" value="1110"/>
<datapoint poiid="2493" period="2010" value="1105"/>
<datapoint poiid="2493" period="2011" value="1105"/>
</result>
and I use this xslt 2.0
<?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="xs" version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="result">
<xsl:for-each-group select="datapoint" group-by="#poiid">
<node type="poiid" id="{#poiid}">
<xsl:for-each select="current-group()">
<node type="period" id="{#period}" value="{#value}"/>
</xsl:for-each>
</node>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
to convert it into
<?xml version="1.0" encoding="UTF-8"?>
<node type="poiid" id="2492">
<node type="period" id="2004" value="1240"/>
<node type="period" id="2005" value="1290"/>
<node type="period" id="2006" value="1280"/>
<node type="period" id="2007" value="1320"/>
<node type="period" id="2008" value="1330"/>
<node type="period" id="2009" value="1340"/>
<node type="period" id="2010" value="1340"/>
<node type="period" id="2011" value="1335"/>
</node>
<node type="poiid" id="2493">
<node type="period" id="2004" value="1120"/>
<node type="period" id="2005" value="1120"/>
<node type="period" id="2006" value="1100"/>
<node type="period" id="2007" value="1100"/>
<node type="period" id="2008" value="1100"/>
<node type="period" id="2009" value="1110"/>
<node type="period" id="2010" value="1105"/>
<node type="period" id="2011" value="1105"/>
</node>
Works smoothly.
Where I got stuck is when I tried to make it more dynamic. The real life input has 6 attributes for each datapoint instead of 3, and the usecase requires the possibility to set the grouping parameters dynamically.
I tried using parameters
<xsl:param name="k1" select="'poiid'"/>
<xsl:param name="k2" select="'period'"/>
but passing them to the rest of the xslt is something that I can't get right. The code below doesn't work, but clarifies hopefully, what I'm looking for.
<xsl:template match="result">
<xsl:for-each-group select="datapoint" group-by="#{$k1}">
<node type="{$k1}" id="#{$k1}">
<xsl:for-each select="current-group()">
<node type="{$k2}" id="#{$k2}" value="{#value}"/>
</xsl:for-each>
</node>
</xsl:for-each-group>
</xsl:template>
Any help appreciated..
Here is how to do it:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="k1" select="'poiid'"/>
<xsl:param name="k2" select="'period'"/>
<xsl:template match="result">
<xsl:for-each-group select="datapoint" group-by="#*[name()= $k1]">
<node type="{$k1}" id="{#*[name() = $k1]}">
<xsl:for-each select="current-group()">
<node type="{k2}" id="{#*[name()= $k2]}" value="{#value}"/>
</xsl:for-each>
</node>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<result>
<datapoint poiid="2492" period="2004" value="1240"/>
<datapoint poiid="2492" period="2005" value="1290"/>
<datapoint poiid="2492" period="2006" value="1280"/>
<datapoint poiid="2492" period="2007" value="1320"/>
<datapoint poiid="2492" period="2008" value="1330"/>
<datapoint poiid="2492" period="2009" value="1340"/>
<datapoint poiid="2492" period="2010" value="1340"/>
<datapoint poiid="2492" period="2011" value="1335"/>
<datapoint poiid="2493" period="2004" value="1120"/>
<datapoint poiid="2493" period="2005" value="1120"/>
<datapoint poiid="2493" period="2006" value="1100"/>
<datapoint poiid="2493" period="2007" value="1100"/>
<datapoint poiid="2493" period="2008" value="1100"/>
<datapoint poiid="2493" period="2009" value="1110"/>
<datapoint poiid="2493" period="2010" value="1105"/>
<datapoint poiid="2493" period="2011" value="1105"/>
</result>
the wanted, correct result is produced:
<node type="poiid" id="2492">
<node type="" id="2004" value="1240"/>
<node type="" id="2005" value="1290"/>
<node type="" id="2006" value="1280"/>
<node type="" id="2007" value="1320"/>
<node type="" id="2008" value="1330"/>
<node type="" id="2009" value="1340"/>
<node type="" id="2010" value="1340"/>
<node type="" id="2011" value="1335"/>
</node>
<node type="poiid" id="2493">
<node type="" id="2004" value="1120"/>
<node type="" id="2005" value="1120"/>
<node type="" id="2006" value="1100"/>
<node type="" id="2007" value="1100"/>
<node type="" id="2008" value="1100"/>
<node type="" id="2009" value="1110"/>
<node type="" id="2010" value="1105"/>
<node type="" id="2011" value="1105"/>
</node>