0

TL;DR; Why can't I use the element name in the XPATH going against a msxsl:node-set? It always returns nothing, as if the node-set is empty, when debugging shows that it is not empty.

Details: I need to use a node-set in an XSLT 1.0 document because my source XML is missing an important node. Instead of having to rewrite the entire XSLT, I'd like to instead inject a node-set so that my XSLT processing can continue as normal. I would like to use XPATH on the node-set but I am not able to use the actual element names, instead only a * works, but I am not sure why, or how I can access the actual element names in the XPATH.

Here is my XML (example only, the XML document here is the least important, see XSLT):

<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="generic.xslt" ?>
<ParentNode xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" i:noNamespaceSchemaLocation="generic.xsd">
  <SomeChildNode>text</SomeChildNode>
</ParentNode>

Here is my XSLT:

<?xml version="1.0" encoding="utf-16"?>
<xsl:stylesheet version="1.0" xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:a="http://schemas.datacontract.org/2004/07/MeM.BizEntities" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <xsl:output method="xml" indent="yes" encoding="utf-16" omit-xml-declaration="no" />

    <!-- Global Variables, used in multiple places -->
    <xsl:variable name="empty"/>

    <!-- Match Templates -->
    <xsl:template match="ParentNode">
        <ArrayOfSalesOrder>
            <xsl:for-each select="SomeChildNode">
                <xsl:call-template name="SomeChildNodeTemplate">
                    <xsl:with-param name="order" select="."/>
                </xsl:call-template>
            </xsl:for-each>
        </ArrayOfSalesOrder>
    </xsl:template>

    <xsl:template name="SomeChildNodeTemplate">
        <xsl:variable name="someRTF">
            <Items>
                <Item>
                    <Code>code</Code>
                    <Price>75</Price>
                    <Quantity>1</Quantity>
                </Item>
                <Item>
                    <Code>code2</Code>
                    <Price>100</Price>
                    <Quantity>3</Quantity>
                </Item>
            </Items>
        </xsl:variable>
        <xsl:call-template name="ItemsTemplate">
            <xsl:with-param name="items" select="msxsl:node-set($someRTF)"/>
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="ItemsTemplate">
        <xsl:param name="items"/>
        <ItemsTransformed>
            <xsl:for-each select="$items/Item">
                <NewItem>
                    <NewCode>
                        <xsl:value-of select="Code"/>
                    </NewCode>
                </NewItem>
            </xsl:for-each>
        </ItemsTransformed>
        <ItemsTransformedThatWorksButNotHowIWant>
            <xsl:for-each select="$items/*/*">
                <NewItem>
                    <NewCode>
                        <xsl:value-of select="*[1]"/>
                    </NewCode>
                    <NewPrice>
                        <xsl:value-of select="*[2]"/>
                    </NewPrice>
                    <NewQuantity>
                        <xsl:value-of select="*[3]"/>
                    </NewQuantity>
                </NewItem>
            </xsl:for-each>
        </ItemsTransformedThatWorksButNotHowIWant>
    </xsl:template>
</xsl:stylesheet>

I would expect to be able to use XPATH to query into the node-set such that I can use their proper element names. This doesn't seem to be the case, and I'm struggling to understand why. I know there can be namespacing issues, but trying *:Item etc. doesn't work for me. I am able to use *[local-name()='Item'] but this seems like a horrible work around, not to mention that I'll have to rewrite any downstream templates and that is what I'm trying to avoid by using the node-set in the first place.

Result:

<?xml version="1.0" encoding="utf-16"?>
<ArrayOfSalesOrder xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:a="http://schemas.datacontract.org/2004/07/MeM.BizEntities" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <ItemsTransformed />
  <ItemsTransformedThatWorksButNotHowIWant>
    <NewItem>
      <NewCode>code</NewCode>
      <NewPrice>75</NewPrice>
      <NewQuantity>1</NewQuantity>
    </NewItem>
    <NewItem>
      <NewCode>code2</NewCode>
      <NewPrice>100</NewPrice>
      <NewQuantity>3</NewQuantity>
    </NewItem>
  </ItemsTransformedThatWorksButNotHowIWant>
</ArrayOfSalesOrder>

As you can see, I can get it to work with * but this is not very usable on a more complex structure. What am I doing wrong? Does this have to do with namespaces?

I would expect to see something under the <ItemsTransformed /> node, but instead it is just empty, and so far I can't get anything except the * to work.

The SO question below is what I was using, I thought I had an answer there, but I can't get the XPATH to work.

Reference: XSLT 1.0 - Create node set and pass as a parameter

Community
  • 1
  • 1
Nateous
  • 757
  • 9
  • 23
  • The example is contrived simply for the purposes of answering this question, my real world example is much more complex, but I have reduced the complexity to get a better answer. – Nateous Feb 19 '15 at 21:07
  • I don't think you can get a good answer without your problem being reproduced. Right now, my processor goes into an infinite loop trying to run the code you have posted. And I have no problem getting the value from your input XML using the explicit path `/ParentNode/SomeChildNode`. So ... – michael.hor257k Feb 19 '15 at 21:35
  • I'll edit the question to better make sense of what I'm asking. – Nateous Feb 19 '15 at 21:42

1 Answers1

1

The problem here is that your stylesheet has a default namespace:

xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2"

Therefore, when you do:

<xsl:variable name="someRTF">
    <Items>
        <Item>
            <Code>code</Code>
            <Price>75</Price>
            <Quantity>1</Quantity>
        </Item>
        <Item>
            <Code>code2</Code>
            <Price>100</Price>
            <Quantity>3</Quantity>
        </Item>
    </Items>
</xsl:variable>

you are populating your variable with elements in the default namespace, so the variable actually contains:

<Items xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2">
      <Item>
        <Code>code</Code>
        <Price>75</Price>
        <Quantity>1</Quantity>
      </Item>
      <Item>
        <Code>code2</Code>
        <Price>100</Price>
        <Quantity>3</Quantity>
      </Item>
</Items>

Naturally, when you try later to select something like:

<xsl:for-each select="xyz:node-set($someRTF)/Items/Item">

you select nothing, because both Items and Item are in the default namespace and you're not calling them by their fully qualified name.

--- edit: ---

The problem can be easily solved by making sure that the root element of the variable - and by extension, all its descendants - are in no namespace.

Here's a simplified example (will run with any input):

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:variable name="someRTF">
    <Items xmlns="">
        <Item>
            <Code>code</Code>
            <Price>75</Price>
            <Quantity>1</Quantity>
        </Item>
        <Item>
            <Code>code2</Code>
            <Price>100</Price>
            <Quantity>3</Quantity>
        </Item>
    </Items>
</xsl:variable>

<xsl:template match="/">
    <ArrayOfSalesOrder>
        <ItemsTransformed>
            <xsl:for-each select="exsl:node-set($someRTF)/Items/Item">
                <NewItem>
                    <NewCode>
                        <xsl:value-of select="Code"/>
                    </NewCode>
                </NewItem>
            </xsl:for-each>
        </ItemsTransformed>
    </ArrayOfSalesOrder>
</xsl:template>

</xsl:stylesheet>

Result:

<?xml version="1.0" encoding="UTF-8"?>
<ArrayOfSalesOrder xmlns="http://schemas.datacontract.org/2004/07/MeM.BizEntities.Integration.DataFeedV2">
   <ItemsTransformed>
      <NewItem>
         <NewCode>code</NewCode>
      </NewItem>
      <NewItem>
         <NewCode>code2</NewCode>
      </NewItem>
   </ItemsTransformed>
</ArrayOfSalesOrder>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Would just removing the default namespace also solve the problem? – Nateous Feb 20 '15 at 13:37
  • 1
    It would solve **this** problem - but then you would need to find another way to output your result in the namespace you want. Another option you could consider is keeping the variable in another document. I am not sure what's the best way here, because I don't understand *why* you're doing this. The variable is hard-coded into your stylesheet; what's the point of processing it, when the result is known ahead? Why not simply hard-code the result and save the CPU cycles? – michael.hor257k Feb 20 '15 at 13:42
  • I suppose I could rewrite my XSLT, but that seemed like more work, I just needed to inject this node because the source didn't contain anything. Thanks for the help! I kept the default namespace because it is needed for the output document. I just modified my XSLT to have the prefix. – Nateous Feb 20 '15 at 14:12
  • 1
    @Nate I came up with a much more simple solution (see the edit above). Earlier, I didn't think it would work without affixing an empty `xmlns` to each and every one element in the variable; but eventually I became convinced that inheritance *should* work here - and testing proves that it does. – michael.hor257k Feb 20 '15 at 16:17
  • that is actually what I had hoped would be possible! Thanks! This solves the problem how I thought it could be solved so that no editing of the downstream templates would be necessary! Now I have a problem accessing ancestor:: nodes but that is because this new/injected node structure has no clue about the source XML document, but that is an entirely different problem. – Nateous Feb 20 '15 at 16:25