Reputation: 11
Here is the XML:
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<text id="b376">
<tspan x="59" y="156" font-size="13px" font-family="Arial">80</tspan>
</text>
<use xlink:href="#b376" fill="#000000"/>
<text id="b374">
<tspan x="59" y="204" font-size="13px" font-family="Arial">60</tspan>
</text>
<use xlink:href="#b374" fill="#000000"/>
<defs>testDef</defs>
</g>
</svg>
Here is my XSL input:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="g">
<g>
<xsl:apply-templates select="use|defs"/>
<defs>
<xsl:apply-templates select="*[name() != 'use' and name() != 'defs']"/>
</defs>
</g>
</xsl:template>
</xsl:stylesheet>
I want to wrap all nodes in a defs tag EXCEPT use tags and defs tags. So the 2 text nodes would be wrapped in defs tags but defs and use would not.
Here is what I am getting
<?xml version="1.0"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<use xlink:href="#b376" fill="#000000"/>
<use xlink:href="#b374" fill="#000000"/>
<defs>testDef</defs>
<defs>
<text id="b376">
<tspan x="59" y="156" font-size="13px" font-family="Arial">80</tspan>
</text>
<text id="b374">
<tspan x="59" y="204" font-size="13px" font-family="Arial">60</tspan>
</text>
</defs>
</g>
</svg>
This is what I want:
<?xml version="1.0"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<use xlink:href="#b376" fill="#000000"/>
<use xlink:href="#b374" fill="#000000"/>
<defs>testDef</defs>
<defs>
<text id="b376">
<tspan x="59" y="156" font-size="13px" font-family="Arial">80</tspan>
</text>
</defs>
<defs>
<text id="b374">
<tspan x="59" y="204" font-size="13px" font-family="Arial">60</tspan>
</text>
</defs>
</g>
</svg>
I am using this online tool to test. Thanks!
Upvotes: 1
Views: 610
Reputation: 30971
Your expected output shows that the order of element is important to you.
You want first use
and defs
tags and only after them
all remaining elements, each wrapped in its own defs
element.
In order to achieve this use the following script:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="g">
<g>
<xsl:apply-templates select="use|defs"/>
<xsl:for-each select="*[name() != 'use' and name() != 'defs']">
<defs>
<xsl:apply-templates select="."/>
</defs>
</xsl:for-each>
</g>
</xsl:template>
<xsl:template match="node()|@*">
<xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>
</xsl:template>
</xsl:stylesheet>
As you see, I added a for-each
loop with the select
attribute copied from your example.
The content of this loop:
defs
element,Upvotes: 1
Reputation: 1180
Your current output, where all of the <text>
elements are wrapped in a single <defs>
tag, is exactly what I would expect from reading your XSL code -- for every <g>
, you have a single <defs>
within which you handle all of the elements that are not <use>
or <defs>
:
<xsl:template match="g">
<g>
<xsl:apply-templates select="use|defs"/>
<!-- This part here: -->
<defs>
<xsl:apply-templates select="*[name() != 'use' and name() != 'defs']"/>
</defs>
</g>
</xsl:template>
Since <xsl:apply-templates select="*[name() != 'use' and name() != 'defs']"/>
is inside the literal <defs>
, all of the non-use
and non-refs
elements are processed as a batch, within that single literal <defs>
element.
You apparently want each non-use
and non-defs
element wrapped in its own <defs>
. In this case, you need to move the literal <defs>
to within a separate template that matches on non-use
and non-defs
elements.
A quick-and-dirty refactoring might look like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Identity template -->
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<!-- We don't really need a specific template for <g>, so
we let the identity template handle that case. -->
<!-- The only case where we need to define a different flow is
for children of <g> elements, that aren't <use> or <defs>. -->
<xsl:template match="*[name(..) = 'g'][name() != 'use' and name() != 'defs']">
<!-- The template matches _each_ such element, so if we
put the literal `<defs>` here, we get that `<defs>` as
a wrapper for _each_ such element. -->
<defs>
<xsl:copy-of select="."/>
</defs>
</xsl:template>
</xsl:stylesheet>
Note that this approach also leaves the original <use>
and <defs>
elements in the same location within the <g>
parent.
Upvotes: 1