views:

466

answers:

3

I would like to avoid producing a repeated namespace in my XSLT output.

(I am using XSLT to massage some XML so that Microsoft's DataContractSerializer sees fit to actually process it properly. One of the things that the DCS doesn't seem to like is defining the same namespace multiple times.)

I am taking all of the "Characteristics" elements from under the XXX element and grouping them together in a new array element like so:

<xsl:stylesheet version="1.0" 
              xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
              xmlns:feature="some namespace">
<xsl:template match="feature:XXX">
  <xsl:copy>
    <feature:Characteristics>
      <xsl:apply-templates select="feature:Characteristics"/>
     </feature:Characteristics>
    <xsl:copy-of select="*[not(self::feature:Characteristics)]" />
  </xsl:copy>
</xsl:template>
<xsl:template match="feature:Characteristics">
  <arrays:unsignedShort xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;
    <xsl:value-of select="." />
  </arrays:unsignedShort>
</xsl:template>
</xsl:stylesheet>

The "feature" namespace is defined at the top of the XSLT file. However the value "feature" is not going to be found in the source XML, it will be some arbitrary prefix (usually "h"). The problem is that if I use this XSLT then the namespace is assigned to 2 prefixes in the output XML: the original namespace from the input XML (usually "h") and "feature" generated by the XSLT. (While valid XML, this confuses poor Microsoft.)

So I would like to completely avoid defining the "feature" namespace in the output XML by instead referring to the current element's namespace instead. I tried variants on this but I don't know how to set the namespace value of the xsl:element correctly in order to get the current context node's namespace prefix...

<xsl:stylesheet version="1.0" 
              xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
              xmlns:feature="some namespace">
  <xsl:template match="feature:XXX">
   <xsl:copy>
    <xsl:element name="Characteristics" namespace="{???}">
      <xsl:apply-templates select="feature:Characteristics"/>
    </xsl:element>
    <xsl:copy-of select="*[not(self::feature:Characteristics)]" />
  </xsl:copy>
</xsl:template>
  <xsl:template match="feature:Characteristics">
  <arrays:unsignedShort xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;
    <xsl:value-of select="." />
  </arrays:unsignedShort>
</xsl:template>
</xsl:stylesheet>

Sample input:

<h:XXX xmlns:h="http://stackoverflow.com"&gt;
 <h:Characteristics>7</h:Characteristics>
 <h:Characteristics>11</h:Characteristics>
 <h:Characteristics>12</h:Characteristics>
 <h:ProductName>blah</h:ProductName>
 <h:Vendor>blah</h:Vendor>
 <h:Version></h:Version>
</h:XXX>

Repeat namespaces (bad):

<h:XXX xmlns:h="http://stackoverflow.com"&gt;
 <feature:Characteristic xmlns:feature="http://stackoverflow.com"&gt;
      <arrays:unsignedShort xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;7&lt;/arrays:unsignedShort&gt;
      <arrays:unsignedShort xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;11&lt;/arrays:unsignedShort&gt;
      <arrays:unsignedShort xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;12&lt;/arrays:unsignedShort&gt;
 </feature:Characteristic>
 <h:ProductName>blah</h:ProductName>
 <h:Vendor>blah</h:Vendor>
 <h:Version></h:Version>
</h:XXX>

Desired output:

<h:XXX xmlns:h="http://stackoverflow.com"&gt;
 <h:Characteristic>
      <arrays:unsignedShort xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;7&lt;/arrays:unsignedShort&gt;
      <arrays:unsignedShort xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;11&lt;/arrays:unsignedShort&gt;
      <arrays:unsignedShort xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;12&lt;/arrays:unsignedShort&gt;
 </h:Characteristic>
 <h:ProductName>blah</h:ProductName>
 <h:Vendor>blah</h:Vendor>
 <h:Version></h:Version>
</h:XXX>

This XSLT almost works, but it involves hardcoding the "h" value, and I would like to support an arbitrary value instead:

<?xml version ="1.0" encoding="iso-8859-1"?>
  <xsl:stylesheet version="1.0" 
              xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
              xmlns:feature="some ns"
              xmlns:h="some ns"
              exclude-result-prefixes="h">

<xsl:template match="feature:XXX">
  <xsl:copy>
    <h:Characteristic>
      <xsl:apply-templates select="feature:Characteristics"/>
    </h:Characteristic>
    <xsl:copy-of select="*[not(self::feature:Characteristics)]" />
  </xsl:copy>
</xsl:template>

<xsl:template match="feature:Characteristics">
  <arrays:unsignedShort xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;
    <xsl:value-of select="." />
  </arrays:unsignedShort>
 </xsl:template>

 </xsl:stylesheet>
+1  A: 

I just removed that feature namespace; if I understood correctly, it'll contain same URI than h, right?

<xsl:stylesheet version="1.0"
              xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
              xmlns:h="http://stackoverflow.com"&gt;
  <xsl:output method="xml" indent="yes" />
  <xsl:template match="h:XXX" >
    <xsl:copy>
      <h:Characteristics>
        <xsl:apply-templates select="h:Characteristics"/>
      </h:Characteristics>
      <xsl:copy-of select="*[not(self::h:Characteristics)]" />
    </xsl:copy>
  </xsl:template>
  <xsl:template match="h:Characteristics">
    <arrays:unsignedShort 
        xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;
      <xsl:value-of select="." />
    </arrays:unsignedShort>
  </xsl:template>
</xsl:stylesheet>
Rubens Farias
The h namespace will get defined a second time on the Characteristics element in the output, which won't work for me.
evilfred
I didn't saw that in my environment; how are you testing that XSLT transformation?
Rubens Farias
in C# // Load the style sheet. XslCompiledTransform xslt = new XslCompiledTransform(); xslt.Load(convertFilename); // Execute the transform and output the results to a file. xslt.Transform(sourceFile, "blah.xml");
evilfred
+1  A: 

This seems to work for me, I am sure there must be an easier way but I cannot think at this moment. All this does it use the name() function to get the prefixed name of the root name, substring-before() to get the prefix then rebuild the values and emit them as unescaped text.

<xsl:stylesheet version="1.0" 
          xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
          xmlns:feature="http://stackoverflow.com"
      exclude-result-prefixes="feature" >
<xsl:template match="feature:XXX">
  <xsl:variable name="p" select="substring-before(name(), ':')"/>
  <xsl:copy>
   <xsl:value-of select="concat('&lt;', $p,':Characteristic', '&gt;')" disable-output-escaping="yes"/>
   <xsl:apply-templates select="feature:Characteristics"/>
   <xsl:value-of select="concat('&lt;/', $p,':Characteristic', '&gt;')" disable-output-escaping="yes"/>
   <xsl:copy-of select="*[not(self::feature:Characteristics)]" /> 
 </xsl:copy>
</xsl:template>
  <xsl:template match="feature:Characteristics">
   <arrays:unsignedShort 
       xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;
     <xsl:value-of select="." />
   </arrays:unsignedShort>
 </xsl:template>
</xsl:stylesheet>
tyranid
It works! Thanks!
evilfred
+1  A: 

If you want consistent namespace prefixes then you will need to generate new elements, rather than copy-of. Set your namespace prefix to whatever you want it to be (feature, h, etc) in the XSLT.

The following XSLT:

<xsl:stylesheet version="1.0"
              xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
              xmlns:feature="http://stackoverflow.com"
              xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;
  <xsl:output method="xml" indent="yes" />

  <xsl:template match="feature:XXX">
   <xsl:element name="feature:XXX">
    <feature:Characteristic>
      <xsl:apply-templates select="feature:Characteristics"/>
    </feature:Characteristic>
    <xsl:apply-templates select="*[not(self::feature:Characteristics)]" />
  </xsl:element>
  </xsl:template>

  <xsl:template match="feature:Characteristics">
    <xsl:element name="arrays:unsignedShort">
        <xsl:value-of select="." />
    </xsl:element>
  </xsl:template>

  <xsl:template match="*">
     <xsl:element name="feature:{local-name()}" >
        <xsl:value-of select="." />
     </xsl:element>
  </xsl:template>

</xsl:stylesheet>

Generates the following output:

<?xml version="1.0" encoding="UTF-16"?>
<feature:XXX xmlns:feature="http://stackoverflow.com"&gt;
 <feature:Characteristic xmlns:arrays="http://schemas.microsoft.com/2003/10/Serialization/Arrays"&gt;
  <arrays:unsignedShort>7</arrays:unsignedShort>
  <arrays:unsignedShort>11</arrays:unsignedShort>
  <arrays:unsignedShort>12</arrays:unsignedShort>
 </feature:Characteristic>
 <feature:ProductName>blah</feature:ProductName>
 <feature:Vendor>blah</feature:Vendor>
 <feature:Version></feature:Version>
</feature:XXX>
Mads Hansen
Thanks, very elegant
evilfred