1 package org.apache.xmlbeans.impl.xpathgen; 2 3 import org.apache.xmlbeans.XmlCursor; 4 import org.apache.xmlbeans.XmlCursor.TokenType; 5 6 import javax.xml.namespace.QName; 7 import javax.xml.namespace.NamespaceContext; 8 9 /** 10 * Generates an XPath String that points to a given position in an XML document 11 */ 12 public class XPathGenerator 13 { 14 /** 15 * Generates an XPath pointing to the position in the document indicated by <code>node</code>. 16 * <p>If the <code>context</code> parameter is null, the XPath is absolute, otherwise the 17 * XPath will be relative to the position indicated by <code>context</code>.</p> 18 * <p>Note: the cursor position for the <code>node</code> parameter is not preserved</p> 19 * @param node the position in the document that the generated path will point to 20 * @param context the context node; the generated path will be relative to it if not null and if 21 * pointing to an element on the path from the document root to <code>node</code> 22 * @param nsctx a namespace context that will be used to obtain prefixes; a (non-default) 23 * namespace mapping must be available for all required namespace URIs 24 * @return the generated path as a <code>String</code> 25 * @throws XPathGenerationException if the path could not be generated: the cursor is in a bad 26 * position (like over a comment) or no prefix mapping was found for one of the namespace URIs 27 */ 28 public static String generateXPath(XmlCursor node, XmlCursor context, NamespaceContext nsctx) 29 throws XPathGenerationException 30 { 31 if (node == null) 32 throw new IllegalArgumentException("Null node"); 33 if (nsctx == null) 34 throw new IllegalArgumentException("Null namespace context"); 35 TokenType tt = node.currentTokenType(); 36 if (context != null && node.isAtSamePositionAs(context)) 37 return "."; 38 switch (tt.intValue()) 39 { 40 case TokenType.INT_ATTR: 41 QName name = node.getName(); 42 node.toParent(); 43 String pathToParent = generateInternal(node, context, nsctx); 44 return pathToParent + '/' + '@' + qnameToString(name, nsctx); 45 case TokenType.INT_NAMESPACE: 46 name = node.getName(); 47 node.toParent(); 48 pathToParent = generateInternal(node, context, nsctx); 49 String prefix = name.getLocalPart(); 50 if (prefix.length() == 0) 51 return pathToParent + "/@xmlns"; 52 else 53 return pathToParent + "/@xmlns:" + prefix; 54 case TokenType.INT_START: 55 case TokenType.INT_STARTDOC: 56 return generateInternal(node, context, nsctx); 57 case TokenType.INT_TEXT: 58 int nrOfTextTokens = countTextTokens(node); 59 node.toParent(); 60 pathToParent = generateInternal(node, context, nsctx); 61 if (nrOfTextTokens == 0) 62 return pathToParent + "/text()"; 63 else 64 return pathToParent + "/text()[position()=" + nrOfTextTokens + ']'; 65 default: 66 throw new XPathGenerationException("Cannot generate XPath for cursor position: " + 67 tt.toString()); 68 } 69 } 70 71 private static String generateInternal(XmlCursor node, XmlCursor context, NamespaceContext nsctx) 72 throws XPathGenerationException 73 { 74 if (node.isStartdoc()) 75 return ""; 76 if (context != null && node.isAtSamePositionAs(context)) 77 return "."; 78 assert node.isStart(); 79 QName name = node.getName(); 80 XmlCursor d = node.newCursor(); 81 if (!node.toParent()) 82 return "/" + name; 83 int elemIndex = 0, i = 1; 84 node.push(); 85 if (!node.toChild(name)) 86 throw new IllegalStateException("Must have at least one child with name: " + name); 87 do 88 { 89 if (node.isAtSamePositionAs(d)) 90 elemIndex = i; 91 else 92 i++; 93 } while (node.toNextSibling(name)); 94 node.pop(); 95 d.dispose(); 96 String pathToParent = generateInternal(node, context, nsctx); 97 return i == 1 ? pathToParent + '/' + qnameToString(name, nsctx) : 98 pathToParent + '/' + qnameToString(name, nsctx) + '[' + elemIndex + ']'; 99 } 100 101 private static String qnameToString(QName qname, NamespaceContext ctx) 102 throws XPathGenerationException 103 { 104 String localName = qname.getLocalPart(); 105 String uri = qname.getNamespaceURI(); 106 if (uri.length() == 0) 107 return localName; 108 String prefix = qname.getPrefix(); 109 if (prefix != null && prefix.length() > 0) 110 { 111 // Try to use the same prefix if it maps to the right URI 112 String mappedUri = ctx.getNamespaceURI(prefix); 113 if (uri.equals(mappedUri)) 114 return prefix + ':' + localName; 115 } 116 // The prefix is not specified, or it is not mapped to the right URI 117 prefix = ctx.getPrefix(uri); 118 if (prefix == null) 119 throw new XPathGenerationException("Could not obtain a prefix for URI: " + uri); 120 if (prefix.length() == 0) 121 throw new XPathGenerationException("Can not use default prefix in XPath for URI: " + uri); 122 return prefix + ':' + localName; 123 } 124 125 /** 126 * Computes how many text nodes the 127 * @param c the position in the document 128 * @return how many text nodes occur before the position determined by <code>c</code> 129 */ 130 private static int countTextTokens(XmlCursor c) 131 { 132 int k = 0; 133 int l = 0; 134 XmlCursor d = c.newCursor(); 135 c.push(); 136 c.toParent(); 137 TokenType tt = c.toFirstContentToken(); 138 while (!tt.isEnd()) 139 { 140 if (tt.isText()) 141 { 142 if (c.comparePosition(d) > 0) 143 // We have moved after the initial position 144 l++; 145 else 146 k++; 147 } 148 else if (tt.isStart()) 149 c.toEndToken(); 150 tt = c.toNextToken(); 151 } 152 c.pop(); 153 return l == 0 ? 0 : k; 154 } 155 156 public static void main(String[] args) throws org.apache.xmlbeans.XmlException 157 { 158 String xml = 159 "<root>\n" + 160 "<ns:a xmlns:ns=\"http://a.com\"><b foo=\"value\">text1<c/>text2<c/>text3<c>text</c>text4</b></ns:a>\n" + 161 "</root>"; 162 NamespaceContext ns = new NamespaceContext() { 163 public String getNamespaceURI(String prefix) 164 { 165 if ("ns".equals(prefix)) 166 return "http://a.com"; 167 else 168 return null; 169 } 170 public String getPrefix(String namespaceUri) 171 { 172 return null; 173 } 174 public java.util.Iterator getPrefixes(String namespaceUri) 175 { 176 return null; 177 } 178 }; 179 XmlCursor c = org.apache.xmlbeans.XmlObject.Factory.parse(xml).newCursor(); 180 c.toFirstContentToken(); // on <root> 181 c.toFirstContentToken(); // on <a> 182 c.toFirstChild(); // on <b> 183 c.toFirstChild(); // on <c> 184 c.push(); System.out.println(generateXPath(c, null, ns)); c.pop(); 185 c.toNextSibling(); 186 c.toNextSibling(); // on the last <c> 187 c.push(); System.out.println(generateXPath(c, null, ns)); c.pop(); 188 XmlCursor d = c.newCursor(); 189 d.toParent(); 190 c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); 191 d.toParent(); 192 c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); 193 c.toFirstContentToken(); // on text content of the last <c> 194 c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); 195 c.toParent(); 196 c.toPrevToken(); // on text content before the last <c> 197 c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); 198 c.toParent(); // on <b> 199 c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); 200 c.toFirstAttribute(); // on the "foo" attribute 201 c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); 202 c.toParent(); 203 c.toParent(); 204 c.toNextToken(); // on the "xmlns:ns" attribute 205 c.push(); System.out.println(generateXPath(c, d, ns)); c.pop(); 206 c.push(); System.out.println(generateXPath(c, null, ns)); c.pop(); 207 } 208 }