View Javadoc

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