View Javadoc

1   /*
2    *   Firemox is a turn based strategy simulator
3    *   Copyright (C) 2003-2007 Fabrice Daugan
4    *
5    *   This program is free software; you can redistribute it and/or modify it 
6    * under the terms of the GNU General Public License as published by the Free 
7    * Software Foundation; either version 2 of the License, or (at your option) any
8    * later version.
9    *
10   *   This program is distributed in the hope that it will be useful, but WITHOUT 
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
13   * details.
14   *
15   *   You should have received a copy of the GNU General Public License along  
16   * with this program; if not, write to the Free Software Foundation, Inc., 
17   * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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 			// Save error and continue to report other errors
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("&", "&amp;").replace("'", "&#39;")
770 				.replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;");
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 }