Convert xml structure to another xml structure using xslt

I have a question. I have the following xml source file:

Xml source:

<Container> <DataHeader> <c id="b" value="TAG" /> <c id="g" value="Info" /> </DataHeader> <Data> <Rows> <r no="1"> <c id="b" value="uid1" uid="TAuid1" /> <c id="g" value="uid1|tag1|attr1|somevalue1" /> </r> <r no="1"> <c id="b" value="uid1" uid="TAuid1" /> <c id="g" value="uid1|tag1|attr2|somevalue2" /> </r> <r no="2"> <c id="b" value="uid1" uid="TAuid1" /> <c id="g" value="uid1|tag2|attr3|somevalue3" /> </r> <r no="10"> <c id="b" value="uid2" uid="TAuid2" /> <c id="g" value="uid2|tag1|attr1|somevalue4" /> </r> <r no="11"> <c id="b" value="uid2" uid="TAuid2" /> <c id="g" value="uid2|tag2|attr3|somevalue5" /> </r> </Rows> </Data> </Container> 

The ' c ' element with id ' g ' is important in the original xml. This is a concatenated string whose values ​​are separated by the " | " character. We need these values ​​to make the target xml. The c 'element with id' b ', which you can use to separate uid .

example and expression 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" must be combined into one single TestTag element (see target xml). Al attributes ( attr1, attr2 ) with the same parent element (e.g. tag1 ') must be added to 1 element. I can use xslt (xpath) 1.0.

The target xml file should look like this after conversion.

Target xml after conversion using xsl:

 <Container> <TestTag> <object UID="TAuid1" Name="uid1"/> <tag1 attr1="somevalue1" attr2="somevalue2"/> <tag2 attr3="*somevalue3"/> </TestTag> <TestTag> <Iobject UID="TAuid2" Name="uid2"/> <tag1 attr1="somevalue4" /> <tag2 attr3="somevalue5"/> </TestTag> </Container> 

What are the possible solutions to convert source xml to target xml? I tried a few things, but now I'm stuck.

+4
source share
1 answer

This is not entirely complicated, but staggering due to the extensive (but 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 the key to kTagByName --> <xsl:variable name="key" select="substring-before(@value, substring-after(substring-after(@value, '|'), '|'))" /> <!-- select 'tag1' - it 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 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="TAuid1" name="uid1" /> <tag1 attr1="somevalue1" attr2="somevalue2"></tag1> <tag2 attr3="somevalue3"></tag2> </TestTag> <TestTag> <object UID="TAuid2" name="uid2" /> <tag1 attr1="somevalue4"></tag1> <tag2 attr3="somevalue5"></tag2> </TestTag> </Container> 

Note that this does not take into account duplicate attribute definitions. If you have uid1|tag1|attr1|somevalue1 and later uid1|tag1|attr1|othervalue1 , then you will get one attribute: attr1="othervalue1" , because in <xsl:for-each> both will receive a turn, and the second wins (i.e. ends at the exit).

Perhaps for this, it will require another key and another Muenchian group, I am going to leave this as an exercise for the reader. Heh.;)

+2
source

Source: https://habr.com/ru/post/1301584/


All Articles