1 /* Copyright 2002-2006 Elliotte Rusty Harold 2 3 This library is free software; you can redistribute it and/or modify 4 it under the terms of version 2.1 of the GNU Lesser General Public 5 License as published by the Free Software Foundation. 6 7 This library is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 GNU Lesser General Public License for more details. 11 12 You should have received a copy of the GNU Lesser General Public 13 License along with this library; if not, write to the 14 Free Software Foundation, Inc., 59 Temple Place, Suite 330, 15 Boston, MA 02111-1307 USA 16 17 You can contact Elliotte Rusty Harold by sending e-mail to 18 elharo@ibiblio.org. Please include the word "XOM" in the 19 subject line. The XOM home page is located at http://www.xom.nu/ 20 */ 21 22 23 24 package nu.xom.xinclude; 25 26 import java.util.ArrayList; 27 import java.util.List; 28 29 import nu.xom.Attribute; 30 import nu.xom.Document; 31 import nu.xom.Element; 32 import nu.xom.IllegalNameException; 33 import nu.xom.Node; 34 import nu.xom.Nodes; 35 import nu.xom.ParentNode; 36 import nu.xom.XMLException; 37 38 /** 39 * 40 * <p> 41 * Right now this is just for XInclude, and hence is non-public. 42 * Once it's more baked it will probably become public and move 43 * to a package of its own. 44 * </p> 45 * 46 * @author Elliotte Rusty Harold 47 * @version 1.2b2 48 * 49 */ 50 class XPointer { 51 52 53 // prevent instantiation XPointer()54 private XPointer() {} 55 56 query(Document doc, String xptr)57 static Nodes query(Document doc, String xptr) 58 throws XPointerSyntaxException, XPointerResourceException { 59 60 Nodes result = new Nodes(); 61 boolean found = false; 62 63 try { // Is this a shorthand XPointer? 64 // Need to include a URI in case this is a colonized scheme name 65 new Element(xptr, "http://www.example.com"); 66 Element identified = findByID(doc.getRootElement(), xptr); 67 if (identified != null) { 68 result.append(identified); 69 return result; 70 } 71 } 72 catch (IllegalNameException ex) { 73 // not a bare name; try element() scheme 74 List elementSchemeData = findElementSchemeData(xptr); 75 if (elementSchemeData.isEmpty()) { 76 // This may be a legal XPointer, but it doesn't 77 // have an element() scheme so we can't handle it. 78 throw new XPointerSyntaxException( 79 "No supported XPointer schemes found" 80 ); 81 } 82 83 for (int i = 0; i < elementSchemeData.size(); i++) { 84 String currentData = (String) (elementSchemeData.get(i)); 85 int[] keys = new int[0]; 86 ParentNode current = doc; 87 if (currentData.indexOf('/') == -1) { 88 // raw id in element like element(f2) 89 try { 90 new Element(currentData); 91 } 92 catch (IllegalNameException inex) { 93 // not a bare name; and doesn't contain a / 94 // This doesn't adhere to the element scheme. 95 // Therefore, according to the XPointer element 96 // scheme spec, " if scheme data in a pointer 97 // part with the element() scheme does not 98 // conform to the syntax defined in this 99 // section the pointer part does not identify 100 // a subresource." 101 continue; 102 } 103 Element identified = findByID( 104 doc.getRootElement(), currentData); 105 if (identified != null) { 106 if (!found) result.append(identified); 107 found = true; 108 } 109 } 110 else if (!currentData.startsWith("/")) { 111 String id = currentData.substring( 112 0, currentData.indexOf('/')); 113 // Check to make sure this is a legal 114 // XML name/ID value 115 try { 116 new Element(id); 117 } 118 catch (XMLException inex) { 119 // doesn't adhere to the element scheme spec; 120 // Therefore this pointer part does not identify 121 // a subresource (See 2nd paragraph of section 122 // 3 of http://www.w3.org/TR/xptr-element/ ) 123 // This is not a resource error unless no 124 // XPointer part identifies a subresource. 125 continue; 126 } 127 current = findByID(doc.getRootElement(), id); 128 keys = split(currentData.substring( 129 currentData.indexOf('/'))); 130 131 if (current == null) continue; 132 } 133 else { 134 keys = split(currentData); 135 } 136 137 for (int j = 0; j < keys.length; j++) { 138 current = findNthChildElement(current, keys[j]); 139 if (current == null) break; 140 } 141 142 if (current != doc && current != null) { 143 if (!found) result.append(current); 144 found = true; 145 } 146 147 } 148 149 } 150 151 if (found) return result; 152 else { 153 // If we get here and still haven't been able to match an 154 // element, the XPointer has failed. 155 throw new XPointerResourceException( 156 "XPointer " + xptr 157 + " did not locate any nodes in the document " 158 + doc.getBaseURI() 159 ); 160 } 161 162 } 163 164 findNthChildElement( ParentNode parent, int position)165 private static Element findNthChildElement( 166 ParentNode parent, int position) { 167 // watch out for 1-based indexing of tumblers 168 int elementCount = 1; 169 for (int i = 0; i < parent.getChildCount(); i++) { 170 Node child = parent.getChild(i); 171 if (child instanceof Element) { 172 if (elementCount == position) return (Element) child; 173 elementCount++; 174 } 175 } 176 return null; 177 } 178 179 split(String tumbler)180 private static int[] split(String tumbler) 181 throws XPointerSyntaxException { 182 183 int numberOfParts = 0; 184 for (int i = 0; i < tumbler.length(); i++) { 185 if (tumbler.charAt(i) == '/') numberOfParts++; 186 } 187 188 int[] result = new int[numberOfParts]; 189 int index = 0; 190 StringBuffer part = new StringBuffer(3); 191 try { 192 for (int i = 1; i < tumbler.length(); i++) { 193 if (tumbler.charAt(i) == '/') { 194 result[index] = Integer.parseInt(part.toString()); 195 index++; 196 part = new StringBuffer(3); 197 } 198 else { 199 part.append(tumbler.charAt(i)); 200 } 201 } 202 result[result.length-1] = Integer.parseInt(part.toString()); 203 } 204 catch (NumberFormatException ex) { 205 XPointerSyntaxException ex2 206 = new XPointerSyntaxException(tumbler 207 + " is not syntactically correct", ex); 208 throw ex2; 209 } 210 211 return result; 212 } 213 findElementSchemeData(String xpointer)214 private static List findElementSchemeData(String xpointer) 215 throws XPointerSyntaxException { 216 217 List result = new ArrayList(1); 218 219 StringBuffer xptr = new StringBuffer(xpointer.trim()); 220 StringBuffer scheme = new StringBuffer(); 221 int i = 0; 222 while (i < xptr.length()) { 223 char c = xptr.charAt(i); 224 if (c == '(') break; 225 else scheme.append(c); 226 i++; 227 } 228 229 // need to verify that scheme is a QName 230 try { 231 // ugly hack because Verifier isn't public 232 new Element(scheme.toString(), "http://www.example.com/"); 233 } 234 catch (IllegalNameException ex) { 235 throw new XPointerSyntaxException(ex.getMessage()); 236 } 237 238 int open = 1; // parentheses count 239 i++; 240 StringBuffer schemeData = new StringBuffer(); 241 try { 242 while (open > 0) { 243 char c = xptr.charAt(i); 244 if (c == '^') { 245 c = xptr.charAt(i+1); 246 schemeData.append(c); 247 if (c != '^' && c != '(' && c != ')') { 248 throw new XPointerSyntaxException( 249 "Illegal XPointer escape sequence" 250 ); 251 } 252 i++; 253 } 254 else if (c == '(') { 255 schemeData.append(c); 256 open++; 257 } 258 else if (c == ')') { 259 open--; 260 if (open > 0) schemeData.append(c); 261 } 262 else { 263 schemeData.append(c); 264 } 265 i++; 266 } 267 } 268 catch (StringIndexOutOfBoundsException ex) { 269 throw new XPointerSyntaxException("Unbalanced parentheses"); 270 } 271 272 if (scheme.toString().equals("element")) { 273 result.add(schemeData.toString()); 274 } 275 276 if (i + 1 < xptr.length()) { 277 result.addAll(findElementSchemeData(xptr.substring(i))); 278 } 279 280 return result; 281 } 282 283 findByID(Element element, String id)284 static Element findByID(Element element, String id) { 285 286 Node current = element; 287 boolean end = false; 288 int index = -1; 289 while (true) { 290 291 if (current instanceof Element) { 292 Element currentElement = (Element) current; 293 for (int i = 0; i < currentElement.getAttributeCount(); i++) { 294 Attribute att = currentElement.getAttribute(i); 295 if (att.getType() == Attribute.Type.ID) { 296 if (att.getValue().trim().equals(id)) { 297 return currentElement; 298 } 299 } 300 } 301 } 302 303 if (!end && current.getChildCount() > 0) { 304 current = current.getChild(0); 305 index = 0; 306 } 307 else { 308 if (end) { 309 if (current == element) break; 310 } 311 end = false; 312 ParentNode parent = current.getParent(); 313 if (parent.getChildCount() - 1 == index) { 314 current = parent; 315 if (current != element) { 316 parent = current.getParent(); 317 index = parent.indexOf(current); 318 } 319 end = true; 320 } 321 else { 322 index++; 323 current = parent.getChild(index); 324 } 325 } 326 } 327 328 return null; 329 330 } 331 332 333 } 334