tags:

views:

69

answers:

3

I am attempting to write XSLT that will select immediate-siblings of a certain type, but stop when a different tag is reached.

Here's the Source XML:

<?xml version="1.0"?> 
<body> 
    <proc>Test</proc> 
    <alert>Test1: alert 1</alert> 
    <alert>Test1: alert 2</p> 
    <para>Test para 1</para> 
    <alert>Test2: alert 1</alert> 
    <alert>Test2: alert 2</alert> 
    <alert>Test2: alert 3</alert> 
    <proc>Test</proc> 
    <alert>Test3: alert 1</alert> 
    <alert>Test3: alert 2</alert> 
    <alert>Test3: alert 3</alert> 
</html>

Here's the expected result:

<?xml version="1.0" encoding="UTF-8"?> 
<body>
    <proc>
        <alert>Test1: alert 1</alert>
        <alert>Test1: alert 2</alert>
    </proc>
    <para>Test para 1</para>
    <alert>Test2: alert 1</alert>
    <alert>Test2: alert 2</alert>
    <alert>Test2: alert 3</alert>
    <proc>
        <alert>Test3: alert 1</alert>
        <alert>Test3: alert 2</alert>
        <alert>Test3: alert 3</alert>
    </proc>
</body>

is this even possible?

Here's my current xsl which isn't doing the trick:

<xsl:template match="proc">
    <xsl:variable name="procedure" select="."/>
    <xsl:apply-templates/>
    <xsl:for-each 
     select="following-sibling::alert[preceding-sibling::proc[1] = $procedure]">
        <xsl:apply-templates select="."/>
    </xsl:for-each>
</xsl:template>


<xsl:template match="c:hhtAlert">...</xsl:template>

<xsl:template match="c:hhtPara">...</xsl:template>
A: 

If you are using XSLT 2 something like the following should work:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:template match="body">
    <xsl:copy>
      <xsl:for-each-group select="*" group-starting-with="para | proc">
        <xsl:choose>
          <xsl:when test="self::para">
            <xsl:apply-templates select="current-group()"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:copy>
              <xsl:apply-templates select="current-group()[position() &gt; 1]"/>
            </xsl:copy>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>
Nick Jones
@Nick Jones: Check this, is outputing wrong results.
Alejandro
@Alejandro: Sorry, meant group-starting-with. And added identity transform to make it complete
Nick Jones
+1  A: 

Here is an efficient and quite short XSLT 1.0 solution:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kFollowing" match="alert"
     use="generate-id(preceding-sibling::*
                      [
                       self::proc
                      or
                       self::para
                       ]
                        [1]
                          [self::proc]
                    )"/>

 <xsl:template match="node()|@*" name="identity">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="proc">
  <proc>
   <xsl:apply-templates select="key('kFollowing', generate-id())" mode="copy"/>
  </proc>
 </xsl:template>

 <xsl:template match="alert" mode="copy">
  <xsl:call-template name="identity"/>
 </xsl:template>

 <xsl:template match="alert[preceding-sibling::*
                [
                 self::proc
                or
                 self::para
                ]
                 [1]
                   [self::proc]
               ]"
                              />
</xsl:stylesheet>

when this transformation is applied on the provided XML document (corrected to be well-formed):

<body>
    <proc>Test</proc>
    <alert>Test1: alert 1</alert>
    <alert>Test1: alert 2</alert>
    <para>Test para 1</para>
    <alert>Test2: alert 1</alert>
    <alert>Test2: alert 2</alert>
    <alert>Test2: alert 3</alert>
    <proc>Test</proc>
    <alert>Test3: alert 1</alert>
    <alert>Test3: alert 2</alert>
    <alert>Test3: alert 3</alert>
</body>

the wanted result is produced:

<body>
   <proc>
      <alert>Test1: alert 1</alert>
      <alert>Test1: alert 2</alert>
   </proc>
   <para>Test para 1</para>
   <alert>Test2: alert 1</alert>
   <alert>Test2: alert 2</alert>
   <alert>Test2: alert 3</alert>
   <proc>
      <alert>Test3: alert 1</alert>
      <alert>Test3: alert 2</alert>
      <alert>Test3: alert 3</alert>
   </proc>
</body>

Do note: The use of keys makes this transformation many times faster (in case of many alert siblings) than using the preceding-sibling:: or following-sibling:: axes.

Dimitre Novatchev
@Dimitre: +1 For clasic solution.
Alejandro
A: 

Besides Dimitre's good answer (probably the clasic solution for this kind of problems), this stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()[1]|@*"/>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="proc">
        <proc>
            <xsl:apply-templates select="following-sibling::node()[1]"
                                 mode="open"/>
        </proc>
        <xsl:apply-templates select="following-sibling::node()
                                         [not(self::alert)][1]"/>
    </xsl:template>
    <xsl:template match="node()" mode="open"/>
    <xsl:template match="alert" mode="open">
        <xsl:copy-of select="."/>
        <xsl:apply-templates select="following-sibling::node()[1]"
                             mode="open"/>
    </xsl:template>
</xsl:stylesheet>

Output:

<body>
    <proc>
        <alert>Test1: alert 1</alert>
        <alert>Test1: alert 2</alert>
    </proc>
    <para>Test para 1</para>
    <alert>Test2: alert 1</alert>
    <alert>Test2: alert 2</alert>
    <alert>Test2: alert 3</alert>
    <proc>
        <alert>Test3: alert 1</alert>
        <alert>Test3: alert 2</alert>
        <alert>Test3: alert 3</alert>
    </proc>
</body>

Note: This use fine grained trasversal.

Edit: Better pattern matching with modes.

Alejandro