Re: Copying elements and their attributes

Subject: Re: Copying elements and their attributes
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Tue, 03 Oct 2000 18:55:19 +0100
Chris,

The crux of your problem lies in understanding what xsl:value-of gives when
you apply it to an element.  In your input you have:

<code type="xml">
  <foo type="fooed">
    <bar>test text</bar>
  </foo>
</code>

When you do:

  <xsl:value-of select="." />

it gives you the 'string value' of the current node.  For an element, the
string value is the concatenation of all the text descendents of the
element.  This can be really useful when you have mixed content - for
example in:

  <p>Text with <em>emphasis</em> in it.</p>

the 'string value' of the 'p' element is "Text with emphasis in it." - very
handy if you don't care about the emphasis existing or not.

The problem is that in your case the elements 'code', 'foo' and 'bar' all
have more or less the same string value (aside from the odd bit of
whitespace here and there).

In your template:

<xsl:template match="@*|node()" mode="code">
  <fo:block text-align="start" line-height="1.5" font-family="Courier"
start-indent="20pt" end-indent="20pt" >
    <fo:inline-sequence color="silver">&lt;<xsl:value-of
select="name()"/>&gt;</fo:inline-sequence>
      <fo:inline-sequence color="#B00"><xsl:value-of
select="."/></fo:inline-sequence>
        <xsl:apply-templates mode="code"/>
        <fo:inline-sequence color="silver">&lt;/<xsl:value-of
select="name()"/>&gt;</fo:inline-sequence>
    </fo:block>
</xsl:template>

when the 'code' element is processed by the template, the inline-sequence:

  <fo:inline-sequence color="#B00">
    <xsl:value-of select="."/>
  </fo:inline-sequence>

gives the string value of the 'code' element (i.e. "test text").  You then
apply templates to the 'code' element's children (the 'foo' element), and
so the template is used again, this time on the 'foo' element, giving its
string value (i.e. "test text"), again applying templates to its children
(the 'bar' element), and therefore again emitting "test text".  This is why
you end up with three copies of the text!

The solution to this problem is to have a separate template that emits the
fo:inline-sequence for any text that's found, and let the generic
xsl:apply-templates deal with applying this template when there's text to
apply it to.  Take out the section in your template that includes the value
of the current node, and add an extra template that matches text() nodes:

<xsl:template match="text()" mode="code">
  <fo:inline-sequence color="#B00">
    <xsl:value-of select="."/>
  </fo:inline-sequence>
</xsl:template>

There are a couple of other problems with the template.  Firstly, it
matches on any kind of node() (and on attributes - which are themselves a
type of node) whereas you actually want it to only apply to elements.
Secondly, it doesn't do anything about the attributes that you want
included - you need to apply templates specifically to emit them.

Here are three templates that may do roughly what you want (the colours
might need changing a bit, and I don't know much about formatting objects,
so I might have done something illegal with them):

<xsl:template match="*" mode="code">
  <fo:block text-align="start" line-height="1.5" font-family="Courier"
start-indent="20pt" end-indent="20pt" >
    <fo:inline-sequence color="silver">
      <xsl:text>&lt;</xsl:text>
      <xsl:value-of select="name()"/>
      <xsl:apply-templates select="@*" mode="code" />
      <xsl:text>&gt;</xsl:text>
    </fo:inline-sequence>
    <xsl:apply-templates mode="code" />
    <fo:inline-sequence color="silver">
       <xsl:text />&lt;/<xsl:value-of select="name()"/>&gt;<xsl:text />
    </fo:inline-sequence>
  </fo:block>
</xsl:template>

<xsl:template match="@*" mode="code">
  <xsl:text> </xsl:text>
  <fo:inline-sequence color="blue">
    <xsl:value-of select="name()" />="<xsl:text />
    <fo:inline-sequence color="#B00">
      <xsl:value-of select="." />
    </fo:inline-sequence>
    <xsl:text>"</xsl:text>
  </fo:inline-sequence>
</xsl:template>

<xsl:template match="text()" mode="code">
  <fo:inline-sequence color="#B00">
    <xsl:value-of select="."/>
  </fo:inline-sequence>
</xsl:template>

I hope that this helps,

Jeni

Jeni Tennison
http://www.jenitennison.com/


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


Current Thread