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