cancel
Showing results for 
Search instead for 
Did you mean: 

namespace-aware XPath expressions?

Former Member
0 Kudos

In xMII 11.5.2 b64, I need to extract a node from a SOAP message:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header/>
   <soapenv:Body>
      <ns0:importRequest xmlns:ns0="http://www.acme.com/1.0/schemas">
        <FOO>
        ...

The following xMII Assign action Link returns me the entire <FOO> nodeset:


Transaction.SOAPRequest{/soapenv:Envelope/soapenv:Body/ns0:importRequest/*}

However, I cannot be guaranteed that the namespace prefixes will always be "soapenv" and "ns0". For example, the following is perfectly valid:


<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP:Header/>
   <SOAP:Body>
      <ZZZ:importRequest xmlns:ZZZ="http://www.acme.com/1.0/schemas">
        <FOO>
        ...

When they are changed the XPath fails. I have tried the following:


Transaction.SOAPRequest{/Envelope/Body/importRequest/*}
Transaction.SOAPRequest{//importRequest/*}

but they clearly don't work because the XPath evaluator in xMII is namespace aware (as it should be).

How can I declare namespaces in my XPath expression? Or are there any other alternate ideas?

Thanks,

-tim

Accepted Solutions (0)

Answers (1)

Answers (1)

Former Member
0 Kudos

Hi, Tim.

When not already present, ns0, ns1, etc. are automatically generated by the xMII instance document generation engine if "default namespaces" are used (e.g. not qualified) in the schemas. These allow you to XPath into the elements even when default namespaces are used.

If xMII gets a document with different prefixes, it should still work (due to the way XPathing with namespaces actually works under the hood). However, be sure you have 11.5 SR3 installed - there were a few tweaks/fixes to namespace handling that may also be affecting you - propagating "dynamically-created" prefix/namespace declarations to the parsing/processing of the returned document(s).

Best regards,

Rick

Former Member
0 Kudos

I installed SR3 and re-ran my tests and it still fails. Here is my XML snippet:


<?xml version="1.0" encoding="UTF-8"?>
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:sch="http://www.acme.com/4.3/schemas">
   <SOAP:Header/>
   <SOAP:Body>
      <sch:importIDOCRequest>
         <MATMAS03>
         ...

Here's the output from various expressions in the Link editor:

example 1 - getting the name of the root node:


"root node name=" & Transaction.SOAPRequest{name(/*)}

output 1 is predictable:


[INFO ]: root node name=SOAP:Envelope

example 2 - using 'senv' instead of 'SOAP' for the soap-envelope namespace prefix:


"importRequest child node name=" & Transaction.SOAPRequest{name(/senv:Envelope/senv:Body/sch:importIDOCRequest/*)}

output 2:


[INFO ]: importRequest child node name=

example 3 - no namespace prefixes specified:


"importRequest child node name=" & Transaction.SOAPRequest{name(/Envelope/Body/importIDOCRequest/*)}

output 3:


[INFO ]: importRequest child node name=

example 4 - using the same node namespaces prefixes as the input XML:


"importRequest child node name=" & Transaction.SOAPRequest{name(/SOAP:Envelope/SOAP:Body/sch:importIDOCRequest/*)}

output 4:


[INFO ]: importRequest child node name=MATMAS03

Any other ideas?

-tim

Former Member
0 Kudos

Seems to be working as expected - there's no senv declaration, so I wouldn't expect that one to work, and the other ones don't have any prefix/namespace qualification, so they shouldn't work, except of course for #4.

Maybe I need to step back a bit and ask you to explain to me the "big picture" of what you're trying to do, how you're currently attempting it, and then we can tackle the specifics.

HTH,

- Rick

Former Member
0 Kudos

Tim:

One other approach, if you are looking for a specific element name, is to use the local-name() XPath function, as in:

//*[local-name() = 'importIDOCRequest']

That should work fine for what you're trying to do, and will ignore namespaces altogether.

Best regards,

Rick

Former Member
0 Kudos

The bigger picture is - how do I locate a sub-node in XML that has multiple namespaces? I can't assume that soap namespace prefix is always going to be "SOAP", nor the ACME prefix will always be "sch". A client application is free to choose whatever prefix they want as long as the namespaces are correct.

I could use local-name() and match just that, but what happens when two nodes have the same local-name but different namespaces?

All the various XPath APIs allow you to define namespaces, assign them to a prefix, then create your XPath expression using those prefixes. For example:


  HashMap map = new HashMap();
  map.put( "s", "http://schemas.xmlsoap.org/soap/envelope/");
  map.put( "a", "http://www.acme.com/4.3/schemas");
  XPath xpath = new JDOMXPath( "/s:Envelope/s:Body/a:importIDOCRequest");
  xpath.setNamespaceContext(new SimpleNamespaceContext(map));

I don't see how to do something similar in xMII.

-tim

Former Member
0 Kudos

Tim, the namespace handling will be automatic if you use the "live" WSDL from the target web service (and uses basically the same code that you show).

In any case, the local-name() technique is what most people do. Imagine trying to do this in XSLT (basically, there's almost no clean way!). There is also a namespace-uri() function if you want to really be specific in your XPath statements.

98% of the time, though, the built-in xMII namespace handling should work. You need to remember that prefixes don't really matter - it is the namespace URI that will. xMII should generally automatically include declared and "synthesized" prefix/URI combinations in XPath handling. The synthesized prefixes are required when default namespaces (xmlns="http://yourthing.com/stuff") are used.

- Rick

Former Member
0 Kudos

Rick, thanks for all the help. You are correct in that your "local-name only" approach is good enough for my immediate needs (thanks!).

To assist myself in developing and testing this xMII web service, I wrote a WSDL that accurately models what the Runner is expecting. As you saw, I'm basically wrapping an IDOC with an operation name and sticking that in a SOAP body. Because of this body type, I cannot use xMII's WSDLGen. I imported this WSDL file into soapui (great, free tool). It properly parses my WSDL service endpoint and request message structure. Everything works because I completely control the input message including the namespace prefixes.

So life is great as long as I'm developing and testing in my happy little world, but along comes a customer.... He's using a different tool - named almost identically as xMII but without the "MI". He doesn't import my WSDL, but imports only my schemas. He's building his own SOAP message by hand. He honors the namespaces (because he has to), but changes the namespace prefixes - I guess because he can...

I honestly was caught off-guard by the lack of on-the-fly namespace/prefix binding in XPath. When he showed me this error, I knew immediately what the problem was and quickly looked in the xMII Help and various XPath resources to figure out how to write an XPath expression where I could specify that namespace "http://foo" is represented in my XPath expression by "foo". I could write XPath like "/foo:Item" and it would match the input document even if it maps namespace "http://foo" to prefix "bar". Nothing jumped out so I posted here. I kept looking - I've done this many times in Java, but never in a pure XPath expression. Finally, many hours later, I'm convinced the XPath spec overlooked this. I believe you can fully qualify a node using another predicate to check the namespace in addition to your predicate checking the local name, but that looks really ugly.

Thanks again for your help. By the way, xMII 12.0 looks very nice. I've only played with it for an hour or so, but I was pleasantly surprised how well it's integrated into NetWeaver and it's kept most of its 11.5 "look and feel".

-tim