Search code examples
xmlxslt

How to combine this child element together using XSLT?


If I have this input:

<root> 
    <library id="L1">
        <shelf1 id="1">
            <book id="1" category="science">
                <attributes>                    
                    <year>2000</year>
                </attributes>
                <attributes>
                    <author>xx</author>
                    <year>2010</year>
                    <isbn>001</isbn>
                </attributes>
                <attributes>
                    <author>yy</author>
                    <publisher>zz</publisher>
                    <isbn>002</isbn>
                </attributes>
                <other>y</other>
            </book>  
            <book id="2" category="science">
                ...
            </book>
        </shelf1>
        
        <shelf2>...</shelf2>
    </library>
</root>

expetced output:

<root> 
    <library id="L1">
        <shelf1 id="1">
            <book id="1" category="science">
                <attributes>
                    <author>yy</author>                    
                    <publisher>zz</publisher>
                    <isbn>002</isbn>
                    <year>2010</year>
                </attributes>
                <other>y</other>
            </book>  
            <book id="2" category="science">
                ...
            </book>
        </shelf1>
        
        <shelf2>...</shelf2>
    </library>
</root>

I need to combine the element 'attributes' together. If two or more attributes exist, when the child of the attributes never exist before, we keep the child as new information, but if it exists previously we simply use the latest value.

How to do such transformation in XSLT 1.0 or 2.0?


Solution

  • This transformation (XSLT 2.0):

    <xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
    
     <xsl:template match="node()|@*">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
     </xsl:template>
    
     <xsl:template match="book[attributes]">
      <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <attributes>
          <xsl:for-each-group select="attributes/*" group-by="name()">
            <xsl:sort select="current-grouping-key()"/>
            <xsl:apply-templates select="."/>
          </xsl:for-each-group>
        </attributes>
        <xsl:apply-templates select="*[not(self::attributes)]"/>
      </xsl:copy>
     </xsl:template>
    </xsl:stylesheet>
    

    when applied on the provided XML document:

    <root>
        <library id="L1">
            <shelf1 id="1">
                <book id="1" category="science">
                    <attributes>
                        <year>2000</year>
                    </attributes>
                    <attributes>
                        <author>xx</author>
                        <year>2010</year>
                        <isbn>001</isbn>
                    </attributes>
                    <attributes>
                        <author>yy</author>
                        <publisher>zz</publisher>
                        <isbn>002</isbn>
                    </attributes>
                    <other>y</other>
                </book>
                <book id="2" category="science">
                ...
                </book>
            </shelf1>
            <shelf2>...</shelf2>
        </library>
    </root>
    

    produces the wanted, correct result:

    <root>
          <library id="L1">
                <shelf1 id="1">
                      <book id="1" category="science">
                <attributes>
                   <author>xx</author>
                   <isbn>001</isbn>
                   <publisher>zz</publisher>
                   <year>2000</year>
                </attributes>
                <other>y</other>
             </book>
                      <book id="2" category="science">
                ...
                </book>
                </shelf1>
                <shelf2>...</shelf2>
          </library>
    </root>
    

    Explanation:

    1. Proper use and overriding of the identity rule.

    2. Proper use of the XSLT 2.0 instruction xsl:for-each-group with attribute group-by.

    3. Proper use of the XSLT 2.0 function current-grouping-key() and the XPath function name().