0

I saw the post XSLT : Add a namespace declaration to the root element

The @StuartLC answer works in the root. I need help... ¿How would you add a new namespace to a non-root node?

The input XML document (yes, a horror but it's from the client)

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Header/>
    <S:Body xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
        <ns3:Response xmlns="ttn/xml" xmlns:ns2="rtm/xml" xmlns:ns3="ws/xml">
            <ns3:record>
                <registryID>
                    <registryNumber>232019324</registryNumber>
                </registryID>
                <ns2:Date>28-08-2019 09:12:32</ns2:Date>
                <ns2:registry>
                    <ns2:type>otp</ns2:type>
                    <sender>
                        <ID>260</ID>
                    </sender>
                </ns2:registry>
            </ns3:record>
        </ns3:Response>
    </S:Body>
</soapenv:Envelope>

And I'd want to obtain:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Header/>
    <S:Body xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
        <ns3:Response xmlns:ns1="ttn/xml" xmlns="ttn/xml" xmlns:ns2="rtm/xml" xmlns:ns3="ws/xml">
            <record>
                <ns1:registryID>
                    <ns1:registryNumber>232019324</ns1:registryNumber>
                </ns1:registryID>
                <ns2:Date>28-08-2019 09:12:32</ns2:Date>
                <ns2:registry>
                    <ns2:type>otp</ns2:type>
                    <ns1:sender>
                        <ns1:ID>260</ns1:ID>
                    </ns1:sender>
                </ns2:registry>
            </record>
        </ns3:Response>
    </S:Body>
</soapenv:Envelope>

I need to add/remove some prefixes nodes. Eg node <ns3:record> -> <record> or node <registryID> -> <ns1:registryID> or node <sender> -> <ns1:sender>.... there are more nodes xml imput but I've only put a few.

I need include ns1 in the node Response because when i import to SAP PO it says "ns1 is not declared". I supposed it i added the prefix ns1 to the node but i don't know. I tried manually in test SAP include ns1 in the node and work it. After in other transformations xslt, only i need the node Response of all xml to map.

This is xslt that im using:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  xmlns="ttn/xml" xmlns:ns2="rtm/xml" xmlns:ns3="ws/xml"     xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

<xsl:output omit-xml-declaration="yes" method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:variable name ="mynoderecord">record</xsl:variable>
<xsl:variable name ="mynodeResponse">Response</xsl:variable> 
<xsl:variable name ="mynoderegistryNumber">registryNumber</xsl:variable>
<xsl:variable name ="mynodeRegistryID">registryID</xsl:variable>
<xsl:variable name ="mynodesender">sender</xsl:variable>


  <xsl:template match="*" >
  <xsl:choose>

    <xsl:when test="local-name() = $mynodeResponse">
     <xsl:element name="ns3:{local-name()}" xmlns:ns3="ws/xml">
    <xsl:attribute name="ns1:nsdf" namespace="ttn/xml">sdf</xsl:attribute>

       <xsl:for-each select="@*">
           <xsl:attribute name="{local-name()}">
           <xsl:value-of select="."/>
         </xsl:attribute>
       </xsl:for-each>
      <xsl:apply-templates/>
     </xsl:element>
    </xsl:when>


    <xsl:when test="local-name() = $mynoderegistryNumber" >
     <xsl:element name="ns1:{name()}" xmlns:ns1="ttn/xml">
       <!-- procesar atributos de nodo -->
       <xsl:for-each select="@*">
       <!-- eliminar prefijo de atributo -->
         <xsl:attribute name="{local-name()}">
           <xsl:value-of select="."/>
         </xsl:attribute>
       </xsl:for-each>
      <xsl:apply-templates/>
     </xsl:element>
    </xsl:when>

    <xsl:when test="local-name() = $mynodesender" >
     <xsl:element name="ns1:{name()}" xmlns:ns1="ttn/xml">
       <!-- procesar atributos de nodo -->
       <xsl:for-each select="@*">
       <!-- eliminar prefijo de atributo -->
         <xsl:attribute name="{local-name()}">
           <xsl:value-of select="."/>
         </xsl:attribute>
       </xsl:for-each>
      <xsl:apply-templates/>
     </xsl:element>
    </xsl:when>

    <xsl:when test="local-name() = $mynodeRegistryID" >
     <xsl:element name="ns1:{name()}" xmlns:ns1="ttn/xml">
       <!-- procesar atributos de nodo -->
       <xsl:for-each select="@*">
       <!-- eliminar prefijo de atributo -->
         <xsl:attribute name="{local-name()}">
           <xsl:value-of select="."/>
         </xsl:attribute>
       </xsl:for-each>
      <xsl:apply-templates/>
     </xsl:element>
    </xsl:when>

    <xsl:when test="local-name() = $mynoderecord">
     <xsl:element name="{local-name()}" xmlns:ns3="ws/xml">
       <!-- procesar atributos de nodo -->
       <xsl:for-each select="@*">
       <!-- eliminar prefijo de atributo -->
         <xsl:attribute name="{local-name()}">
           <xsl:value-of select="."/>
         </xsl:attribute>
       </xsl:for-each>
      <xsl:apply-templates/>
     </xsl:element>
    </xsl:when>


    <xsl:otherwise>
     <xsl:element name="{name()}">
       <!-- procesar atributos de nodo -->
       <xsl:for-each select="@*">
       <!-- eliminar prefijo de atributo -->
         <xsl:attribute name="{local-name()}">
           <xsl:value-of select="."/>
         </xsl:attribute>
       </xsl:for-each>
      <xsl:apply-templates/>
     </xsl:element>
     </xsl:otherwise>

  </xsl:choose>

  </xsl:template>

</xsl:stylesheet>

Searching i saw that adding an attribute to node <ns3:Response>, namespace ns1 is automatically added... but i don't like. Surely there is a simply solution with better code ... the xsl will drive me crazy.

I tried, but I couldn't find any easier suitable solution .

Could you please advise me?

Thanks

Blas
  • 3
  • 4
  • The result you show makes very little sense: `record` is in the `"ttn/xml"` namespace (inherited from the default namespace declaration on the parent `ns3:Response`). `ns1:registryID` is also in the same `"ttn/xml"` namespace, because that's the namespace bound to the `ns1` prefix. Why use a prefix on one but not on the other (or vice versa)? – michael.hor257k Sep 28 '19 at 15:42
  • @michael.hor257k yes true, but i have to add prefix in nodes hasn't prefix, ... and all the nodes from default namespace ttn/xml will have bound to the ns1 (i don't undestand well) – Blas Sep 30 '19 at 06:35
  • So why shouldn't `record` be `ns1:record`? -- Note that prefixes should not be important. Likewise the placement of the namespace declarations. What is important is for the node to be in the correct namespace. If you're dealing with a parser that depends on either of the two, you are facing unpredictable results no matter what you do. – michael.hor257k Sep 30 '19 at 07:27
  • @michael.hor257k yes, i agree with you ... but the client response is that, the record element eg have prefix and i have to remove this, other i have to add eg registryID ... because i have to import to SAP with that structure. – Blas Sep 30 '19 at 09:10

2 Answers2

0

You need to distinguish "adding a namespace node" from "changing the expanded name of an element".

For the Response element, you are adding a namespace node without changing the names of any element or attribute. In XSLT 2.0 you can do that with the xsl:namespace instruction. In XSLT 1.0 it can only be done with a rather convoluted workaround: create a dummy element in the new namespace, and then copy its namespace node:

<xsl:variable name="dummy">
  <ns1:dummy xmlns:ns1="ttn/xml"/>
</xsl:variable>
<xsl:copy-of select="exsl:node-set($dummy)//namespace::*"/>

For the registryID element you are actually changing the expanded name of the element, and to achieve that you typically need to use <xsl:element name="ns1:{local-name()}/>`

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • sorry but, where should i put that code in the code? out or Response element? – Blas Sep 30 '19 at 06:47
  • Well, I would totally restructure your code to use template rules rather than a big xsl:choose and to use literal result elements rather than xsl:element, and I wouldn't try to generate unnecessary namespaces anyway; but if you want an extra namespace on the Response element, which you seem to, then that's where to put it. – Michael Kay Sep 30 '19 at 09:16
  • i tried to include in Response element in my code but the namespace doesn't appear – Blas Sep 30 '19 at 09:39
  • I would suggest starting a new question to show your code, your expected output, and your actual output. – Michael Kay Sep 30 '19 at 13:27
0

This may or may not return the exact result you expect to get, depending on your XSLT's processor's whim:

XSLT 1.0

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

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

<xsl:template match="ns3:Response">
     <ns3:Response xmlns:ns1="ttn/xml" xmlns="ttn/xml" xmlns:ns2="rtm/xml" xmlns:ns3="ws/xml">
        <xsl:apply-templates/>
    </ns3:Response>
</xsl:template>

<xsl:template match="ns3:record">
    <record xmlns="ttn/xml">
        <xsl:apply-templates/>
    </record>
</xsl:template>

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

</xsl:stylesheet>

Note that only the 3rd template does anything substantial: it moves ns3:record to a different namespace. The other templates are pure cosmetics and shouldn't be necessary.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Exactly!! But can you explain me the 4th template if registryID element hasn't prefix, how found template match="ns1:*"?? :O – Blas Sep 30 '19 at 11:50
  • @Blas The template does not look for elements with `ns1` prefix. It looks for elements that are in the namespace bound to the `ns1` prefix in the namespace declaration at the top level of the XSLT stylesheet. – michael.hor257k Sep 30 '19 at 14:35
  • it work but when i import to SAP i saw "missing the record element" and not work. Sure it's namespace problem :( – Blas Oct 01 '19 at 08:12
  • Does this happen when you try to import the **result** of the transformation? If so, the problem is with your expected result. – michael.hor257k Oct 01 '19 at 10:54
  • yes, the result of the transformation must map to a structure , the record element fails without prefix, i don't know what happen – Blas Oct 01 '19 at 12:34
  • I am afraid I cannot help you with that.You asked how to transform the given XML to the expected result. If you need another result, you must tell us what it is. – michael.hor257k Oct 01 '19 at 14:43