1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.xml;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.net.URL;
25 import java.util.AbstractList;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.Map;
30 import java.util.NoSuchElementException;
31 import java.util.Stack;
32 import java.util.StringTokenizer;
33
34 import javax.xml.parsers.SAXParser;
35 import javax.xml.parsers.SAXParserFactory;
36
37 import org.eclipse.jetty.util.LazyList;
38 import org.eclipse.jetty.util.log.Log;
39 import org.eclipse.jetty.util.log.Logger;
40 import org.eclipse.jetty.util.resource.Resource;
41 import org.xml.sax.Attributes;
42 import org.xml.sax.ContentHandler;
43 import org.xml.sax.InputSource;
44 import org.xml.sax.SAXException;
45 import org.xml.sax.SAXParseException;
46 import org.xml.sax.XMLReader;
47 import org.xml.sax.helpers.DefaultHandler;
48
49
50
51
52
53
54
55
56
57
58
59 public class XmlParser
60 {
61 private static final Logger LOG = Log.getLogger(XmlParser.class);
62
63 private Map<String,URL> _redirectMap = new HashMap<String,URL>();
64 private SAXParser _parser;
65 private Map<String,ContentHandler> _observerMap;
66 private Stack<ContentHandler> _observers = new Stack<ContentHandler>();
67 private String _xpath;
68 private Object _xpaths;
69 private String _dtd;
70
71
72
73
74
75 public XmlParser()
76 {
77 SAXParserFactory factory = SAXParserFactory.newInstance();
78 boolean validating_dft = factory.getClass().toString().startsWith("org.apache.xerces.");
79 String validating_prop = System.getProperty("org.eclipse.jetty.xml.XmlParser.Validating", validating_dft ? "true" : "false");
80 boolean validating = Boolean.valueOf(validating_prop).booleanValue();
81 setValidating(validating);
82 }
83
84
85
86
87
88 public XmlParser(boolean validating)
89 {
90 setValidating(validating);
91 }
92
93
94 public void setValidating(boolean validating)
95 {
96 try
97 {
98 SAXParserFactory factory = SAXParserFactory.newInstance();
99 factory.setValidating(validating);
100 _parser = factory.newSAXParser();
101
102 try
103 {
104 if (validating)
105 _parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/schema", validating);
106 }
107 catch (Exception e)
108 {
109 if (validating)
110 LOG.warn("Schema validation may not be supported: ", e);
111 else
112 LOG.ignore(e);
113 }
114
115 _parser.getXMLReader().setFeature("http://xml.org/sax/features/validation", validating);
116 _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespaces", true);
117 _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes", false);
118 try
119 {
120 if (validating)
121 _parser.getXMLReader().setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", validating);
122 }
123 catch (Exception e)
124 {
125 LOG.warn(e.getMessage());
126 }
127 }
128 catch (Exception e)
129 {
130 LOG.warn(Log.EXCEPTION, e);
131 throw new Error(e.toString());
132 }
133 }
134
135
136
137
138
139
140 public synchronized void redirectEntity(String name, URL entity)
141 {
142 if (entity != null)
143 _redirectMap.put(name, entity);
144 }
145
146
147
148
149
150
151 public String getXpath()
152 {
153 return _xpath;
154 }
155
156
157
158
159
160
161
162
163 public void setXpath(String xpath)
164 {
165 _xpath = xpath;
166 StringTokenizer tok = new StringTokenizer(xpath, "| ");
167 while (tok.hasMoreTokens())
168 _xpaths = LazyList.add(_xpaths, tok.nextToken());
169 }
170
171
172 public String getDTD()
173 {
174 return _dtd;
175 }
176
177
178
179
180
181
182
183
184
185
186 public synchronized void addContentHandler(String trigger, ContentHandler observer)
187 {
188 if (_observerMap == null)
189 _observerMap = new HashMap<>();
190 _observerMap.put(trigger, observer);
191 }
192
193
194 public synchronized Node parse(InputSource source) throws IOException, SAXException
195 {
196 _dtd=null;
197 Handler handler = new Handler();
198 XMLReader reader = _parser.getXMLReader();
199 reader.setContentHandler(handler);
200 reader.setErrorHandler(handler);
201 reader.setEntityResolver(handler);
202 if (LOG.isDebugEnabled())
203 LOG.debug("parsing: sid=" + source.getSystemId() + ",pid=" + source.getPublicId());
204 _parser.parse(source, handler);
205 if (handler._error != null)
206 throw handler._error;
207 Node doc = (Node) handler._top.get(0);
208 handler.clear();
209 return doc;
210 }
211
212
213
214
215
216 public synchronized Node parse(String url) throws IOException, SAXException
217 {
218 if (LOG.isDebugEnabled())
219 LOG.debug("parse: " + url);
220 return parse(new InputSource(url));
221 }
222
223
224
225
226
227 public synchronized Node parse(File file) throws IOException, SAXException
228 {
229 if (LOG.isDebugEnabled())
230 LOG.debug("parse: " + file);
231 return parse(new InputSource(Resource.toURL(file).toString()));
232 }
233
234
235
236
237
238 public synchronized Node parse(InputStream in) throws IOException, SAXException
239 {
240 _dtd=null;
241 Handler handler = new Handler();
242 XMLReader reader = _parser.getXMLReader();
243 reader.setContentHandler(handler);
244 reader.setErrorHandler(handler);
245 reader.setEntityResolver(handler);
246 _parser.parse(new InputSource(in), handler);
247 if (handler._error != null)
248 throw handler._error;
249 Node doc = (Node) handler._top.get(0);
250 handler.clear();
251 return doc;
252 }
253
254
255
256 protected InputSource resolveEntity(String pid, String sid)
257 {
258 if (LOG.isDebugEnabled())
259 LOG.debug("resolveEntity(" + pid + ", " + sid + ")");
260
261 if (sid!=null && sid.endsWith(".dtd"))
262 _dtd=sid;
263
264 URL entity = null;
265 if (pid != null)
266 entity = (URL) _redirectMap.get(pid);
267 if (entity == null)
268 entity = (URL) _redirectMap.get(sid);
269 if (entity == null)
270 {
271 String dtd = sid;
272 if (dtd.lastIndexOf('/') >= 0)
273 dtd = dtd.substring(dtd.lastIndexOf('/') + 1);
274
275 if (LOG.isDebugEnabled())
276 LOG.debug("Can't exact match entity in redirect map, trying " + dtd);
277 entity = (URL) _redirectMap.get(dtd);
278 }
279
280 if (entity != null)
281 {
282 try
283 {
284 InputStream in = entity.openStream();
285 if (LOG.isDebugEnabled())
286 LOG.debug("Redirected entity " + sid + " --> " + entity);
287 InputSource is = new InputSource(in);
288 is.setSystemId(sid);
289 return is;
290 }
291 catch (IOException e)
292 {
293 LOG.ignore(e);
294 }
295 }
296 return null;
297 }
298
299
300
301 private class NoopHandler extends DefaultHandler
302 {
303 Handler _next;
304 int _depth;
305
306 NoopHandler(Handler next)
307 {
308 this._next = next;
309 }
310
311
312 public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException
313 {
314 _depth++;
315 }
316
317
318 public void endElement(String uri, String localName, String qName) throws SAXException
319 {
320 if (_depth == 0)
321 _parser.getXMLReader().setContentHandler(_next);
322 else
323 _depth--;
324 }
325 }
326
327
328
329 private class Handler extends DefaultHandler
330 {
331 Node _top = new Node(null, null, null);
332 SAXParseException _error;
333 private Node _context = _top;
334 private NoopHandler _noop;
335
336 Handler()
337 {
338 _noop = new NoopHandler(this);
339 }
340
341
342 void clear()
343 {
344 _top = null;
345 _error = null;
346 _context = null;
347 }
348
349
350 public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException
351 {
352 String name = null;
353 if (_parser.isNamespaceAware())
354 name = localName;
355
356 if (name == null || "".equals(name))
357 name = qName;
358
359 Node node = new Node(_context, name, attrs);
360
361
362
363 if (_xpaths != null)
364 {
365 String path = node.getPath();
366 boolean match = false;
367 for (int i = LazyList.size(_xpaths); !match && i-- > 0;)
368 {
369 String xpath = (String) LazyList.get(_xpaths, i);
370
371 match = path.equals(xpath) || xpath.startsWith(path) && xpath.length() > path.length() && xpath.charAt(path.length()) == '/';
372 }
373
374 if (match)
375 {
376 _context.add(node);
377 _context = node;
378 }
379 else
380 {
381 _parser.getXMLReader().setContentHandler(_noop);
382 }
383 }
384 else
385 {
386 _context.add(node);
387 _context = node;
388 }
389
390 ContentHandler observer = null;
391 if (_observerMap != null)
392 observer = (ContentHandler) _observerMap.get(name);
393 _observers.push(observer);
394
395 for (int i = 0; i < _observers.size(); i++)
396 if (_observers.get(i) != null)
397 ((ContentHandler) _observers.get(i)).startElement(uri, localName, qName, attrs);
398 }
399
400
401 public void endElement(String uri, String localName, String qName) throws SAXException
402 {
403 _context = _context._parent;
404 for (int i = 0; i < _observers.size(); i++)
405 if (_observers.get(i) != null)
406 ((ContentHandler) _observers.get(i)).endElement(uri, localName, qName);
407 _observers.pop();
408 }
409
410
411 public void ignorableWhitespace(char buf[], int offset, int len) throws SAXException
412 {
413 for (int i = 0; i < _observers.size(); i++)
414 if (_observers.get(i) != null)
415 ((ContentHandler) _observers.get(i)).ignorableWhitespace(buf, offset, len);
416 }
417
418
419 public void characters(char buf[], int offset, int len) throws SAXException
420 {
421 _context.add(new String(buf, offset, len));
422 for (int i = 0; i < _observers.size(); i++)
423 if (_observers.get(i) != null)
424 ((ContentHandler) _observers.get(i)).characters(buf, offset, len);
425 }
426
427
428 public void warning(SAXParseException ex)
429 {
430 LOG.debug(Log.EXCEPTION, ex);
431 LOG.warn("WARNING@" + getLocationString(ex) + " : " + ex.toString());
432 }
433
434
435 public void error(SAXParseException ex) throws SAXException
436 {
437
438 if (_error == null)
439 _error = ex;
440 LOG.debug(Log.EXCEPTION, ex);
441 LOG.warn("ERROR@" + getLocationString(ex) + " : " + ex.toString());
442 }
443
444
445 public void fatalError(SAXParseException ex) throws SAXException
446 {
447 _error = ex;
448 LOG.debug(Log.EXCEPTION, ex);
449 LOG.warn("FATAL@" + getLocationString(ex) + " : " + ex.toString());
450 throw ex;
451 }
452
453
454 private String getLocationString(SAXParseException ex)
455 {
456 return ex.getSystemId() + " line:" + ex.getLineNumber() + " col:" + ex.getColumnNumber();
457 }
458
459
460 public InputSource resolveEntity(String pid, String sid)
461 {
462 return XmlParser.this.resolveEntity(pid,sid);
463 }
464 }
465
466
467
468
469
470
471 public static class Attribute
472 {
473 private String _name;
474 private String _value;
475
476 Attribute(String n, String v)
477 {
478 _name = n;
479 _value = v;
480 }
481
482 public String getName()
483 {
484 return _name;
485 }
486
487 public String getValue()
488 {
489 return _value;
490 }
491 }
492
493
494
495
496
497
498 public static class Node extends AbstractList<Object>
499 {
500 Node _parent;
501 private ArrayList<Object> _list;
502 private String _tag;
503 private Attribute[] _attrs;
504 private boolean _lastString = false;
505 private String _path;
506
507
508 Node(Node parent, String tag, Attributes attrs)
509 {
510 _parent = parent;
511 _tag = tag;
512
513 if (attrs != null)
514 {
515 _attrs = new Attribute[attrs.getLength()];
516 for (int i = 0; i < attrs.getLength(); i++)
517 {
518 String name = attrs.getLocalName(i);
519 if (name == null || name.equals(""))
520 name = attrs.getQName(i);
521 _attrs[i] = new Attribute(name, attrs.getValue(i));
522 }
523 }
524 }
525
526
527 public Node getParent()
528 {
529 return _parent;
530 }
531
532
533 public String getTag()
534 {
535 return _tag;
536 }
537
538
539 public String getPath()
540 {
541 if (_path == null)
542 {
543 if (getParent() != null && getParent().getTag() != null)
544 _path = getParent().getPath() + "/" + _tag;
545 else
546 _path = "/" + _tag;
547 }
548 return _path;
549 }
550
551
552
553
554
555 public Attribute[] getAttributes()
556 {
557 return _attrs;
558 }
559
560
561
562
563
564
565
566 public String getAttribute(String name)
567 {
568 return getAttribute(name, null);
569 }
570
571
572
573
574
575
576
577 public String getAttribute(String name, String dft)
578 {
579 if (_attrs == null || name == null)
580 return dft;
581 for (int i = 0; i < _attrs.length; i++)
582 if (name.equals(_attrs[i].getName()))
583 return _attrs[i].getValue();
584 return dft;
585 }
586
587
588
589
590
591 public int size()
592 {
593 if (_list != null)
594 return _list.size();
595 return 0;
596 }
597
598
599
600
601
602
603
604 public Object get(int i)
605 {
606 if (_list != null)
607 return _list.get(i);
608 return null;
609 }
610
611
612
613
614
615
616
617
618 public Node get(String tag)
619 {
620 if (_list != null)
621 {
622 for (int i = 0; i < _list.size(); i++)
623 {
624 Object o = _list.get(i);
625 if (o instanceof Node)
626 {
627 Node n = (Node) o;
628 if (tag.equals(n._tag))
629 return n;
630 }
631 }
632 }
633 return null;
634 }
635
636
637 @Override
638 public void add(int i, Object o)
639 {
640 if (_list == null)
641 _list = new ArrayList<Object>();
642 if (o instanceof String)
643 {
644 if (_lastString)
645 {
646 int last = _list.size() - 1;
647 _list.set(last, (String) _list.get(last) + o);
648 }
649 else
650 _list.add(i, o);
651 _lastString = true;
652 }
653 else
654 {
655 _lastString = false;
656 _list.add(i, o);
657 }
658 }
659
660
661 public void clear()
662 {
663 if (_list != null)
664 _list.clear();
665 _list = null;
666 }
667
668
669
670
671
672
673
674
675
676
677 public String getString(String tag, boolean tags, boolean trim)
678 {
679 Node node = get(tag);
680 if (node == null)
681 return null;
682 String s = node.toString(tags);
683 if (s != null && trim)
684 s = s.trim();
685 return s;
686 }
687
688
689 public synchronized String toString()
690 {
691 return toString(true);
692 }
693
694
695
696
697
698
699
700 public synchronized String toString(boolean tag)
701 {
702 StringBuilder buf = new StringBuilder();
703 toString(buf, tag);
704 return buf.toString();
705 }
706
707
708
709
710
711
712
713 public synchronized String toString(boolean tag, boolean trim)
714 {
715 String s = toString(tag);
716 if (s != null && trim)
717 s = s.trim();
718 return s;
719 }
720
721
722 private synchronized void toString(StringBuilder buf, boolean tag)
723 {
724 if (tag)
725 {
726 buf.append("<");
727 buf.append(_tag);
728
729 if (_attrs != null)
730 {
731 for (int i = 0; i < _attrs.length; i++)
732 {
733 buf.append(' ');
734 buf.append(_attrs[i].getName());
735 buf.append("=\"");
736 buf.append(_attrs[i].getValue());
737 buf.append("\"");
738 }
739 }
740 }
741
742 if (_list != null)
743 {
744 if (tag)
745 buf.append(">");
746 for (int i = 0; i < _list.size(); i++)
747 {
748 Object o = _list.get(i);
749 if (o == null)
750 continue;
751 if (o instanceof Node)
752 ((Node) o).toString(buf, tag);
753 else
754 buf.append(o.toString());
755 }
756 if (tag)
757 {
758 buf.append("</");
759 buf.append(_tag);
760 buf.append(">");
761 }
762 }
763 else if (tag)
764 buf.append("/>");
765 }
766
767
768
769
770
771
772
773
774 public Iterator<Node> iterator(final String tag)
775 {
776 return new Iterator<Node>()
777 {
778 int c = 0;
779 Node _node;
780
781
782 public boolean hasNext()
783 {
784 if (_node != null)
785 return true;
786 while (_list != null && c < _list.size())
787 {
788 Object o = _list.get(c);
789 if (o instanceof Node)
790 {
791 Node n = (Node) o;
792 if (tag.equals(n._tag))
793 {
794 _node = n;
795 return true;
796 }
797 }
798 c++;
799 }
800 return false;
801 }
802
803
804 public Node next()
805 {
806 try
807 {
808 if (hasNext())
809 return _node;
810 throw new NoSuchElementException();
811 }
812 finally
813 {
814 _node = null;
815 c++;
816 }
817 }
818
819
820 public void remove()
821 {
822 throw new UnsupportedOperationException("Not supported");
823 }
824 };
825 }
826 }
827 }