Aggregate multiple nodes to a single node in XSLT

2019-08-07 21:14发布

Here is the XML source that I need to transform using XSLT

<?xml version="1.0" encoding="UTF-8"?>
<tns:Grand_Parent_XML xmlns:tns="">
  <GrandParent>
    <Parent>
        <Child>
            <Age>3</Age>
        </Child>
        <Child>
            <Gender>Male</Gender>
        </Child>
        <Child>
            <Name>Todd</Name>
        </Child>
        <Other>1234</Other>
    </Parent>
  </GrandParent>
</tns:Grand_Parent_XML>

Here is the desired output after transforming through XSLT

<?xml version="1.0" encoding="UTF-8"?>
<tns:Grand_Parent_XML xmlns:tns="">
  <GrandParent>
    <Parent>
        <Child>
            <Age>3</Age>
            <Gender>Male</Gender>
            <Name>Todd</Name>
        </Child>
        <Other>1234</Other>
    </Parent>
  </GrandParent>
</tns:Grand_Parent_XML>

Here's what's actually happening...

<?xml version="1.0" encoding="UTF-8"?>
<tns:Grand_Parent_XML xmlns:tns="">
  <GrandParent>
    <Child>
        <Age>3</Age>
        <Gender>Male</Gender>
        <Name>Todd</Name>
    </Child>
  </GrandParent>
</tns:Grand_Parent_XML>

And I'm using this XSLT...

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="">
       <xsl:template match="Grand_Parent_XML/GrandParent/Parent">
         <Child>
           <xsl:for-each select="Child">
             <xsl:if test="Age !=''">
               <Age><xsl:value-of select="Age"/></Age>
             </xsl:if>
             <xsl:if test="Gender !=''">
               <Gender><xsl:value-of select="Gender"/></Gender>
             </xsl:if>
             <xsl:if test="Name !=''">
               <Name><xsl:value-of select="Nanme"/></Name>
             </xsl:if>
           </xsl:for-each> 
         </Child>
       </xsl:template>               
</xsl:stylesheet>

I have little command of XSLT as of the moment and I would appreciate any help I can get. Using the XSLT I've created, the Parent is overridden by Child which shouldn't be the case. Also, the other child nodes of Parent i.e Other is removed. The actual XML I'm using has far more fields than the one I included here. I can choose to manually include all the nodes in the XSLT but I feel that there's a more efficient way to do it. Thank you!

标签: xml xslt
2条回答
手持菜刀,她持情操
2楼-- · 2019-08-07 21:45

Here is a generic approach that should work on a variety of different inputs:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*" />
  <xsl:key name="kNamedSiblings" match="*" 
           use="concat(generate-id(..), '+', name())"/>

  <xsl:template match="*">
      <xsl:copy>
        <xsl:apply-templates select="key('kNamedSiblings', 
                                         concat(generate-id(..), '+', name())
                                        )/node()" />
      </xsl:copy>
  </xsl:template>

  <xsl:template match="*[not(*) and . = '']" />
  <xsl:template match="*[generate-id() != 
                         generate-id(key('kNamedSiblings', 
                                         concat(generate-id(..), '+', name()))[1]
                                    )]" />
</xsl:stylesheet>

When run on your sample input, the result is:

<tns:Grand_Parent_XML xmlns:tns="...">
  <GrandParent>
    <Parent>
      <Child>
        <Age>3</Age>
        <Gender>Male</Gender>
        <Name>Todd</Name>
      </Child>
      <Other>1234</Other>
    </Parent>
  </GrandParent>
</tns:Grand_Parent_XML>
查看更多
Viruses.
3楼-- · 2019-08-07 21:52

Try it this way:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="Parent">
    <xsl:copy>
        <Child>
            <xsl:apply-templates select="Child/*"/>
        </Child>
        <xsl:apply-templates select="*[not(self::Child)]"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Or, if you prefer:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="Parent">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
        <Child>
            <xsl:apply-templates select="Child/*"/>
        </Child>
    </xsl:copy>
</xsl:template>

<xsl:template match="Child"/>

</xsl:stylesheet>
查看更多
登录 后发表回答