e8210159951327dfbb7646f28c8d05bfe55e7f80
[cfb.git] / prod / net / jaekl / qd / xml / ParseResult.java
1 // Copyright (C) 2004, 2015 Christian Jaekl
2
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.
6 //
7 // Note that this code will need to be augmented and fixed if XML namespace support is desired.
8
9 package net.jaekl.qd.xml;
10
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.Iterator;
15 import java.util.Stack;
16
17 import org.xml.sax.Attributes;
18 import org.xml.sax.helpers.AttributesImpl;
19
20 public abstract class ParseResult
21 {
22         public static final String TAG_FIELD_NAME = "TAG";
23         
24         Stack<CurrentInfo> m_current;                                                           // Name of the element that we're currently inside
25         StringBuilder m_chars;                                                                          // character content of m_current.peek()
26         ArrayList<ParseResult> m_childParsers;                                          // Set of all child parsers
27         boolean m_haveSeenMyTag;                                                                        // Have I encountered my own (root) tag yet?
28
29         String m_tagName;                                                                                       // Name of the (root) element tag that I'm parsing
30         HashSet<String> m_internal;                                                                     // Tags that we will store as members of this class instance
31         HashMap<String,Class<? extends ParseResult>> m_external;        // Tags that we will store as child ParseResult-derived objects
32
33         // Information about the "current" tag, which we'll keep on the m_current stack.
34         static class CurrentInfo {
35                 String m_tagName;
36                 Attributes m_attr;
37                 
38                 public CurrentInfo(String tagName, Attributes attr) {
39                         m_tagName = tagName;
40                         m_attr = new AttributesImpl(attr);
41                 }
42                 
43                 public String getTagName() { return m_tagName; }
44                 public final Attributes getAttributes() { return m_attr; }
45         }
46         
47         @SuppressWarnings("unchecked")
48         public ParseResult(String tagName, String[] internalMemberTags, Object[][] externalParserTags)
49         {
50                 m_current = new Stack<CurrentInfo>();
51                 m_chars = new StringBuilder();
52                 m_childParsers = new ArrayList<ParseResult>();
53                 m_haveSeenMyTag = false;
54                 
55                 m_tagName = tagName;
56                 m_internal = new HashSet<String>();
57                 m_external = new HashMap<String, Class<? extends ParseResult>>();
58
59                 for (String internalTag : internalMemberTags) {
60                         m_internal.add(internalTag);
61                 }
62
63                 for (int idx = 0; idx < externalParserTags.length; ++idx) {
64                         String externalTag = (String)externalParserTags[idx][0];
65                         Class<? extends ParseResult>  parserClass = (Class<? extends ParseResult>)externalParserTags[idx][1];
66                         m_external.put(externalTag, parserClass);
67                 }
68         }
69
70         public abstract void endContents(String uri, String localName, String qName, String chars, Attributes attr) throws XmlParseException;
71         public abstract void endExternal(String uri, String localName, String qName) throws XmlParseException;
72         
73         public String getTagName() { return m_tagName; }
74         public boolean haveSeenMyTag() { return m_haveSeenMyTag; }
75
76         public void characters(char[] ch, int start, int length) throws XmlParseException
77         {
78                 m_chars.append(ch, start, length);
79         }
80         
81         protected ParseResult[] collectParsedChildren(Class<? extends ParseResult> cls) {
82                 ArrayList<ParseResult> collection = new ArrayList<ParseResult>();
83                 Iterator<ParseResult> iter = m_childParsers.iterator();
84                 while (iter.hasNext()) {
85                         ParseResult pr = iter.next();
86                         if (pr.getClass().isAssignableFrom(cls)) {
87                                 collection.add(pr);
88                                 iter.remove();
89                         }
90                 }
91                 
92                 ParseResult[] result = new ParseResult[collection.size()];
93                 return collection.toArray(result);
94         }
95         
96         // returns true if this ParseResult's context has ended with this endElement() call
97         public boolean endElement(String uri, String localName, String qName) throws XmlParseException
98         {
99                 assert (null != localName);
100                 
101                 boolean isInternal = m_internal.contains(localName);
102
103                 if (! m_haveSeenMyTag) {
104                         // We're in some unrecognised prologue.  Ignore it and move on.
105                         return false;
106                 }
107                 
108                 if (m_tagName.equals(localName)) {
109                         validate();
110                         return true;
111                 }
112                 
113                 if (!isInternal) {
114                         // Unrecognized tag.  Ignore it.
115                         return false;
116                 }
117                 
118                 CurrentInfo info = m_current.pop();
119                 String tag = info.getTagName();
120                 if ( ! tag.equals(localName) ) {
121                         throw new MismatchedTagsException(tag, localName);
122                 }
123                 
124                 String chars = m_chars.toString();
125                 endContents(uri, localName, qName, chars, info.getAttributes());
126                 
127                 return false;
128         }
129         
130         // returns either itself, or a new ParseResult-derived object, whichever should handle parsing the inside of this element
131         public ParseResult startElement(String uri, String localName, String qName, Attributes attributes) 
132                         throws XmlParseException
133         {
134                 assert (null != localName);
135
136                 m_chars.setLength(0);
137                 
138                 if (! m_haveSeenMyTag) {
139                         // Have we opened our own (root) tag yet?
140                         
141                         if (m_tagName.equals(localName)) {
142                                 m_haveSeenMyTag = true;
143                                 return this;
144                         }
145                         else {
146                                 // One of two things has happened here.
147                                 // Either (a) we've got some sort of wrapper here, and have not yet reach our own tag, 
148                                 //     or (b) we're parsing XML that doesn't match expectations.
149                                 // In either case, we're going to ignore this tag, and scan forward looking for our own root.
150                                 return this;
151                         }
152                 }
153
154                 if (m_internal.contains(localName)) {
155                         CurrentInfo info = new CurrentInfo(localName, attributes);
156                         m_current.push(info);
157                         return this;
158                 }
159
160                 Class<? extends ParseResult> parserClass = m_external.get(localName);
161                 if (null != parserClass) {
162                         try {
163                                 ParseResult childParser = (ParseResult) parserClass.newInstance();
164                                 m_childParsers.add(childParser);
165                                 return childParser.startElement(uri, localName, qName, attributes);
166                         }
167                         catch (IllegalAccessException iae) {
168                                 throw new XmlParseException(iae);
169                         }
170                         catch (InstantiationException ie) {
171                                 throw new XmlParseException(ie);
172                         }
173                 }
174                 
175                 // Not a recognized tag.  Ignore it, rather than complaining. 
176                 return this;
177         }
178         
179         public void validate() throws XmlParseException
180         {
181                 // Default implementation is a no-op.
182                 // Override if you want to validate on endElement()
183         }
184         
185         protected String getRequiredAttr(String tagName, Attributes attr, String attrName)
186                 throws MissingAttributeException
187         {
188                 String result = attr.getValue(attrName);
189                 if (null == result) {
190                         throw new MissingAttributeException(tagName, attrName);
191                 }
192                 return result;
193         }
194         
195         protected String getOptionalAttr(Attributes attr, String attrName, String defaultValue)
196         {
197                 String value = attr.getValue(attrName);
198                 if (null == value) {
199                         return defaultValue;
200                 }
201                 return value;
202         }
203 }
204