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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to