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