Re: [xsl] Partial Implementation of XInclude include element

Subject: Re: [xsl] Partial Implementation of XInclude include element
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Mon, 23 Sep 2002 08:44:34 +0100
Hi Eliot,

> The code is shown below. This is a partial implementation in that it
> does not handle any possible XPointer, only document-level URLs.
> However, if you hooked in my XPointer implementation it should be
> possible to have a complete implementation. However, as XInclude is
> largely intended to replace the use of external parsed entities it's
> probably rare that people would use it to include subtrees of
> documents in most cases (although that happens to be a very useful
> feature in certain re-use and document management use cases).

You should find it fairly easy to extend what you have to handled
simple fragment identifiers, e.g.:

  foo.xml#bar

by splitting the string if it contains a '#' and using the id()
function to retrieve the node from the referenced document.

> The only big limitation I've found so far is that unparsed entity
> resolution doesn't work, presumably because there's no way to relate
> the nodes in the result tree fragment back to the documents they
> were copied from in order to lookup entity URIs. But the easy answer
> there is to not use external unparsed entities but direct URI
> references. I suppose there's probably a way to build up a mapping
> of entity names to URIs for later use but I haven't put any effort
> into it. One challenge there is that there's no way I know of to
> determine if a given attribute is of type ENTITY or ENTITIES from
> the XSLT style sheet--if there is, then the fix is pretty easy.

I guess that this is a use case for XPath 2.0's data typing support,
then. I think in XPath/XSLT 2.0 you'd handle it by either copying the
attribute (which should copy the value along with its type) or by
explicitly checking whether the attribute is an ENTITY or ENTITIES
type.

> <xsl:template match="*"  mode="xinclude">
>   <xsl:variable name="tagname" select="name()"/>
>   <xsl:element name="{$tagname}">
>     <xsl:for-each select="attribute::*">
>       <xsl:variable name="attname" select="name()"/>
>       <xsl:attribute name="{$attname}"><xsl:value-of
> select="."/></xsl:attribute>
>     </xsl:for-each>
>     <xsl:apply-templates mode="xinclude"/>
>   </xsl:element>
> </xsl:template>

I think that it's probably better to use an identity template here,
namely:

<xsl:template match="node() | @*" mode="xinclude">
  <xsl:copy>
    <xsl:apply-templates select="@* | node()" mode="xinclude" />
  </xsl:copy>
</xsl:template>

First, it's simpler. Second, it copies over comments and PIs, which
you probably should do. Third, and most important, it manages
namespaces correctly. As you currently have it, say you were
including:

<doc xmlns="http://www.example.com/foo";>
  ...
</doc>

and in your stylesheet:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                xmlns:xi="http://www.w3.org/2001/XInclude";
                xmlns="http://www.example.com/bar";>
...
</xsl:stylesheet>

The default namespace in the stylesheet is http://www.example.com/bar
while the default namespace in the document you're including is
http://www.example.com/foo.

Currently, if the <doc> element above were included, you'd be creating
the element with:

  <xsl:element name="doc">
    ...
  </xsl:element>

When you create an element in this way then the namespace for the
element is derived by looking at the prefix given in the name
attribute. If there's no prefix then the default namespace is used. So
whereas the <doc> element in the document you're including is in the
http://www.example.com/foo, the <doc> element you create has the
default namespace in the stylesheet -- http://www.example.com/bar.

You could do the right thing by doing:

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

instead, but xsl:copy is shorter, and it solves another
namespace-related problem.

The other namespace-related problem is that just creating the element
with xsl:element doesn't copy over any namespace nodes on the element
that you're "copying". This gets sticky if there are values that
contain namespace prefixes. For example, if I had:

<doc xmlns="http://www.example.com/foo";
     xmlns:baz="http://www.example.com/baz";>
  ...baz:value...
</doc>

and the "baz:value" was supposed to be interpreted as a qualified
name, what I'd end up with with your current code (even assuming the
default namespace declaration is http://www.example.com/foo) is:

<doc xmlns="http://www.example.com/foo";>
  ...baz:value...
</doc>

which won't make sense because 'baz' isn't bound to a namespace.

xsl:copy solves all these problems because it does a namespace-aware
copy and includes the namespace nodes from the element when it does
the copy.
  
> <xsl:template match="xi:include" mode="xinclude">
>   <xsl:variable name="xpath" select="@xi:href"/>
>   <xsl:choose>
>     <xsl:when test="$xpath != ''">
>       <xsl:message>Including <xsl:value-of select="$xpath"/></xsl:message>
>       <xsl:apply-templates select="document($xpath)" mode="xinclude"/>
>     </xsl:when>
>     <xsl:otherwise>
>       <xsl:message>Xinclude: Failed to get a value for the xi:href=
> attribute of xi:include element.</xsl:message>
>     </xsl:otherwise>
>   </xsl:choose>
> </xsl:template>

I believe (based on the February CR) that the 'href' attribute on
xi:include is a plain old 'href' attribute rather than an 'href'
attribute in the XInclude namespace ('xi:href'). I can't currently
access the W3C site to check whether this has changed in some later
version.

Cheers,

Jeni

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


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


Current Thread