All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xmlbeans.impl.xpathgen.XPathGenerator Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*   Copyright 2004 The Apache Software Foundation
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.xmlbeans.impl.xpathgen;

import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlCursor.TokenType;
import org.apache.xmlbeans.XmlObject;

import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;

/**
 * Generates an XPath String that points to a given position in an XML document
 */
public class XPathGenerator
{
    /**
     * Generates an XPath pointing to the position in the document indicated by {@code node}.
     * 

If the {@code context} parameter is null, the XPath is absolute, otherwise the * XPath will be relative to the position indicated by {@code context}.

*

Note: the cursor position for the {@code node} parameter is not preserved

* @param node the position in the document that the generated path will point to * @param context the context node; the generated path will be relative to it if not null and if * pointing to an element on the path from the document root to {@code node} * @param nsctx a namespace context that will be used to obtain prefixes; a (non-default) * namespace mapping must be available for all required namespace URIs * @return the generated path as a {@code String} * @throws XPathGenerationException if the path could not be generated: the cursor is in a bad * position (like over a comment) or no prefix mapping was found for one of the namespace URIs */ public static String generateXPath(XmlCursor node, XmlCursor context, NamespaceContext nsctx) throws XPathGenerationException { if (node == null) throw new IllegalArgumentException("Null node"); if (nsctx == null) throw new IllegalArgumentException("Null namespace context"); TokenType tt = node.currentTokenType(); if (context != null && node.isAtSamePositionAs(context)) return "."; switch (tt.intValue()) { case TokenType.INT_ATTR: QName name = node.getName(); node.toParent(); String pathToParent = generateInternal(node, context, nsctx); return pathToParent + '/' + '@' + qnameToString(name, nsctx); case TokenType.INT_NAMESPACE: name = node.getName(); node.toParent(); pathToParent = generateInternal(node, context, nsctx); String prefix = name.getLocalPart(); if (prefix.length() == 0) return pathToParent + "/@xmlns"; else return pathToParent + "/@xmlns:" + prefix; case TokenType.INT_START: case TokenType.INT_STARTDOC: return generateInternal(node, context, nsctx); case TokenType.INT_TEXT: int nrOfTextTokens = countTextTokens(node); node.toParent(); pathToParent = generateInternal(node, context, nsctx); if (nrOfTextTokens == 0) return pathToParent + "/text()"; else return pathToParent + "/text()[position()=" + nrOfTextTokens + ']'; default: throw new XPathGenerationException("Cannot generate XPath for cursor position: " + tt.toString()); } } private static String generateInternal(XmlCursor node, XmlCursor context, NamespaceContext nsctx) throws XPathGenerationException { if (node.isStartdoc()) return ""; if (context != null && node.isAtSamePositionAs(context)) return "."; assert node.isStart(); QName name = node.getName(); int elemIndex = 0, i = 1; try (XmlCursor d = node.newCursor()) { if (!node.toParent()) return "/" + name; node.push(); if (!node.toChild(name)) throw new IllegalStateException("Must have at least one child with name: " + name); do { if (node.isAtSamePositionAs(d)) elemIndex = i; else i++; } while (node.toNextSibling(name)); node.pop(); } String pathToParent = generateInternal(node, context, nsctx); return i == 1 ? pathToParent + '/' + qnameToString(name, nsctx) : pathToParent + '/' + qnameToString(name, nsctx) + '[' + elemIndex + ']'; } private static String qnameToString(QName qname, NamespaceContext ctx) throws XPathGenerationException { String localName = qname.getLocalPart(); String uri = qname.getNamespaceURI(); if (uri.length() == 0) return localName; String prefix = qname.getPrefix(); if (prefix != null && prefix.length() > 0) { // Try to use the same prefix if it maps to the right URI String mappedUri = ctx.getNamespaceURI(prefix); if (uri.equals(mappedUri)) return prefix + ':' + localName; } // The prefix is not specified, or it is not mapped to the right URI prefix = ctx.getPrefix(uri); if (prefix == null) throw new XPathGenerationException("Could not obtain a prefix for URI: " + uri); if (prefix.length() == 0) throw new XPathGenerationException("Can not use default prefix in XPath for URI: " + uri); return prefix + ':' + localName; } /** * Computes how many text nodes the * @param c the position in the document * @return how many text nodes occur before the position determined by {@code c} */ private static int countTextTokens(XmlCursor c) { int k = 0; int l = 0; try (XmlCursor d = c.newCursor()) { c.push(); c.toParent(); TokenType tt = c.toFirstContentToken(); while (!tt.isEnd()) { if (tt.isText()) { if (c.comparePosition(d) > 0) // We have moved after the initial position l++; else k++; } else if (tt.isStart()) c.toEndToken(); tt = c.toNextToken(); } } c.pop(); return l == 0 ? 0 : k; } public static void main(String[] args) throws org.apache.xmlbeans.XmlException { String xml = "\n" + "text1text2text3texttext4\n" + ""; NamespaceContext ns = new NamespaceContext() { public String getNamespaceURI(String prefix) { if ("ns".equals(prefix)) return "http://a.com"; else return null; } public String getPrefix(String namespaceUri) { return null; } public java.util.Iterator getPrefixes(String namespaceUri) { return null; } }; try (XmlCursor c = XmlObject.Factory.parse(xml).newCursor()) { c.toFirstContentToken(); // on c.toFirstContentToken(); // on c.toFirstChild(); // on c.toFirstChild(); // on c.push(); System.out.println(generateXPath(c, null, ns)); c.pop(); c.toNextSibling(); c.toNextSibling(); // on the last c.push(); System.out.println(generateXPath(c, null, ns)); c.pop(); try (XmlCursor d = c.newCursor()) { d.toParent(); c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); d.toParent(); c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); c.toFirstContentToken(); // on text content of the last c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); c.toParent(); c.toPrevToken(); // on text content before the last c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); c.toParent(); // on c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); c.toFirstAttribute(); // on the "foo" attribute c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); c.toParent(); c.toParent(); c.toNextToken(); // on the "xmlns:ns" attribute c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); } c.push(); System.out.println(generateXPath(c, null, ns)); c.pop(); } } }