Search code examples
xmlxsltnamespacesprefix

Map XSLT input document prefixes to preferred values


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


Solution

  • 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>