XSLT:没有属​​性且没有子元素转换为父属性的元素

XSLT: element that has no attributes and no children transform to parent attribute

给定的 .xml 文件的结构、名称和值未知。

对于每个具有简单结构(没有子节点,没有属性,但有文本且不为空)的非根元素,将其转换为父元素的属性。

我有 .xml 文件:

<list>
   <worker>
      <name atr="ss">val1</name>
   </worker>
   <worker>
      <make1>val2</make1>
   </worker>
   <worker>
      <name>
        <make2>val3</make2>
      </name>
   </worker>
   <worker>
      <name>
        <doo atr="ss1">val4</doo>
        <make3></make3>
      </name>
   </worker>
</list>

我想得到这个:

<list>
   <worker>
      <name atr="ss">val1</name>
   </worker>
   <worker make1="val2"/>
   <worker>
      <name make2="val3"/>
   </worker>
   <worker>
      <name>
        <doo atr="ss1">val4</doo>
        <make3/>
      </name>
   </worker>
</list>

这是我现在的 .xsl(不能正常工作):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes" method="xml"/>
    <xsl:template match="@*|node()">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:template>
    <xsl:template match="//*[not(*|@*)]">
        <xsl:copy>
            <xsl:attribute name="{name()}">
                <xsl:value-of select="text()"/>
            </xsl:attribute>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

怎么样:

XSL 1.0

<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="*"/>

<xsl:template match="*">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates select="*[not(*|@*)]" mode="attribute"/>
        <xsl:apply-templates select="*[*|@*] | text()" />
    </xsl:copy>
</xsl:template>

<xsl:template match="*" mode="attribute">
    <xsl:attribute name="{name()}">
        <xsl:value-of select="."/>
    </xsl:attribute>
</xsl:template>

</xsl:stylesheet>

请记住,属性必须在子元素之前创建。


为响应需求变化而添加:

在某些时候,条件的数量足以证明只写一次,并通过根据集合差异(即非-交集):

XSLT 1.0

<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="*"/>

<xsl:template match="*">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:variable name="my-set" select="*[text() and not(*|@*)]" />
        <xsl:apply-templates select="$my-set" mode="attribute"/>
        <xsl:apply-templates select="node()[not(count(.|$my-set) = count($my-set)]" />
    </xsl:copy>
</xsl:template>

<xsl:template match="*" mode="attribute">
    <xsl:attribute name="{name()}">
        <xsl:value-of select="."/>
    </xsl:attribute>
</xsl:template>

</xsl:stylesheet>

您的代码

您有两个模板:

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

<xsl:template match="*[not(*) and not(@*)]">
    <xsl:copy>
        <xsl:attribute name="{name()}">
            <xsl:value-of select="text()"/>
        </xsl:attribute>
    </xsl:copy>
</xsl:template>

您的输出生成 <worker><make1 make1="val2"/></worker> 而不是 <worker make1="val2"/>。这是因为外部 <worker> 元素由顶部模板处理,顶部模板只是复制它,然后将子元素传递给底部模板处理。

一种工作方法

以下适用于我,并且只使用一个模板。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

    <xsl:output indent="yes"/>

    <xsl:strip-space elements="*"/>

    <xsl:template match="*">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <!-- Capture any child elements with no attributes and no children. -->
            <xsl:for-each select="*[not(@*) and not(*)]">
                <xsl:attribute name="{name()}">
                    <xsl:value-of select="."/>
                </xsl:attribute>
            </xsl:for-each>
            <!-- Apply templates to **only** those children that have either
                attributes or children of their own, and to text. -->
            <xsl:apply-templates select="*[@* or *]|text()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

关键区别:任何符合您标准的元素——没有子元素,没有属性,只有文本——不会通过应用模板进行处理,而是在 for-each 循环中进行处理。所以我们永远不会得到该元素的副本。

更新

我们现在有一个明确的规定,即连文本都没有的空元素将作为独立元素保留。所以对于像下面这样的片段,空的 EXTRA 元素:

<worker>
    <name>
        <doo atr="ss1">val4</doo>
        <make3>val4</make3>
        <EXTRA></EXTRA>
    </name>
</worker>

...我们希望输出如下:

<worker>
    <name make3="val4">
        <doo atr="ss1">val4</doo>
        <EXTRA/>
    </name>
</worker>

... 将 EXTRA 保持为一个独立的元素,并且只对 make3 元素进行属性化。

这个 XSL 应该可以解决问题。这重写了上面代码中的 select 语句。

<xsl:template match="*">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <!-- Capture any child elements with no attributes and no children,
            and that also have text. -->
        <xsl:for-each select="*[not(@*) and not(*) and text()]">
            <xsl:attribute name="{name()}">
                <xsl:value-of select="."/>
            </xsl:attribute>
        </xsl:for-each>
        <!-- Apply templates to **only** those children that have no text, or
            that have attributes or children of their own, and also apply to text. -->
        <xsl:apply-templates select="*[@* or * or not(text())] | text()"/>
    </xsl:copy>
</xsl:template>