1
votes

(XML version="1.0") I'm quite new to XSLT. I have been given the the following XML:

<SNAPSHOTS>
  <SNAPSHOT>
    <ID>1</ID>
    <STATUS>COM</STATUS>
  </SNAPSHOT>
  <SNAPSHOT>
    <ID>2</ID>
    <STATUS>ACC</STATUS>
  </SNAPSHOT>
</SNAPSHOTS>

As of my requirements I've managed to transformed the provided XML to be contained within a <DC> root element and include a header before the <SNAPSHOTS> element.

<?xml version="1.0" encoding="utf-8"?>
<DC>
  <Header>
    <FileName>TEST.xml</FileName>
    <NoOfRecords>2</NoOfRecords>
  </Header>

  <SNAPSHOTS>
    <SNAPSHOT>
      <ID>1</ID>
      <STATUS>COM</STATUS>
    </SNAPSHOT>
    <SNAPSHOT>
      <ID>2</ID>
      <STATUS>ACC</STATUS>
    </SNAPSHOT>
  </SNAPSHOTS>
<DC>

This is the XSD I've created:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" />

    <xsl:template match="/">
        <DC>
         <Header>
            <FileName>TEST.xml</FileName>
            <NoOfRecords><xsl:value-of select="count(SNAPSHOTS/SNAPSHOT)"/></NoOfRecords>
         </Header>
        <xsl:apply-templates select="node()"/>
        </DC>
    </xsl:template>

    <xsl:strip-space elements="*"/>

     <xsl:template match="node()" name="removeNode">
         <xsl:copy>
           <xsl:apply-templates select="node()"/>
         </xsl:copy>
     </xsl:template>

 </xsl:stylesheet>

When I include namespaces into the <DC> the namespace gets copied to the node also.

    <?xml version="1.0"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:output method="xml" indent="yes" />

        <xsl:template match="/">
            <DC xmlns="http://TEST/TEST.xsd"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"> <<-- NAMESPACE ADDED
             <Header>
                <FileName>TEST.xml</FileName>
                <NoOfRecords><xsl:value-of select="count(SNAPSHOTS/SNAPSHOT)"/></NoOfRecords>
             </Header>
            <xsl:apply-templates select="node()"/>
            </DC>

Output:

<?xml version="1.0" encoding="utf-8"?>
<DC xmlns="http://TEST/TEST.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <Header>
    <FileName>TEST.xml</FileName>
    <NoOfRecords>2</NoOfRecords>
  </Header>

  <SNAPSHOTS xmlns="">    <<--INCORRECT
    <SNAPSHOT>
      <ID>1</ID>
      <STATUS>COM</STATUS>
    </SNAPSHOT>
    <SNAPSHOT>
      <ID>2</ID>
      <STATUS>ACC</STATUS>
    </SNAPSHOT>
  </SNAPSHOTS>
<DC>

I've tried many solutions (Googling 'XSLT copy node without namespace' etc.). One of the way's I've tried is the following (xsl copy nodes without xmlns) but It does not remove the xmlns=""

<xsl:apply-templates select="*"  mode="copy-no-namespaces"/>

<xsl:template match="*" mode="copy-no-namespaces">
    <xsl:element name="{local-name()}">
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates select="node()" mode="copy-no-namespaces"/>
    </xsl:element>
</xsl:template>

I've also read the following but don't fully understand the accepted answer. enter link description here

I have been working on this for hours and there is obviously something I just don't get. I sorry if I'm missing something simple considering similar questions have been asked before!! Many thanks your any help you can provide! Thomas

2

2 Answers

2
votes

In your input XML, the SNAPSHOTS node, and all descendant nodes, are not part of any namespace. In your XSLT, you are adding a default namespace to DC, but you then copy the SNAPSHOTS from the input which has no namespace, so the xmlns='' has to be added to show the SNAPSHOTS is not in any namespace. Without it, it would mean SNAPSHOTS was part of the default namespace you added for DC.

Now, assuming you want your output like this:

<DC xmlns="http://TEST/TEST.xsd">
  <Header>
    <FileName>TEST.xml</FileName>
    <NoOfRecords>2</NoOfRecords>
  </Header>
  <SNAPSHOTS>
    <SNAPSHOT>
      <ID>1</ID>
      <STATUS>COM</STATUS>
    </SNAPSHOT>
    <SNAPSHOT>
      <ID>2</ID>
      <STATUS>ACC</STATUS>
    </SNAPSHOT>
  </SNAPSHOTS>
</DC>

Then, instead of copying the SNAPSHOTS nodes as-is, you create new nodes with the same local-name, but which are part of the same namespace.

Try this XSLT

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

    <xsl:template match="/">
        <DC xmlns="http://TEST/TEST.xsd">
         <Header>
            <FileName>TEST.xml</FileName>
            <NoOfRecords><xsl:value-of select="count(SNAPSHOTS/SNAPSHOT)"/></NoOfRecords>
         </Header>
        <xsl:apply-templates select="node()"/>
        </DC>
    </xsl:template>

     <xsl:template match="*" name="removeNode">
         <xsl:element name="{local-name()}" namespace="http://TEST/TEST.xsd">
           <xsl:apply-templates select="node()"/>
         </xsl:element>
     </xsl:template>
</xsl:stylesheet>

Note that SNAPSHOTS is now part of the http://TEST/TEST.xsd and so not the same as the SNAPSHOTS in the input, which is not part of a namespace.

On the otherhand, perhaps SNAPSHOTS should not be part of a namespace? In this case, you could try using namespace prefixes, like so:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" />

    <xsl:strip-space elements="*"/>

    <xsl:template match="/">
        <t:DC xmlns:t="http://TEST/TEST.xsd">
         <t:Header>
            <t:FileName>TEST.xml</t:FileName>
            <t:NoOfRecords><xsl:value-of select="count(SNAPSHOTS/SNAPSHOT)"/></t:NoOfRecords>
         </t:Header>
        <xsl:apply-templates select="node()"/>
        </t:DC>
    </xsl:template>

     <xsl:template match="node()" name="removeNode">
         <xsl:copy>
           <xsl:apply-templates select="node()"/>
         </xsl:copy>
     </xsl:template>
</xsl:stylesheet>

This should output the following

<t:DC xmlns:t="http://TEST/TEST.xsd">
   <t:Header>
      <t:FileName>TEST.xml</t:FileName>
      <t:NoOfRecords>2</t:NoOfRecords>
   </t:Header>
   <SNAPSHOTS>
      <SNAPSHOT>
         <ID>1</ID>
         <STATUS>COM</STATUS>
      </SNAPSHOT>
      <SNAPSHOT>
         <ID>2</ID>
         <STATUS>ACC</STATUS>
      </SNAPSHOT>
   </SNAPSHOTS>
</t:DC>
1
votes

The problem is the following: You declare a default namespace on one output element (DC, a newly created element), but not on the elements inside it (SNAPSHOT and so on, which are retrieved from the input XML). The XSLT processor is forced to assume that you meant to say that those elements inside are not in this default namespace.

If you also declare the same default namespace on the remainder of the elements from the input XML:

<xsl:template match="*" name="removeNode">
   <xsl:element name="{name()}" xmlns="http://TEST/TEST.xsd">

the namespace undeclaration disappears. To me, it is still unclear why you need to declare those two namespaces on the DC element.

Stylesheet

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" />

    <xsl:template match="/">
        <DC xmlns="http://TEST/TEST.xsd"
                xmlns:xs="http://www.w3.org/2001/XMLSchema">
         <Header>
            <FileName>TEST.xml</FileName>
            <NoOfRecords><xsl:value-of select="count(SNAPSHOTS/SNAPSHOT)"/></NoOfRecords>
         </Header>
        <xsl:apply-templates select="node()"/>
        </DC>
    </xsl:template>

    <xsl:strip-space elements="*"/>

     <xsl:template match="*" name="removeNode">
         <xsl:element name="{name()}" xmlns="http://TEST/TEST.xsd">
           <xsl:apply-templates/>
         </xsl:element>
     </xsl:template>

 </xsl:stylesheet>

XML Output

<?xml version="1.0" encoding="utf-8"?>
<DC xmlns="http://TEST/TEST.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <Header>
      <FileName>TEST.xml</FileName>
      <NoOfRecords>2</NoOfRecords>
   </Header>
   <SNAPSHOTS>
      <SNAPSHOT>
         <ID>1</ID>
         <STATUS>COM</STATUS>
      </SNAPSHOT>
      <SNAPSHOT>
         <ID>2</ID>
         <STATUS>ACC</STATUS>
      </SNAPSHOT>
   </SNAPSHOTS>
</DC>