0

I am working on a project who has thus far relied on setting: DocumentBuilderFactory.setNamespaceAware(false); To achieve namespace flexible xpaths (that ignore whatever prefix may be passed in).

This only worked in the past because the xalan transformer was being used and although technically the behavior of setting namespace aware to false should be undefined at best, for the given xml, xalan treats the following xpath as valid.

xml:

<t:GiftBasket xmlns:t="http://tastyTreats.com/giftbasket">
    <t:Fruit>
        <t:Apple>
            <t:Name>Cameo</t:Name>
            <t:Color>Red</t:Color>
            <t:TasteDescription>Juicy, crisp, and sweet with just a touch of tart, the Cameo is thought to come from both the Red and the Yellow Delicious.</t:TasteDescription>
        </t:Apple>
        <t:Apple>
            <t:Name>Empire</t:Name>
            <t:Color>Red</t:Color>
            <t:TasteDescription>The interior is crisp and creamy white while being firmer than the McIntosh, so it makes for a good cooking apple. </t:TasteDescription>
        </t:Apple>
        <t:Apple>
            <t:Name>Granny Smith</t:Name>
            <t:Color>Green</t:Color>
            <t:TasteDescription>Hard feel, crisp bite, and extremely tart taste. Some prefer to cook it, which sweetens it up.</t:TasteDescription>
        </t:Apple>
    </t:Fruit>
    <t:Candy>
        <t:Chocolate>Dark</t:Chocolate>
    </t:Candy>
</t:GiftBasket>

xpath:

/GiftBasket/Fruit/Apple[Color='Red'][contains(TasteDescription,'sweet')]

In switching over to xslt 2.0 I switched to the Saxon-HE transformer. Saxon is a more exact transformer (good thing IMO). It recognizes that these xpath expressions are "wrong" and I now have to fix a bunch of xpath expressions like the above to work regardless of namespace prefix referenced (client may choose to prefix the URI http://tastyTreats.com/giftbasket as 'fluffybunnies' for all i know)

I have received other good counsel here on how to utilize the local-name() function to achieve most of what I need. My xpath now reads:

/*[local-name()='GiftBasket']/*[local-name()='Fruit']/*[local-name()='Apple'][Color='Red'][contains(TasteDescription,'sweet')]

This is still not accurate, however, as the elements referenced in the predicates are still referencing exact element names Color and TasteDescription without being namespace flexible. Is there a better way to write an xpath for all the red apples that contain 'sweet' in their taste description while remaining namespace prefix flexible?

Community
  • 1
  • 1
Russ
  • 1,996
  • 3
  • 19
  • 31
  • 1
    The prefix used in the input XML does not matter at all for your XPath expressions, what matters is the namespace URI used. Assuming that is constant and known then with Saxon you can set it up as the default namespace for XPath and can use `/GiftBasket/Fruit/Apple[Color='Red'][contains(TasteDescription,'sweet')]` as before. How you set up the default namespace depends on the Java API you use so you need to tell us whether you continue to use the JAXP API or have switched to the Saxon s9api. – Martin Honnen Aug 22 '16 at 14:53
  • I read the xml from a file into an InputSource which is turned into a org.w3c.dom.Document by calling javax.xml.parsers.DocumentBuilderFactory.parse(is). I then separately instantiate a javax.xml.xpath.XPath by calling javax.xml.xpath.XPathFactory.newInstance().newXPath(). Finally I evaluate the expression using xpath.evaluate(expression, document, javax.xml.xpath.XPathConstants.NODESET) and interrogating the resulting org.w3c.dom.NodeList.getLength() > 0. I do not see a reference to the JAXP or Saxon s9api. How can I find out which is being used? – Russ Aug 22 '16 at 15:11
  • See the answer, I think it should suffice to apply the code shown there on your `xpath` object. – Martin Honnen Aug 22 '16 at 15:15
  • I did a debugging session to verify, sorry for all the noobish questions. Concrete xpath evaluator class being used is: net.sf.saxon.xpath.XPathEvaluator while that class references a context that is of type et.sf.saxon.xpath.JAXPXPathStaticContext, so I'd assume your answer will be correct. I'll give it a shot and thanks for all the hand holding. – Russ Aug 22 '16 at 15:18

1 Answers1

1

As you mention XSLT 2.0, in XSLT 2.0 you can define

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

to then use XPath expressions and XSLT match patterns without prefixed names to select elements in that particular namespace, that is, the path /GiftBasket/Fruit/Apple[Color='Red'][contains(TasteDescription,'sweet')] should work fine to select the elements in your input document with the namespace http://tastyTreats.com/giftbasket.

If you use the JAXP XPath API with Saxon 9, then see https://saxonica.plan.io/boards/3/topics/1649 on how to set up such a default namespace with that API:

((XPathEvaluator)xpath).getStaticContext().setDefaultElementNamespace("http://tastyTreats.com/giftbasket");
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Correct answer as specified. Specifically of importance for xpath expressions is the last line of code and allows me to use predicates as initially intended. The XSLT reference is handy for folks who need to run an xslt as well. Thanks for all the help and hand holding! This significantly cleans the xpath expressions back up :) – Russ Aug 22 '16 at 15:28