Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

xslt and xpath: match preceding comments

Tags:

xslt

xpath

given this xml:

<root>
    <list>
        <!-- foo's comment -->
        <item name="foo" />
        <item name="bar" />
        <!-- another foo's comment -->
        <item name="another foo" />
    </list>
</root>

I'd like to use a XPath to select all item-nodes that have a comment immediately preceding them, that is I like to select the "foo" and "another foo" items, but not the "bar" item.

I already fiddled about the preceding-sibling axis and the comment() function but to no avail.

like image 781
miasbeck Avatar asked Apr 10 '10 11:04

miasbeck


2 Answers

This seems to work:

//comment()/following-sibling::*[1]/self::item

It looks for immediately following siblings of comments which are also <item> elements. I don't know a better way to express the ::*[1]/self::item part, which is ugly; note that if it were written ::item[1] then it would also find <item>s not immediately proceded by a comment.

like image 140
Kevin Reid Avatar answered Oct 22 '22 03:10

Kevin Reid


The currently selected solution:

//comment()/following-sibling::*[1]/self::item

doesn't work in the case where there is a procesing instruction (or a whole group of processing instructions) between the comment and the element -- as noticed in a comment by Martin Honnen.

The solution below doesn't have such a problem.

The following XPath expression selects only elements nodes that are either immediately preceded by a comment node, or are immediately preceded by a white-space-only text node, which is immediately preceded by a comment node:

(//comment()
   /following-sibling::node()
     [1]
     [self::text()
    and 
     not(normalize-space())
     ]
      /following-sibling::node()
             [1] [self::item]
 ) 
|
(//comment()
   /following-sibling::node()
     [1]
     [self::item]
 ) 

Here is a complete test:

We use this XML document:

<root>
    <list>
        <!-- foo's comment -->
        <item name="foo" />
        <item name="bar" />
        <!-- another foo's comment -->
        <item name="another foo" />
        <!-- comment 3 --><item name="immed.after 3"/>
        <!-- comment 4 --><?PI ?><item name="after PI"/>
    </list>
</root>

When the following transformation is applied on the above XML document:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>


 <xsl:template match="/">
   <xsl:copy-of select=
    "
    (//comment()
       /following-sibling::node()
         [1]
         [self::text()
        and
         not(normalize-space())
         ]
          /following-sibling::node()
                 [1] [self::item]
     )
    |
    (//comment()
       /following-sibling::node()
         [1]
         [self::item]
     )
    "/>
 </xsl:template>
</xsl:stylesheet>

the wanted, correct result is produced:

<item name="foo"/>
<item name="another foo"/>
<item name="immed.after 3"/>
like image 3
Dimitre Novatchev Avatar answered Oct 22 '22 02:10

Dimitre Novatchev