Reputation: 63
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>
Upvotes: 2
Views: 1733
Reputation: 117073
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.
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>
Upvotes: 2
Reputation: 1180
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.
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.
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>
Upvotes: 1