Initial
[frank.git] / prod / net / jaekl / qd / xml / ParseResult.java
1 // Copyright (C) 2004, 2014 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
19 public abstract class ParseResult
20 {
21         Stack<String> m_current;                                                                        // Name of the element that we're currently inside
22         StringBuilder m_chars;                                                                          // character content of m_current.peek()
23         ArrayList<ParseResult> m_childParsers;                                          // Set of all child parsers
24         boolean m_haveSeenMyTag;                                                                        // Have I encountered my own (root) tag yet?
25
26         String m_tagName;                                                                                       // Name of the (root) element tag that I'm parsing
27         HashSet<String> m_internal;                                                                     // Tags that we will store as members of this class instance
28         HashMap<String,Class<? extends ParseResult>> m_external;        // Tags that we will store as child ParseResult-derived objects
29
30         @SuppressWarnings("unchecked")
31         public ParseResult(String tagName, String[] internalMemberTags, Object[][] externalParserTags)
32         {
33                 m_current = new Stack<String>();
34                 m_chars = new StringBuilder();
35                 m_childParsers = new ArrayList<ParseResult>();
36                 m_haveSeenMyTag = false;
37                 
38                 m_tagName = tagName;
39                 m_internal = new HashSet<String>();
40                 m_external = new HashMap<String, Class<? extends ParseResult>>();
41
42                 for (String internalTag : internalMemberTags) {
43                         m_internal.add(internalTag);
44                 }
45
46                 for (int idx = 0; idx < externalParserTags.length; ++idx) {
47                         String externalTag = (String)externalParserTags[idx][0];
48                         Class<? extends ParseResult>  parserClass = (Class<? extends ParseResult>)externalParserTags[idx][1];
49                         m_external.put(externalTag, parserClass);
50                 }
51         }
52
53         public abstract void endContents(String uri, String localName, String qName, String chars) throws XmlParseException;
54         public abstract void endExternal(String uri, String localName, String qName) throws XmlParseException;
55         
56         public String getTagName() { return m_tagName; }
57         public boolean haveSeenMyTag() { return m_haveSeenMyTag; }
58
59         public void characters(char[] ch, int start, int length) throws XmlParseException
60         {
61                 m_chars.append(ch, start, length);
62         }
63         
64         protected ParseResult[] collectParsedChildren(Class<? extends ParseResult> cls) {
65                 ArrayList<ParseResult> collection = new ArrayList<ParseResult>();
66                 Iterator<ParseResult> iter = m_childParsers.iterator();
67                 while (iter.hasNext()) {
68                         ParseResult pr = iter.next();
69                         if (pr.getClass().isAssignableFrom(cls)) {
70                                 collection.add(pr);
71                                 iter.remove();
72                         }
73                 }
74                 
75                 ParseResult[] result = new ParseResult[collection.size()];
76                 return collection.toArray(result);
77         }
78
79         // returns true if this ParseResult's context has ended with this endElement() call
80         public boolean endElement(String uri, String localName, String qName) throws XmlParseException
81         {
82                 assert (null != localName);
83                 
84                 boolean isInternal = m_internal.contains(localName);
85
86                 if (! m_haveSeenMyTag) {
87                         // We're in some unrecognised prologue.  Ignore it and move on.
88                         return false;
89                 }
90                 
91                 if (m_tagName.equals(localName)) {
92                         validate();
93                         return true;
94                 }
95                 
96                 if (!isInternal) {
97                         // Unrecognized tag.  Ignore it.
98                         return false;
99                 }
100                 
101                 String tag = m_current.pop();
102                 if ( ! tag.equals(localName) ) {
103                         throw new MismatchedTagsException(tag, localName);
104                 }
105                 
106                 String chars = m_chars.toString();
107                 endContents(uri, localName, qName, chars);
108                 
109                 return false;
110         }
111         
112         // returns either itself, or a new ParseResult-derived object, whichever should handle parsing the inside of this element
113         public ParseResult startElement(String uri, String localName, String qName, Attributes attributes) 
114                         throws XmlParseException
115         {
116                 assert (null != localName);
117
118                 m_chars.setLength(0);
119                 
120                 if (! m_haveSeenMyTag) {
121                         // Have we opened our own (root) tag yet?
122                         
123                         if (m_tagName.equals(localName)) {
124                                 m_haveSeenMyTag = true;
125                                 return this;
126                         }
127                         else {
128                                 // One of two things has happened here.
129                                 // Either (a) we've got some sort of wrapper here, and have not yet reach our own tag, 
130                                 //     or (b) we're parsing XML that doesn't match expectations.
131                                 // In either case, we're going to ignore this tag, and scan forward looking for our own root.
132                                 return this;
133                         }
134                 }
135
136                 if (m_internal.contains(localName)) {
137                         m_current.push(localName);
138                         return this;
139                 }
140
141                 Class<? extends ParseResult> parserClass = m_external.get(localName);
142                 if (null != parserClass) {
143                         try {
144                                 ParseResult childParser = (ParseResult) parserClass.newInstance();
145                                 m_childParsers.add(childParser);
146                                 return childParser.startElement(uri, localName, qName, attributes);
147                         }
148                         catch (IllegalAccessException iae) {
149                                 throw new XmlParseException(iae);
150                         }
151                         catch (InstantiationException ie) {
152                                 throw new XmlParseException(ie);
153                         }
154                 }
155                 
156                 // Not a recognized tag.  Ignore it, rather than complaining. 
157                 return this;
158         }
159         
160         public void validate() throws XmlParseException
161         {
162                 // Default implementation is a no-op.
163                 // Override if you want to validate on endElement()
164         }
165 }
166