JPM
JPM

Reputation: 2066

XSLT Match attribute and then its element

In my source XML, any element can have an @n attribute. If one does, I want to output it before processing the element and all its children.

For example

<line n="2">Ipsum lorem</line>
<verse n="5">The sounds of silence</verse>
<verse>Four score and seven</verse>
<sentence n="3">
    <word n="1">Hello</word>
    <word n="2">world</word>
</sentence>

I have templates that match "line", "verse", "sentence" and "word". If any of those elements has an @n value, I want to output it in front of whatever the element's template generates.

The above might come out something like

2 <div class="line">Ipsum lorem</span>
5 <span class="verse">The sounds of silence</span>
<span class="verse">Four score and seven</span>
3 <p class="sentence">
   1 <span class="word">Hello</span>
   2 <span class="word">world</span>
  </p>

where the templates for "line", "verse", etc. generated the div, span and p elements.

How should I think of this problem? -- Match the attribute and then apply-templates to its parent? (What would the syntax for that be?) Put a call-template at the beginning of every element's template? (That's unappealing.) Something else? (Probably!)

I tried a few things but got either an infinite loop, or nothing, or processing of the attribute and then its parent's children, but not the parent itself.

Upvotes: 2

Views: 6479

Answers (2)

user663031
user663031

Reputation:

To simplify matters, I've placed the mapping from XML to HTML elements in an in-document data structure (accessible via the document() function with no arguments). Now only one template is needed requiring special processing of the @n attribute in only one place.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>

  <map>
    <elt xml="line" html="class"/>
    <elt xml="verse" html="span"/>
    <elt xml="sentence" html="p"/>
    <elt xml="word" html="span"/>
  </map>

  <xsl:template match="line|verse|sentence|word">
    <xsl:if test="@n"><xsl:value-of select="@n"/> </xsl:if>
    <xsl:element name="{document()/map/elt[@xml=name()]/@html}">
      <xsl:attribute name="class"><xsl:value-of select="name()"/></xsl:attibute>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

Upvotes: 3

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243459

Here is one simple way to do this:

<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:template match="*/*[@n]">
  <xsl:value-of select="concat('&#xA;', @n, ' ')"/>

  <xsl:apply-templates select="self::*" mode="content"/>
 </xsl:template>

 <xsl:template match="*/*[not(@*)]">
  <xsl:apply-templates select="." mode="content"/>
 </xsl:template>

 <xsl:template match="line" mode="content">
  <div class="line"><xsl:apply-templates/></div>
 </xsl:template>

 <xsl:template match="verse | word" mode="content">
  <span class="{name()}"><xsl:apply-templates mode="content"/></span>
 </xsl:template>

 <xsl:template match="sentence" mode="content">
  <p class="sentence"><xsl:apply-templates/></p>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<t>
    <line n="2">Ipsum lorem</line>
    <verse n="5">The sounds of silence</verse>
    <verse>Four score and seven</verse>
    <sentence n="3">
        <word n="1">Hello</word>
        <word n="2">world</word>
    </sentence>
</t>

the wanted, correct result is produced:

2 <div class="line">Ipsum lorem</div>
5 <span class="verse">The sounds of silence</span>
<span class="verse">Four score and seven</span>
3 <p class="sentence">
1 <span class="word">Hello</span>
2 <span class="word">world</span>
</p>

Explanation: Appropriate use of templates and modes.

Upvotes: 1

Related Questions