Replace the XML element's name with the value

2019-08-14 11:11发布

My questions is based on this: How to create XML nodes with attributes from Table

The OP there asked for a chance to create XML with dynamically created element's names.

For this question the generation of the XML is not of importance (but might be tracked in the original post).

This XML

<NodeA>
  <NodeB></NodeB>
  <NodeC AttributeX="">
    <Row NodeName="RowA" AttributeA="" AttributeB="abcd" AttributeC="efgh" />
    <Row NodeName="RowB" AttributeA="wxyz" />
    <Row NodeName="RowC" AttributeB="qwer" AttributeC="tyui" />
    <Row NodeName="RowD" AttributeA="stuv" AttributeB="erty" AttributeC="fghj" />
  </NodeC>
</NodeA>

Should be converted to this:

<NodeA>
  <NodeB/>
  <NodeC AttributeX="">
        <RowA AttributeA="" AttributeB="abcd" AttributeC="efgh"/>
        <RowB AttributeA="wxyz"/>
        <RowC AttributeB="qwer" AttributeC="tyui"/>
        <RowD AttributeA="stuv" AttributeB="erty" AttributeC="fghj"/>
    </NodeC>
</NodeA>

The only difference is, that the "Row" element's names were replaced by the attribute value of "NodeName" and the attribute "NodeName" itself disappeared.

In the original post I suggested an XSLT approach (Can be tested here) which works but seems to me much to complicated. Is there a better way to reach this goal?

标签: xml xslt
1条回答
可以哭但决不认输i
2楼-- · 2019-08-14 11:56

You should start off by using the identity template, which on its own will copy all nodes and attributes

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

You then add templates from the nodes you wish to change; in this Row elements with NodeName attribute. Or maybe any element with a NodeName can be transformed. In which case the template match is like this:

<xsl:template match="*[@NodeName]">

This template has a higher priority than the identity template, so will be matched first. One thing to note that in the question you have linked to you have a template matching "*" and another matching "node()". Both of these have the same priority, which is considered an error. XSLT processors can either flag the error, or pick the last template. (See https://www.w3.org/TR/xslt#conflict)

In any case, inside the template, you can then create a new element with a name of NodeName attribute

    <xsl:element name="{@NodeName}">
        <xsl:apply-templates select="@*|node()"/>
    </xsl:element>

Apart from this, you just need another template to ensure the NodeName attribute itself does not get output.

Try this XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" />

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

    <xsl:template match="*[@NodeName]">
        <xsl:element name="{@NodeName}">
            <xsl:apply-templates select="@*|node()"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="@NodeName" />
</xsl:stylesheet>
查看更多
登录 后发表回答