1

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>
1
  • Good question (+1). See my answer for an efficient and quite short XSLT 1.0 solution. :) Commented Sep 23, 2010 at 13:03

3 Answers 3

0

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">
 <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.

Sign up to request clarification or add additional context in comments.

Comments

0

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">
  <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>

1 Comment

@Alejandro: Sorry, meant group-starting-with. And added identity transform to make it complete
0

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">
    <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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.