1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sf.firemox.xml;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.PrintWriter;
25 import java.net.URI;
26 import java.net.URISyntaxException;
27 import java.util.AbstractList;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Stack;
31
32 import javax.xml.XMLConstants;
33 import javax.xml.parsers.SAXParser;
34 import javax.xml.parsers.SAXParserFactory;
35
36 import net.sf.firemox.token.IdConst;
37 import net.sf.firemox.tools.MToolKit;
38
39 import org.apache.commons.lang.StringUtils;
40 import org.xml.sax.Attributes;
41 import org.xml.sax.ContentHandler;
42 import org.xml.sax.InputSource;
43 import org.xml.sax.SAXException;
44 import org.xml.sax.SAXParseException;
45 import org.xml.sax.XMLReader;
46 import org.xml.sax.helpers.DefaultHandler;
47
48 /***
49 * XML Parser wrapper. This class wraps any standard JAXP1.1 parser with
50 * convenient error and entity handlers and a mini dom-like document tree.
51 * <P>
52 * By default, the parser is created as a validating parser. This can be changed
53 * by setting the "org.mortbay.xml.XmlParser.NotValidating" system property to
54 * true.
55 *
56 * @version $Id$
57 * @author Greg Wilkins (gregw)
58 */
59 public class XmlParser {
60
61 /***
62 * Constructor.
63 *
64 * @throws SAXException
65 */
66 public XmlParser() throws SAXException {
67 this(false);
68 }
69
70 /***
71 * Constructor.
72 *
73 * @param validation
74 * validation flag.
75 * @throws SAXException
76 */
77 public XmlParser(boolean validation) throws SAXException {
78 try {
79 SAXParserFactory factory = SAXParserFactory.newInstance();
80 factory.setNamespaceAware(true);
81 factory.setValidating(validation);
82 parser = factory.newSAXParser();
83 if (validation) {
84 parser.setProperty(JAXP_SCHEMA_LANGUAGE,
85 XMLConstants.W3C_XML_SCHEMA_NS_URI);
86 parser.setProperty(JAXP_SCHEMA_SOURCE, MToolKit.getFile(MP_XML_SCHEMA));
87 }
88 } catch (Exception e) {
89 throw new SAXException(e.toString());
90 }
91 }
92
93 /***
94 * @param source
95 * the document source.
96 * @return the node of document.
97 * @throws IOException
98 * error while opening the stream.
99 * @throws SAXException
100 * error while parsing.
101 */
102 public synchronized Node parse(InputSource source) throws IOException,
103 SAXException {
104 Handler handler = new Handler();
105 XMLReader reader = parser.getXMLReader();
106 reader.setContentHandler(handler);
107 reader.setErrorHandler(handler);
108 reader.setEntityResolver(handler);
109 parser.parse(source, handler);
110 if (handler.error != null) {
111 throw handler.error;
112 }
113 Node doc = (Node) handler.top.get(0);
114 handler.clear();
115 return doc;
116 }
117
118 /***
119 * Parse string URL.
120 *
121 * @param url
122 * the document source.
123 * @return the node of document.
124 * @throws IOException
125 * error while opening the stream.
126 * @throws SAXException
127 * error while parsing.
128 */
129 public synchronized Node parse(String url) throws IOException, SAXException {
130 return parse(new InputSource(url));
131 }
132
133 /***
134 * Parse InputStream.
135 *
136 * @param in
137 * the document source.
138 * @return the node of document.
139 * @throws IOException
140 * error while opening the stream.
141 * @throws SAXException
142 * error while parsing.
143 */
144 public synchronized Node parse(InputStream in) throws IOException,
145 SAXException {
146 Handler handler = new Handler();
147 XMLReader reader = parser.getXMLReader();
148 reader.setContentHandler(handler);
149 reader.setErrorHandler(handler);
150 reader.setEntityResolver(handler);
151 parser.parse(new InputSource(in), handler);
152 if (handler.error != null) {
153 throw handler.error;
154 }
155 Node doc = (Node) handler.top.get(0);
156 handler.clear();
157 return doc;
158 }
159
160 /***
161 * The Default handler
162 *
163 * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan
164 * </a>
165 */
166 class Handler extends DefaultHandler {
167
168 Node top = new Node(null, null, null);
169
170 SAXParseException error;
171
172 @Override
173 public InputSource resolveEntity(String publicId, String systemId) {
174 if (systemId.endsWith(".xsd")) {
175 String xsdFile;
176 try {
177 xsdFile = IdConst.TBS_DIR + "/"
178 + new File(new URI(systemId)).getName();
179 } catch (URISyntaxException e) {
180 return null;
181 }
182 return new InputSource(xsdFile);
183 }
184 return null;
185 }
186
187 private Node context = top;
188
189 void clear() {
190 top = null;
191 error = null;
192 context = null;
193 }
194
195 @Override
196 public void startElement(String uri, String localName, String qName,
197 Attributes attrs) throws SAXException {
198 String name = uri == null || uri.length() == 0 ? qName : localName;
199 Node node = new Node(context, name, attrs);
200 context.add(node);
201 context = node;
202 ContentHandler observer = null;
203 observers.push(observer);
204 for (ContentHandler handler : observers) {
205 if (handler != null) {
206 handler.startElement(uri, localName, qName, attrs);
207 }
208 }
209 }
210
211 @Override
212 public void notationDecl(String name, String publicId, String systemId) {
213 System.out.println("name:" + name + ",publicId:" + publicId
214 + ",systemId:" + systemId);
215 }
216
217 @Override
218 public void endElement(String uri, String localName, String qName)
219 throws SAXException {
220 context = context.aParent;
221 for (ContentHandler handler : observers) {
222 if (handler != null) {
223 handler.endElement(uri, localName, qName);
224 }
225 }
226 observers.pop();
227 }
228
229 @Override
230 public void ignorableWhitespace(char[] buf, int offset, int len)
231 throws SAXException {
232 for (ContentHandler handler : observers) {
233 if (handler != null) {
234 handler.ignorableWhitespace(buf, offset, len);
235 }
236 }
237 }
238
239 @Override
240 public void characters(char[] buf, int offset, int len) throws SAXException {
241 context.add(new String(buf, offset, len));
242 for (int i = 0; i < observers.size(); i++) {
243 if (observers.get(i) != null) {
244 observers.get(i).characters(buf, offset, len);
245 }
246 }
247 }
248
249 @Override
250 public void warning(SAXParseException ex) {
251 XmlConfiguration.warning("@" + getLocationString(ex) + " : "
252 + ex.toString());
253 }
254
255 @Override
256 public void error(SAXParseException ex) {
257
258 if (error == null) {
259 error = ex;
260 }
261 XmlConfiguration.error("@" + getLocationString(ex) + " : "
262 + ex.getMessage());
263 }
264
265 @Override
266 public void fatalError(SAXParseException ex) {
267 error = ex;
268 XmlConfiguration.fatal("@" + getLocationString(ex) + " : "
269 + ex.getMessage());
270 }
271
272 private String getLocationString(SAXParseException ex) {
273 return "line:" + ex.getLineNumber() + " col:" + ex.getColumnNumber();
274 }
275
276 }
277
278 /***
279 * XML Attribute.
280 */
281 public static class Attribute {
282
283 private String aName;
284
285 private String aValue;
286
287 /***
288 * Create a new instance of this class.
289 *
290 * @param n
291 * attribute name
292 * @param v
293 * attribute value
294 */
295 public Attribute(String n, String v) {
296 aName = n;
297 aValue = v;
298 }
299
300 /***
301 * @return name
302 */
303 public String getName() {
304 return aName;
305 }
306
307 /***
308 * Set the attribute value.
309 *
310 * @param value
311 * the attrivute value.
312 */
313 public void setValue(String value) {
314 this.aValue = value;
315 }
316
317 /***
318 * @return value
319 */
320 public String getValue() {
321 return aValue;
322 }
323 }
324
325
326
327 /***
328 * XML Node. Represents an XML element with optional attributes and ordered
329 * content.
330 */
331 public static class Node extends AbstractList<Object> {
332
333 Node aParent;
334
335 /***
336 * Nodes
337 */
338 public List<Object> aList;
339
340 /***
341 * Tag name.
342 */
343 public String aTag;
344
345 /***
346 * Attributes
347 */
348 public Attribute[] aAttrs;
349
350 /***
351 * Is the last object is a string.
352 */
353 public boolean aLastString = false;
354
355 /***
356 * Create a new instance of this class.
357 *
358 * @param parent
359 * parent node.
360 * @param tag
361 * tag name.
362 * @param attrs
363 * attributes
364 */
365 public Node(Node parent, String tag, Attributes attrs) {
366 aParent = parent;
367 aTag = tag;
368 if (attrs != null) {
369 aAttrs = new Attribute[attrs.getLength()];
370 for (int i = 0; i < attrs.getLength(); i++) {
371 String name = attrs.getQName(i);
372 if (name == null || name.length() == 0) {
373 name = attrs.getQName(i);
374 }
375 aAttrs[i] = new Attribute(name, attrs.getValue(i));
376 }
377 }
378 }
379
380 /***
381 * @param attr
382 * attribute to add.
383 */
384 public void addFirstAttribute(Attribute attr) {
385 Attribute[] attrs = new Attribute[this.aAttrs.length + 1];
386 System.arraycopy(this.aAttrs, 0, attrs, 1, this.aAttrs.length);
387 attrs[0] = attr;
388 this.aAttrs = attrs;
389 }
390
391 /***
392 * Remove the first attribute with specified name.
393 *
394 * @param attributeName
395 * the attribute to remove.
396 */
397 public void removeAttribute(String attributeName) {
398 for (int i = 0; i < this.aAttrs.length; i++) {
399 if (this.aAttrs[i].getName().equals(attributeName)) {
400 final Attribute[] attrs = new Attribute[this.aAttrs.length - 1];
401 if (attrs.length == 0) {
402 this.aAttrs = attrs;
403 } else {
404 if (i == 0) {
405 System.arraycopy(this.aAttrs, 1, attrs, 0, attrs.length);
406 } else {
407 System.arraycopy(this.aAttrs, 0, attrs, 0, i);
408 if (i != attrs.length) {
409 System
410 .arraycopy(this.aAttrs, i + 1, attrs, i, attrs.length - i);
411 }
412 }
413 this.aAttrs = attrs;
414 }
415 return;
416 }
417 }
418 }
419
420 /***
421 * @param attr
422 * attribute to add.
423 */
424 public void addAttribute(Attribute attr) {
425 for (Attribute attribute : aAttrs) {
426 if (attr.getName().equals(attribute.getName())) {
427 attribute.setValue(attr.getValue());
428 return;
429 }
430 }
431 final Attribute[] aAttrs = new Attribute[this.aAttrs.length + 1];
432 System.arraycopy(this.aAttrs, 0, aAttrs, 0, this.aAttrs.length);
433 aAttrs[this.aAttrs.length] = attr;
434 this.aAttrs = aAttrs;
435 }
436
437 /***
438 * @return parent
439 */
440 public Node getParent() {
441 return aParent;
442 }
443
444 /***
445 * @return tag
446 */
447 public String getTag() {
448 return aTag;
449 }
450
451 /***
452 * Get an element attribute.
453 *
454 * @param name
455 * the attribute name.
456 * @return attribute or null.
457 */
458 public String getAttribute(String name) {
459 return getAttribute(name, null);
460 }
461
462 /***
463 * Get an element attribute.
464 *
465 * @param name
466 * the attribute name.
467 * @param dft
468 * the default value.
469 * @return attribute or null.
470 */
471 public String getAttribute(String name, String dft) {
472 if (aAttrs == null || name == null) {
473 return dft;
474 }
475 for (Attribute attribute : aAttrs) {
476 if (name.equals(attribute.getName())) {
477 return attribute.getValue();
478 }
479 }
480 return dft;
481 }
482
483 /***
484 * Get the number of children nodes.
485 *
486 * @return size
487 */
488 @Override
489 public int size() {
490 if (aList != null) {
491 return aList.size();
492 }
493 return 0;
494 }
495
496 /***
497 * Get the number of non text children nodes.
498 *
499 * @return the number of non text children nodes.
500 */
501 public int getNbNodes() {
502 if (aList == null)
503 return 0;
504 int count = 0;
505 for (Object obj : aList) {
506 if (obj instanceof Node) {
507 count++;
508 }
509 }
510 return count;
511 }
512
513 /***
514 * Get the ith child node or content.
515 *
516 * @param i
517 * index of node.
518 * @return Node or String.
519 */
520 @Override
521 public Object get(int i) {
522 if (aList != null) {
523 return aList.get(i);
524 }
525 return null;
526 }
527
528 /***
529 * Get the first child node with the tag.
530 *
531 * @param tag
532 * the tag name of node.
533 * @return Node or null.
534 */
535 public Node get(String tag) {
536 if (aList != null) {
537 for (Object o : aList) {
538 if (o instanceof Node) {
539 Node n = (Node) o;
540 if (tag.equals(n.aTag)) {
541 return n;
542 }
543 }
544 }
545 }
546 return null;
547 }
548
549 /***
550 * Get the child nodes with the tag.
551 *
552 * @param tag
553 * the tag name of node.
554 * @return list of nodes or null.
555 */
556 public List<Node> getNodes(String tag) {
557 final List<Node> list = new ArrayList<Node>();
558 if (aList != null) {
559 for (Object o : aList) {
560 if (o instanceof Node) {
561 Node n = (Node) o;
562 if (tag.equals(n.aTag)) {
563 list.add(n);
564 }
565 }
566 }
567 }
568 return list;
569 }
570
571 @Override
572 public void add(int i, Object o) {
573 if (aList == null) {
574 aList = new ArrayList<Object>();
575 }
576 if (o instanceof String) {
577 if (aLastString) {
578 int last = aList.size() - 1;
579 aList.set(last, (String) aList.get(last) + o);
580 } else {
581 aList.add(i, o);
582 }
583 aLastString = true;
584 } else {
585 aLastString = false;
586 aList.add(i, o);
587 }
588 }
589
590 /***
591 * Inserts the specified element at the specified position in this list
592 * (optional operation). Shifts the element currently at that position (if
593 * any) and any subsequent elements to the right (adds one to their
594 * indices).
595 * <p>
596 * This implementation always throws an UnsupportedOperationException.
597 *
598 * @param o
599 * element to be inserted.
600 */
601 public void removeElement(XmlParser.Node o) {
602 aLastString = false;
603 aList.remove(o);
604 }
605
606 /***
607 * Removes the element at the specified position in this list (optional
608 * operation). Shifts any subsequent elements to the left (subtracts one
609 * from their indices). Returns the element that was removed from the list.
610 *
611 * @param index
612 * the index of the element to removed.
613 */
614 public void removeElement(int index) {
615 aLastString = false;
616 aList.remove(index);
617 }
618
619 /***
620 * Replaces the element at the specified position in this list with the
621 * specified element (optional operation).
622 *
623 * @param index
624 * index of element to replace.
625 * @param element
626 * element to be stored at the specified position.
627 */
628 public void setElement(int index, Object element) {
629 aList.set(index, element);
630 }
631
632 @Override
633 public void clear() {
634 if (aList != null) {
635 aList.clear();
636 }
637 aList = null;
638 }
639
640 /***
641 * Print this node using the specified printer.
642 *
643 * @param printer
644 * used printer to add this node.
645 */
646 public synchronized void print(PrintWriter printer) {
647 for (Attribute attribute : aAttrs) {
648 if ("xsi:schemaLocation".equals(attribute.getName())) {
649 XmlParser.Attribute[] attributes = new XmlParser.Attribute[aAttrs.length + 2];
650 System.arraycopy(aAttrs, 0, attributes, 2, aAttrs.length);
651 attributes[0] = new XmlParser.Attribute("xmlns:xsi",
652 XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
653 attributes[1] = new XmlParser.Attribute("xmlns", attribute.getValue()
654 .split(" ")[0]);
655 aAttrs = attributes;
656 break;
657 }
658 }
659 print(printer, "");
660 }
661
662 @Override
663 public synchronized String toString() {
664 return toString("");
665 }
666
667 @Override
668 public boolean equals(Object obj) {
669 return this == obj;
670 }
671
672 @Override
673 public int hashCode() {
674 return aTag.hashCode();
675 }
676
677 /***
678 * Convert to a string.
679 *
680 * @param tag
681 * If false, only content is shown.
682 * @return string
683 */
684 private String toString(String ident) {
685 StringBuilder buf = new StringBuilder();
686 toString(buf, ident);
687 return buf.toString();
688 }
689
690 private void print(PrintWriter printer, String ident) {
691 printer.print(ident);
692 printer.print("<");
693 printer.print(aTag);
694 if (aAttrs != null) {
695 for (Attribute attribute : aAttrs) {
696 printer.print(' ');
697 printer.print(attribute.getName());
698 printer.print("=\"");
699 printer.print(normalize(attribute.getValue()));
700 printer.print("\"");
701 }
702 }
703 if (aList != null) {
704 printer.print(">");
705 printer.println();
706 for (Object o : aList) {
707 if (o == null) {
708 continue;
709 }
710 if (o instanceof Node) {
711 ((Node) o).print(printer, ident + "\t");
712 } else {
713 printer.print(normalize(StringUtils.trimToEmpty(o.toString())));
714 }
715 }
716 printer.print(ident);
717 printer.print("</");
718 printer.print(aTag);
719 printer.print(">");
720 } else {
721 printer.print("/>");
722 }
723 printer.println();
724 }
725
726 private void toString(StringBuilder buf, String ident) {
727 buf.append(ident);
728 buf.append("<");
729 buf.append(aTag);
730 if (aAttrs != null) {
731 for (Attribute attribute : aAttrs) {
732 buf.append(' ');
733 buf.append(attribute.getName());
734 buf.append("=\"");
735 buf.append(normalize(attribute.getValue()));
736 buf.append("\"");
737 }
738 }
739 if (aList != null) {
740 buf.append(">\n");
741 for (Object o : aList) {
742 if (o == null) {
743 continue;
744 }
745 if (o instanceof Node) {
746 ((Node) o).toString(buf, ident + "\t");
747 } else {
748 buf.append(normalize(StringUtils.trimToEmpty(o.toString())));
749 }
750 }
751 buf.append(ident);
752 buf.append("</");
753 buf.append(aTag);
754 buf.append(">\n");
755 } else {
756 buf.append("/>\n");
757 }
758 }
759 }
760
761 /***
762 * Return the given text as XML string.
763 *
764 * @param text
765 * the text containing non serializable tags.
766 * @return the given text as XML string.
767 */
768 public static String normalize(String text) {
769 return text.replace("&", "`").replace("&", "&").replace("'", "'")
770 .replace("<", "<").replace(">", ">").replace("\"", """);
771 }
772
773 /***
774 * The SAX parser
775 */
776 private SAXParser parser;
777
778 Stack<ContentHandler> observers = new Stack<ContentHandler>();
779
780 static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
781
782 static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
783
784 static final String MP_XML_SCHEMA = IdConst.TBS_DIR + "/" + IdConst.TBS_XSD;
785 }