1
votes

I am using xslt 2.0, I have following sample xml, I want to get all "header-lightgray" nodes between the two "header-gray" nodes, the problem here is: I don't know how many "header-lightgray" there. so I can not use position() function. and xsl for each don't support break.

<t>
<parent class="header-gray"></parent>
<parent class="header-lightgray"></parent>
<parent class="header-lightgray"></parent>
<parent class="header-lightgray"></parent>
...
<parent class="header-gray"></parent>
<parent class="header-lightgray"></parent>
<parent class="header-lightgray"></parent>
<parent class="header-lightgray"></parent>
<parent class="header-gray"></parent>
</t>

I want go through them and replace all nodes, header-gray with div and header-lightgray with span. the output expected is:

<div class="header-gray">
<span class="header-lightgray"/>
<span class="header-lightgray"/>
<span class="header-lightgray"/>
...
</div>
<div class="header-gray">
<span class="header-lightgray"/>
<span class="header-lightgray"/>
<span class="header-lightgray"/>
</div>

So how to do it?

Any help is appreciated.

Solution I'd like to share according to Jan Vlcinsky's answer:

<xsl:for-each select="/t/parent[@class='header-gray']">
  <xsl:variable name="ns1" select="current()/following-sibling::parent"/>
  <xsl:variable name="ns2" select="current()/following-sibling::parent[@class='header-gray'][1]/preceding-sibling::parent"/>
  <div class="header-gray">
  <xsl:for-each select="$ns1[count(.| $ns2)=count($ns2)]">
     <span class="header-lightgray"/>      
  </xsl:for-each>
  </div>
</xsl:for-each>

I don't verify above code, but it's the same logic in my real code. I didn't use xslt 2.0 way because I found that lxml 2.3 I am using does not support xslt 2.

3
What do you want to do with these elements once you have them? What version of XSLT are you using?Borodin
I want go through them and replace all nodes, header-gray with div and header-lightgray with span. the span is in the div.Shrek Zhou

3 Answers

0
votes

Look to the section "3. Positional grouping problem" at the bottom of page http://gandhimukul.tripod.com/xslt/grouping20.html

Your question seems to be sort of duplicate of: How would you find all nodes between two H3's using XPATH?

Question XPath : select all following siblings until another sibling deals with the same too.

0
votes

This transformation:

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

 <xsl:template match="/*">
  <t>
   <xsl:for-each-group select="*"
        group-starting-with="parent[@class='header-gray']">
     <div class="header-gray">
       <xsl:apply-templates select="current-group()[position() ne 1]"/>
     </div>
   </xsl:for-each-group>
  </t>
 </xsl:template>

 <xsl:template match="parent">
   <span class="{@class}"/>
 </xsl:template>
</xsl:stylesheet>

when applied on the following XML document (the provided fragment, wrapped into a single top element so that it becomes a well-formed XML document):

<t>
    <parent class="header-gray"></parent>
    <parent class="header-lightgray"></parent>
    <parent class="header-lightgray"></parent>
    <parent class="header-lightgray"></parent>
    <parent class="header-gray"></parent>
    <parent class="header-lightgray"></parent>
    <parent class="header-lightgray"></parent>
    <parent class="header-lightgray"></parent>
    <parent class="header-gray"></parent>
</t>

produces the wanted, correct result:

<t>
   <div class="header-gray">
      <span class="header-lightgray"/>
      <span class="header-lightgray"/>
      <span class="header-lightgray"/>
   </div>
   <div class="header-gray">
      <span class="header-lightgray"/>
      <span class="header-lightgray"/>
      <span class="header-lightgray"/>
   </div>
   <div class="header-gray"/>
</t>
-1
votes

Here's one way to do it.

When this XSLT 2.0 solution:

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

   <xsl:template match="/*">
      <t>
         <xsl:apply-templates
           select="parent[@class = 'header-gray'][following-sibling::*]"/>
      </t>
   </xsl:template>

   <xsl:template match="*[@class = 'header-gray']">
      <div class="{@class}">
         <xsl:apply-templates
           select="
             following-sibling::*[@class = 'header-lightgray'] 
               intersect
             following-sibling::*[@class = 'header-gray'][1]/
                 preceding-sibling::*[@class = 'header-lightgray']"/>
      </div>
   </xsl:template>

   <xsl:template match="*[@class = 'header-lightgray']">
      <span class="{@class}"/>
   </xsl:template>

</xsl:stylesheet>

...is applied to the provided XML (wrapped in a top-level element to make the document well-formed):

<t>
  <parent class="header-gray"/>
  <parent class="header-lightgray"/>
  <parent class="header-lightgray"/>
  <parent class="header-lightgray"/>
  <parent class="header-gray"/>
  <parent class="header-lightgray"/>
  <parent class="header-lightgray"/>
  <parent class="header-lightgray"/>
  <parent class="header-gray"/>
</t>

...the desired result is produced:

<t>
  <div class="header-gray">
    <span class="header-lightgray" />
    <span class="header-lightgray" />
    <span class="header-lightgray" />
  </div>
  <div class="header-gray">
    <span class="header-lightgray" />
    <span class="header-lightgray" />
    <span class="header-lightgray" />
  </div>
</t>

This solution makes use of XPath 2.0's intersect operation.