views:

95

answers:

3

Using XSLT, I'm wondering how to get the output to use my stylesheet's namespace prefixes rather than the input document's prefixes. By way of example, given this very simplified document:

<?xml version="1.0"?>
<a:node xmlns:a="urn:schemas:blah:"/>

And the following XSL transform:

<?xml version="1.0"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:blah="urn:schemas:blah:" version="2.0">
   <xsl:output indent="yes">
   <xsl:template match="/blah:node">
      <xsl:copy/><!-- marked -->
   </xsl:template>
</xsl:transform>

I can tell that the processor (Saxon8 if it matters) recognizes the equivalence of the prefixes 'blah:' and 'a:', but fn:in-scope-prefixes() for example doesn't show 'blah', only 'a'. Changing the <!-- marked --> line above to:

<node><xsl:value-of select="in-scope-prefixes(.)"/></node>

Outputs:

<?xml version="1.0" encoding="UTF-8"?>
<node xmlns:blah="urn:schemas:blah:">xml a</node>

How can I map the input prefix 'a' to 'blah' without knowing in advance that the input file calls that prefix 'a'? (So <xsl:namespace-alias/> won't work for me.)

As further context, if it points toward a better solution, this is for viewing XML documents that are generated externally. The external process creates the input document using automatically-generated prefixes 'a:', 'b:', 'c:', etc. I want to be able to display those prefixes using 'friendlier' namespace prefixes.

Update: The in-scope-prefixes() behavior is explained by the definition of Statically known namespaces

A: 

Take a look at the specification for the <xsl:namespace-alias ...> top-level element. I believe it will accomplish what you want.

Jim Garrison
The specification that I specifically mentioned wouldn't work. :-) I looked again. It appears to operate on prefix names, not namespace URIs. So, the fact that 'urn:schemas:blah:' is 'a:' in one run of the external process and 'b:' in another run prevents it from working here, right?
benizi
+1  A: 

How about the identity transform plus this template:

<xsl:template match="blah:*">
  <xsl:element name="blah:{local-name()}">
    <xsl:apply-templates select="*|@*" />
  </xsl:element>
</xsl:template>

I'm not certain that this is the most elegant way to do it in XSLT 2.0, but it works.

Tomalak
This looks like it'll end up working for me (using this as a first-pass to clean up the prefix names, then using my old XSLT as the second). Is there a nice way to get the namespaces declared on the root element? As it is, if I have <r><a:node/></r>, the namespace nodes are attached to the <blah:node/> rather than the <r>.
benizi
+2  A: 

This transformation (both in XSLT 1.0 and XSLT 2.0 (just change the version attribute)) :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my"
 >
  <xsl:output omit-xml-declaration="yes" indent="yes"/>
  <xsl:strip-space elements="*"/>

  <my:namespaces>
    <ns prefix="blah" uri="urn:schemas:blah:"/>
    <ns prefix="foo" uri="uff:anotherNamespace"/>
  </my:namespaces>

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

 <xsl:template match=
 "*[namespace-uri()=document('')/*/my:namespaces/*/@uri]">
  <xsl:variable name="vNS" select=
  "document('')/*/my:namespaces/*
                   [@uri=namespace-uri(current())]"/>
  <xsl:element name="{$vNS/@prefix}:{local-name()}"
       namespace="{namespace-uri()}">
   <xsl:copy-of select=
    "namespace::*[not(. = namespace-uri(current()))]"/>
   <xsl:copy-of select="@*"/>
   <xsl:apply-templates/>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

Copies any XML document and only replaces the prefixes this document uses for select namespaces with the prefixes we have specified.

when applied on this XML document:

<t>
    <a:node xmlns:a="urn:schemas:blah:"/>
    <b:node xmlns:b="urn:schemas:blah:"/>
    <c:node xmlns:c="uff:anotherNamespace"/>
</t>

the wanted result is produced:

<t>
   <blah:node xmlns:blah="urn:schemas:blah:"/>
   <blah:node xmlns:blah="urn:schemas:blah:"/>
   <foo:node xmlns:foo="uff:anotherNamespace"/>
</t>
Dimitre Novatchev
Very nice. Easy to add namespace+prefix pairs, and unchanged prefixes get nicely passed through as-is.
benizi
Nice solution, as always.
Tomalak