Re: [xsl] Trouble with namespaces and running identity transform on XHTML

Subject: Re: [xsl] Trouble with namespaces and running identity transform on XHTML
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 10 Mar 2004 09:23:33 +0000
Hi James,

> As far as I understand, it transforms elements and attributes from
> the source document, which could be either in the
> "http://www.w3.org/1999/xhtml"; namespace or the null namespace, into
> elements that by default are in the default namespace declared in
> the xsl:transform element. (AFAIK, since I used name() rather than
> local-name() in the templates matching "*" and "@*", a source
> document containing both XHTML and MathML should pass through intact
> . . . I think.)

This isn't quite true. The XSLT processor interprets the value of the
name attribute based on the namespace declarations that are in effect
on the <xsl:element> element. So if you've got:

  <xsl:element name="{name()}">
    ...
  </xsl:element>

then if the name() is "p" (no prefix) then you will get an element
called "p" in the default namespace for the stylesheet (in this case
the XHTML namespace). However, if the name() is "html:p" or
"math:math" you will get an error because the prefixes 'html' and
'math' aren't declared in the stylesheet. Also, if the name() is
"math", and the namespace is the MathML namespace then the result will
be a <math> element in the XHTML namespace (since that's the default
namespace in the stylesheet).

>From your description, if an element is in any namespace (XHTML or
MathML or whatever), you want to copy it. If it's not in any
namespace, you want to create an element with the same local name and
in the XHTML namespace. To do that, you should use:

  <xsl:choose>
    <xsl:when test="namespace-uri()">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()" />
      </xsl:copy>
    </xsl:when>
    <xsl:otherwise>
      <xsl:element name="{name()}"
                   namespace="http://www.w3.org/1999/xhtml";>
        <xsl:apply-templates select="@*|node()" />
      </xsl:element>
    </xsl:otherwise>
  </xsl:choose>

If you want the output normalised such that none of the elements use
prefixes (which is a good idea with XHTML, so that legacy browsers
understand it) then you could also test for that, and use local-name()
to set the name of the element (and the namespace attribute to set its
namespace explicitly).

> Here's what I don't get. If I use just "h1" in place of
> "*[local-name() = 'h1']", then the stylesheet only works for XHTML
> sources with *no* DOCTYPE or explicitly set xmlns attribute. Judging
> from http://www.dpawson.co.uk/xsl/sect2/N2281.html#d3008e295, I know
> that if xmlns:h="http://www.w3.org/1999/xhtml"; were in
> xsl:transform, then the pattern "h:h1" would match h1 elements in an
> XHTML source document with an xmlns attribute set to
> "http://www.w3.org/1999/xhtml";. Extrapolating from that, I would
> have concluded that if if xmlns="http://www.w3.org/1999/xhtml"; were
> in xsl:transform, then the pattern "h1" should match h1 elements in
> an XHTML source document with an xmlns attribute set to
> "http://www.w3.org/1999/xhtml";,

This extrapolation is where you've gone wrong. The default namespace
declaration (xmlns attribute) *doesn't* get used when interpreting
element names in XPaths or patterns. The pattern "h1" only matches
elements that are called "h1" and are in *no* namespace. If you want
to match elements that are in a namespace, you have to give the
element name a prefix in the stylesheet.

(This is something that often catches people out quite a lot in XSLT
1.0; in XSLT 2.0, you can use the "xpath-default-namespace" attribute
to specify a namespace that's used to interpret element names that
don't have a prefix.)

> and from that, concluded that if I replaced "*[local-name() = 'h1']"
> with "h1" in the above stylesheet, the stylesheet would then work
> only on a) XHTML documents with the xmlns attribute set to
> "http://www.w3.org/1999/xhtml"; and on b) XHTML documents with
> DOCTYPE declarations--if the XSLT processor actually makes use of
> the DTD declared in the DOCTYPE declaration. Yet it works out the
> exact opposite of what I expect.

The pattern "*[local-name() = 'h1']" will match any element whose
local name (the bit after the prefix, if there is one) is 'h1'. That
element can be in any namespace at all, it doesn't matter. If you only
want to match h1 elements in the XHTML namespace, you should declare
the XHTML namespace with a prefix (e.g. 'h') and use the pattern
"h:h1".

Cheers,

Jeni

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


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


Current Thread