XSLT / Xpath: Selecting a preceding comment
-
18-09-2019 - |
Question
I have XML in the following format which I want to reformat:
<blocks>
<!-- === apples === -->
<block name="block1">
...
</block>
<!-- === bananas === -->
<block name="block2">
...
</block>
<!-- === oranges === -->
<block name="block3">
...
</block>
</blocks>
My problem is I can't figure out how to select the comments above each block tag. I have the following XSL:
<xsl:template match="//blocks">
<xsl:apply-templates select="block" />
</xsl:template>
<xsl:template match="block">
<xsl:apply-templates select="../comment()[following-sibling::block[@name = ./@name]]" />
<xsl:value-of select="./@name" />
</xsl:template>
<xsl:template match="comment()[following-sibling::block]">
<xsl:value-of select="."></xsl:value-of>
</xsl:template>
The output that I am trying for is:
=== apples ===
block1
=== bananas ===
block2
=== oranges ===
block3
But the best I can get is:
=== apples ===
=== bananas ===
=== oranges ===
block1
=== apples ===
=== bananas ===
=== oranges ===
block2
=== apples ===
=== bananas ===
=== oranges ===
block3
I am using PHP if that makes any difference.
Solution
You can apply the templates for comments also in your first apply-templates instead of the second one, so that it happens in order - Also, this solution is dependent on the order of the data in the source xml..
<xsl:template match="//blocks">
<xsl:apply-templates select="block | comment()" />
</xsl:template>
PS:- You could avoid using "//" in your expressions as it can be non-optimal.
[EDIT] Complete Stylesheet
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//blocks">
<xsl:apply-templates select="block | comment()"/>
</xsl:template>
<xsl:template match="block">
<xsl:value-of select="./@name"/>
</xsl:template>
<xsl:template match="comment()">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Add the following statement if you want newlines, after you print the value in both the block and the comment.
<xsl:text> </xsl:text>
OTHER TIPS
Your stylesheet is a bit overly complicated.
You should try the stylesheet below and you will find that it matches the output that you want!
<xsl:template match="//blocks">
<xsl:apply-templates select="block" />
</xsl:template>
<xsl:template match="block">
<xsl:apply-templates select="preceding-sibling::comment()[1]" />
<xsl:value-of select="./@name" />
</xsl:template>
<xsl:template match="comment()">
<xsl:value-of select="."></xsl:value-of>
</xsl:template>
This code always matches 1 or 0 comments that start right before the current block tag.