Transform xml structure to another xml structure with xslt

匿名 (未验证) 提交于 2019-12-03 08:48:34

问题:

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. This 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.

回答1:

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. ;)



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!