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

霸气de小男生 提交于 2019-12-11 00:27:51

问题


The structure, names, values of the given .xml files is unknown.

For every not-root element that has simple structure(has no child-nodes, has no attributes, BUT has text and is not empty) transform it into parent's attribute.

I have .xml file:

<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>

And I want to get this:

<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>

Here is my .xsl for now (doesn't work correctly):

<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>

回答1:


Your code

You've got two templates:

<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>

Your output produces <worker><make1 make1="val2"/></worker> instead of <worker make1="val2"/>. This is because the outer <worker> element is processed by the top template, which just copies it and then passes the child along, which gets processed by the bottom template.

A working approach

The following works for me, and uses only one template.

<?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>

The key difference: any element that meets your criteria -- has no child elements, has no attributes, only has text -- does not get processed by applying a template, and instead it gets processed within that for-each loop. So we never wind up with a copy of that element.

Update

We now have a clarified provision that empty elements that lack even text are to be kept as independent elements. So for a snippet like the following, with the empty EXTRA element:

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

... we would want output like:

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

... which maintains EXTRA as an independent element, and only attribute-ifies the make3 element.

This XSL should do the trick. This reworks the select statements from the code above.

<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>



回答2:


How about:

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>

Keep in mind that attributes must be created before child elements.


Added in response to change in requirements:

At some point, the amount of conditions becomes sufficient to justify writing them only once, and avoid duplicating them in the negative by defining the "other" node-set in terms of set-difference (i.e. non-intersection):

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>


来源:https://stackoverflow.com/questions/44554119/xslt-element-that-has-no-attributes-and-no-children-transform-to-parent-attribu

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!