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