65 | */
66 | public class RootElement extends Element {
67 |
68 | final Handler handler = new Handler();
69 |
70 | /**
71 | * Constructs a new root element with the given name.
72 | *
73 | * @param uri the namespace
74 | * @param localName the local name
75 | */
76 | public RootElement(String uri, String localName) {
77 | super(null, uri, localName, 0);
78 | }
79 |
80 | /**
81 | * Constructs a new root element with the given name. Uses an empty string
82 | * as the namespace.
83 | *
84 | * @param localName the local name
85 | */
86 | public RootElement(String localName) {
87 | this("", localName);
88 | }
89 |
90 | /**
91 | * Gets the SAX {@code ContentHandler}. Pass this to your SAX parser.
92 | */
93 | public ContentHandler getContentHandler() {
94 | return this.handler;
95 | }
96 |
97 | class Handler extends DefaultHandler {
98 |
99 | Locator locator;
100 | int depth = -1;
101 | Element current = null;
102 | StringBuilder bodyBuilder = null;
103 |
104 | @Override
105 | public void setDocumentLocator(Locator locator) {
106 | this.locator = locator;
107 | }
108 |
109 | @Override
110 | public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
111 | int depth = ++this.depth;
112 |
113 | if (depth == 0) {
114 | // This is the root element.
115 | startRoot(uri, localName, attributes);
116 | return;
117 | }
118 |
119 | // Prohibit mixed text and elements.
120 | if (bodyBuilder != null) {
121 | throw new BadXmlException(
122 | "Encountered mixed content"
123 | + " within text element named " + current + ".",
124 | locator
125 | );
126 | }
127 |
128 | // If we're one level below the current element.
129 | if (depth == current.depth + 1) {
130 | // Look for a child to push onto the stack.
131 | Children children = current.children;
132 | if (children != null) {
133 | Element child = children.get(uri, localName);
134 | if (child != null) {
135 | start(child, attributes);
136 | }
137 | }
138 | }
139 | }
140 |
141 | void startRoot(String uri, String localName, Attributes attributes) throws SAXException {
142 | Element root = RootElement.this;
143 | if (root.uri.compareTo(uri) != 0 || root.localName.compareTo(localName) != 0) {
144 | throw new BadXmlException(
145 | "Root element name does"
146 | + " not match. Expected: " + root + ", Got: "
147 | + Element.toString(uri, localName), locator
148 | );
149 | }
150 |
151 | start(root, attributes);
152 | }
153 |
154 | void start(Element e, Attributes attributes) {
155 | // Push element onto the stack.
156 | this.current = e;
157 |
158 | if (e.startElementListener != null) {
159 | e.startElementListener.start(attributes);
160 | }
161 |
162 | if (e.endTextElementListener != null) {
163 | this.bodyBuilder = new StringBuilder();
164 | }
165 |
166 | e.resetRequiredChildren();
167 | e.visited = true;
168 | }
169 |
170 | @Override
171 | public void characters(char[] buffer, int start, int length)
172 | throws SAXException {
173 | if (bodyBuilder != null) {
174 | bodyBuilder.append(buffer, start, length);
175 | }
176 | }
177 |
178 | @Override
179 | public void endElement(String uri, String localName, String qName)
180 | throws SAXException {
181 | Element current = this.current;
182 |
183 | // If we've ended the current element...
184 | if (depth == current.depth) {
185 | current.checkRequiredChildren(locator);
186 |
187 | // Invoke end element listener.
188 | if (current.endElementListener != null) {
189 | current.endElementListener.end();
190 | }
191 |
192 | // Invoke end text element listener.
193 | if (bodyBuilder != null) {
194 | String body = bodyBuilder.toString();
195 | bodyBuilder = null;
196 |
197 | // We can assume that this listener is present.
198 | current.endTextElementListener.end(body);
199 | }
200 |
201 | // Pop element off the stack.
202 | this.current = current.parent;
203 | }
204 |
205 | depth--;
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/sexp/src/main/java/com/novoda/sax/StartElementListener.java:
--------------------------------------------------------------------------------
1 | package com.novoda.sax;
2 | /*
3 | * Copyright (C) 2007 The Android Open Source Project
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | import org.xml.sax.Attributes;
19 |
20 | /**
21 | * Listens for the beginning of elements.
22 | */
23 | public interface StartElementListener {
24 |
25 | /**
26 | * Invoked at the beginning of an element.
27 | *
28 | * @param attributes from the element
29 | */
30 | void start(Attributes attributes);
31 | }
--------------------------------------------------------------------------------
/sexp/src/main/java/com/novoda/sax/TextElementListener.java:
--------------------------------------------------------------------------------
1 | package com.novoda.sax;
2 |
3 | /*
4 | * Copyright (C) 2007 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | /**
20 | * Listens for the beginning and ending of text elements.
21 | */
22 | public interface TextElementListener extends StartElementListener,
23 | EndTextElementListener {
24 | }
--------------------------------------------------------------------------------
/sexp/src/main/java/com/novoda/sexp/Instigator.java:
--------------------------------------------------------------------------------
1 | package com.novoda.sexp;
2 |
3 | import com.novoda.sax.EndElementListener;
4 | import com.novoda.sax.RootElement;
5 |
6 | /**
7 | * {@link SimpleTagInstigator } is an example implementation
8 | */
9 | public interface Instigator extends EndElementListener {
10 | /**
11 | * @return The root tag of your XML file
12 | */
13 | RootTag getRootTag();
14 |
15 | /**
16 | * This is where you traverse the xml 'tree' by using
17 | * {@link com.novoda.sexp.finder.ElementFinder ElementFinder}
18 | * or {@link com.novoda.sexp.parser.Parser Parser}
19 | * objects to parse the XML.
20 | * You can use the {@link com.novoda.sexp.finder.ElementFinderFactory ElementFinderFactory}
21 | * to create tree crawlers, or you can create your own if you implement one of:
22 | *
30 | *
31 | * @param rootElement the root element of your XML file
32 | */
33 | void create(RootElement rootElement);
34 |
35 | /**
36 | * Called when the corresponding closing root tag of your XML file is found
37 | */
38 | @Override
39 | void end();
40 | }
41 |
--------------------------------------------------------------------------------
/sexp/src/main/java/com/novoda/sexp/RootTag.java:
--------------------------------------------------------------------------------
1 | package com.novoda.sexp;
2 |
3 | public class RootTag {
4 |
5 | private static final String UNUSED_NAMESPACE = "";
6 |
7 | private final String tag;
8 | private final String namespace;
9 |
10 | public static RootTag create(String tag) {
11 | return create(tag, UNUSED_NAMESPACE);
12 | }
13 |
14 | public static RootTag create(String tag, String namespace) {
15 | return new RootTag(tag, namespace);
16 | }
17 |
18 | private RootTag(String tag, String namespace) {
19 | this.tag = tag;
20 | this.namespace = namespace;
21 | }
22 |
23 | public String getTag() {
24 | return tag;
25 | }
26 |
27 | public String getNamespace() {
28 | return namespace;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/sexp/src/main/java/com/novoda/sexp/SimpleEasyXmlParser.java:
--------------------------------------------------------------------------------
1 | package com.novoda.sexp;
2 |
3 | import com.novoda.sax.RootElement;
4 | import com.novoda.sexp.finder.ElementFinderFactory;
5 |
6 | import java.io.ByteArrayInputStream;
7 | import java.io.InputStream;
8 | import java.io.UnsupportedEncodingException;
9 | import java.util.concurrent.CountDownLatch;
10 |
11 | import org.xml.sax.XMLReader;
12 |
13 | public class SimpleEasyXmlParser {
14 |
15 | /**
16 | * @return an {@link ElementFinderFactory} this Factory is how you select which XML elements to parse as what java types
17 | * see it's javadoc for more
18 | */
19 | public static ElementFinderFactory getElementFinderFactory() {
20 | return new ElementFinderFactory();
21 | }
22 |
23 | /**
24 | * @param xml the xml to parse
25 | * @param encoding the encoding of your xml
26 | * @param instigator your fully created parser of the xml
27 | * @throws UnsupportedEncodingException if the encoding you passed isn't suppored
28 | */
29 | public static void parse(String xml, String encoding, Instigator instigator) throws UnsupportedEncodingException {
30 | parse(new ByteArrayInputStream(xml.getBytes(encoding)), instigator, getDefaultSEXPXMLReader());
31 | }
32 |
33 | /**
34 | * @param xml the xml to parse
35 | * @param instigator your fully created parser of the xml
36 | */
37 | public static void parse(String xml, Instigator instigator) {
38 | parse(new ByteArrayInputStream(xml.getBytes()), instigator, getDefaultSEXPXMLReader());
39 | }
40 |
41 | /**
42 | * @param xml the xml to parse
43 | * @param instigator your fully created parser of the xml
44 | */
45 | public static void parse(InputStream xml, Instigator instigator) {
46 | parse(xml, instigator, getDefaultSEXPXMLReader());
47 | }
48 |
49 | /**
50 | * @param xml the xml to parse
51 | * @param instigator your fully created parser of the xml
52 | * @param xmlReader is the interface that an XML parser's SAX2 driver must implement , using this? _bad ass_ alert
53 | */
54 | public static void parse(String xml, Instigator instigator, XMLReader xmlReader) {
55 | parse(new ByteArrayInputStream(xml.getBytes()), instigator, xmlReader);
56 | }
57 |
58 | /**
59 | * @param xml the xml to parse
60 | * @param instigator your fully created parser of the xml
61 | * @param xmlReader is the interface that an XML parser's SAX2 driver must implement , using this? _bad ass_ alert
62 | */
63 | public static void parse(InputStream xml, Instigator instigator, XMLReader xmlReader) {
64 | RootTag rootTag = instigator.getRootTag();
65 | RootElement rootElement = new RootElement(rootTag.getNamespace(), rootTag.getTag());
66 | rootElement.setEndElementListener(instigator);
67 | instigator.create(rootElement);
68 | xmlReader.setContentHandler(rootElement.getContentHandler());
69 | XmlParser xmlParser = new XmlParser();
70 | xmlParser.parse(xml, xmlReader);
71 | }
72 |
73 | /**
74 | * @param xml the xml to parse
75 | * @param encoding the encoding of your xml
76 | * @param streamer your fully created streamer of the xml
77 | * @param the expected type of your parsed XML
78 | * @return your parsed XML as an object
79 | * @throws UnsupportedEncodingException if the encoding you passed isn't suppored
80 | */
81 | public static T parse(String xml, String encoding, Streamer streamer) throws UnsupportedEncodingException {
82 | return parse(new ByteArrayInputStream(xml.getBytes(encoding)), streamer, getDefaultSEXPXMLReader());
83 | }
84 |
85 | /**
86 | * @param xml the xml to parse
87 | * @param streamer your fully created streamer of the xml
88 | * @param the expected type of your parsed XML
89 | * @return your parsed XML as an object
90 | */
91 | public static T parse(String xml, Streamer streamer) {
92 | return parse(new ByteArrayInputStream(xml.getBytes()), streamer, getDefaultSEXPXMLReader());
93 | }
94 |
95 | /**
96 | * @param xml the xml to parse
97 | * @param streamer your fully created streamer of the xml
98 | * @param the expected type of your parsed XML
99 | * @return your parsed XML as an object
100 | */
101 | public static T parse(InputStream xml, Streamer streamer) {
102 | return parse(xml, streamer, getDefaultSEXPXMLReader());
103 | }
104 |
105 | /**
106 | * @param xml the xml to parse
107 | * @param streamer your fully created streamer of the xml
108 | * @param xmlReader is the interface that an XML parser's SAX2 driver must implement , using this? _bad ass_ alert
109 | * @param the expected type of your parsed XML
110 | * @return your parsed XML as an object
111 | */
112 | public static T parse(String xml, Streamer streamer, XMLReader xmlReader) {
113 | return parse(new ByteArrayInputStream(xml.getBytes()), streamer, xmlReader);
114 | }
115 |
116 | /**
117 | * @param xml the xml to parse
118 | * @param streamer your fully created streamer of the xml
119 | * @param xmlReader is the interface that an XML parser's SAX2 driver must implement , using this? _bad ass_ alert
120 | * @param the expected type of your parsed XML
121 | * @return your parsed XML as an object
122 | */
123 | public static T parse(InputStream xml, final Streamer streamer, XMLReader xmlReader) {
124 | final CountDownLatch latch = new CountDownLatch(1);
125 | Instigator latchedInstigator = new Instigator() {
126 | @Override
127 | public RootTag getRootTag() {
128 | return streamer.getRootTag();
129 | }
130 |
131 | @Override
132 | public void create(RootElement rootElement) {
133 | streamer.stream(rootElement);
134 | }
135 |
136 | @Override
137 | public void end() {
138 | latch.countDown();
139 | }
140 | };
141 |
142 | RootTag rootTag = streamer.getRootTag();
143 | RootElement rootElement = new RootElement(rootTag.getNamespace(), rootTag.getTag());
144 | rootElement.setEndElementListener(latchedInstigator);
145 | streamer.stream(rootElement);
146 | xmlReader.setContentHandler(rootElement.getContentHandler());
147 | XmlParser xmlParser = new XmlParser();
148 | xmlParser.parse(xml, xmlReader);
149 |
150 | try {
151 | latch.await();
152 | } catch (InterruptedException e) {
153 | throw new RuntimeException(e);
154 | }
155 |
156 | return streamer.getStreamResult();
157 | }
158 |
159 | private static XMLReader getDefaultSEXPXMLReader() {
160 | try {
161 | return new XMLReaderBuilder().allowNamespaceProcessing(true).build();
162 | } catch (XMLReaderBuilder.XMLReaderCreationException e) {
163 | throw new RuntimeException(e);
164 | }
165 | }
166 |
167 | }
168 |
--------------------------------------------------------------------------------
/sexp/src/main/java/com/novoda/sexp/SimpleTagInstigator.java:
--------------------------------------------------------------------------------
1 | package com.novoda.sexp;
2 |
3 | import com.novoda.sax.RootElement;
4 | import com.novoda.sexp.finder.ElementFinder;
5 | import com.novoda.sexp.parser.ParseFinishWatcher;
6 |
7 | public abstract class SimpleTagInstigator implements Instigator {
8 |
9 | private final ElementFinder> elementFinder;
10 | private final String elementTag;
11 | private final ParseFinishWatcher parseFinishWatcher;
12 |
13 | public SimpleTagInstigator(ElementFinder> elementFinder, String elementTag, ParseFinishWatcher parseFinishWatcher) {
14 | this.elementFinder = elementFinder;
15 | this.elementTag = elementTag;
16 | this.parseFinishWatcher = parseFinishWatcher;
17 | }
18 |
19 | @Override
20 | public void create(RootElement element) {
21 | elementFinder.find(element, elementTag);
22 | }
23 |
24 | @Override
25 | public void end() {
26 | parseFinishWatcher.onFinish();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/sexp/src/main/java/com/novoda/sexp/Streamer.java:
--------------------------------------------------------------------------------
1 | package com.novoda.sexp;
2 |
3 | import com.novoda.sax.RootElement;
4 |
5 | /**
6 | * This streamer can be used when you want to return your parsed object synchronously.
7 | * A good example being {@link SimpleEasyXmlParser#parse(String, Streamer)}
8 | *
9 | * @param the type of object you expect as a result of xml parsing
10 | */
11 | public interface Streamer {
12 | /**
13 | * @return The root tag of your XML file
14 | */
15 | RootTag getRootTag();
16 |
17 | /**
18 | * This is where you stream the xml 'tree' by using
19 | * {@link com.novoda.sexp.finder.ElementFinder ElementFinder}
20 | * or {@link com.novoda.sexp.parser.Parser Parser}
21 | * objects to parse the XML.
22 | * You can use the {@link com.novoda.sexp.finder.ElementFinderFactory ElementFinderFactory}
23 | * to create tree crawlers, or you can create your own if you implement one of:
24 | *
33 | */
34 | public class ElementFinderFactory {
35 |
36 | /**
37 | * Will parse the body of an XML tag into a {@link String}
38 | *
39 | * @return {@link ElementFinder}
40 | */
41 | public ElementFinder getStringFinder() {
42 | return getTypeFinder(new StringBodyMarshaller());
43 | }
44 |
45 | /**
46 | * Will parse the body of an XML tag into an {@link Integer}
47 | *
48 | * @return {@link ElementFinder}
49 | */
50 | public ElementFinder getIntegerFinder() {
51 | return getTypeFinder(new IntegerBodyMarshaller());
52 | }
53 |
54 | /**
55 | * Will parse the body of an XML tag into an {@link Boolean}
56 | *
57 | * @return {@link ElementFinder}
58 | */
59 | public ElementFinder getBooleanFinder() {
60 | return getTypeFinder(new BooleanBodyMarshaller());
61 | }
62 |
63 | /**
64 | * Will parse the body of an XML tag into a simple {@link Integer} wrapper class. This is a class that
65 | * has a constructor that takes a single primitive int parameter.
66 | *
67 | * @param clazz The class of your Integer Wrapper Class
68 | * @param The type you wish to create from the XML body
69 | * @return {@link ElementFinder}
70 | */
71 | public ElementFinder getIntegerWrapperTypeFinder(Class clazz) {
72 | return getTypeFinder(new BasicParser(getIntegerWrapperMarshaller(clazz)));
73 | }
74 |
75 | /**
76 | * Will parse the body of an XML tag into a simple {@link String} wrapper class. This is a class that
77 | * has a constructor that takes a single {@link String} parameter.
78 | *
79 | * @param clazz The class of your String Wrapper Class
80 | * @param The type you wish to create from the XML body
81 | * @return {@link ElementFinder}
82 | */
83 | public ElementFinder getStringWrapperTypeFinder(Class clazz) {
84 | return getTypeFinder(new BasicParser(getStringWrapperMarshaller(clazz)));
85 | }
86 |
87 | /**
88 | * Will parse the body of all XML tags with the {@code tag} argument
89 | * into a {@link java.util.List} of {@link Object} using the supplied {@link BodyMarshaller}.
90 | *