1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.qedeq.kernel.xml.tracker;
16
17 import java.io.File;
18 import java.io.FileInputStream;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.Reader;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26
27 import javax.xml.parsers.ParserConfigurationException;
28 import javax.xml.parsers.SAXParser;
29 import javax.xml.parsers.SAXParserFactory;
30
31 import org.qedeq.base.io.IoUtility;
32 import org.qedeq.base.io.SourceArea;
33 import org.qedeq.base.io.SourcePosition;
34 import org.qedeq.base.io.TextInput;
35 import org.qedeq.base.trace.Trace;
36 import org.qedeq.base.utility.Enumerator;
37 import org.qedeq.kernel.xml.handler.common.SimpleHandler;
38 import org.xml.sax.Attributes;
39 import org.xml.sax.InputSource;
40 import org.xml.sax.SAXException;
41 import org.xml.sax.XMLReader;
42
43 import com.sun.syndication.io.XmlReader;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 public final class XPathLocationParser extends SimpleHandler {
61
62
63 private static final Class CLASS = XPathLocationParser.class;
64
65
66 private static final String NAMESPACES_FEATURE_ID = "http://xml.org/sax/features/namespaces";
67
68
69 private static final String VALIDATION_FEATURE_ID = "http://xml.org/sax/features/validation";
70
71
72 private final XMLReader reader;
73
74
75 private final SimpleXPath find;
76
77
78 private SimpleXPath current;
79
80
81
82 private SimpleXPath summary;
83
84
85 private File xmlFile;
86
87
88 private final List elements;
89
90
91 private int level;
92
93
94 private SourcePosition startDelta;
95
96
97 private SourcePosition endDelta;
98
99
100 private SourcePosition start;
101
102
103 private SourcePosition end;
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134 public static SourceArea findSourceArea(final String address, final SimpleXPath xpath,
135 final SourcePosition startDelta, final SourcePosition endDelta, final File file) {
136 final String method = "findSourceArea(String, SimpleXPath, SourcePosition, SourcePosition, File)";
137 final String message = "Could not find \"" + xpath + "\" within \"" + file + "\"";
138 try {
139 XPathLocationParser parser = new XPathLocationParser(xpath, startDelta, endDelta);
140 parser.parse(file);
141 if (parser.getStart() == null || parser.getEnd() == null) {
142 Trace.fatal(CLASS, method, message, null);
143 if (Boolean.TRUE.toString().equalsIgnoreCase(
144 System.getProperty("qedeq.test.xmlLocationFailures"))) {
145 throw new RuntimeException(message);
146 }
147 return new SourceArea(address);
148 }
149 return new SourceArea(address, parser.getStart(), parser.getEnd());
150 } catch (ParserConfigurationException e) {
151 Trace.fatal(CLASS, method, message, e);
152 } catch (SAXException e) {
153 Trace.fatal(CLASS, method, message, e);
154 } catch (IOException e) {
155 Trace.fatal(CLASS, method, message, e);
156 } catch (RuntimeException e) {
157 Trace.fatal(CLASS, method, message, e);
158 }
159 return null;
160 }
161
162
163
164
165
166
167
168
169
170
171 public static SourceArea findSourceArea(final File file, final SimpleXPath xpath) {
172 return findSourceArea(file.toString(), xpath, null, null, file);
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186 public XPathLocationParser(final SimpleXPath xpath, final SourcePosition startDelta,
187 final SourcePosition endDelta) throws ParserConfigurationException,
188 SAXException {
189 super();
190
191 this.find = xpath;
192 this.startDelta = startDelta;
193 this.endDelta = endDelta;
194 elements = new ArrayList(20);
195 level = 0;
196
197 final String factoryImpl = System.getProperty("javax.xml.parsers.SAXParserFactory");
198 if (factoryImpl == null) {
199 System.setProperty("javax.xml.parsers.SAXParserFactory",
200 "org.apache.xerces.jaxp.SAXParserFactoryImpl");
201 }
202 SAXParserFactory factory = SAXParserFactory.newInstance();
203 factory.setNamespaceAware(false);
204 factory.setValidating(false);
205
206 factory.setFeature(NAMESPACES_FEATURE_ID, false);
207 factory.setFeature(VALIDATION_FEATURE_ID, false);
208
209 final SAXParser parser = factory.newSAXParser();
210
211 reader = parser.getXMLReader();
212
213
214 reader.setFeature(NAMESPACES_FEATURE_ID, false);
215 reader.setFeature(VALIDATION_FEATURE_ID, false);
216 }
217
218
219
220
221
222
223
224
225 public final void parse(final File file) throws IOException, SAXException {
226 xmlFile = file;
227 elements.clear();
228 level = 0;
229 InputStream stream = null;
230 try {
231 current = new SimpleXPath();
232 summary = new SimpleXPath();
233 reader.setContentHandler(this);
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250 stream = new FileInputStream(file);
251 reader.parse(new InputSource(stream));
252 } catch (XPathLocationFoundException e) {
253
254 } catch (SAXException e) {
255 Trace.trace(CLASS, this, "parse", e);
256 throw e;
257 } finally {
258 IoUtility.close(stream);
259 }
260 }
261
262
263
264
265
266
267 public void endDocument() throws SAXException {
268 elements.clear();
269 level = 0;
270 }
271
272
273
274
275
276
277 public void startDocument() throws SAXException {
278 elements.clear();
279 level = 0;
280 }
281
282
283
284
285
286
287 public void characters(final char[] ch, final int start, final int length) throws SAXException {
288
289 }
290
291
292
293
294
295
296 public void ignorableWhitespace(final char[] ch, final int start, final int length)
297 throws SAXException {
298
299 }
300
301
302
303
304
305
306 public void endPrefixMapping(final String prefix) throws SAXException {
307
308 }
309
310
311
312
313
314
315 public void skippedEntity(final String name) throws SAXException {
316
317 }
318
319
320
321
322
323
324 public void processingInstruction(final String target, final String data) throws SAXException {
325
326 }
327
328
329
330
331
332
333 public void startPrefixMapping(final String prefix, final String uri) throws SAXException {
334
335 }
336
337
338
339
340
341
342
343 public void startElement(final String namespaceURI, final String localName, final String qName,
344 final Attributes atts) throws SAXException {
345 final String method = "startElement(String, String, Attributes)";
346 level++;
347 summary.addElement("*", addOccurence("*"));
348 current.addElement(qName, addOccurence(qName));
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365 if (getLocator() == null) {
366 throw new SAXException("Locator unexpectedly null");
367 }
368 if (find.matchesElements(current, summary)) {
369 Trace.trace(CLASS, this, method, "matching elements");
370 Trace.param(CLASS, this, method, qName, current);
371 TextInput xml = null;
372 Reader xmlReader = null;
373 try {
374 xmlReader = new XmlReader(xmlFile);
375 xml = new TextInput(xmlReader);
376
377
378 } catch (IOException io) {
379 Trace.fatal(CLASS, this, method, "File \"" + xmlFile + "\" should be readable", io);
380 if (getLocator() == null) {
381 throw new SAXException("Locator unexpectedly null");
382 }
383
384 start = new SourcePosition(
385 getLocator().getLineNumber(), getLocator().getColumnNumber());
386 return;
387 }
388 try {
389 xml.setRow(getLocator().getLineNumber());
390 xml.setColumn(getLocator().getColumnNumber());
391 if (startDelta != null) {
392 xml.skipWhiteSpace();
393 final String cdata = "<![CDATA[";
394 final String read = xml.readString(cdata.length());
395 final int cdataLength = (cdata.equals(read) ? cdata.length() : 0);
396 start = addDelta(xml, cdataLength, startDelta);
397 end = addDelta(xml, cdataLength, endDelta);
398 return;
399 }
400 try {
401 xml.skipBackToBeginOfXmlTag();
402 } catch (RuntimeException e) {
403 Trace.trace(CLASS, this, method, e);
404 }
405 start = new SourcePosition(xml.getRow(), xml.getColumn());
406 if (find.getAttribute() != null) {
407 xml.read();
408 xml.readNextXmlName();
409 String tag;
410 do {
411 xml.skipWhiteSpace();
412 int row = xml.getRow();
413 int col = xml.getColumn();
414 try {
415 tag = xml.readNextXmlName();
416 } catch (IllegalArgumentException e) {
417 break;
418 }
419 if (tag.equals(find.getAttribute())) {
420 start = new SourcePosition(row, col);
421 xml.readNextAttributeValue();
422 end = new SourcePosition(xml.getRow(), xml.getColumn());
423 throw new XPathLocationFoundException();
424 }
425 try {
426 xml.readNextAttributeValue();
427 } catch (IllegalArgumentException e) {
428 break;
429 }
430 } while (true);
431
432 if (end == null) {
433 end = new SourcePosition(xml.getRow(), xml.getColumn());
434 throw new XPathLocationFoundException();
435 }
436 }
437 } finally {
438 IoUtility.close(xml);
439 }
440 }
441 }
442
443
444
445
446
447
448
449
450
451 private SourcePosition addDelta(final TextInput xml, final int cdataLength,
452 final SourcePosition delta) {
453 xml.setRow(getLocator().getLineNumber());
454 xml.setColumn(getLocator().getColumnNumber());
455 if (delta.getRow() == 1 && cdataLength > 0) {
456 xml.addColumn(cdataLength + delta.getColumn() - 1);
457 } else {
458 xml.addPosition(delta);
459 }
460 return new SourcePosition(xml.getRow(), xml.getColumn());
461 }
462
463
464
465
466
467
468
469 private int addOccurence(final String name) {
470 while (level < elements.size()) {
471 elements.remove(elements.size() - 1);
472 }
473 while (level > elements.size()) {
474 elements.add(new HashMap());
475 }
476 final Map levelMap = (Map) elements.get(level - 1);
477 final Enumerator counter;
478 if (levelMap.containsKey(name)) {
479 counter = (Enumerator) levelMap.get(name);
480 counter.increaseNumber();
481 } else {
482 counter = new Enumerator(1);
483 levelMap.put(name, counter);
484 }
485 return counter.getNumber();
486 }
487
488
489
490
491
492
493
494 public void endElement(final String namespaceURI, final String localName, final String qName)
495 throws SAXException {
496 final String method = "endElement(String, String, Attributes)";
497 level--;
498 if (getLocator() == null) {
499 current.deleteLastElement();
500 summary.deleteLastElement();
501 throw new SAXException("Locator unexpectly null");
502 }
503 if (find.matchesElements(current, summary) && find.getAttribute() == null
504 && startDelta == null) {
505 TextInput xml = null;
506 Reader xmlReader = null;
507 try {
508 xmlReader = new XmlReader(xmlFile);
509 xml = new TextInput(xmlReader);
510
511
512 } catch (IOException io) {
513 Trace.fatal(CLASS, this, method, "File \"" + xmlFile + "\" should be readable", io);
514 if (getLocator() == null) {
515 throw new SAXException("Locator unexpectedly null");
516 }
517
518 start = new SourcePosition(getLocator().getLineNumber(),
519 getLocator().getColumnNumber());
520 return;
521 } finally {
522 IoUtility.close(xmlReader);
523 }
524 try {
525 xml.setRow(getLocator().getLineNumber());
526 xml.setColumn(getLocator().getColumnNumber());
527
528 end = new SourcePosition(xml.getRow(), xml.getColumn());
529 throw new XPathLocationFoundException();
530 } finally {
531 IoUtility.close(xml);
532 }
533 }
534 current.deleteLastElement();
535 summary.deleteLastElement();
536 }
537
538
539
540
541
542
543 private SourcePosition getStart() {
544 return start;
545 }
546
547
548
549
550
551
552 private SourcePosition getEnd() {
553 return end;
554 }
555
556 }