Transform xml structure to another xml structure with xslt
I have a question. I have the following source xml file:
Source xml:
<Container>
<DataHeader>
<c id="b" value="TAG" />
<c id="g" value="Info" />
</DataHeader>
<Data>
<Rows>
<r no="1">
<c id="b" value="uid1" uid="T.A.uid1" />
<c id="g" value="uid1|tag1|attr1|somevalue1" />
</r>
<r no="1">
<c id="b" value="uid1" uid="T.A.uid1" />
<c id="g" value="uid1|tag1|attr2|somevalue2" />
</r>
<r no="2">
<c id="b" value="uid1" uid="T.A.uid1" />
<c id="g" value="uid1|tag2|attr3|somevalue3" />
</r>
<r no="10">
<c id="b" value="uid2" uid="T.A.uid2" />
<c id="g" value="uid2|tag1|attr1|somevalue4" />
</r>
<r no="11">
<c id="b" value="uid2" uid="T.A.uid2" />
<c id="g" value="uid2|tag2|attr3|somevalue5" />
</r>
</Rows>
</Data>
</Container>
The element 'c' with id 'g' is important in the source xml. Thi开发者_开发百科s is a concatened string which values are seperated by a '|'. We need this values to make the target xml. The element 'c' with id 'b' you can use to separate the 'uid'.
example and explantion of values:
<c id="g" value="uid1|tag1|attr1|somevalue1" />
**uid value** | element node | **attribute** | attribute value
**uid1** | tag1 | **attr1** |somevalue1
Al elements with the same 'uid' have to be aggregated into 1 single "TestTag" element (see target xml). Al attributes (attr1, attr2) with same parent element (for example 'tag1') needs to be added to 1 element. I only can make use of xslt(xpath) 1.0.
The target xml file should look like this after transforming.
Target xml after transformed by xsl:
<Container>
<TestTag>
<object UID="T.A.uid1" Name="uid1"/>
<tag1 attr1="somevalue1" attr2="somevalue2"/>
<tag2 attr3="*somevalue3"/>
</TestTag>
<TestTag>
<Iobject UID="T.A.uid2" Name="uid2"/>
<tag1 attr1="somevalue4" />
<tag2 attr3="somevalue5"/>
</TestTag>
</Container>
What are possible solutions for transforming source xml to target xml? I tried several things but I'm stuck right now.
This is not exactly difficult, but is mind-boggling due to extensive (yet necessary) nested use of substring-before()
and substring-after()
.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<!-- index <c> nodes by their @id + "uid value" -->
<xsl:key name="kObject" match="r/c" use="
concat(@id, '|', @value)
" />
<!-- index <c> nodes by their @id + "uid value" -->
<xsl:key name="kTagByUid" match="r/c" use="
concat(@id, '|', substring-before(@value, '|'))
" />
<!-- index <c> nodes by their @id + "uid value" + "tag name" -->
<xsl:key name="kTagByName" match="r/c" use="
concat(@id, '|',
substring-before(
@value,
substring-after(substring-after(@value, '|'), '|')
)
)
" />
<xsl:variable name="vTagId" select="/Container/DataHeader/c[@value='TAG'][1]/@id" />
<xsl:variable name="vInfoId" select="/Container/DataHeader/c[@value='Info'][1]/@id" />
<!-- processing starts here -->
<xsl:template match="Container">
<xsl:copy>
<!-- apply templates to unique <c @id=$vTagId> tags -->
<xsl:apply-templates mode="tag" select="
Data/Rows/r/c[@id=$vTagId][
generate-id()
=
generate-id(key('kObject', concat(@id, '|', @value))[1])
]
" />
</xsl:copy>
</xsl:template>
<xsl:template match="c" mode="tag">
<TestTag>
<object UID="{@uid}" name="{@value}" />
<!-- apply templates to unique <c @id="g"> tags -->
<xsl:apply-templates mode="info" select="
key('kTagByUid', concat($vInfoId, '|', @value))[
generate-id()
=
generate-id(
key(
'kTagByName',
concat(@id, '|',
substring-before(
@value,
substring-after(substring-after(@value, '|'), '|')
)
)
)[1]
)
]
" />
</TestTag>
</xsl:template>
<xsl:template match="c" mode="info">
<!-- select 'uid1|tag1|' - it's the key to kTagByName -->
<xsl:variable name="key" select="substring-before(@value, substring-after(substring-after(@value, '|'), '|'))" />
<!-- select 'tag1' - it's the element name -->
<xsl:variable name="name" select="substring-before(substring-after($key, '|'), '|')" />
<xsl:element name="{$name}">
<xsl:for-each select="key('kTagByName', concat(@id, '|', $key))">
<!-- select 'attr1|somevalue1' - it's the attribute definition -->
<xsl:variable name="attrDef" select="substring-after(@value, $key)" />
<!-- create an attribute -->
<xsl:attribute name="{substring-before($attrDef, '|')}">
<xsl:value-of select="substring-after($attrDef, '|')" />
</xsl:attribute>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
generates:
<Container>
<TestTag>
<object UID="T.A.uid1" name="uid1" />
<tag1 attr1="somevalue1" attr2="somevalue2"></tag1>
<tag2 attr3="somevalue3"></tag2>
</TestTag>
<TestTag>
<object UID="T.A.uid2" name="uid2" />
<tag1 attr1="somevalue4"></tag1>
<tag2 attr3="somevalue5"></tag2>
</TestTag>
</Container>
Note that this does not pay attention to duplicate attribute definitions. If you happen to have uid1|tag1|attr1|somevalue1
and later uid1|tag1|attr1|othervalue1
, then you will end up with one attribute: attr1="othervalue1"
because in the <xsl:for-each>
both get their turn, and the latter one wins (i.e. ends up in the output).
It is possible to cater for that as well, it would require one more key and one more Muenchian grouping, I'm going to leave that as an exercise for the reader. Heh. ;)
精彩评论