1 // Copyright (C) 2004, 2015 Christian Jaekl
3 // Abstract class representing the result of parsing an XML Element.
4 // A class derived from this one will know how to parse a subtree inside an XML file, and
5 // will contain the result of that parse within itself when the parse has completed.
7 // Note that this code will need to be augmented and fixed if XML namespace support is desired.
9 package net.jaekl.qd.xml;
11 import java.io.PrintWriter;
12 import java.util.ArrayList;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.Stack;
18 import org.xml.sax.Attributes;
19 import org.xml.sax.helpers.AttributesImpl;
21 public abstract class ParseResult
23 public static final String TAG_FIELD_NAME = "TAG";
25 Stack<CurrentInfo> m_current; // Name of the element that we're currently inside
26 StringBuilder m_chars; // character content of m_current.peek()
27 ArrayList<ParseResult> m_childParsers; // Set of all child parsers
28 boolean m_haveSeenMyTag; // Have I encountered my own (root) tag yet?
30 String m_tagName; // Name of the (root) element tag that I'm parsing
31 HashSet<String> m_internal; // Tags that we will store as members of this class instance
32 HashMap<String,Class<? extends ParseResult>> m_external; // Tags that we will store as child ParseResult-derived objects
34 // Information about the "current" tag, which we'll keep on the m_current stack.
35 static class CurrentInfo {
39 public CurrentInfo(String tagName, Attributes attr) {
41 m_attr = new AttributesImpl(attr);
44 public String getTagName() { return m_tagName; }
45 public final Attributes getAttributes() { return m_attr; }
48 @SuppressWarnings("unchecked")
49 public ParseResult(String tagName, String[] internalMemberTags, Object[][] externalParserTags)
51 m_current = new Stack<CurrentInfo>();
52 m_chars = new StringBuilder();
53 m_childParsers = new ArrayList<ParseResult>();
54 m_haveSeenMyTag = false;
57 m_internal = new HashSet<String>();
58 m_external = new HashMap<String, Class<? extends ParseResult>>();
60 for (String internalTag : internalMemberTags) {
61 m_internal.add(internalTag);
64 for (int idx = 0; idx < externalParserTags.length; ++idx) {
65 String externalTag = (String)externalParserTags[idx][0];
66 Class<? extends ParseResult> parserClass = (Class<? extends ParseResult>)externalParserTags[idx][1];
67 m_external.put(externalTag, parserClass);
71 public abstract void endContents(String uri, String localName, String qName, String chars) throws XmlParseException;
72 public abstract void endExternal(String uri, String localName, String qName) throws XmlParseException;
74 // Called once for this tag itself
75 public void handleMainAttributes(Attributes attr) throws MissingAttributeException {
76 // Default action is to do nothing.
77 // Override this if you want to process tag attributes.
80 // Called once for each internally-handled subtag
81 public void handleInternalAttributes(String tagName, Attributes attr) throws MissingAttributeException {
82 // Default action is to do nothing.
83 // Override this if you want to process tag attributes.
86 public String getTagName() { return m_tagName; }
88 public void characters(char[] ch, int start, int length) throws XmlParseException
90 m_chars.append(ch, start, length);
93 // Dump human-readable text describing this tag and its children.
94 // Default implemenation: print the tag name (only)
95 public void dump(PrintWriter pw, int indent) {
96 String margin = String.format("%"+indent+"s", "");
97 pw.println(margin + getTagName());
100 protected ParseResult[] collectParsedChildren(Class<? extends ParseResult> cls) {
101 ArrayList<ParseResult> collection = new ArrayList<ParseResult>();
102 Iterator<ParseResult> iter = m_childParsers.iterator();
103 while (iter.hasNext()) {
104 ParseResult pr = iter.next();
105 if (pr.getClass().isAssignableFrom(cls)) {
111 ParseResult[] result = new ParseResult[collection.size()];
112 return collection.toArray(result);
115 // returns true if this ParseResult's context has ended with this endElement() call
116 protected boolean endElement(String uri, String localName, String qName) throws XmlParseException
118 assert (null != localName);
120 boolean isInternal = m_internal.contains(localName);
122 if (! m_haveSeenMyTag) {
123 // We're in some unrecognised prologue. Ignore it and move on.
127 if (m_tagName.equals(localName)) {
133 // Unrecognized tag. Ignore it.
137 CurrentInfo info = m_current.pop();
138 String tag = info.getTagName();
139 if ( ! tag.equals(localName) ) {
140 throw new MismatchedTagsException(tag, localName);
143 String chars = m_chars.toString();
144 endContents(uri, localName, qName, chars);
149 // returns either itself, or a new ParseResult-derived object, whichever should handle parsing the inside of this element
150 public ParseResult startElement(String uri, String localName, String qName, Attributes attrs)
151 throws XmlParseException
153 assert (null != localName);
155 m_chars.setLength(0);
157 if (! m_haveSeenMyTag) {
158 // Have we opened our own (root) tag yet?
160 if (m_tagName.equals(localName)) {
161 m_haveSeenMyTag = true;
162 handleMainAttributes(attrs);
166 // One of two things has happened here.
167 // Either (a) we've got some sort of wrapper here, and have not yet reach our own tag,
168 // or (b) we're parsing XML that doesn't match expectations.
169 // In either case, we're going to ignore this tag, and scan forward looking for our own root.
174 if (m_internal.contains(localName)) {
175 handleInternalAttributes(localName, attrs);
176 CurrentInfo info = new CurrentInfo(localName, attrs);
177 m_current.push(info);
181 Class<? extends ParseResult> parserClass = m_external.get(localName);
182 if (null != parserClass) {
184 ParseResult childParser = (ParseResult) parserClass.newInstance();
185 m_childParsers.add(childParser);
186 return childParser.startElement(uri, localName, qName, attrs);
188 catch (IllegalAccessException iae) {
189 throw new XmlParseException(iae);
191 catch (InstantiationException ie) {
192 throw new XmlParseException(ie);
196 // Not a recognized tag. Ignore it, rather than complaining.
200 public void complete() throws XmlParseException
202 // Default implementation is a no-op.
203 // Override if you want to validate on endElement()
206 protected String getRequiredAttr(String tagName, Attributes attr, String attrName)
207 throws MissingAttributeException
209 String result = attr.getValue(attrName);
210 if (null == result) {
211 throw new MissingAttributeException(tagName, attrName);
216 protected String getOptionalAttr(Attributes attr, String attrName, String defaultValue)
218 String value = attr.getValue(attrName);