xsl-list
[Top] [All Lists]

Re: Trouble with namespaces and running identity transform on XHTML

2004-03-10 02:23:33
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