Reputation: 3397
Given the following XML document:
<foo>
<bar>
<type>1</type>
<id>1</id>
<a1>0</a1>
<other_stuff/>
</bar>
<bar>
<type>1</type>
<id>1</id>
<a1>0</a1>
<other_stuff/>
</bar>
<bar>
<type>1</type>
<id>2</id>
<a1>0</a1>
<other_stuff/>
</bar>
<bar>
<type>1</type>
<id>2</id>
<a1>0</a1>
<other_stuff/>
</bar>
</foo>
The XML elements bar
are list elements, logically grouped by type and id. The sample document therefore contains two ordered lists (type=1 and id=1, and type=1 and id=2) with two elements each. In the real document there are many more lists (using different types and ids) of various length.
Now I need to update the first element a1
of each list with a different value, resulting e.g. in the following document:
<foo>
<bar>
<type>1</type>
<id>1</id>
<a1>-100</a1>
<other_stuff/>
</bar>
<bar>
<type>1</type>
<id>1</id>
<a1>0</a1>
<other_stuff/>
</bar>
<bar>
<type>1</type>
<id>2</id>
<a1>-100</a1>
<other_stuff/>
</bar>
<bar>
<type>1</type>
<id>2</id>
<a1>0</a1>
<other_stuff/>
</bar>
</foo>
In pseudo SQL this would likely look like this:
update bar set a1 = -100 where position() = 1 group by type, id
Is this possible with XSLT 1.0? I think it boils down to being able to write an XPath expression giving the same result as my pseudo SQL statement.
Upvotes: 0
Views: 765
Reputation: 167716
Use Muenchian grouping to identify the first item in each group and change the child, copy the rest with the identity transformation:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="new-value" select="-100"/>
<xsl:key name="group" match="bar" use="concat(type, '|', id)"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="bar[generate-id() = generate-id(key('group', concat(type, '|', id))[1])]/a1">
<xsl:copy>
<xsl:value-of select="$new-value"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
As for further explanation, the key definition <xsl:key name="group" match="bar" use="concat(type, '|', id)"/>
lets the XSLT processor index the bar
elements on the concat(type, '|', id)
so that we can use the key function with e.g. key('group', '1|1')
to find all the bar
elements with that value for concat(type, '|', id)
.
You only want to change the first bar
element in each group so we need to have a way to identify that first item inside of a predicate to bar[...]
where we need to find a pattern that expresses the condition
bar[. is the first item in its group]
The group we get inside the pattern with key('group', concat(type, '|', id))
, so we want to write a condition
bar[. is the first item in key('group', concat(type, '|', id))]
which we can express nearly literally in XSLT 2.0 with the is
operator and the pattern
bar[. is key('group', concat(type, '|', id))[1]]
With XSLT 1.0 we can express it using generate-id, as done in
bar[generate-id() = generate-id(key('group', concat(type, '|', id))[1])]
which selects/matches those bar
element for which the generated id is equal to the generated id of the first ([1]
) item in the group computed by key('group', concat(type, '|', id))
.
Upvotes: 2