Reputation: 367
As will become very apparent, XSLT is not my thing.
Hpwever I have some XHTML content, and I wish to concatenate the contents of some custom tags it contains, but only if they share a given attribute value.
So for example I would like to turn this:
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title />
</head>
<body>
<p>
<mytag myid="0">Don't </mytag>
<mytag myid="1">concatenate. </mytag>
Other text
<mytag myid="2">Text </mytag>
<mytag myid="2">to </mytag>
<mytag myid="2">concatenate. </mytag>
More text
</p>
</body>
</html>
into this:
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title />
</head>
<body>
<p>
<mytag myid="0">Don't </mytag>
<mytag myid="1">concatenate. </mytag>
Other text
<mytag myid="2">Text to concatenate. </mytag>
More text
</p>
</body>
</html>
Here's as far as I've got, with my XSLT:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xsl:stylesheet xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="xhtml"
version="1.0">
<xsl:template match="*|@*">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Concatenate the contents of custom tags 'mytag'-->
<!-- that share an attribute myid value-->
<xsl:template match="//xhtml:mytag">
<mytag>
<xsl:variable name="this-id" select="@myid"/>
<xsl:attribute name="myid">
<xsl:value-of select="$this-id"/>
</xsl:attribute>
<xsl:value-of select="."/>
<xsl:for-each select="following-sibling::xhtml:mytag[@myid=$this-id]">
<xsl:value-of select="."/>
</xsl:for-each>
</mytag>
</xsl:template>
</xsl:stylesheet>
However try as I might, I can't work out how to stop the tags that have already been processed with the 'following-sibling' select being processed again. Consequently my output currently looks rather tragically like this:
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title/>
</head>
<body>
<p>
<mytag myid="0">Don't </mytag>
<mytag myid="1">concatenate. </mytag>
Other text
<mytag myid="2">Text to concatenate. </mytag>
<mytag myid="2">to concatenate. </mytag>
<mytag myid="2">concatenate. </mytag>
More text
</p>
</body>
</html>
If feels like this question, should answer my problem too - but I am too stupid to see it.
Help?
Upvotes: 0
Views: 438
Reputation: 70648
You can tweak your current template to include an xsl:if
that checks that there is not a preceding sibling with the same id, to prevent the mytag
being output multiple times.
<xsl:stylesheet xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="xhtml"
version="1.0">
<xsl:strip-space elements="*" />
<xsl:template match="*|@*">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xhtml:mytag">
<xsl:variable name="this-id" select="@myid"/>
<xsl:if test="not(preceding-sibling::xhtml:mytag[@myid = $this-id])">
<xsl:copy>
<xsl:attribute name="myid">
<xsl:value-of select="$this-id"/>
</xsl:attribute>
<xsl:value-of select="."/>
<xsl:for-each select="following-sibling::xhtml:mytag[@myid=$this-id]">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
However, depending on your requirements, this may "fail" if your XML looked like this
<p>
<mytag myid="0">Don't </mytag>
<mytag myid="1">concatenate. </mytag>
Other text
<mytag myid="2">Text </mytag>
<mytag myid="2">to </mytag>
<mytag myid="2">concatenate. </mytag>
More text
<mytag myid="1">Concatenate separately. </mytag>
</p>
The given XSLT would produce this...
<body xmlns="http://www.w3.org/1999/xhtml">
<p>
<mytag myid="0">Don't </mytag>
<mytag myid="1">concatenate. Concatenate separately. </mytag>
Other text
<mytag myid="2">Text to concatenate. </mytag>
More text
</p>
</body>
This is because the following-sibling axis gets all following siblings, not just the one the immediately follows the current node.
If you actually wanted this instead....
<body xmlns="http://www.w3.org/1999/xhtml">
<p>
<mytag myid="0">Don't </mytag>
<mytag myid="1">concatenate. </mytag>
Other text
<mytag myid="2">Text to concatenate. </mytag>
More text
<mytag myid="1">Concatenate separately. </mytag>
</p>
</body>
Then you would have to look at the following siblings one at a time. Try this XSLT instead
<xsl:stylesheet xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="xhtml"
version="1.0">
<xsl:strip-space elements="*" />
<xsl:template match="*|@*">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xhtml:mytag">
<xsl:variable name="this-id" select="@myid"/>
<xsl:if test="not(preceding-sibling::*[1][self::xhtml:mytag[@myid = $this-id]])">
<xsl:copy>
<xsl:attribute name="myid">
<xsl:value-of select="$this-id"/>
</xsl:attribute>
<xsl:apply-templates select="self::*" mode="concatenate" />
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="xhtml:mytag" mode="concatenate">
<xsl:variable name="this-id" select="@myid"/>
<xsl:value-of select="." />
<xsl:apply-templates select="following-sibling::*[1][self::xhtml:mytag[@myid = $this-id]]" mode="concatenate" />
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Reputation: 163625
This can be achieved very simply in XSLT 2.0 using
<xsl:template match="p">
<xsl:for-each-group select="mytag" group-adjacent="@myid">
<mytag myid="{current-grouping-key()}">
<xsl:value-of select="string-join(current-group(), '')"/>
</mytag>
</xsl:for-each-group>
</xsl:template>
Upvotes: 0