You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@xmlbeans.apache.org by Radu Preotiuc-Pietro <ra...@bea.com> on 2008/04/03 04:57:00 UTC

RE: How to generate XPath for XMLObject/XMLCursor?

I have checked in something under

src/tools/org/apache/xmlbeans/impl/xpathgen/XPathGenerator.java

Main difference to the code below is that I use a NamespaceContext
passed in by the caller to avoid the need to generate an "ugly" XMLPath
containing namespace declarations. That assumes that in most situation a
map of declared prefixes is available. But if people have usecases where
they need the namespace declarations inside the generated XPath, please
speak up and I'll add the option to declare those as well.

Also, the code I checked in can generate XPaths to attributes and text
nodes and can generate relative XPaths.

Radu

> -----Original Message-----
> From: Willis Morse [mailto:willismorse@mac.com] 
> Sent: Tuesday, December 04, 2007 2:01 PM
> To: user@xmlbeans.apache.org
> Subject: Re: How to generate XPath for XMLObject/XMLCursor?
> 
> Turns out to be a little more nuanced than I expected.
> 
> You have to deal with a number of issues:
> 
> - Same name siblings
> - Namespace prefixes for each element (and there amy be more 
> than one in the same path)
> - Not overrunning the top of the document as you traverse up. 
> I wasn't expecting a the root element to go up another level 
> when I called  toParent() :-)
> 
> 
> Anyway, below is some code that I think does a reasonable job 
> of handling the details. Any comments on how robust this code 
> is going to be? I don't really know how many edge cases I'm 
> missing here. (No attempt at optimizations here, BTW. Ideally 
> it would probably use one prebuilt Map and StringBuffer).
> 
> Finally, if this turns out to be useful code, any interest in 
> a submission to the XmlBeans codebase? I'd imagine this is a 
> pretty common set of operations.
> 
> - Willis Morse
> 
> 
> Code below
> ==============================================================
> ==========
> ==========================================
> 
> public class XmlBeansUtils
> {
>      /**
>       * Return a string that contains the name of this 
> element, preceeded by its namespace prefix if there is one.
>       *
>       * @param inCursor
>       * @param namespaceMap
>       * @return element name, including namespace prefix, of 
> the form "ns1:my-element"
>       */
>      static String getElementName(XmlCursor inCursor, Map<String,  
> String> namespaceMap)
>      {
>          QName qName = inCursor.getName();
> 
>          String namespacePrefix = qName.getPrefix();
>          String namespaceURI = qName.getNamespaceURI();
> 
>          if (namespacePrefix.length() > 1)
>          {
>              namespaceMap.put(namespacePrefix, namespaceURI);
>              namespacePrefix += ":";
>          }
> 
>          String name = namespacePrefix + qName.getLocalPart();
> 
>          name += getSameNameSiblingIndex(inCursor);
> 
>          return name;
>      }
> 
>      /**
>       * Return a string encoding the proper index for this element.
>       *
>       * @param inCursor
>       * @return index string of the form "[2]", or empty 
> string if there are no same-name siblings.
>       */
>      @Nullable
>      static String getSameNameSiblingIndex(XmlCursor inCursor)
>      {
>          String localName = inCursor.getName().getLocalPart();
> 
>          // Count the number of same-name siblings above this 
> element to see what index this element should use
>          inCursor.push();
>          int currentCount = 1;
>          while (inCursor.toPrevSibling())
>          {
>              String siblingName = inCursor.getName().getLocalPart();
> 
>              if (siblingName.equalsIgnoreCase(localName))
>              {
>                  currentCount += 1;
>              }
>          }
>          inCursor.pop();
> 
>          int numberOfSNS = 0;
>          if (currentCount == 1)
>          {
>              // If this is the first element with this name, 
> we need to see if there are any more SNS to decide whether
>              // to use "[1]" for this element's index. So now 
> we count the number of same-name siblings below this element.
>              inCursor.push();
>              while (inCursor.toNextSibling())
>              {
>                  String siblingName = 
> inCursor.getName().getLocalPart();
> 
>                  if (siblingName.equalsIgnoreCase(localName))
>                  {
>                      numberOfSNS += 1;
>                  }
>              }
>              inCursor.pop();
>          }
> 
>          String indexString = "";
>          if ((currentCount > 1) || (numberOfSNS > 1))
>          {
>              indexString = "[" + currentCount + "]";
>          }
>          return indexString;
>      }
> 
>      /**
>       * Return an xpath for this element. This path will 
> contain namespace prefixes (if any) and the proper index if there
>       * are same-name siblings anywhere in the path. The path 
> will also contain the required namespace declaration if there.
>       * are any namespace prefixes.
>       *
>       * @param inXmlObject
>       * @return an XPath
>       */
>      static String getXPathForElement(XmlObject inXmlObject)
>      {
>          StringBuffer path = new StringBuffer();
> 
> 
>          XmlCursor cursor = inXmlObject.newCursor();
>          Map<String, String> namespaceMap = new HashMap<String,  
> String>();
>          String name = getElementName(cursor, namespaceMap);
>          path.insert(0, "/" + name);
>          while (cursor.toParent())
>          {
>              // Avoid running off the top (there's a STARTDOC 
> token above the root element)
>              // START tokens indicate an actual element, so 
> hopefully this is enough to stop the overrun
>              if (cursor.currentTokenType().isStart())
>              {
>                  path.insert(0, "/" + getElementName(cursor, 
> namespaceMap));
>              }
>          }
> 
>          // Build up a string that declares all the namespace 
> prefix and uri combinations that appear in this path
>          // Presumably multiple namespace pairs can simply be 
> concatenated, seperated by spaces. I haven't seen any
>          // docs to indicate that they need a special 
> seperator character.
>          String namespaceDeclaration = "";
>          for (String namespacePrefix : namespaceMap.keySet())
>          {
>              String namespaceURI = namespaceMap.get(namespacePrefix);
>              if (namespaceURI != null)
>              {
>                  namespaceDeclaration += namespacePrefix + 
> "='" + namespaceURI + "' ";
>              }
>          }
> 
>          if (namespaceDeclaration.length() > 0)
>          {
>              path.insert(0, "declare namespace " + 
> namespaceDeclaration);
>          }
> 
> 
>          return path.toString();
>      }
> }
> 
> ==============================================================
> ==========
> ==========================================
> 
> 
> On Nov 24, 2007, at 6:11 AM, Willis Morse wrote:
> 
> > Thanks, Cezar. I can probably hack something together that way. I  
> > just wanted to make sure I wasn't missing something obvious before  
> > I reinvent the wheel.
> >
> > - WIll
> >
> >
> >
> > On Nov 21, 2007, at 4:59 PM, Cezar Andrei wrote:
> >
> >> Unfortunately, from what I know, there isn't an API to get an  
> >> XPath. It
> >> shouldn't be very hard to implement using a cursor.
> >>
> >> Cezar
> >>
> >>> -----Original Message-----
> >>>
> >>> Is there any way to derive an Xpath from an XmlObject or  
> >>> XmlCursor at
> >>> runtime?
> >>>
> >>> Or, alternatively, is there anyway to convert an 
> XmlBookmark into an
> >>> XPath?
> >>>
> >>> My app lets users choose elements from within an XML 
> document, and I
> >>> need a way to persist xpaths to these chosen elements 
> back into the
> >>> XML document.
> >>>
> >>> Thanks,
> >>> Willis Morse
> >>>
> >
> >
> > 
> ---------------------------------------------------------------------
> > To unsubscribe, e-mail: user-unsubscribe@xmlbeans.apache.org
> > For additional commands, e-mail: user-help@xmlbeans.apache.org
> >
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: user-unsubscribe@xmlbeans.apache.org
> For additional commands, e-mail: user-help@xmlbeans.apache.org
> 
> 

Notice:  This email message, together with any attachments, may contain information  of  BEA Systems,  Inc.,  its subsidiaries  and  affiliated entities,  that may be confidential,  proprietary,  copyrighted  and/or legally privileged, and is intended solely for the use of the individual or entity named in this message. If you are not the intended recipient, and have received this message in error, please immediately return this by email and then delete it.

---------------------------------------------------------------------
To unsubscribe, e-mail: user-unsubscribe@xmlbeans.apache.org
For additional commands, e-mail: user-help@xmlbeans.apache.org