user2687359
user2687359

Reputation: 25

XSLT2.0 giving empty output when Input XML is having namespcaes

The requirement is to find the duplicate element(BaseName) in XML and marked the parent element(Account) with isDuplicate attribute. The XSL is working correctly when the input XML RootElement has no namespaces. When the root element has namespace then I get empty object. I am not sure why the namespace is causing XSL to generate empty output. Any help to get the right output would be greatly appreciated.`

Input XML WITH NAMESPACE

 <?xml version="1.0"?>
    <objects xmlns="urn:s.sexmaple.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <Account>
            <Id>001A00F</Id>
            <RecordTypeId>012A00</RecordTypeId>
            <BaseName>EFGH</BaseName>
        </Account>
       <Account>
            <Id>001A0</Id>
            <RecordTypeId>012A0</RecordTypeId>
            <BaseName>ABCD</BaseName>
        </Account>
       <Account>
            <Id>001A</Id>
            <RecordTypeId>012A</RecordTypeId>
            <BaseName>ABCD</BaseName>
        </Account>
    </objects>

XSL

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
        <xsl:output method="xml"
                    version="1.0"
                    encoding="UTF-8"
                    indent="yes"/>
        <xsl:strip-space elements="*" />
        <xsl:template match="node()|@*">
            <xsl:copy copy-namespaces="no">
                <xsl:apply-templates select="node()|@*" />
            </xsl:copy>
    </xsl:template>
    <xsl:template match="/">
        <xsl:variable name="Accounts">
            <objects>
                <xsl:for-each select="//Account">
                    <xsl:sort select="BaseName" />
                    <xsl:apply-templates select="." />
                </xsl:for-each>
            </objects>
        </xsl:variable>        
        <xsl:variable name="unqentity">
            <objects>
                <xsl:for-each select="$Accounts/objects/Account">
                    <xsl:choose>
                        <xsl:when test="not(following-sibling::Account/BaseName=./BaseName) and not(preceding-sibling::Account/BaseName=./BaseName) ">
                            <xsl:copy-of select="." />
                        </xsl:when>
                        <xsl:otherwise>
                            <Account>
                                <xsl:attribute name="isDuplicate">yes</xsl:attribute>
                                <xsl:for-each select="child::*">
                                    <xsl:element name="{name()}">
                                        <xsl:copy-of select="@*|node()" />
                                    </xsl:element>
                                </xsl:for-each>
                            </Account>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each>
            </objects>
        </xsl:variable>
        <xsl:copy-of select="$unqentity" />
    </xsl:template>
</xsl:stylesheet>

Output XML WHEN INPUT XML HAS NAMESPACE

 <?xml version="1.0" encoding="UTF-8"?>
    <objects/>

Output XML when Input has no Namespaces

<?xml version="1.0" encoding="UTF-8"?>
     <objects>
            <Account>
                <Id>001A00F</Id>
                <RecordTypeId>012A00</RecordTypeId>
                <BaseName>EFGH</BaseName>
            </Account>
           <Account isDuplicate="yes">
                <Id>001A0</Id>
                <RecordTypeId>012A0</RecordTypeId>
                <BaseName>ABCD</BaseName>
            </Account>
           <Account isDuplicate="yes">
                <Id>001A</Id>
                <RecordTypeId>012A</RecordTypeId>
                <BaseName>ABCD</BaseName>
            </Account>
        </objects>

Upvotes: 2

Views: 896

Answers (2)

user764357
user764357

Reputation:

The reason your input XML without a namespace works were the one with a namespace doesn't, isn't because of the input XML, but because of the XSLT stylesheet.

When your XML file has a default namespace, that namespace will need to be declared in the stylesheet itself.

For example, with the following XML:

<test xmlns="test.xml.schema">
  <element>Content</element>
</test>

When I apply the following XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:template match="/">
    <out>
       <namespace>None</namespace>
       <xsl:apply-templates />
    </out>
  </xsl:template>

  <xsl:template match="test">
    <test out="True">hello</test>
  </xsl:template>
  <xsl:template match="*"/>

</xsl:stylesheet>

The output is just:

<out><namespace>None</namespace></out>

The <xsl:template match="test"> can't match on the test element in the input xml, as it is actually test.xml.schema:test while the match in the stylesheet, with no namespace is actually :test. Thus no match is possible.

However, when we just add a namespace for the input document adn modify the template, like so:

<xsl:stylesheet xmlns:t="test.xml.schema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:template match="/">
    <out>
       <namespace>test.xml.schema</namespace>
       <xsl:apply-templates />
    </out>
  </xsl:template>

  <xsl:template match="t:test">
    <test out="True">hello</test>
  </xsl:template>
  <xsl:template match="*"/>

</xsl:stylesheet>    

The output becomes:

<out xmlns:t="test.xml.schema">
  <namespace>test.xml.schema</namespace>
  <test out="True">hello</test>
</out>

Its important to note that the namespace abbreviation in the input document and XSL don't need to be the same (eg. blank vs. "t"), but the namespaces themselfs do: (e.g both blank and "t" must be bound to test.xml.schema).

Also note, that using a default namespace in XSLT can be fraught with issues. So its best to use declared namespaces in XSLT.

Upvotes: 0

Tim C
Tim C

Reputation: 70638

When you have a namespace, it means the element within the namespace is not the same as an element without a namespace (or indeed an element in a different name space).

This means when you do this in your XSLT...

 <xsl:for-each select="//Account">

You are looking for an Account element with no namespace, and so it will not match the Account element in your source XML, which is in the amusingly titled "urn:s.sexmaple.com" (which I suspect is a misspelling)

As you are using XSLT2.0 though, there is a simple way to get around this, by specifying a default namespace for any xpath expressions, using the xpath-default-namespace. Normally, this may be enough, but you have slightly complicated matters by creating new elements within a variable, which you then later try to select.

<xsl:for-each select="$Accounts/objects/Account">

This means when you create the objects and Account elements in the $Accounts variable, they will need to be part of the namespace too.

To cut to the chase, this is what your xsl:stylesheet element needs to look like

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" 
                xmlns="urn:s.sexmaple.com" 
                xpath-default-namespace="urn:s.sexmaple.com">

So, the xpath-default-namespace="urn:s.sexmaple.com" is used to match elements in your source XML, whilst the xmlns="urn:s.sexmaple.com" is used to ensure the elements you create in the variable have this namespace and can be matched later on.

Having said all that, you have rather over-complicated your whole XSLT. Are you simply trying to add an IsDuplicate attribute to Account elements with the same BaseName? Well, create a key to look up duplicates, like so

<xsl:key name="account" match="Account" use="BaseName" />

Then you can look up duplicates like so:

         <xsl:if test="key('account', BaseName)[2]">
            <xsl:attribute name="isDuplicate">Yes</xsl:attribute>
         </xsl:if>

Try this XSLT, which should give the same results

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="urn:s.sexmaple.com">
    <xsl:output method="xml" indent="yes"/>

   <xsl:key name="account" match="Account" use="BaseName" />

   <xsl:template match="Account">
           <xsl:copy>
             <xsl:if test="key('account', BaseName)[2]">
                <xsl:attribute name="isDuplicate">Yes</xsl:attribute>
             </xsl:if>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
   </xsl:template>  

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

Note how this only needs to use xpath-default-namespace as it is not creating whole new elements, just copying existing ones (which get their namespace copied across too).

Upvotes: 2

Related Questions