[xsl] Handling Mixed Child Elements and Text() Nodes

Subject: [xsl] Handling Mixed Child Elements and Text() Nodes
From: Brook Ellingwood <brook@xxxxxxxxxxx>
Date: Tue, 30 Sep 2003 16:32:33 -0700
Hi,

I'm running into unforeseen circumstances with the "splitting the contents
of a <text> element containing <link> and <subhead> elements and text()
nodes delimited by newlines into multiple <div> elements delimited by the
newlines, while transforming the child elements as well" problem that I got
help with from the list a while ago.

The permutations I've run into now are pretty complex, so it's not
surprising that the XSL is having problems. I've been trying to figure out
any one of the new issues, but I'm really just taking stabs in the dark at
this point. It may all be too complex to pull off with one set of templates,
but I'd really like to try. Any insights would be appreciated.

The biggest problem is that when I have a string of child elements separated
by newlines, they aren't wrapped in the element tags, but instead are output
sequentially until a sibling text() node is reached, after which the element
tag is closed and a new one is begun.

Less of a problem is that if the first thing encountered in the parent
element is a child element it still gets contained in a <div> element with
the class that a text() node in the same spot should receive as well as in
one with the class that the particular child element should receive. I think
I can figure this one out, but I've been focusing on the bigger issue all
day and I can't see straight anymore.

My third issue is that within this process, I haven't been able to find a
way to translate() double newlines ("&#10;&#10;") into singles. If I have
to, I can always clean them out of the XML, but for maximum flexibility, I
was hoping to handle them on the fly.

Thanks in advance for any assistance.

-- Brook



// Sample XML:

<page>
<body>
<text>
<subhead>Linked Text</subhead>

<link url="a">A</link>

<link url="b">B</link>

<link url="c">C</link>

<link url="d">D</link> (AKA X)

<link url="e">E</link>

<subhead>Unlinked Text</subhead>

F

G

H

I

J

</text>
</body>
</page>



// Desired Output:

<div class="body_subhead">Linked Text</div>
<div class="body"><a href="a" class="body">A</a></div>
<div class="body"><a href="b" class="body">B</a></div>
<div class="body"><a href="c" class="body">C</a></div>
<div class="body"><a href="d" class="body">D</a> (AKA X)</div>
<div class="body"><a href="e" class="body">E</a></div>
<div class="body_subhead">Unlinked Text</div>
<div class="body">F</div>
<div class="body">G</div>
<div class="body">H</div>
<div class="body">I</div>
<div class="body">J</div>



// Actual output (parsed by hand for legibility):

<div class="body">
    <div class="body_subhead">Linked Text</div>
    <a href="a" class="body">A</a>
    <a href="b" class="body">B</a>
    <a href="c" class="body">D</a> (AKA X)</div>
    <div class="body"></div>
    <div class="body"><a href="e" class="body">E</a></div>
</div>
<div class="body_subhead">Unlinked Text</div>
<div class="body">F</div>
<div class="body"></div>
<div class="body">G</div>
<div class="body"></div>
<div class="body">H</div>
<div class="body"></div>
<div class="body">I</div>
<div class="body"></div>
<div class="body">J</div>



// Relevant parts of Stylesheet

<xsl:key name="x" match="text/node()"
use="generate-id((..|preceding-sibling::text()[contains(.,'&#10;')][1])[last
()])"/>

<xsl:key name="y" match="text/node()"
use="generate-id((..|following-sibling::text()[contains(.,'&#10;')][1])[last
()])"/>


<xsl:template match="link">
     <xsl:param name="linkClass"/>
     <xsl:param name="divClass"/>
     <a href="{@url}"><xsl:attribute name="class"><xsl:value-of
select="$linkClass"/></xsl:attribute>
        <xsl:apply-templates>
               <xsl:with-param name="linkClass" select="$linkClass"/>
        </xsl:apply-templates>
    </a>    
</xsl:template>


<xsl:template match="subhead">
     <xsl:param name="divClass"/>
     <div><xsl:attribute name="class"><xsl:value-of
select="$divClass"/>_subhead</xsl:attribute><xsl:value-of select="."/></div>
</xsl:template>

<xsl:template match="text">
     <xsl:param name="divClass"/>
     <xsl:param name="linkClass"/>
    <xsl:for-each select=".|text()[contains(.,'&#10;')]">
         <xsl:call-template name="d1">
             <xsl:with-param name="s"
select="substring-after(self::text(),'&#10;')"/>
             <xsl:with-param name="divClass" select="$divClass"/>
               <xsl:with-param name="linkClass" select="$linkClass"/>
         </xsl:call-template>
    </xsl:for-each>
</xsl:template>

<xsl:template name="d1">
     <xsl:param name="s"/>
     <xsl:param name="divClass"/>
     <xsl:param name="linkClass"/>
      <xsl:text>&#10;</xsl:text>
    <xsl:choose>
         <xsl:when test="contains($s,'&#10;')">
              <div><xsl:attribute name="class"><xsl:value-of
select="$divClass"/></xsl:attribute><xsl:value-of
select="substring-before($s,'&#10;')"/></div>
                 <xsl:call-template name="d1">
                <xsl:with-param name="s"
select="substring-after($s,'&#10;')"/>
                   <xsl:with-param name="linkClass" select="$linkClass"/>
                 <xsl:with-param name="divClass" select="$divClass"/>
             </xsl:call-template>
         </xsl:when>
         <xsl:otherwise>
              <div><xsl:attribute name="class"><xsl:value-of
select="$divClass"/></xsl:attribute>
                 <xsl:value-of select="$s"/>
                 <xsl:apply-templates
select="key('x',generate-id(.))[position()&lt;last()]">
                       <xsl:with-param name="linkClass"
select="$linkClass"/>
                     <xsl:with-param name="divClass" select="$divClass"/>
                   </xsl:apply-templates>
                <xsl:value-of
select="substring-before(key('x',generate-id(.))[last()],'&#10;')"/>
                <xsl:if test="position() = last()">
                    <xsl:apply-templates
select="key('x',generate-id(.))[last()]">
                           <xsl:with-param name="linkClass"
select="$linkClass"/>
                         <xsl:with-param name="divClass"
select="$divClass"/>
                       </xsl:apply-templates>
                </xsl:if>
              </div>
         </xsl:otherwise>
     </xsl:choose>
</xsl:template>

<xsl:for-each select="./page/body">
    <xsl:apply-templates>
               <xsl:with-param name="textNode"
select="key('x',generate-id(.))[position()&lt;last()]"/>
               <xsl:with-param name="divClass" select="'body'"/>
               <xsl:with-param name="linkClass" select="'body'"/>
    </xsl:apply-templates>
</xsl:for-each>    



// One of Many Modifications I've Tried

<xsl:template match="link">
     <xsl:param name="linkClass"/>
     <xsl:param name="divClass"/>
     <a href="{@url}"><xsl:attribute name="class"><xsl:value-of
select="$linkClass"/></xsl:attribute>
        <xsl:apply-templates>
               <xsl:with-param name="linkClass" select="$linkClass"/>
        </xsl:apply-templates>
    </a>    
</xsl:template>


// Output With this Modification -- Has a Bunch of New Problems

<div class="body">
    <div class="body_subhead">Linked Text</div>
    <a href="a" class="body">A</a>
    <div class="body"></div>
    <a href="b" class="body">B</a>
    <div class="body"></div>
    <a href="c" class="body">C</a>
    <div class="body"></div>
    <a href="d" class="body">D</a>
    <div class="body"></div> (AKA X)</div>
    <div class="body"></div>
    <div class="body"><a href="f" class="body">F</a></div>
</div>
<div class="body_subhead">Unlinked Text</div>
<div class="body">F</div>
<div class="body"></div>
<div class="body">G</div>
<div class="body"></div>
<div class="body">H</div>
<div class="body"></div>
<div class="body">I</div>
<div class="body"></div>
<div class="body">J</div>


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread