简体   繁体   中英

Cannot add namespace prefix to children using XSL

I checked many answers here and I think I am almost there. One thing that is bugging me (and for some reason my peer needs it) follows:

I have the following input XML:

<?xml version="1.0" encoding="utf-8"?>
<MyRoot>
  <MyRequest CompletionCode="0" CustomerID="9999999999"/>
  <List TotalList="1">
    <Order CustomerID="999999999" OrderNo="0000000001" Status="Shipped">
      <BillToAddress ZipCode="22221"/>
      <ShipToAddress ZipCode="22222"/>
      <Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
    </Order>
  </List>
  <Errors/>
</MyRoot>

I was asked to produce this:

<ns:MyNewRoot xmlns:ns="http://schemas.foo.com/response"  
xmlns:N1="http://schemas.foo.com/request"  
xmlns:N2="http://schemas.foo.com/details">
    <N1:MyRequest CompletionCode="0" CustomerID="9999999999"/>
    <ns:List TotalList="1">
            <N2:Order CustomerID="999999999" Level="Preferred" Status="Shipped">
                    <N2:BillToAddress ZipCode="22221"/>
                    <N2:ShipToAddress ZipCode="22222"/>
                    <N2:Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
            </N2:Order>
    </ns:List>
    <ns:Errors/>
</ns:MyNewRoot>

Note the children of the N2:Order also needs N2: prefix as well as the ns: prefix for the rest of the elements.

I use the XSL transformation below:

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

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


<xsl:template match="/MyRoot">
 <MyNewRoot xmlns="http://schemas.foo.com/response"
   xmlns:N1="http://schemas.foo.com/request"
   xmlns:N2="http://schemas.foo.com/details">
     <xsl:apply-templates/>
 </MyNewRoot>
 </xsl:template>

<xsl:template match="/MyRoot/MyRequest">
  <xsl:element name="N1:{name()}" namespace="http://schemas.foo.com/request">
    <xsl:copy-of select="namespace::*"/>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="/MyRoot/List/Order">
  <xsl:element name="N2:{name()}" namespace="http://schemas.foo.com/details">
    <xsl:copy-of select="namespace::*"/>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:element>
 </xsl:template>

</xsl:stylesheet>

This one doesn't process the ns (I couldn't figure this out). When I process thru the above the XSL transformation with AltovaXML I end up with below:

<MyNewRoot xmlns="http://schemas.foo.com/response"  
xmlns:N1="http://schemas.foo.com/request"  
xmlns:N2="http://schemas.foo.com/details">
    <N1:MyRequest CompletionCode="0" CustomerID="9999999999"/>
    <List xmlns="" TotalList="1">
            <N2:Order CustomerID="999999999" Level="Preferred" Status="Shipped">
                    <BillToAddress ZipCode="22221"/>
                    <ShipToAddress ZipCode="22222"/>
                    <Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
            </N2:Order>
    </List>
    <Errors/>
</MyNewRoot>

Note that N2: prefix for the children of Order is not there after the XSL transformation. Also additional xmlns="" in the Order header (for some reason). I couldn't figure out putting the ns: prefix for the rest of the elements (like Errors and List).

First of all, why would I need to put the prefix for the children if the parent already has it. Doesn't the parent namespace dictate the children nodes/attribute namespaces?

Secondly, I want to add the prefixes in the above XML as expected, how can I do that with XSL?

This transformation (only 42 lines) :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ns="http://schemas.foo.com/response"
 xmlns:N1="http://schemas.foo.com/request"
 xmlns:N2="http://schemas.foo.com/details"
 >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="*">
  <xsl:element name="ns:{name()}"
       namespace="http://schemas.foo.com/response">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="@*">
   <xsl:copy-of select="."/>
 </xsl:template>

 <xsl:template match="/MyRoot">
  <xsl:element name="ns:{name()}"
       namespace="http://schemas.foo.com/response">
    <xsl:copy-of select=
     "document('')/*/namespace::*[name()='N1' or name()='N2']"/>
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="MyRequest">
  <xsl:element name="N1:{name()}"
       namespace="http://schemas.foo.com/request">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="*[ancestor-or-self::Order]">
  <xsl:element name="N2:{name()}"
       namespace="http://schemas.foo.com/details">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document :

<MyRoot>
  <MyRequest CompletionCode="0" CustomerID="9999999999"/>
  <List TotalList="1">
    <Order CustomerID="999999999" OrderNo="0000000001" Status="Shipped">
      <BillToAddress ZipCode="22221"/>
      <ShipToAddress ZipCode="22222"/>
      <Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
    </Order>
  </List>
  <Errors/>
</MyRoot>

produces the wanted result :

<ns:MyRoot xmlns:N1="http://schemas.foo.com/request" xmlns:N2="http://schemas.foo.com/details" xmlns:ns="http://schemas.foo.com/response">
    <N1:MyRequest CompletionCode="0" CustomerID="9999999999"/>
    <ns:List TotalList="1">
        <N2:Order CustomerID="999999999" OrderNo="0000000001" Status="Shipped">
            <N2:BillToAddress ZipCode="22221"/>
            <N2:ShipToAddress ZipCode="22222"/>
            <N2:Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
        </N2:Order>
    </ns:List>
    <ns:Errors/>
</ns:MyRoot>

Do note :

  1. The use of <xsl:element> and its name and namespace attributes.

  2. How the identity template has been evolved into the first two templates of the transformation -- this decision was based on the fact that only in exceptional cases an element must not be in the ns: namespace.

  3. How the N2: namespace is specified for the Order element or any of its descendent elements.

If you really care about what the namespace prefixes are in the output then you will want to use literal-result elements in your templates, rather than the xsl:element constructor:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns="http://schemas.foo.com/response"
    xmlns:N1="http://schemas.foo.com/request"  
    xmlns:N2="http://schemas.foo.com/details">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

    <xsl:template match="MyRoot">
        <ns:MyNewRoot>
            <xsl:apply-templates/>
        </ns:MyNewRoot>
    </xsl:template>

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

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

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

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

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

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

    <xsl:template match="Errors" />

</xsl:stylesheet>

You should know that the namespace is important , the namespace-prefix is not . It is syntactic sugar. You can have multiple namespace-prefixes bound to the same namespace-uri, or not have a namespace prefix and still produce the same type of elements(bound to a particular namespace-uri).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM