├── .gitignore ├── LICENSE.txt ├── README.md ├── benchmark ├── .gitignore ├── README.md ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── novoda │ │ ├── XmlParsingBenchmark.java │ │ ├── jackson │ │ ├── medium │ │ │ └── JacksonMediumXmlBenchmark.java │ │ └── small │ │ │ └── JacksonSmallXmlBenchmark.java │ │ ├── sexp │ │ ├── medium │ │ │ ├── AuthorParser.java │ │ │ ├── EntryParser.java │ │ │ ├── FeedParser.java │ │ │ ├── LinkAttributeMarshaller.java │ │ │ └── SexpMediumXmlBenchmark.java │ │ └── small │ │ │ └── SexpSmallXmlBenchmark.java │ │ └── simple │ │ ├── medium │ │ └── SimpleFrameworkMediumXmlBenchmark.java │ │ └── small │ │ └── SimpleFrameworkSmallXmlBenchmark.java │ └── resources │ ├── medium.xml │ └── small.xml ├── build.gradle ├── demo ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── novoda │ └── demo │ ├── Example.java │ ├── SEXParsingDemo.java │ ├── advanced │ └── podcast │ │ ├── PodcastExample.java │ │ ├── PodcastExampleHelper.java │ │ ├── parser │ │ ├── ChannelImageParser.java │ │ ├── PodcastChannelParser.java │ │ └── PodcastItemParser.java │ │ └── pojo │ │ ├── Author.java │ │ ├── Channel.java │ │ ├── ChannelImage.java │ │ ├── Link.java │ │ ├── PodcastItem.java │ │ ├── Title.java │ │ └── itunes │ │ └── ItunesDuration.java │ ├── onetag │ ├── OneElementInstigator.java │ └── OneTagExample.java │ ├── simple │ └── SimpleExample.java │ └── team │ ├── TeamExample.java │ └── TeamMember.java ├── demoAndroid ├── build.gradle ├── libs │ └── android-support-v4.jar └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── novoda │ │ └── demoandroid │ │ ├── MenuActivity.java │ │ ├── ParsingTask.java │ │ ├── SecondLevelBaseActivity.java │ │ ├── onetag │ │ └── OneTagActivity.java │ │ ├── podcast │ │ ├── PodcastExampleActivity.java │ │ └── PodcastExampleHelper.java │ │ ├── returnvalue │ │ └── ReturnValueActivity.java │ │ ├── simple │ │ └── SimpleExampleActivity.java │ │ └── team │ │ └── TeamExampleActivity.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_menu.xml │ └── activity_result_parsing.xml │ ├── values-sw600dp │ └── dimens.xml │ ├── values-sw720dp-land │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── sexp ├── build.gradle └── src ├── main └── java │ └── com │ └── novoda │ ├── sax │ ├── BadXmlException.java │ ├── Children.java │ ├── Element.java │ ├── ElementListener.java │ ├── EndElementListener.java │ ├── EndTextElementListener.java │ ├── RootElement.java │ ├── StartElementListener.java │ └── TextElementListener.java │ └── sexp │ ├── Instigator.java │ ├── RootTag.java │ ├── SimpleEasyXmlParser.java │ ├── SimpleTagInstigator.java │ ├── Streamer.java │ ├── XMLReaderBuilder.java │ ├── XmlParser.java │ ├── finder │ ├── BasicElementFinder.java │ ├── ElementFinder.java │ ├── ElementFinderFactory.java │ └── ListElementFinder.java │ ├── marshaller │ ├── AttributeMarshaller.java │ ├── BodyMarshaller.java │ ├── BooleanBodyMarshaller.java │ ├── DoubleBodyMarshaller.java │ ├── DoubleWrapperBodyMarshaller.java │ ├── IntegerBodyMarshaller.java │ ├── IntegerWrapperBodyMarshaller.java │ ├── LongBodyMarshaller.java │ ├── LongWrapperBodyMarshaller.java │ ├── StringBodyMarshaller.java │ └── StringWrapperBodyMarshaller.java │ └── parser │ ├── BasicAttributeParser.java │ ├── BasicParser.java │ ├── ListParseWatcher.java │ ├── ListParser.java │ ├── ParseFinishWatcher.java │ ├── ParseWatcher.java │ └── Parser.java └── test └── java └── com └── novoda ├── sax └── ChildrenShould.java └── sexp ├── RootTagShould.java ├── XmlParserShould.java ├── finder ├── BasicElementFinderShould.java └── ListElementFinderShould.java ├── marshaller ├── BooleanBodyMarshallerShould.java ├── DoubleBodyMarshallerShould.java ├── DoubleWrapperBodyMarshallerShould.java ├── IntegerBodyMarshallerShould.java ├── IntegerWrapperBodyMarshallerShould.java ├── LongBodyMarshallerShould.java ├── LongWrapperBodyMarshallerShould.java ├── StringBodyMarshallerShould.java └── StringWrapperBodyMarshallerShould.java └── parser ├── BasicAttributeParserShould.java ├── BasicParserShould.java ├── ListParserShould.java └── ParserHelper.java /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | .gradle 15 | .idea 16 | build/ 17 | 18 | # Local configuration file (sdk path, etc) 19 | local.properties 20 | 21 | # Eclipse project files 22 | .classpath 23 | .project 24 | 25 | # IntelliJ project files 26 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🛑 THIS REPOSITORY IS OFFICIALLY NO LONGER UNDER MAINTENANCE since 10/02/2022 🛑 2 | 3 | # simple-easy-xml-parser [![](https://ci.novoda.com/buildStatus/icon?job=simple-easy-xml-parser)](https://ci.novoda.com/job/simple-easy-xml-parser/lastBuild/console) [![Download](https://api.bintray.com/packages/novoda/maven/simple-easy-xml-parser/images/download.svg) ](https://bintray.com/novoda/maven/simple-easy-xml-parser/_latestVersion) [![Apache 2.0 Licence](https://img.shields.io/github/license/novoda/simple-easy-xml-parser.svg)](https://github.com/novoda/simple-easy-xml-parser/blob/master/LICENSE.txt) 4 | 5 | XML parsing is now sexy! 6 | 7 | 8 | ## Description 9 | 10 | A simple, high performance, typed XML parser based upon Android sax parser but written in pure Java. SEXP gives callbacks for all parsing events and being written in pure java allows faster and more comprehensive testability. 11 | 12 | Moreover SEXP is a pull-parsing XML deserialiser which affects both performance (no reflection is required) and low memory footprint (unncessary objects are not allocated). Our testing shows that SEXP performs: 13 | 14 | - 1.7x faster than [Jackson](https://github.com/FasterXML/jackson-dataformat-xml) 15 | - 2.5x faster than [Simple-Framework](http://simple.sourceforge.net/). 16 | 17 | You can refer to the following [micro-benchmarking](https://github.com/novoda/simple-easy-xml-parser/tree/master/benchmark) for further information. 18 | 19 | 20 | ## Adding to your project 21 | 22 | To start using this library, add these lines to the `build.gradle` of your project: 23 | 24 | ```groovy 25 | repositories { 26 | jcenter() 27 | } 28 | 29 | dependencies { 30 | compile 'com.novoda:sexp:1.0.8' 31 | } 32 | ``` 33 | 34 | ## Simple usage 35 | 36 | ```java 37 | String XML = 38 | "" + 39 | "Blue" + 40 | ""; 41 | 42 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 43 | elementFinder = factory.getStringFinder(); 44 | Streamer streamer = new SimpleStreamer(elementFinder); 45 | String favouriteColour = SimpleEasyXmlParser.parse(XML, streamer); 46 | 47 | private static class SimpleStreamer implements Streamer { 48 | 49 | private final ElementFinder elementFinder; 50 | private final String elementTag; 51 | 52 | public SimpleStreamer(ElementFinder elementFinder, String elementTag) { 53 | this.elementFinder = elementFinder; 54 | this.elementTag = elementTag; 55 | } 56 | 57 | @Override 58 | public RootTag getRootTag() { 59 | return RootTag.create("novoda"); 60 | } 61 | 62 | @Override 63 | public void stream(RootElement rootElement) { 64 | elementFinder.find(rootElement, elementTag); 65 | } 66 | 67 | @Override 68 | public String getStreamResult() { 69 | return elementFinder.getResultOrThrow(); 70 | } 71 | } 72 | ``` 73 | (See the `demo` and `demoAndroid` modules for more.) 74 | 75 | ## Links 76 | 77 | Here are a list of useful links: 78 | 79 | * We always welcome people to contribute new features or bug fixes, [here is how](https://github.com/novoda/novoda/blob/master/CONTRIBUTING.md) 80 | * If you have a problem check the [Issues Page](https://github.com/novoda/simple-easy-xml-parser/issues) first to see if we are working on it 81 | * For further usage or to delve more deeply checkout the [Project Wiki](https://github.com/novoda/simple-easy-xml-parser/wiki) 82 | * Looking for community help, browse the already asked [Stack Overflow Questions](http://stackoverflow.com/questions/tagged/support-sexp) or use the tag: `support-sexp` when posting a new question 83 | -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | Attempting to find out which dahm XML parser to use! 2 | 3 | Run the main method inside `XmlParsingBenchmark.java` and wait for the results. 4 | 5 | At the moment we test: 6 | 7 | - A simple XML input of two tags 8 | 9 | - A medium XML input simulating a feed composed of 100 entries of medium complexity. 10 | 11 | Future: 12 | 13 | - More complex tests and perhaps some discussive text about the API needed 14 | 15 | Results: 16 | 17 | - For the medium XML input SEXP is performing 1.7x faster than [Jackson](https://github.com/FasterXML/jackson-dataformat-xml) and 2.5x faster than [Simple-Framework](http://simple.sourceforge.net/) [Details](https://github.com/novoda/simple-easy-xml-parser/tree/master/benchmark) 18 | 19 | 20 | We rely on https://github.com/google/caliper/wiki/ to do the hard work of twiddling the nobs. 21 | -------------------------------------------------------------------------------- /benchmark/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | dependencies { 4 | compile fileTree(dir: 'libs', include: ['*.jar']) 5 | compile 'net.trajano.caliper:caliper:1.1.1' 6 | 7 | compile project(':sexp') 8 | compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.6.4' 9 | compile 'org.simpleframework:simple-xml:2.7.1' 10 | } 11 | 12 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/XmlParsingBenchmark.java: -------------------------------------------------------------------------------- 1 | package com.novoda; 2 | 3 | import com.google.caliper.BeforeExperiment; 4 | import com.google.caliper.Benchmark; 5 | import com.google.caliper.api.VmOptions; 6 | import com.google.caliper.runner.CaliperMain; 7 | import com.novoda.jackson.medium.JacksonMediumXmlBenchmark; 8 | import com.novoda.jackson.small.JacksonSmallXmlBenchmark; 9 | import com.novoda.sexp.medium.SexpMediumXmlBenchmark; 10 | import com.novoda.sexp.small.SexpSmallXmlBenchmark; 11 | import com.novoda.simple.medium.SimpleFrameworkMediumXmlBenchmark; 12 | import com.novoda.simple.small.SimpleFrameworkSmallXmlBenchmark; 13 | 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | 17 | public class XmlParsingBenchmark { 18 | 19 | public static void main(String[] args) { 20 | CaliperMain.main(XmlBenchmark.class, args); 21 | } 22 | 23 | @VmOptions("-XX:-TieredCompilation") 24 | public static class XmlBenchmark { 25 | 26 | private String xmlSmall; 27 | private String xmlMedium; 28 | 29 | @BeforeExperiment 30 | public void setUp() throws Exception { 31 | xmlSmall = loadResource("small.xml"); 32 | xmlMedium = loadResource("medium.xml"); 33 | } 34 | 35 | @Benchmark 36 | public void jackson_InputSmall() throws Exception { 37 | new JacksonSmallXmlBenchmark().parse(xmlSmall); 38 | } 39 | 40 | @Benchmark 41 | public void jackson_InputMedium() throws Exception { 42 | new JacksonMediumXmlBenchmark().parse(xmlMedium); 43 | } 44 | 45 | @Benchmark 46 | public void sexp_InputSmall() throws Exception { 47 | new SexpSmallXmlBenchmark().parse(xmlSmall); 48 | } 49 | 50 | @Benchmark 51 | public void sexp_InputMedium() throws Exception { 52 | new SexpMediumXmlBenchmark().parse(xmlMedium); 53 | } 54 | 55 | @Benchmark 56 | public void simpleframework_InputSmall() throws Exception { 57 | new SimpleFrameworkSmallXmlBenchmark().parse(xmlSmall); 58 | } 59 | 60 | @Benchmark 61 | public void simpleframework_InputMedium() throws Exception { 62 | new SimpleFrameworkMediumXmlBenchmark().parse(xmlMedium); 63 | } 64 | 65 | private String loadResource(String resourceName) throws IOException { 66 | ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 67 | try (InputStream stream = classLoader.getResourceAsStream(resourceName)) { 68 | return convertStreamToString(stream); 69 | } 70 | } 71 | 72 | private static String convertStreamToString(java.io.InputStream is) { 73 | java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); 74 | return s.hasNext() ? s.next() : ""; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/jackson/medium/JacksonMediumXmlBenchmark.java: -------------------------------------------------------------------------------- 1 | package com.novoda.jackson.medium; 2 | 3 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; 4 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; 5 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; 6 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; 7 | 8 | import java.util.List; 9 | 10 | public class JacksonMediumXmlBenchmark { 11 | 12 | public void parse(String xml) throws Exception { 13 | XmlMapper mapper = new XmlMapper(); 14 | Feed feed = mapper.readValue(xml, Feed.class); 15 | System.out.println(getClass().getSimpleName() + " " + feed); 16 | } 17 | 18 | public static class Feed { 19 | public String id; 20 | public Title title; 21 | public String updated; 22 | public Author author; 23 | public String logo; 24 | public Link link; 25 | public String generator; 26 | @JacksonXmlProperty(localName = "entry") 27 | @JacksonXmlElementWrapper(useWrapping = false) 28 | public List entries; 29 | 30 | @Override 31 | public String toString() { 32 | return "Feed{" + 33 | "id='" + id + '\'' + 34 | ", title='" + title + '\'' + 35 | ", updated='" + updated + '\'' + 36 | ", author=" + author + 37 | ", logo='" + logo + '\'' + 38 | ", link='" + link + '\'' + 39 | ", generator='" + generator + '\'' + 40 | ", entries=" + entries + 41 | '}'; 42 | } 43 | } 44 | 45 | public static class Author { 46 | public String name; 47 | 48 | @Override 49 | public String toString() { 50 | return "Author{" + 51 | "name='" + name + '\'' + 52 | '}'; 53 | } 54 | } 55 | 56 | public static class Entry { 57 | public String id; 58 | public Title title; 59 | public Summary summary; 60 | public String updated; 61 | @JacksonXmlProperty(localName = "link") 62 | @JacksonXmlElementWrapper(useWrapping = false) 63 | public List links; 64 | 65 | @Override 66 | public String toString() { 67 | return "Entry{" + 68 | "id='" + id + '\'' + 69 | ", title='" + title + '\'' + 70 | ", summary='" + summary + '\'' + 71 | ", updated='" + updated + '\'' + 72 | ", links=" + links + 73 | '}'; 74 | } 75 | } 76 | 77 | public static class Title { 78 | public String type; 79 | 80 | @JacksonXmlText(value = true) 81 | public String title; 82 | 83 | @Override 84 | public String toString() { 85 | return title; 86 | } 87 | } 88 | 89 | public static class Summary { 90 | public String type; 91 | 92 | @JacksonXmlText(value = true) 93 | public String summary; 94 | 95 | @Override 96 | public String toString() { 97 | return summary; 98 | } 99 | } 100 | 101 | public static class Link { 102 | public String href; 103 | public String title; 104 | public String rel; 105 | public String type; 106 | @Override 107 | public String toString() { 108 | return "Link{" + 109 | "url='" + href + '\'' + 110 | ", title='" + title + '\'' + 111 | ", rel='" + rel + '\'' + 112 | ", type='" + type + '\'' + 113 | '}'; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/jackson/small/JacksonSmallXmlBenchmark.java: -------------------------------------------------------------------------------- 1 | package com.novoda.jackson.small; 2 | 3 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; 4 | 5 | public class JacksonSmallXmlBenchmark { 6 | 7 | public void parse(String xml) throws Exception { 8 | XmlMapper mapper = new XmlMapper(); 9 | Employee employee = mapper.readValue(xml, Employee.class); 10 | System.out.println(getClass().getSimpleName() + " " + employee.name); 11 | } 12 | 13 | public static class Employee { 14 | public String name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/sexp/medium/AuthorParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.medium; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sax.ElementListener; 5 | import com.novoda.sexp.finder.ElementFinder; 6 | import com.novoda.sexp.finder.ElementFinderFactory; 7 | import com.novoda.sexp.parser.ParseWatcher; 8 | import com.novoda.sexp.parser.Parser; 9 | 10 | import org.xml.sax.Attributes; 11 | 12 | public class AuthorParser implements Parser { 13 | 14 | private static final String TAG_NAME = "name"; 15 | 16 | private final ElementFinder nameFinder; 17 | 18 | private ParseWatcher listener; 19 | 20 | public AuthorParser(ElementFinderFactory factory) { 21 | nameFinder = factory.getStringFinder(); 22 | } 23 | 24 | @Override 25 | public void parse(Element element, ParseWatcher listener) { 26 | this.listener = listener; 27 | 28 | element.setElementListener(authorListener); 29 | 30 | nameFinder.find(element, TAG_NAME); 31 | } 32 | 33 | private final ElementListener authorListener = new ElementListener() { 34 | @Override 35 | public void start(Attributes attributes) { 36 | // no op 37 | } 38 | 39 | @Override 40 | public void end() { 41 | SexpMediumXmlBenchmark.Author author = new SexpMediumXmlBenchmark.Author(nameFinder.getResultOrThrow()); 42 | listener.onParsed(author); 43 | } 44 | 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/sexp/medium/EntryParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.medium; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sax.ElementListener; 5 | import com.novoda.sexp.finder.ElementFinder; 6 | import com.novoda.sexp.finder.ElementFinderFactory; 7 | import com.novoda.sexp.parser.ParseWatcher; 8 | import com.novoda.sexp.parser.Parser; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import org.xml.sax.Attributes; 14 | 15 | public class EntryParser implements Parser { 16 | 17 | private static final String TAG_ID = "id"; 18 | private static final String TAG_TITLE = "title"; 19 | private static final String TAG_SUMMARY = "summary"; 20 | private static final String TAG_UPDATE = "updated"; 21 | private static final String TAG_LINK = "link"; 22 | private static final String ATTR_HREF = "href"; 23 | private static final String ATTR_REL = "rel"; 24 | private static final String ATTR_TITLE = "title"; 25 | private static final String ATTR_TYPE = "type"; 26 | 27 | private final ElementFinder idFinder; 28 | private final ElementFinder titleFinder; 29 | private final ElementFinder summaryFinder; 30 | private final ElementFinder updatedFinder; 31 | private final ElementFinder linkFinder; 32 | 33 | private ParseWatcher listener; 34 | private final List linkList = new ArrayList<>(); 35 | 36 | public EntryParser(ElementFinderFactory factory) { 37 | idFinder = factory.getStringFinder(); 38 | titleFinder = factory.getStringFinder(); 39 | summaryFinder = factory.getStringFinder(); 40 | updatedFinder = factory.getStringFinder(); 41 | linkFinder = factory.getListAttributeFinder(new LinkAttributeMarshaller(), linkParseWatcher, ATTR_HREF, ATTR_REL, ATTR_TITLE, ATTR_TYPE); 42 | } 43 | 44 | private final ParseWatcher linkParseWatcher = new ParseWatcher() { 45 | @Override 46 | public void onParsed(SexpMediumXmlBenchmark.Link link) { 47 | linkList.add(link); 48 | } 49 | }; 50 | 51 | 52 | @Override 53 | public void parse(Element element, ParseWatcher listener) { 54 | this.listener = listener; 55 | 56 | element.setElementListener(entryListener); 57 | 58 | idFinder.find(element, TAG_ID); 59 | titleFinder.find(element, TAG_TITLE); 60 | summaryFinder.find(element, TAG_SUMMARY); 61 | updatedFinder.find(element, TAG_UPDATE); 62 | linkFinder.find(element, TAG_LINK); 63 | 64 | } 65 | 66 | private final ElementListener entryListener = new ElementListener() { 67 | @Override 68 | public void start(Attributes attributes) { 69 | linkList.clear(); 70 | } 71 | 72 | @Override 73 | public void end() { 74 | SexpMediumXmlBenchmark.Entry entry = new SexpMediumXmlBenchmark.Entry(); 75 | entry.id = idFinder.getResultOrThrow(); 76 | entry.summary = summaryFinder.getResultOrThrow(); 77 | entry.title = titleFinder.getResultOrThrow(); 78 | entry.updated = updatedFinder.getResultOrThrow(); 79 | entry.links = new ArrayList<>(linkList); 80 | 81 | listener.onParsed(entry); 82 | } 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/sexp/medium/FeedParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.medium; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sexp.finder.ElementFinder; 5 | import com.novoda.sexp.finder.ElementFinderFactory; 6 | import com.novoda.sexp.parser.ParseWatcher; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class FeedParser { 12 | 13 | private static final String TAG_ID = "id"; 14 | private static final String TAG_TITLE = "title"; 15 | private static final String TAG_UPDATED = "updated"; 16 | private static final String TAG_AUTHOR = "author"; 17 | private static final String TAG_LOGO = "logo"; 18 | private static final String TAG_LINK = "link"; 19 | private static final String TAG_GENERATOR = "generator"; 20 | private static final String TAG_ENTRY = "entry"; 21 | private static final String ATTR_HREF = "href"; 22 | private static final String ATTR_REL = "rel"; 23 | private static final String ATTR_TITLE = "title"; 24 | private static final String ATTR_TYPE = "type"; 25 | 26 | private final ElementFinder idFinder; 27 | private final ElementFinder titleFinder; 28 | private final ElementFinder updatedFinder; 29 | private final ElementFinder authorFinder; 30 | private final ElementFinder logoFinder; 31 | private final ElementFinder generatorFinder; 32 | private final ElementFinder linkFinder; 33 | private final ElementFinder entryFinder; 34 | 35 | private FeedHolder feedHolder; 36 | 37 | public FeedParser(ElementFinderFactory factory) { 38 | this.idFinder = factory.getStringFinder(); 39 | this.titleFinder = factory.getStringFinder(); 40 | this.updatedFinder = factory.getStringFinder(); 41 | this.authorFinder = factory.getTypeFinder(new AuthorParser(factory)); 42 | this.logoFinder = factory.getStringFinder(); 43 | this.generatorFinder = factory.getStringFinder(); 44 | this.linkFinder = factory.getAttributeFinder(new LinkAttributeMarshaller(), ATTR_HREF, ATTR_REL, ATTR_TITLE, ATTR_TYPE); 45 | this.entryFinder = factory.getListElementFinder(new EntryParser(factory), parseWatcher); 46 | } 47 | 48 | private final ParseWatcher parseWatcher = new ParseWatcher() { 49 | @Override 50 | public void onParsed(SexpMediumXmlBenchmark.Entry entry) { 51 | feedHolder.entries.add(entry); 52 | } 53 | }; 54 | 55 | public void parse(Element element) { 56 | feedHolder = new FeedHolder(); 57 | idFinder.find(element, TAG_ID); 58 | titleFinder.find(element, TAG_TITLE); 59 | updatedFinder.find(element, TAG_UPDATED); 60 | authorFinder.find(element, TAG_AUTHOR); 61 | logoFinder.find(element, TAG_LOGO); 62 | generatorFinder.find(element, TAG_GENERATOR); 63 | linkFinder.find(element, TAG_LINK); 64 | entryFinder.find(element, TAG_ENTRY); 65 | } 66 | 67 | public SexpMediumXmlBenchmark.Feed getResult() { 68 | feedHolder.id = idFinder.getResultOrThrow(); 69 | feedHolder.title = titleFinder.getResultOrThrow(); 70 | feedHolder.updated = updatedFinder.getResultOrThrow(); 71 | feedHolder.author = authorFinder.getResultOrThrow(); 72 | feedHolder.logo = logoFinder.getResultOrThrow(); 73 | feedHolder.generator = generatorFinder.getResultOrThrow(); 74 | feedHolder.link = linkFinder.getResultOrThrow(); 75 | 76 | return feedHolder.asFeed(); 77 | } 78 | 79 | private static class FeedHolder { 80 | private String id; 81 | private String title; 82 | private String updated; 83 | private SexpMediumXmlBenchmark.Author author; 84 | private String logo; 85 | private String generator; 86 | private SexpMediumXmlBenchmark.Link link; 87 | private List entries = new ArrayList<>(); 88 | 89 | public SexpMediumXmlBenchmark.Feed asFeed() { 90 | return new SexpMediumXmlBenchmark.Feed(id, title, updated, author, logo, generator, link, entries); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/sexp/medium/LinkAttributeMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.medium; 2 | 3 | import com.novoda.sexp.marshaller.AttributeMarshaller; 4 | 5 | public class LinkAttributeMarshaller implements AttributeMarshaller { 6 | @Override 7 | public SexpMediumXmlBenchmark.Link marshal(String... input) { 8 | SexpMediumXmlBenchmark.Link link = new SexpMediumXmlBenchmark.Link(); 9 | link.href = input[0]; 10 | link.rel = input[1]; 11 | link.title = input[2]; 12 | link.type = input[3]; 13 | 14 | return link; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/sexp/medium/SexpMediumXmlBenchmark.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.medium; 2 | 3 | import com.novoda.sax.RootElement; 4 | import com.novoda.sexp.Instigator; 5 | import com.novoda.sexp.RootTag; 6 | import com.novoda.sexp.SimpleEasyXmlParser; 7 | import com.novoda.sexp.finder.ElementFinderFactory; 8 | 9 | import java.util.List; 10 | 11 | public class SexpMediumXmlBenchmark { 12 | 13 | private final FeedParser parser; 14 | 15 | public SexpMediumXmlBenchmark() { 16 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 17 | parser = new FeedParser(factory); 18 | } 19 | 20 | public void parse(String xml) throws Exception { 21 | Instigator instigator = new FeedInstigator( 22 | parser, 23 | new FeedInstigator.Callback() { 24 | @Override 25 | public void onFinish(Feed feed) { 26 | System.out.println(SexpMediumXmlBenchmark.this.getClass().getSimpleName() + " " + feed); 27 | } 28 | } 29 | ); 30 | 31 | SimpleEasyXmlParser.parse(xml, instigator); 32 | 33 | } 34 | 35 | public static class FeedInstigator implements Instigator { 36 | 37 | private final FeedParser parser; 38 | private final Callback callback; 39 | 40 | public FeedInstigator(FeedParser parser, Callback callback) { 41 | this.parser = parser; 42 | this.callback = callback; 43 | } 44 | 45 | @Override 46 | public RootTag getRootTag() { 47 | return RootTag.create("feed"); 48 | } 49 | 50 | @Override 51 | public void create(RootElement rootElement) { 52 | parser.parse(rootElement); 53 | } 54 | 55 | @Override 56 | public void end() { 57 | Feed end = parser.getResult(); 58 | callback.onFinish(end); 59 | } 60 | 61 | interface Callback { 62 | void onFinish(Feed feed); 63 | } 64 | } 65 | 66 | public static class Feed { 67 | public String id; 68 | public String title; 69 | public String updated; 70 | public Author author; 71 | public String logo; 72 | public Link link; 73 | public String generator; 74 | public List entries; 75 | 76 | public Feed(String id, String title, String updated, Author author, String logo, String generator, Link link, List entries) { 77 | this.id = id; 78 | this.title = title; 79 | this.updated = updated; 80 | this.author = author; 81 | this.logo = logo; 82 | this.generator = generator; 83 | this.link = link; 84 | this.entries = entries; 85 | } 86 | 87 | public String toString() { 88 | return "Feed{" + 89 | "id='" + id + '\'' + 90 | ", title='" + title + '\'' + 91 | ", updated='" + updated + '\'' + 92 | ", author=" + author + 93 | ", logo='" + logo + '\'' + 94 | ", link='" + link + '\'' + 95 | ", generator='" + generator + '\'' + 96 | ", entries=" + entries + 97 | '}'; 98 | } 99 | } 100 | 101 | public static class Entry { 102 | public String id; 103 | public String title; 104 | public String summary; 105 | public String updated; 106 | public List links; 107 | 108 | @Override 109 | public String toString() { 110 | return "Entry{" + 111 | "id='" + id + '\'' + 112 | ", title='" + title + '\'' + 113 | ", summary='" + summary + '\'' + 114 | ", updated='" + updated + '\'' + 115 | ", links=" + links + 116 | '}'; 117 | } 118 | } 119 | 120 | public static class Link { 121 | public String href; 122 | public String title; 123 | public String rel; 124 | public String type; 125 | 126 | @Override 127 | public String toString() { 128 | return "Link{" + 129 | "url='" + href + '\'' + 130 | ", title='" + title + '\'' + 131 | ", rel='" + rel + '\'' + 132 | ", type='" + type + '\'' + 133 | '}'; 134 | } 135 | } 136 | 137 | public static class Author { 138 | private final String name; 139 | 140 | public Author(String name) { 141 | this.name = name; 142 | } 143 | 144 | @Override 145 | public String toString() { 146 | return name; 147 | } 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/sexp/small/SexpSmallXmlBenchmark.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.small; 2 | 3 | import com.novoda.sax.RootElement; 4 | import com.novoda.sexp.Instigator; 5 | import com.novoda.sexp.RootTag; 6 | import com.novoda.sexp.SimpleEasyXmlParser; 7 | import com.novoda.sexp.finder.ElementFinder; 8 | import com.novoda.sexp.finder.ElementFinderFactory; 9 | 10 | public class SexpSmallXmlBenchmark { 11 | 12 | public void parse(String xml) throws Exception { 13 | ElementFinderFactory finderFactory = SimpleEasyXmlParser.getElementFinderFactory(); 14 | 15 | SimpleEasyXmlParser.parse( 16 | xml, 17 | new SexpInstigator( 18 | finderFactory.getStringFinder(), 19 | new SexpInstigator.Callback() { 20 | @Override 21 | public void onFinish(String result) { 22 | System.out.println(SexpSmallXmlBenchmark.this.getClass().getSimpleName() + " " + result); 23 | } 24 | } 25 | ) 26 | ); 27 | } 28 | 29 | private static class SexpInstigator implements Instigator { 30 | 31 | private final ElementFinder elementFinder; 32 | private final Callback callback; 33 | 34 | public SexpInstigator(ElementFinder elementFinder, Callback callback) { 35 | this.elementFinder = elementFinder; 36 | this.callback = callback; 37 | } 38 | 39 | @Override 40 | public RootTag getRootTag() { 41 | return RootTag.create("employee"); 42 | } 43 | 44 | @Override 45 | public void create(RootElement rootElement) { 46 | elementFinder.find(rootElement, "name"); 47 | } 48 | 49 | @Override 50 | public void end() { 51 | String result = elementFinder.getResultOrThrow(); 52 | callback.onFinish(result); 53 | } 54 | 55 | interface Callback { 56 | void onFinish(String result); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/simple/medium/SimpleFrameworkMediumXmlBenchmark.java: -------------------------------------------------------------------------------- 1 | package com.novoda.simple.medium; 2 | 3 | import java.util.List; 4 | 5 | import org.simpleframework.xml.Attribute; 6 | import org.simpleframework.xml.Element; 7 | import org.simpleframework.xml.ElementList; 8 | import org.simpleframework.xml.Path; 9 | import org.simpleframework.xml.Root; 10 | import org.simpleframework.xml.Serializer; 11 | import org.simpleframework.xml.core.Persister; 12 | 13 | public class SimpleFrameworkMediumXmlBenchmark { 14 | 15 | public void parse(String xml) throws Exception { 16 | Serializer serializer = new Persister(); 17 | Feed feed = serializer.read(Feed.class, xml); 18 | System.out.println(getClass().getSimpleName() + " " + feed); 19 | } 20 | 21 | @Root(name = "employee", strict = false) 22 | public static class Feed { 23 | @Element 24 | public String id; 25 | 26 | @Element 27 | public String title; 28 | 29 | @Element 30 | public String updated; 31 | 32 | @Path("author") 33 | @Element(name = "name") 34 | public String author; 35 | 36 | @Element 37 | public String logo; 38 | 39 | @Element 40 | public Link link; 41 | 42 | @Element 43 | public String generator; 44 | 45 | @ElementList(name = "entry", inline = true) 46 | public List entries; 47 | 48 | public String toString() { 49 | return "Feed{" + 50 | "id='" + id + '\'' + 51 | ", title='" + title + '\'' + 52 | ", updated='" + updated + '\'' + 53 | ", author=" + author + 54 | ", logo='" + logo + '\'' + 55 | ", link='" + link + '\'' + 56 | ", generator='" + generator + '\'' + 57 | ", entries=" + entries + 58 | '}'; 59 | } 60 | } 61 | 62 | public static class Entry { 63 | @Element 64 | public String id; 65 | @Element 66 | public String title; 67 | @Element 68 | public String summary; 69 | @Element 70 | public String updated; 71 | @ElementList(name = "link", inline = true) 72 | public List links; 73 | 74 | @Override 75 | public String toString() { 76 | return "Entry{" + 77 | "id='" + id + '\'' + 78 | ", title='" + title + '\'' + 79 | ", summary='" + summary + '\'' + 80 | ", updated='" + updated + '\'' + 81 | ", links=" + links + 82 | '}'; 83 | } 84 | } 85 | 86 | public static class Link { 87 | @Attribute 88 | public String href; 89 | @Attribute(required = false) 90 | public String title; 91 | @Attribute 92 | public String rel; 93 | @Attribute 94 | public String type; 95 | 96 | @Override 97 | public String toString() { 98 | return "Link{" + 99 | "url='" + href + '\'' + 100 | ", title='" + title + '\'' + 101 | ", rel='" + rel + '\'' + 102 | ", type='" + type + '\'' + 103 | '}'; 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /benchmark/src/main/java/com/novoda/simple/small/SimpleFrameworkSmallXmlBenchmark.java: -------------------------------------------------------------------------------- 1 | package com.novoda.simple.small; 2 | 3 | import org.simpleframework.xml.Element; 4 | import org.simpleframework.xml.Root; 5 | import org.simpleframework.xml.Serializer; 6 | import org.simpleframework.xml.core.Persister; 7 | 8 | public class SimpleFrameworkSmallXmlBenchmark { 9 | 10 | public void parse(String xml) throws Exception { 11 | Serializer serializer = new Persister(); 12 | Employee employee = serializer.read(Employee.class, xml); 13 | System.out.println(getClass().getSimpleName() + " " + employee.name); 14 | } 15 | 16 | @Root(name = "employee", strict = false) 17 | public static class Employee { 18 | @Element(required = true) 19 | public String name; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /benchmark/src/main/resources/small.xml: -------------------------------------------------------------------------------- 1 | 2 | Paul 3 | 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | repositories { 3 | mavenCentral() 4 | maven { 5 | url "https://oss.sonatype.org/content/repositories/snapshots" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'application' 3 | mainClassName = "com.novoda.demo.SEXParsingDemo" 4 | 5 | sourceCompatibility = 1.6 6 | targetCompatibility = 1.6 7 | 8 | dependencies { 9 | compile project(':sexp') 10 | } -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/Example.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo; 2 | 3 | public interface Example { 4 | void execute(); 5 | } 6 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/SEXParsingDemo.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo; 2 | 3 | import com.novoda.demo.advanced.podcast.PodcastExample; 4 | import com.novoda.demo.onetag.OneTagExample; 5 | import com.novoda.demo.simple.SimpleExample; 6 | import com.novoda.demo.team.TeamExample; 7 | 8 | public class SEXParsingDemo { 9 | 10 | public static void main(String[] args) { 11 | executeAndLog(new OneTagExample()); 12 | executeAndLog(new SimpleExample()); 13 | executeAndLog(new TeamExample()); 14 | executeAndLog(new PodcastExample()); 15 | } 16 | 17 | public static void executeAndLog(Example example) { 18 | System.out.println(example.getClass().getSimpleName()); 19 | example.execute(); 20 | System.out.println("___________________________"); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/PodcastExample.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast; 2 | 3 | import com.novoda.demo.Example; 4 | import com.novoda.demo.advanced.podcast.parser.PodcastChannelParser; 5 | import com.novoda.demo.advanced.podcast.pojo.Channel; 6 | import com.novoda.sexp.Instigator; 7 | import com.novoda.sexp.RootTag; 8 | import com.novoda.sexp.SimpleEasyXmlParser; 9 | import com.novoda.sexp.SimpleTagInstigator; 10 | import com.novoda.sexp.finder.ElementFinder; 11 | import com.novoda.sexp.finder.ElementFinderFactory; 12 | import com.novoda.sexp.parser.ParseFinishWatcher; 13 | 14 | import static com.novoda.demo.advanced.podcast.PodcastExampleHelper.printAllPodcastItems; 15 | import static com.novoda.demo.advanced.podcast.PodcastExampleHelper.printChannelDetails; 16 | import static com.novoda.demo.advanced.podcast.PodcastExampleHelper.printChannelImage; 17 | import static com.novoda.demo.advanced.podcast.PodcastExampleHelper.printSpace; 18 | 19 | public class PodcastExample implements Example { 20 | 21 | private static ElementFinder elementFinder; 22 | 23 | @Override 24 | public void execute() { 25 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 26 | 27 | elementFinder = factory.getTypeFinder(new PodcastChannelParser(factory)); 28 | 29 | Instigator instigator = new PodcastInstigator(elementFinder, finishWatcher); 30 | SimpleEasyXmlParser.parse(PodcastExampleHelper.SINGLE_PODCAST_ITEM, instigator); 31 | } 32 | 33 | private ParseFinishWatcher finishWatcher = new ParseFinishWatcher() { 34 | @Override 35 | public void onFinish() { 36 | Channel channel = elementFinder.getResultOrThrow(); 37 | 38 | printChannelDetails(channel); 39 | printChannelImage(channel.image); 40 | 41 | printSpace(); 42 | 43 | printAllPodcastItems(channel.podcastItems); 44 | } 45 | }; 46 | 47 | public static class PodcastInstigator extends SimpleTagInstigator { 48 | 49 | public PodcastInstigator(ElementFinder elementFinder, ParseFinishWatcher parseFinishWatcher) { 50 | super(elementFinder, "channel", parseFinishWatcher); 51 | } 52 | 53 | @Override 54 | public RootTag getRootTag() { 55 | return RootTag.create("rss"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/PodcastExampleHelper.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast; 2 | 3 | import com.novoda.demo.advanced.podcast.pojo.Channel; 4 | import com.novoda.demo.advanced.podcast.pojo.ChannelImage; 5 | import com.novoda.demo.advanced.podcast.pojo.PodcastItem; 6 | 7 | import java.util.List; 8 | 9 | public class PodcastExampleHelper { 10 | 11 | //language=XML 12 | public static final String SINGLE_PODCAST_ITEM = 13 | "" + 14 | "" + 15 | "CNET UK Podcast" + 16 | "http://crave.cnet.co.uk/podcast/" + 17 | "" + 18 | "http://www.cnet.co.uk/images/rss/logo-cnet.jpg" + 19 | "http://crave.cnet.co.uk/podcast/" + 20 | "CNET UK Podcast" + 21 | "88" + 22 | "56" + 23 | "" + 24 | "" + 25 | "podcast@cnet.co.uk (CNET UK)" + 26 | "" + 27 | "New BBC HD channels and the future of TV in Podcast 348 " + 28 | "" + 29 | "" + 30 | "http://crave.cnet.co.uk/podcast/new-bbc-hd-channels-and-the-future-of-tv-in-podcast-348-50011743/ " + 31 | "" + 32 | "Thu, 18 Jul 2013 12:07:02 +0100 " + 33 | " " + 34 | "cnetuk/podcast/50011743 " + 35 | "Jason Jenkins, Andrew Hoyle, Rich Trenholm " + 36 | "00:42:50 " + 37 | "no " + 38 | " " + 39 | " " + 40 | " " + 41 | "New BBC HD channels and the future of TV in Podcast 348 " + 42 | " " + 43 | " " + 44 | "As the BBC goes HD, Netflix reinvents the TV series, and BT and Sky square off, what's the future of television -- and is Apple in it? " + 45 | " " + 46 | "" + 47 | "" + 48 | "podcast@cnet.co.uk (CNET UK)" + 49 | "Free stuff for your phone in Podcast 346" + 50 | "" + 51 | "http://crave.cnet.co.uk/podcast/free-stuff-for-your-phone-in-podcast-346-50011642/" + 52 | "" + 53 | "Thu, 04 Jul 2013 12:05:07 +0100" + 54 | "" + 55 | "cnetuk/podcast/50011642" + 56 | "Luke Westaway, Andrew Hoyle, Rich Trenholm" + 57 | "00:37:43" + 58 | "no" + 59 | "" + 60 | "" + 61 | "Free stuff for your phone in Podcast 346" + 62 | "" + 63 | "Bag free stuff from your phone network as we explore the world of extras, bolt-ons and Freebeez in Podcast 346." + 64 | "" + 65 | "" + 66 | "" + 67 | ""; 68 | 69 | public static void printChannelDetails(Channel channel) { 70 | System.out.println("Channel Title : " + channel.title); 71 | System.out.println("Channel Link : " + channel.link); 72 | } 73 | 74 | public static void printChannelImage(ChannelImage image) { 75 | System.out.println("Channel Image Title : " + image.title); 76 | System.out.println("Channel Image Url : " + image.url); 77 | } 78 | 79 | public static void printAllPodcastItems(List podcastItems) { 80 | for (PodcastItem podcastItem : podcastItems) { 81 | System.out.println("Title : " + podcastItem.title); 82 | System.out.println("Author : " + podcastItem.author); 83 | System.out.println("Link : " + podcastItem.link); 84 | System.out.println("Itunes Duration : " + podcastItem.itunesDuration); 85 | System.out.println("Itunes Image : " + podcastItem.image); 86 | printSpace(); 87 | } 88 | } 89 | 90 | public static void printSpace() { 91 | System.out.println(""); 92 | System.out.println(""); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/parser/ChannelImageParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.parser; 2 | 3 | import com.novoda.demo.advanced.podcast.pojo.ChannelImage; 4 | import com.novoda.sax.Element; 5 | import com.novoda.sax.ElementListener; 6 | import com.novoda.sexp.finder.ElementFinder; 7 | import com.novoda.sexp.finder.ElementFinderFactory; 8 | import com.novoda.sexp.parser.ParseWatcher; 9 | import com.novoda.sexp.parser.Parser; 10 | 11 | import org.xml.sax.Attributes; 12 | 13 | public class ChannelImageParser implements Parser { 14 | 15 | private static final String TAG_TITLE = "title"; 16 | private static final String TAG_LINK = "link"; 17 | private static final String TAG_URL = "url"; 18 | private static final String TAG_WIDTH = "width"; 19 | private static final String TAG_HEIGHT = "height"; 20 | 21 | private final ElementFinder titleFinder; 22 | private final ElementFinder linkFinder; 23 | private final ElementFinder urlFinder; 24 | private final ElementFinder widthFinder; 25 | private final ElementFinder heightFinder; 26 | 27 | private ParseWatcher listener; 28 | private ImageHolder imageHolder; 29 | 30 | public ChannelImageParser(ElementFinderFactory factory) { 31 | this.titleFinder = factory.getStringFinder(); 32 | this.linkFinder = factory.getStringFinder(); 33 | this.urlFinder = factory.getStringFinder(); 34 | this.widthFinder = factory.getIntegerFinder(); 35 | this.heightFinder = factory.getIntegerFinder(); 36 | } 37 | 38 | @Override 39 | public void parse(Element element, ParseWatcher listener) { 40 | this.listener = listener; 41 | element.setElementListener(imageListener); 42 | titleFinder.find(element, TAG_TITLE); 43 | linkFinder.find(element, TAG_LINK); 44 | urlFinder.find(element, TAG_URL); 45 | widthFinder.find(element, TAG_WIDTH); 46 | heightFinder.find(element, TAG_HEIGHT); 47 | } 48 | 49 | private final ElementListener imageListener = new ElementListener() { 50 | @Override 51 | public void start(Attributes attributes) { 52 | imageHolder = new ImageHolder(); 53 | } 54 | 55 | @Override 56 | public void end() { 57 | imageHolder.title = titleFinder.getResultOrThrow(); 58 | imageHolder.link = linkFinder.getResultOrThrow(); 59 | imageHolder.url = urlFinder.getResultOrThrow(); 60 | imageHolder.width = widthFinder.getResultOrThrow(); 61 | imageHolder.height = heightFinder.getResultOrThrow(); 62 | 63 | listener.onParsed(imageHolder.asImage()); 64 | } 65 | }; 66 | 67 | private static class ImageHolder { 68 | 69 | public String title; 70 | public String link; 71 | public String url; 72 | public Integer width; 73 | public Integer height; 74 | 75 | public ChannelImage asImage() { 76 | return new ChannelImage(title, link, url, width, height); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/parser/PodcastChannelParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.parser; 2 | 3 | import com.novoda.demo.advanced.podcast.pojo.Channel; 4 | import com.novoda.demo.advanced.podcast.pojo.ChannelImage; 5 | import com.novoda.demo.advanced.podcast.pojo.Link; 6 | import com.novoda.demo.advanced.podcast.pojo.PodcastItem; 7 | import com.novoda.demo.advanced.podcast.pojo.Title; 8 | import com.novoda.sax.Element; 9 | import com.novoda.sax.ElementListener; 10 | import com.novoda.sexp.finder.ElementFinder; 11 | import com.novoda.sexp.finder.ElementFinderFactory; 12 | import com.novoda.sexp.parser.ParseWatcher; 13 | import com.novoda.sexp.parser.Parser; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import org.xml.sax.Attributes; 19 | 20 | public class PodcastChannelParser implements Parser { 21 | 22 | private static final String TAG_ITEM = "item"; 23 | private static final String TAG_TITLE = "title"; 24 | private static final String TAG_LINK = "link"; 25 | private static final String TAG_IMAGE = "image"; 26 | 27 | private final ElementFinder podcastItemFinder; 28 | private final ElementFinder titleFinder; 29 | private final ElementFinder<Link> linkFinder; 30 | private final ElementFinder<ChannelImage> imageFinder; 31 | 32 | private ParseWatcher<Channel> listener; 33 | private ChannelHolder channelHolder; 34 | 35 | public PodcastChannelParser(ElementFinderFactory factory) { 36 | this.podcastItemFinder = factory.getListElementFinder(new PodcastItemParser(factory), parseWatcher); 37 | this.titleFinder = factory.getStringWrapperTypeFinder(Title.class); 38 | this.linkFinder = factory.getStringWrapperTypeFinder(Link.class); 39 | this.imageFinder = factory.getTypeFinder(new ChannelImageParser(factory)); 40 | } 41 | 42 | private final ParseWatcher<PodcastItem> parseWatcher = new ParseWatcher<PodcastItem>() { 43 | @Override 44 | public void onParsed(PodcastItem item) { 45 | channelHolder.podcastItems.add(item); 46 | } 47 | }; 48 | 49 | @Override 50 | public void parse(Element element, final ParseWatcher<Channel> listener) { 51 | this.listener = listener; 52 | element.setElementListener(channelParseListener); 53 | podcastItemFinder.find(element, TAG_ITEM); 54 | titleFinder.find(element, TAG_TITLE); 55 | linkFinder.find(element, TAG_LINK); 56 | imageFinder.find(element, TAG_IMAGE); 57 | } 58 | 59 | private final ElementListener channelParseListener = new ElementListener() { 60 | @Override 61 | public void start(Attributes attributes) { 62 | channelHolder = new ChannelHolder(); 63 | } 64 | 65 | @Override 66 | public void end() { 67 | channelHolder.title = titleFinder.getResultOrThrow(); 68 | channelHolder.link = linkFinder.getResultOrThrow(); 69 | channelHolder.image = imageFinder.getResultOrThrow(); 70 | 71 | listener.onParsed(channelHolder.asChannel()); 72 | } 73 | }; 74 | 75 | private static class ChannelHolder { 76 | private final List<PodcastItem> podcastItems = new ArrayList<PodcastItem>(); 77 | private Title title; 78 | private Link link; 79 | private ChannelImage image; 80 | 81 | public Channel asChannel() { 82 | return new Channel(title, link, image, podcastItems); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/parser/PodcastItemParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.parser; 2 | 3 | import com.novoda.demo.advanced.podcast.pojo.Author; 4 | import com.novoda.demo.advanced.podcast.pojo.Link; 5 | import com.novoda.demo.advanced.podcast.pojo.PodcastItem; 6 | import com.novoda.demo.advanced.podcast.pojo.Title; 7 | import com.novoda.demo.advanced.podcast.pojo.itunes.ItunesDuration; 8 | import com.novoda.sax.Element; 9 | import com.novoda.sax.ElementListener; 10 | import com.novoda.sexp.finder.ElementFinder; 11 | import com.novoda.sexp.finder.ElementFinderFactory; 12 | import com.novoda.sexp.marshaller.AttributeMarshaller; 13 | import com.novoda.sexp.parser.ParseWatcher; 14 | import com.novoda.sexp.parser.Parser; 15 | 16 | import org.xml.sax.Attributes; 17 | 18 | public class PodcastItemParser implements Parser<PodcastItem> { 19 | 20 | private static final String TAG_TITLE = "title"; 21 | private static final String TAG_AUTHOR = "author"; 22 | private static final String TAG_LINK = "link"; 23 | private static final String TAG_ITUNES_DURATION = "duration"; 24 | private static final String TAG_ITUNES_IMAGE = "image"; 25 | private static final String TAG_ITUNES_IMAGE_ATTR = "href"; 26 | private static final String TAG_ITUNES_NAMESPACE = "http://www.itunes.com/dtds/podcast-1.0.dtd"; 27 | 28 | private final ElementFinder<Author> authorFinder; 29 | private final ElementFinder<Link> linkFinder; 30 | private final ElementFinder<Title> titleFinder; 31 | private final ElementFinder<ItunesDuration> itunesDurationFinder; 32 | private final ElementFinder<Link> itunesImageFinder; 33 | 34 | private ItemHolder itemHolder; 35 | private ParseWatcher<PodcastItem> listener; 36 | 37 | public PodcastItemParser(ElementFinderFactory factory) { 38 | this.titleFinder = factory.getStringWrapperTypeFinder(Title.class); 39 | this.authorFinder = factory.getStringWrapperTypeFinder(Author.class); 40 | this.linkFinder = factory.getStringWrapperTypeFinder(Link.class); 41 | itunesDurationFinder = factory.getStringWrapperTypeFinder(ItunesDuration.class); 42 | itunesImageFinder = factory.getAttributeFinder(new ImageAttributeMarshaller(), TAG_ITUNES_IMAGE_ATTR); 43 | } 44 | 45 | @Override 46 | public void parse(Element element, final ParseWatcher<PodcastItem> listener) { 47 | this.listener = listener; 48 | element.setElementListener(itemParseListener); 49 | titleFinder.find(element, TAG_TITLE); 50 | authorFinder.find(element, TAG_AUTHOR); 51 | linkFinder.find(element, TAG_LINK); 52 | itunesDurationFinder.find(element, TAG_ITUNES_NAMESPACE, TAG_ITUNES_DURATION); 53 | itunesImageFinder.find(element, TAG_ITUNES_NAMESPACE, TAG_ITUNES_IMAGE); 54 | } 55 | 56 | private final ElementListener itemParseListener = new ElementListener() { 57 | @Override 58 | public void start(Attributes attributes) { 59 | itemHolder = new ItemHolder(); 60 | } 61 | 62 | @Override 63 | public void end() { 64 | itemHolder.title = titleFinder.getResultOrThrow(); 65 | itemHolder.author = authorFinder.getResultOrThrow(); 66 | itemHolder.link = linkFinder.getResultOrThrow(); 67 | itemHolder.image = itunesImageFinder.getResultOrThrow(); 68 | itemHolder.itunesDuration = itunesDurationFinder.getResultOrThrow(); 69 | 70 | listener.onParsed(itemHolder.asItem()); 71 | } 72 | }; 73 | 74 | private static class ItemHolder { 75 | Title title; 76 | Author author; 77 | Link link; 78 | Link image; 79 | ItunesDuration itunesDuration; 80 | 81 | public PodcastItem asItem() { 82 | return new PodcastItem(title, author, image, link, itunesDuration); 83 | } 84 | } 85 | 86 | private static class ImageAttributeMarshaller implements AttributeMarshaller<Link> { 87 | @Override 88 | public Link marshal(String... input) { 89 | return new Link(input[0]); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/pojo/Author.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.pojo; 2 | 3 | public class Author { 4 | 5 | private final String author; 6 | 7 | public Author(String author) { 8 | this.author = author; 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | return author; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/pojo/Channel.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.pojo; 2 | 3 | import java.util.List; 4 | 5 | public class Channel { 6 | 7 | public final Title title; 8 | public final Link link; 9 | public final ChannelImage image; 10 | public final List<PodcastItem> podcastItems; 11 | 12 | public Channel(Title title, Link link, ChannelImage image, List<PodcastItem> item) { 13 | this.title = title; 14 | this.link = link; 15 | this.image = image; 16 | this.podcastItems = item; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/pojo/ChannelImage.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.pojo; 2 | 3 | public class ChannelImage { 4 | 5 | public final String title; 6 | public final String link; 7 | public final String url; 8 | public final Integer width; 9 | public final Integer height; 10 | 11 | public ChannelImage(String title, String link, String url, Integer width, Integer height) { 12 | this.title = title; 13 | this.link = link; 14 | this.url = url; 15 | this.width = width; 16 | this.height = height; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/pojo/Link.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.pojo; 2 | 3 | public class Link { 4 | 5 | private final String link; 6 | 7 | public Link(String link) { 8 | this.link = link; 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | return link; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/pojo/PodcastItem.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.pojo; 2 | 3 | import com.novoda.demo.advanced.podcast.pojo.itunes.ItunesDuration; 4 | 5 | public class PodcastItem { 6 | 7 | public final Title title; 8 | public final Author author; 9 | public final Link image; 10 | public final Link link; 11 | public final ItunesDuration itunesDuration; 12 | 13 | public PodcastItem(Title title, Author author, Link image, Link link, ItunesDuration itunesDuration) { 14 | this.title = title; 15 | this.author = author; 16 | this.image = image; 17 | this.link = link; 18 | this.itunesDuration = itunesDuration; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/pojo/Title.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.pojo; 2 | 3 | public class Title { 4 | 5 | private final String title; 6 | 7 | public Title(String title) { 8 | this.title = title; 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | return title; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/advanced/podcast/pojo/itunes/ItunesDuration.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.advanced.podcast.pojo.itunes; 2 | 3 | public class ItunesDuration { 4 | 5 | private final String durationRaw; 6 | 7 | public ItunesDuration(String durationRaw) { 8 | this.durationRaw = durationRaw; 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | return durationRaw; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/onetag/OneElementInstigator.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.onetag; 2 | 3 | import com.novoda.sax.EndTextElementListener; 4 | import com.novoda.sax.RootElement; 5 | import com.novoda.sexp.Instigator; 6 | import com.novoda.sexp.RootTag; 7 | import com.novoda.sexp.finder.ElementFinder; 8 | import com.novoda.sexp.parser.ParseFinishWatcher; 9 | 10 | /** 11 | * Because this is a rare case, it is actually harder to setup the parsing of 12 | * one single XML element, but it is possible with the below Instigator: 13 | */ 14 | public class OneElementInstigator implements Instigator { 15 | 16 | private final ElementFinder<String> elementFinder; 17 | private final String elementTag; 18 | private final ParseFinishWatcher parseFinishWatcher; 19 | 20 | public OneElementInstigator(ElementFinder<String> elementFinder, String elementTag, ParseFinishWatcher parseFinishWatcher) { 21 | this.elementFinder = elementFinder; 22 | this.elementTag = elementTag; 23 | this.parseFinishWatcher = parseFinishWatcher; 24 | } 25 | 26 | @Override 27 | public RootTag getRootTag() { 28 | return RootTag.create(elementTag); 29 | } 30 | 31 | @Override 32 | public void create(RootElement element) { 33 | element.setEndTextElementListener( 34 | new EndTextElementListener() { 35 | @Override 36 | public void end(String body) { 37 | elementFinder.onParsed(body); 38 | parseFinishWatcher.onFinish(); 39 | } 40 | } 41 | ); 42 | } 43 | 44 | @Override 45 | public void end() { 46 | // unused in an XML file with only one tag as we need the body of the element 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/onetag/OneTagExample.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.onetag; 2 | 3 | import com.novoda.demo.Example; 4 | import com.novoda.sexp.Instigator; 5 | import com.novoda.sexp.SimpleEasyXmlParser; 6 | import com.novoda.sexp.finder.ElementFinder; 7 | import com.novoda.sexp.finder.ElementFinderFactory; 8 | import com.novoda.sexp.parser.ParseFinishWatcher; 9 | 10 | public class OneTagExample implements Example { 11 | 12 | //language=XML 13 | private static final String XML = "<novoda>Hello XML</novoda>"; 14 | private static ElementFinder<String> elementFinder; 15 | 16 | @Override 17 | public void execute() { 18 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 19 | elementFinder = factory.getStringFinder(); 20 | Instigator instigator = new OneElementInstigator(elementFinder, "novoda", finishWatcher); 21 | SimpleEasyXmlParser.parse(XML, instigator); 22 | } 23 | 24 | private ParseFinishWatcher finishWatcher = new ParseFinishWatcher() { 25 | @Override 26 | public void onFinish() { 27 | System.out.println("Found : " + elementFinder.getResultOrThrow()); 28 | } 29 | }; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/simple/SimpleExample.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.simple; 2 | 3 | import com.novoda.demo.Example; 4 | import com.novoda.sexp.Instigator; 5 | import com.novoda.sexp.RootTag; 6 | import com.novoda.sexp.SimpleEasyXmlParser; 7 | import com.novoda.sexp.SimpleTagInstigator; 8 | import com.novoda.sexp.finder.ElementFinder; 9 | import com.novoda.sexp.finder.ElementFinderFactory; 10 | import com.novoda.sexp.parser.ParseFinishWatcher; 11 | 12 | public class SimpleExample implements Example { 13 | 14 | //language=XML 15 | private static final String XML = 16 | "<novoda>" + 17 | "<favouriteColour>Blue</favouriteColour>" + 18 | "</novoda>"; 19 | private static ElementFinder<String> elementFinder; 20 | 21 | @Override 22 | public void execute() { 23 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 24 | elementFinder = factory.getStringFinder(); 25 | Instigator instigator = new SimpleInstigator(elementFinder, finishWatcher); 26 | SimpleEasyXmlParser.parse(XML, instigator); 27 | } 28 | 29 | private ParseFinishWatcher finishWatcher = new ParseFinishWatcher() { 30 | @Override 31 | public void onFinish() { 32 | System.out.println("Found : " + elementFinder.getResultOrThrow()); 33 | } 34 | }; 35 | 36 | public static class SimpleInstigator extends SimpleTagInstigator { 37 | 38 | public SimpleInstigator(ElementFinder<?> elementFinder, ParseFinishWatcher parseFinishWatcher) { 39 | super(elementFinder, "favouriteColour", parseFinishWatcher); 40 | } 41 | 42 | @Override 43 | public RootTag getRootTag() { 44 | return RootTag.create("novoda"); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/team/TeamExample.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.team; 2 | 3 | import com.novoda.demo.Example; 4 | import com.novoda.sexp.Instigator; 5 | import com.novoda.sexp.RootTag; 6 | import com.novoda.sexp.SimpleEasyXmlParser; 7 | import com.novoda.sexp.SimpleTagInstigator; 8 | import com.novoda.sexp.finder.ElementFinder; 9 | import com.novoda.sexp.finder.ElementFinderFactory; 10 | import com.novoda.sexp.parser.ParseFinishWatcher; 11 | 12 | import java.util.List; 13 | 14 | public class TeamExample implements Example { 15 | 16 | //language=XML 17 | private static final String XML = 18 | "<novoda>" + 19 | "<team>" + 20 | "<name>Adam</name>" + 21 | "<name>Ben</name>" + 22 | "<name>Carl</name>" + 23 | "<name>David</name>" + 24 | "<name>Franky</name>" + 25 | "<name>Kevin</name>" + 26 | "<name>Moe</name>" + 27 | "<name>Paul</name>" + 28 | "<name>Peter</name>" + 29 | "<name>Shiv</name>" + 30 | "</team>" + 31 | "</novoda>"; 32 | private static ElementFinder<List<TeamMember>> elementFinder; 33 | 34 | @Override 35 | public void execute() { 36 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 37 | elementFinder = factory.getStringWrapperTypeListFinder("name", TeamMember.class); 38 | Instigator instigator = new TeamInstigator(elementFinder, finishWatcher); 39 | SimpleEasyXmlParser.parse(XML, instigator); 40 | } 41 | 42 | private ParseFinishWatcher finishWatcher = new ParseFinishWatcher() { 43 | @Override 44 | public void onFinish() { 45 | System.out.println("Found : " + elementFinder.getResultOrThrow()); 46 | } 47 | }; 48 | 49 | public static class TeamInstigator extends SimpleTagInstigator { 50 | 51 | public TeamInstigator(ElementFinder<?> elementFinder, ParseFinishWatcher parseFinishWatcher) { 52 | super(elementFinder, "team", parseFinishWatcher); 53 | } 54 | 55 | @Override 56 | public RootTag getRootTag() { 57 | return RootTag.create("novoda"); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /demo/src/main/java/com/novoda/demo/team/TeamMember.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demo.team; 2 | 3 | public class TeamMember { 4 | private final String name; 5 | 6 | public TeamMember(String name) { 7 | this.name = name; 8 | } 9 | 10 | @Override 11 | public String toString() { 12 | return name; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /demoAndroid/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'android' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | } 14 | 15 | buildscript { 16 | repositories { 17 | mavenCentral() 18 | } 19 | dependencies { 20 | classpath 'com.android.tools.build:gradle:1.3.1' 21 | } 22 | } 23 | 24 | configurations { 25 | compile.exclude group: 'stax' 26 | compile.exclude group: 'xpp3' 27 | } 28 | 29 | dependencies { 30 | compile project(':demo') 31 | } 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /demoAndroid/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoda/simple-easy-xml-parser/2213dd60621124d5726427bad0c4375ecaf50528/demoAndroid/libs/android-support-v4.jar -------------------------------------------------------------------------------- /demoAndroid/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 | package="com.novoda.demoandroid"> 4 | 5 | <application 6 | android:allowBackup="true" 7 | android:icon="@drawable/ic_launcher" 8 | android:label="@string/app_name" 9 | android:theme="@android:style/Theme.WithActionBar"> 10 | <activity 11 | android:name="com.novoda.demoandroid.MenuActivity" 12 | android:label="@string/title_activity_menu"> 13 | <intent-filter> 14 | <action android:name="android.intent.action.MAIN" /> 15 | 16 | <category android:name="android.intent.category.LAUNCHER" /> 17 | </intent-filter> 18 | </activity> 19 | 20 | <activity 21 | android:name="com.novoda.demoandroid.onetag.OneTagActivity" 22 | android:label="@string/title_activity_one_tag" 23 | android:parentActivityName="com.novoda.demoandroid.MenuActivity" /> 24 | <activity 25 | android:name=".returnvalue.ReturnValueActivity" 26 | android:label="@string/title_activity_return_value" 27 | android:parentActivityName="com.novoda.demoandroid.MenuActivity" /> 28 | <activity 29 | android:name="com.novoda.demoandroid.simple.SimpleExampleActivity" 30 | android:label="@string/title_activity_simple_example" 31 | android:parentActivityName="com.novoda.demoandroid.MenuActivity" /> 32 | <activity 33 | android:name="com.novoda.demoandroid.team.TeamExampleActivity" 34 | android:label="@string/title_activity_team_example" 35 | android:parentActivityName="com.novoda.demoandroid.MenuActivity" /> 36 | <activity 37 | android:name="com.novoda.demoandroid.podcast.PodcastExampleActivity" 38 | android:label="@string/title_activity_podcast" 39 | android:parentActivityName="com.novoda.demoandroid.MenuActivity" /> 40 | <activity 41 | android:name="com.novoda.demoandroid.SecondLevelBaseActivity" 42 | android:label="@string/title_activity_base" 43 | android:parentActivityName="com.novoda.demoandroid.MenuActivity" /> 44 | </application> 45 | 46 | </manifest> 47 | -------------------------------------------------------------------------------- /demoAndroid/src/main/java/com/novoda/demoandroid/MenuActivity.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demoandroid; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | import android.widget.AdapterView; 9 | import android.widget.AdapterView.OnItemClickListener; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.ListView; 12 | 13 | import com.novoda.demoandroid.returnvalue.ReturnValueActivity; 14 | import com.novoda.demoandroid.onetag.OneTagActivity; 15 | import com.novoda.demoandroid.podcast.PodcastExampleActivity; 16 | import com.novoda.demoandroid.simple.SimpleExampleActivity; 17 | import com.novoda.demoandroid.team.TeamExampleActivity; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | public class MenuActivity extends Activity { 23 | private static final int DIVIDER_HEIGHT = 2; 24 | 25 | private Context context; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_menu); 31 | 32 | context = this; 33 | ListView menu = (ListView) findViewById(R.id.lv_menu); 34 | List<NavigationItem> items = populateNavigationMenu(); 35 | ArrayAdapter<NavigationItem> adapter; 36 | adapter = new ArrayAdapter<NavigationItem>(this, android.R.layout.simple_list_item_1, android.R.id.text1, items); 37 | 38 | menu.setAdapter(adapter); 39 | menu.setDividerHeight(DIVIDER_HEIGHT); 40 | menu.setOnItemClickListener( 41 | new OnItemClickListener() { 42 | @Override 43 | public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 44 | NavigationItem current = (NavigationItem) parent.getItemAtPosition(position); 45 | Intent intent = new Intent(context, current.getType()); 46 | startActivity(intent); 47 | } 48 | } 49 | ); 50 | } 51 | 52 | private List<NavigationItem> populateNavigationMenu() { 53 | List<NavigationItem> items = new ArrayList<NavigationItem>(); 54 | items.add(new NavigationItem(OneTagActivity.class, getResources().getString(R.string.title_activity_one_tag))); 55 | items.add(new NavigationItem(ReturnValueActivity.class, getResources().getString(R.string.title_activity_return_value))); 56 | items.add(new NavigationItem(SimpleExampleActivity.class, getResources().getString(R.string.title_activity_simple_example))); 57 | items.add(new NavigationItem(PodcastExampleActivity.class, getResources().getString(R.string.title_activity_podcast))); 58 | items.add(new NavigationItem(TeamExampleActivity.class, getResources().getString(R.string.title_activity_team_example))); 59 | return items; 60 | } 61 | 62 | public static class NavigationItem { 63 | Class<? extends Activity> type; 64 | String title; 65 | 66 | public Class<? extends Activity> getType() { 67 | return type; 68 | } 69 | 70 | public String getTitle() { 71 | return title; 72 | } 73 | 74 | public NavigationItem(Class<? extends Activity> type, String title) { 75 | this.type = type; 76 | this.title = title; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return title; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /demoAndroid/src/main/java/com/novoda/demoandroid/ParsingTask.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demoandroid; 2 | 3 | import android.os.AsyncTask; 4 | 5 | import com.novoda.sexp.Instigator; 6 | import com.novoda.sexp.SimpleEasyXmlParser; 7 | 8 | public class ParsingTask extends AsyncTask<Void, Void, Void> { 9 | private String xmlToParse; 10 | private Instigator instigator; 11 | 12 | /** 13 | * ParsingTask constructor 14 | * 15 | * @param xml XML string to parse 16 | * @param anInstigator Instigator used to parse 17 | */ 18 | public ParsingTask(String xml, Instigator anInstigator) { 19 | xmlToParse = xml; 20 | instigator = anInstigator; 21 | } 22 | 23 | @Override 24 | protected Void doInBackground(Void... params) { 25 | SimpleEasyXmlParser.parse(xmlToParse, instigator); 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /demoAndroid/src/main/java/com/novoda/demoandroid/SecondLevelBaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demoandroid; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | public class SecondLevelBaseActivity extends Activity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | getActionBar().setDisplayShowHomeEnabled(false); 12 | getActionBar().setDisplayHomeAsUpEnabled(true); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /demoAndroid/src/main/java/com/novoda/demoandroid/onetag/OneTagActivity.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demoandroid.onetag; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.LinearLayout; 6 | import android.widget.ProgressBar; 7 | import android.widget.TextView; 8 | 9 | import com.novoda.demo.onetag.OneElementInstigator; 10 | import com.novoda.demoandroid.ParsingTask; 11 | import com.novoda.demoandroid.R; 12 | import com.novoda.demoandroid.SecondLevelBaseActivity; 13 | import com.novoda.sexp.Instigator; 14 | import com.novoda.sexp.SimpleEasyXmlParser; 15 | import com.novoda.sexp.finder.ElementFinder; 16 | import com.novoda.sexp.finder.ElementFinderFactory; 17 | import com.novoda.sexp.parser.ParseFinishWatcher; 18 | 19 | public class OneTagActivity extends SecondLevelBaseActivity { 20 | // language=XML 21 | private static final String XML = "<novoda>Hello XML</novoda>"; 22 | private static ElementFinder<String> elementFinder; 23 | 24 | private TextView parsingResult; 25 | private ProgressBar progressBar; 26 | private LinearLayout container; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_result_parsing); 32 | 33 | parsingResult = (TextView) findViewById(R.id.tv_result); 34 | progressBar = (ProgressBar) findViewById(R.id.pb_oneTag); 35 | container = (LinearLayout) findViewById(R.id.ll_oneTag); 36 | 37 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 38 | elementFinder = factory.getStringFinder(); 39 | Instigator instigator = new OneElementInstigator(elementFinder, "novoda", finishWatcher); 40 | 41 | new ParsingTask(XML, instigator).execute(); 42 | } 43 | 44 | private ParseFinishWatcher finishWatcher = new ParseFinishWatcher() { 45 | @Override 46 | public void onFinish() { 47 | runOnUiThread( 48 | new Runnable() { 49 | @Override 50 | public void run() { 51 | parsingResult.setText(elementFinder.getResultOrThrow()); 52 | container.setVisibility(View.VISIBLE); 53 | progressBar.setVisibility(View.GONE); 54 | } 55 | } 56 | ); 57 | 58 | } 59 | }; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /demoAndroid/src/main/java/com/novoda/demoandroid/podcast/PodcastExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demoandroid.podcast; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.LinearLayout; 6 | import android.widget.ProgressBar; 7 | import android.widget.TextView; 8 | 9 | import com.novoda.demo.advanced.podcast.parser.PodcastChannelParser; 10 | import com.novoda.demo.advanced.podcast.pojo.Channel; 11 | import com.novoda.demoandroid.ParsingTask; 12 | import com.novoda.demoandroid.R; 13 | import com.novoda.demoandroid.SecondLevelBaseActivity; 14 | import com.novoda.sexp.Instigator; 15 | import com.novoda.sexp.RootTag; 16 | import com.novoda.sexp.SimpleEasyXmlParser; 17 | import com.novoda.sexp.SimpleTagInstigator; 18 | import com.novoda.sexp.finder.ElementFinder; 19 | import com.novoda.sexp.finder.ElementFinderFactory; 20 | import com.novoda.sexp.parser.ParseFinishWatcher; 21 | 22 | public class PodcastExampleActivity extends SecondLevelBaseActivity { 23 | private static ElementFinder<Channel> elementFinder; 24 | private TextView parsingResult; 25 | private ProgressBar progressBar; 26 | private LinearLayout container; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_result_parsing); 32 | 33 | parsingResult = (TextView) findViewById(R.id.tv_result); 34 | progressBar = (ProgressBar) findViewById(R.id.pb_oneTag); 35 | container = (LinearLayout) findViewById(R.id.ll_oneTag); 36 | 37 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 38 | elementFinder = factory.getTypeFinder(new PodcastChannelParser(factory)); 39 | Instigator instigator = new PodcastInstigator(elementFinder, finishWatcher); 40 | 41 | new ParsingTask(PodcastExampleHelper.SINGLE_PODCAST_ITEM, instigator).execute(); 42 | } 43 | 44 | private ParseFinishWatcher finishWatcher = new ParseFinishWatcher() { 45 | @Override 46 | public void onFinish() { 47 | Channel channel = elementFinder.getResultOrThrow(); 48 | 49 | StringBuilder sb = new StringBuilder(); 50 | String channelDetails = PodcastExampleHelper.getChannelDetailsString(channel); 51 | String channelImage = PodcastExampleHelper.getChannelImageString(channel.image); 52 | String podcasts = PodcastExampleHelper.getAllPodcastItemsString(channel.podcastItems); 53 | String space = PodcastExampleHelper.getSpace(); 54 | 55 | sb.append(channelDetails); 56 | sb.append(channelImage); 57 | sb.append(space); 58 | sb.append(podcasts); 59 | final String result = sb.toString(); 60 | 61 | runOnUiThread( 62 | new Runnable() { 63 | @Override 64 | public void run() { 65 | parsingResult.setText(result); 66 | container.setVisibility(View.VISIBLE); 67 | progressBar.setVisibility(View.GONE); 68 | } 69 | } 70 | ); 71 | } 72 | }; 73 | 74 | public static class PodcastInstigator extends SimpleTagInstigator { 75 | 76 | public PodcastInstigator(ElementFinder<?> elementFinder, 77 | ParseFinishWatcher parseFinishWatcher) { 78 | super(elementFinder, "channel", parseFinishWatcher); 79 | } 80 | 81 | @Override 82 | public RootTag getRootTag() { 83 | return RootTag.create("rss"); 84 | } 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /demoAndroid/src/main/java/com/novoda/demoandroid/podcast/PodcastExampleHelper.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demoandroid.podcast; 2 | 3 | import com.novoda.demo.advanced.podcast.pojo.Channel; 4 | import com.novoda.demo.advanced.podcast.pojo.ChannelImage; 5 | import com.novoda.demo.advanced.podcast.pojo.PodcastItem; 6 | 7 | import java.util.List; 8 | 9 | public class PodcastExampleHelper { 10 | 11 | // language=XML 12 | public static final String SINGLE_PODCAST_ITEM = "<rss xmlns:itunes=\"http://www.itunes.com/dtds/podcast-1.0.dtd\" xmlns:atom=\"http://www.w3.org/2005/Atom\" version=\"2.0\">" 13 | + "<channel>" 14 | + "<title>CNET UK Podcast" 15 | + "http://crave.cnet.co.uk/podcast/" 16 | + "" 17 | + "http://www.cnet.co.uk/images/rss/logo-cnet.jpg" 18 | + "http://crave.cnet.co.uk/podcast/" 19 | + "CNET UK Podcast" 20 | + "88" 21 | + "56" 22 | + "" 23 | + "" 24 | + "podcast@cnet.co.uk (CNET UK)" 25 | + "" 26 | + "New BBC HD channels and the future of TV in Podcast 348 " 27 | + "" 28 | + "" 29 | + "http://crave.cnet.co.uk/podcast/new-bbc-hd-channels-and-the-future-of-tv-in-podcast-348-50011743/ " 30 | + "" 31 | + "Thu, 18 Jul 2013 12:07:02 +0100 " 32 | + " " 33 | + "cnetuk/podcast/50011743 " 34 | + "Jason Jenkins, Andrew Hoyle, Rich Trenholm " 35 | + "00:42:50 " 36 | + "no " 37 | + " " 38 | + " " 39 | + " " 40 | + "New BBC HD channels and the future of TV in Podcast 348 " 41 | + " " 42 | + " " 43 | + "As the BBC goes HD, Netflix reinvents the TV series, and BT and Sky square off, what's the future of television -- and is Apple in it? " 44 | + " " 45 | + "" 46 | + "" 47 | + "podcast@cnet.co.uk (CNET UK)" 48 | + "Free stuff for your phone in Podcast 346" 49 | + "" 50 | + "http://crave.cnet.co.uk/podcast/free-stuff-for-your-phone-in-podcast-346-50011642/" 51 | + "" 52 | + "Thu, 04 Jul 2013 12:05:07 +0100" 53 | + "" 54 | + "cnetuk/podcast/50011642" 55 | + "Luke Westaway, Andrew Hoyle, Rich Trenholm" 56 | + "00:37:43" 57 | + "no" 58 | + "" 59 | + "" 60 | + "Free stuff for your phone in Podcast 346" 61 | + "" 62 | + "Bag free stuff from your phone network as we explore the world of extras, bolt-ons and Freebeez in Podcast 346." 63 | + "" + "" + "" + ""; 64 | 65 | public static String getChannelDetailsString(Channel channel) { 66 | StringBuilder sb = new StringBuilder(); 67 | sb.append("Channel Title : " + channel.title + "\n"); 68 | sb.append("Channel Link : " + channel.link + "\n"); 69 | return sb.toString(); 70 | 71 | } 72 | 73 | public static String getChannelImageString(ChannelImage image) { 74 | StringBuilder sb = new StringBuilder(); 75 | sb.append("Channel Image Title : " + image.title + "\n"); 76 | sb.append("Channel Image Url : " + image.url + "\n"); 77 | return sb.toString(); 78 | } 79 | 80 | public static String getAllPodcastItemsString(List podcastItems) { 81 | StringBuilder sb = new StringBuilder(); 82 | for (PodcastItem podcastItem : podcastItems) { 83 | sb.append("Title : " + podcastItem.title + "\n"); 84 | sb.append("Author : " + podcastItem.author + "\n"); 85 | sb.append("Link : " + podcastItem.link + "\n"); 86 | sb.append("Itunes Duration : " + podcastItem.itunesDuration + "\n"); 87 | sb.append("Itunes Image : " + podcastItem.image + "\n"); 88 | sb.append(getSpace()); 89 | } 90 | return sb.toString(); 91 | } 92 | 93 | public static String getSpace() { 94 | return "\n\n"; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /demoAndroid/src/main/java/com/novoda/demoandroid/returnvalue/ReturnValueActivity.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demoandroid.returnvalue; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.AsyncTask; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.LinearLayout; 8 | import android.widget.ProgressBar; 9 | import android.widget.TextView; 10 | 11 | import com.novoda.demoandroid.R; 12 | import com.novoda.demoandroid.SecondLevelBaseActivity; 13 | import com.novoda.sax.RootElement; 14 | import com.novoda.sexp.Streamer; 15 | import com.novoda.sexp.RootTag; 16 | import com.novoda.sexp.SimpleEasyXmlParser; 17 | import com.novoda.sexp.finder.ElementFinder; 18 | import com.novoda.sexp.finder.ElementFinderFactory; 19 | 20 | public class ReturnValueActivity extends SecondLevelBaseActivity { 21 | // language=XML 22 | private static final String XML = "" 23 | + "Blue" + ""; 24 | 25 | private TextView parsingResult; 26 | private ProgressBar progressBar; 27 | private LinearLayout container; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_result_parsing); 33 | 34 | parsingResult = (TextView) findViewById(R.id.tv_result); 35 | progressBar = (ProgressBar) findViewById(R.id.pb_oneTag); 36 | container = (LinearLayout) findViewById(R.id.ll_oneTag); 37 | 38 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 39 | ElementFinder elementFinder = factory.getStringFinder(); 40 | SimpleStreamer instigator = new SimpleStreamer(elementFinder, "favouriteColour"); 41 | 42 | new ParsingTask(XML, instigator).execute(); 43 | } 44 | 45 | private static class SimpleStreamer implements Streamer { 46 | 47 | private final ElementFinder elementFinder; 48 | private final String elementTag; 49 | 50 | public SimpleStreamer(ElementFinder elementFinder, String elementTag) { 51 | this.elementFinder = elementFinder; 52 | this.elementTag = elementTag; 53 | } 54 | 55 | @Override 56 | public RootTag getRootTag() { 57 | return RootTag.create("novoda"); 58 | } 59 | 60 | @Override 61 | public void stream(RootElement rootElement) { 62 | elementFinder.find(rootElement, elementTag); 63 | } 64 | 65 | @Override 66 | public String getStreamResult() { 67 | return elementFinder.getResultOrThrow(); 68 | } 69 | } 70 | 71 | private class ParsingTask extends AsyncTask { 72 | private String xmlToParse; 73 | private SimpleStreamer instigator; 74 | 75 | public ParsingTask(String xml, SimpleStreamer anInstigator) { 76 | xmlToParse = xml; 77 | instigator = anInstigator; 78 | } 79 | 80 | @Override 81 | protected String doInBackground(Void... params) { 82 | return SimpleEasyXmlParser.parse(xmlToParse, instigator); 83 | } 84 | 85 | @SuppressLint("SetTextI18n") // Not in demo scope 86 | @Override 87 | protected void onPostExecute(String result) { 88 | parsingResult.setText("Got " + result + " as a return value (not using a callback)."); 89 | container.setVisibility(View.VISIBLE); 90 | progressBar.setVisibility(View.GONE); 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /demoAndroid/src/main/java/com/novoda/demoandroid/simple/SimpleExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demoandroid.simple; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.LinearLayout; 6 | import android.widget.ProgressBar; 7 | import android.widget.TextView; 8 | 9 | import com.novoda.demoandroid.ParsingTask; 10 | import com.novoda.demoandroid.R; 11 | import com.novoda.demoandroid.SecondLevelBaseActivity; 12 | import com.novoda.sexp.Instigator; 13 | import com.novoda.sexp.RootTag; 14 | import com.novoda.sexp.SimpleEasyXmlParser; 15 | import com.novoda.sexp.SimpleTagInstigator; 16 | import com.novoda.sexp.finder.ElementFinder; 17 | import com.novoda.sexp.finder.ElementFinderFactory; 18 | import com.novoda.sexp.parser.ParseFinishWatcher; 19 | 20 | public class SimpleExampleActivity extends SecondLevelBaseActivity { 21 | // language=XML 22 | private static final String XML = "" 23 | + "Blue" + ""; 24 | private static ElementFinder elementFinder; 25 | 26 | private TextView parsingResult; 27 | private ProgressBar progressBar; 28 | private LinearLayout container; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_result_parsing); 34 | 35 | parsingResult = (TextView) findViewById(R.id.tv_result); 36 | progressBar = (ProgressBar) findViewById(R.id.pb_oneTag); 37 | container = (LinearLayout) findViewById(R.id.ll_oneTag); 38 | 39 | ElementFinderFactory factory = SimpleEasyXmlParser 40 | .getElementFinderFactory(); 41 | elementFinder = factory.getStringFinder(); 42 | Instigator instigator = new SimpleInstigator( 43 | elementFinder, 44 | finishWatcher 45 | ); 46 | 47 | new ParsingTask(XML, instigator).execute(); 48 | } 49 | 50 | private ParseFinishWatcher finishWatcher = new ParseFinishWatcher() { 51 | @Override 52 | public void onFinish() { 53 | runOnUiThread( 54 | new Runnable() { 55 | @Override 56 | public void run() { 57 | parsingResult.setText(elementFinder.getResultOrThrow()); 58 | container.setVisibility(View.VISIBLE); 59 | progressBar.setVisibility(View.GONE); 60 | } 61 | } 62 | ); 63 | } 64 | }; 65 | 66 | public static class SimpleInstigator extends SimpleTagInstigator { 67 | 68 | public SimpleInstigator(ElementFinder elementFinder, ParseFinishWatcher parseFinishWatcher) { 69 | super(elementFinder, "favouriteColour", parseFinishWatcher); 70 | } 71 | 72 | @Override 73 | public RootTag getRootTag() { 74 | return RootTag.create("novoda"); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /demoAndroid/src/main/java/com/novoda/demoandroid/team/TeamExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.novoda.demoandroid.team; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.LinearLayout; 6 | import android.widget.ProgressBar; 7 | import android.widget.TextView; 8 | 9 | import com.novoda.demo.team.TeamMember; 10 | import com.novoda.demoandroid.ParsingTask; 11 | import com.novoda.demoandroid.R; 12 | import com.novoda.demoandroid.SecondLevelBaseActivity; 13 | import com.novoda.sexp.Instigator; 14 | import com.novoda.sexp.RootTag; 15 | import com.novoda.sexp.SimpleEasyXmlParser; 16 | import com.novoda.sexp.SimpleTagInstigator; 17 | import com.novoda.sexp.finder.ElementFinder; 18 | import com.novoda.sexp.finder.ElementFinderFactory; 19 | import com.novoda.sexp.parser.ParseFinishWatcher; 20 | 21 | import java.util.List; 22 | 23 | public class TeamExampleActivity extends SecondLevelBaseActivity { 24 | // language=XML 25 | private static final String XML = "" + "" 26 | + "Adam" + "Ben" + "Carl" 27 | + "David" + "Franky" 28 | + "Kevin" + "Moe" + "Paul" 29 | + "Peter" + "Shiv" + "" 30 | + ""; 31 | private static ElementFinder> elementFinder; 32 | 33 | private TextView parsingResult; 34 | private ProgressBar progressBar; 35 | private LinearLayout container; 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_result_parsing); 41 | 42 | parsingResult = (TextView) findViewById(R.id.tv_result); 43 | progressBar = (ProgressBar) findViewById(R.id.pb_oneTag); 44 | container = (LinearLayout) findViewById(R.id.ll_oneTag); 45 | 46 | ElementFinderFactory factory = SimpleEasyXmlParser.getElementFinderFactory(); 47 | elementFinder = factory.getStringWrapperTypeListFinder("name", TeamMember.class); 48 | Instigator instigator = new TeamInstigator(elementFinder, finishWatcher); 49 | 50 | new ParsingTask(XML, instigator).execute(); 51 | } 52 | 53 | private ParseFinishWatcher finishWatcher = new ParseFinishWatcher() { 54 | @Override 55 | public void onFinish() { 56 | runOnUiThread( 57 | new Runnable() { 58 | @Override 59 | public void run() { 60 | parsingResult.setText( 61 | elementFinder.getResultOrThrow() 62 | .toString() 63 | ); 64 | container.setVisibility(View.VISIBLE); 65 | progressBar.setVisibility(View.GONE); 66 | } 67 | } 68 | ); 69 | } 70 | }; 71 | 72 | public static class TeamInstigator extends SimpleTagInstigator { 73 | 74 | public TeamInstigator(ElementFinder elementFinder, 75 | ParseFinishWatcher parseFinishWatcher) { 76 | super(elementFinder, "team", parseFinishWatcher); 77 | } 78 | 79 | @Override 80 | public RootTag getRootTag() { 81 | return RootTag.create("novoda"); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /demoAndroid/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoda/simple-easy-xml-parser/2213dd60621124d5726427bad0c4375ecaf50528/demoAndroid/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /demoAndroid/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoda/simple-easy-xml-parser/2213dd60621124d5726427bad0c4375ecaf50528/demoAndroid/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /demoAndroid/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoda/simple-easy-xml-parser/2213dd60621124d5726427bad0c4375ecaf50528/demoAndroid/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demoAndroid/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoda/simple-easy-xml-parser/2213dd60621124d5726427bad0c4375ecaf50528/demoAndroid/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demoAndroid/src/main/res/layout/activity_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /demoAndroid/src/main/res/layout/activity_result_parsing.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 17 | 18 | 23 | 24 | 29 | 30 | 31 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /demoAndroid/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /demoAndroid/src/main/res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 128dp 8 | 9 | 10 | -------------------------------------------------------------------------------- /demoAndroid/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 8 | -------------------------------------------------------------------------------- /demoAndroid/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | demoAndroid 5 | 6 | Menu 7 | Return Value Example 8 | One Tag Example 9 | Simple Example 10 | Team Example 11 | Podcast Example 12 | BaseActivity 13 | 14 | Parsing result : 15 | 16 | 17 | -------------------------------------------------------------------------------- /demoAndroid/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 15 | 16 | 17 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoda/simple-easy-xml-parser/2213dd60621124d5726427bad0c4375ecaf50528/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Aug 08 16:44:46 BST 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-2.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sexp' 2 | include ':benchmark' 3 | include ':demo' 4 | include ':demoAndroid' 5 | 6 | -------------------------------------------------------------------------------- /sexp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'bintray-release' 3 | 4 | buildscript { 5 | repositories { 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.novoda:bintray-release:0.2.4' 10 | } 11 | } 12 | 13 | group = 'com.novoda' 14 | version = '1.0.8' 15 | 16 | sourceCompatibility = 1.6 17 | targetCompatibility = 1.6 18 | 19 | 20 | task wrapper(type: Wrapper) { 21 | gradleVersion = '2.1' 22 | } 23 | 24 | repositories { 25 | mavenCentral() 26 | } 27 | 28 | dependencies { 29 | testCompile group: 'junit', name: 'junit', version: '4.12' 30 | testCompile 'org.easytesting:fest-assert-core:2.0M10' 31 | testCompile group: 'org.mockito', name: 'mockito-all', version: '1.9.5' 32 | } 33 | 34 | publish { 35 | userOrg = 'novoda' 36 | groupId = 'com.novoda' 37 | artifactId = 'sexp' 38 | uploadName = 'simple-easy-xml-parser' 39 | version = project.version 40 | description = 'A simple XML parser based upon Android sax parser but written in pure Java.' 41 | website = 'https://github.com/novoda/simple-easy-xml-parser' 42 | } 43 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sax/BadXmlException.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 | import org.xml.sax.Locator; 20 | import org.xml.sax.SAXParseException; 21 | 22 | /** 23 | * An XML parse exception which includes the line number in the message. 24 | */ 25 | class BadXmlException extends SAXParseException { 26 | 27 | public BadXmlException(String message, Locator locator) { 28 | super(message, locator); 29 | } 30 | 31 | public String getMessage() { 32 | return "Line " + getLineNumber() + ": " + super.getMessage(); 33 | } 34 | } -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sax/Children.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 | /** 19 | * Contains element children. Using this class instead of HashMap results in 20 | * measurably better performance. 21 | */ 22 | class Children { 23 | 24 | Child[] children = new Child[16]; 25 | 26 | /** 27 | * Looks up a child by name and creates a new one if necessary. 28 | */ 29 | Element getOrCreate(Element parent, String uri, String localName) { 30 | int hash = uri.hashCode() * 31 + localName.hashCode(); 31 | int index = hash & 15; 32 | 33 | Child current = children[index]; 34 | if (current == null) { 35 | // We have no children in this bucket yet. 36 | current = new Child(parent, uri, localName, parent.depth + 1, hash); 37 | children[index] = current; 38 | return current; 39 | } else { 40 | // Search this bucket. 41 | Child previous; 42 | do { 43 | if (current.hash == hash 44 | && current.uri.compareTo(uri) == 0 45 | && current.localName.compareTo(localName) == 0) { 46 | // We already have a child with that name. 47 | return current; 48 | } 49 | 50 | previous = current; 51 | current = current.next; 52 | } while (current != null); 53 | 54 | // Add a new child to the bucket. 55 | current = new Child(parent, uri, localName, parent.depth + 1, hash); 56 | previous.next = current; 57 | return current; 58 | } 59 | } 60 | 61 | /** 62 | * Looks up a child by name. 63 | */ 64 | Element get(String uri, String localName) { 65 | int hash = uri.hashCode() * 31 + localName.hashCode(); 66 | int index = hash & 15; 67 | 68 | Child current = children[index]; 69 | if (current == null) { 70 | return null; 71 | } else { 72 | do { 73 | if (current.hash == hash 74 | && current.uri.compareTo(uri) == 0 75 | && current.localName.compareTo(localName) == 0) { 76 | return current; 77 | } 78 | current = current.next; 79 | } while (current != null); 80 | 81 | return null; 82 | } 83 | } 84 | 85 | static class Child extends Element { 86 | 87 | final int hash; 88 | Child next; 89 | 90 | Child(Element parent, String uri, String localName, int depth, 91 | int hash) { 92 | super(parent, uri, localName, depth); 93 | this.hash = hash; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sax/Element.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 | import java.util.ArrayList; 20 | 21 | import org.xml.sax.Locator; 22 | import org.xml.sax.SAXParseException; 23 | 24 | /** 25 | * An XML element. Provides access to child elements and hooks to listen 26 | * for events related to this element. 27 | * 28 | * @see RootElement 29 | */ 30 | public class Element { 31 | 32 | final String uri; 33 | final String localName; 34 | final int depth; 35 | final Element parent; 36 | 37 | Children children; 38 | ArrayList requiredChilden; 39 | 40 | boolean visited; 41 | 42 | StartElementListener startElementListener; 43 | EndElementListener endElementListener; 44 | EndTextElementListener endTextElementListener; 45 | 46 | Element(Element parent, String uri, String localName, int depth) { 47 | this.parent = parent; 48 | this.uri = uri; 49 | this.localName = localName; 50 | this.depth = depth; 51 | } 52 | 53 | /** 54 | * Gets the child element with the given name. Uses an empty string as the 55 | * namespace. 56 | */ 57 | public Element getChild(String localName) { 58 | return getChild("", localName); 59 | } 60 | 61 | /** 62 | * Gets the child element with the given name. 63 | */ 64 | public Element getChild(String uri, String localName) { 65 | if (endTextElementListener != null) { 66 | throw new IllegalStateException( 67 | "This element already has an end" 68 | + " text element listener. It cannot have children." 69 | ); 70 | } 71 | 72 | if (children == null) { 73 | children = new Children(); 74 | } 75 | 76 | return children.getOrCreate(this, uri, localName); 77 | } 78 | 79 | /** 80 | * Gets the child element with the given name. Uses an empty string as the 81 | * namespace. We will throw a {@link org.xml.sax.SAXException} at parsing 82 | * time if the specified child is missing. This helps you ensure that your 83 | * listeners are called. 84 | */ 85 | public Element requireChild(String localName) { 86 | return requireChild("", localName); 87 | } 88 | 89 | /** 90 | * Gets the child element with the given name. We will throw a 91 | * {@link org.xml.sax.SAXException} at parsing time if the specified child 92 | * is missing. This helps you ensure that your listeners are called. 93 | */ 94 | public Element requireChild(String uri, String localName) { 95 | Element child = getChild(uri, localName); 96 | 97 | if (requiredChilden == null) { 98 | requiredChilden = new ArrayList(); 99 | requiredChilden.add(child); 100 | } else { 101 | if (!requiredChilden.contains(child)) { 102 | requiredChilden.add(child); 103 | } 104 | } 105 | 106 | return child; 107 | } 108 | 109 | /** 110 | * Sets start and end element listeners at the same time. 111 | */ 112 | public void setElementListener(ElementListener elementListener) { 113 | setStartElementListener(elementListener); 114 | setEndElementListener(elementListener); 115 | } 116 | 117 | /** 118 | * Sets start and end text element listeners at the same time. 119 | */ 120 | public void setTextElementListener(TextElementListener elementListener) { 121 | setStartElementListener(elementListener); 122 | setEndTextElementListener(elementListener); 123 | } 124 | 125 | /** 126 | * Sets a listener for the start of this element. 127 | */ 128 | public void setStartElementListener(StartElementListener startElementListener) { 129 | if (this.startElementListener != null) { 130 | throw new IllegalStateException( 131 | "Start element listener has already been set." 132 | ); 133 | } 134 | this.startElementListener = startElementListener; 135 | } 136 | 137 | /** 138 | * Sets a listener for the end of this element. 139 | */ 140 | public void setEndElementListener(EndElementListener endElementListener) { 141 | if (this.endElementListener != null) { 142 | throw new IllegalStateException( 143 | "End element listener has already been set." 144 | ); 145 | } 146 | this.endElementListener = endElementListener; 147 | } 148 | 149 | /** 150 | * Sets a listener for the end of this text element. 151 | */ 152 | public void setEndTextElementListener( 153 | EndTextElementListener endTextElementListener) { 154 | if (this.endTextElementListener != null) { 155 | throw new IllegalStateException( 156 | "End text element listener has already been set." 157 | ); 158 | } 159 | 160 | if (children != null) { 161 | throw new IllegalStateException( 162 | "This element already has children." 163 | + " It cannot have an end text element listener." 164 | ); 165 | } 166 | 167 | this.endTextElementListener = endTextElementListener; 168 | } 169 | 170 | @Override 171 | public String toString() { 172 | return toString(uri, localName); 173 | } 174 | 175 | static String toString(String uri, String localName) { 176 | return "'" + (uri.equals("") ? localName : uri + ":" + localName) + "'"; 177 | } 178 | 179 | /** 180 | * Clears flags on required children. 181 | */ 182 | void resetRequiredChildren() { 183 | ArrayList requiredChildren = this.requiredChilden; 184 | if (requiredChildren != null) { 185 | for (int i = requiredChildren.size() - 1; i >= 0; i--) { 186 | requiredChildren.get(i).visited = false; 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * Throws an exception if a required child was not present. 193 | */ 194 | void checkRequiredChildren(Locator locator) throws SAXParseException { 195 | ArrayList requiredChildren = this.requiredChilden; 196 | if (requiredChildren != null) { 197 | for (int i = requiredChildren.size() - 1; i >= 0; i--) { 198 | Element child = requiredChildren.get(i); 199 | if (!child.visited) { 200 | throw new BadXmlException( 201 | "Element named " + this + " is missing required" 202 | + " child element named " 203 | + child + ".", locator 204 | ); 205 | } 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sax/ElementListener.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 elements. 21 | */ 22 | public interface ElementListener extends StartElementListener, EndElementListener { 23 | } -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sax/EndElementListener.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 end of elements. 21 | */ 22 | public interface EndElementListener { 23 | 24 | /** 25 | * Invoked at the end of an element. 26 | */ 27 | void end(); 28 | } -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sax/EndTextElementListener.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 end of text elements. 21 | */ 22 | public interface EndTextElementListener { 23 | 24 | /** 25 | * Invoked at the end of a text element with the body of the element. 26 | * 27 | * @param body of the element 28 | */ 29 | void end(String body); 30 | } -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sax/RootElement.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 | import org.xml.sax.Attributes; 20 | import org.xml.sax.ContentHandler; 21 | import org.xml.sax.Locator; 22 | import org.xml.sax.SAXException; 23 | import org.xml.sax.helpers.DefaultHandler; 24 | 25 | /** 26 | * The root XML element. The entry point for this API. Not safe for concurrent 27 | * use. 28 | *

29 | *

For example, passing this XML: 30 | *

31 | *

 32 |  * <feed xmlns='http://www.w3.org/2005/Atom'>
 33 |  *   <entry>
 34 |  *     <id>bob</id>
 35 |  *   </entry>
 36 |  * </feed>
 37 |  * 
38 | *

39 | * to this code: 40 | *

41 | *

 42 |  * static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
 43 |  *
 44 |  * ...
 45 |  *
 46 |  * RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
 47 |  * Element entry = root.getChild(ATOM_NAMESPACE, "entry");
 48 |  * entry.getChild(ATOM_NAMESPACE, "id").setEndTextElementListener(
 49 |  *   new EndTextElementListener() {
 50 |  *     public void end(String body) {
 51 |  *       System.out.println("Entry ID: " + body);
 52 |  *     }
 53 |  *   });
 54 |  *
 55 |  * XMLReader reader = ...;
 56 |  * reader.setContentHandler(root.getContentHandler());
 57 |  * reader.parse(...);
 58 |  * 
59 | *

60 | * would output: 61 | *

62 | *

 63 |  * Entry ID: bob
 64 |  * 
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 | *
    23 | *
  • {@link com.novoda.sexp.finder.ElementFinder ElementFinder}
  • 24 | *
  • {@link com.novoda.sexp.marshaller.AttributeMarshaller AttributeMarshaller}
  • 25 | *
  • {@link com.novoda.sexp.marshaller.BodyMarshaller BodyMarshaller}
  • 26 | *
  • {@link com.novoda.sexp.parser.Parser Parser}
  • 27 | *
  • {@link com.novoda.sexp.parser.ParseWatcher ParseWatcher}
  • 28 | *
  • {@link com.novoda.sexp.parser.ListParseWatcher ListParseWatcher}
  • 29 | *
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 | *
    25 | *
  • {@link com.novoda.sexp.finder.ElementFinder ElementFinder}
  • 26 | *
  • {@link com.novoda.sexp.marshaller.AttributeMarshaller AttributeMarshaller}
  • 27 | *
  • {@link com.novoda.sexp.marshaller.BodyMarshaller BodyMarshaller}
  • 28 | *
  • {@link com.novoda.sexp.parser.Parser Parser}
  • 29 | *
  • {@link com.novoda.sexp.parser.ParseWatcher ParseWatcher}
  • 30 | *
  • {@link com.novoda.sexp.parser.ListParseWatcher ListParseWatcher}
  • 31 | *
32 | * 33 | * @param rootElement the root element of your XML file 34 | */ 35 | void stream(RootElement rootElement); 36 | 37 | /** 38 | * @return the object you expected to be streamed 39 | */ 40 | R getStreamResult(); 41 | } 42 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/XMLReaderBuilder.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp; 2 | 3 | import javax.xml.parsers.ParserConfigurationException; 4 | import javax.xml.parsers.SAXParser; 5 | import javax.xml.parsers.SAXParserFactory; 6 | import javax.xml.validation.Schema; 7 | 8 | import org.xml.sax.SAXException; 9 | import org.xml.sax.SAXNotRecognizedException; 10 | import org.xml.sax.SAXNotSupportedException; 11 | import org.xml.sax.XMLReader; 12 | 13 | public class XMLReaderBuilder { 14 | 15 | private static final String FEATURE_NAMESPACE = "http://xml.org/sax/features/namespaces"; 16 | private static final String FEATURE_NAMESPACE_PREFIX = "http://xml.org/sax/features/namespace-prefixes"; 17 | 18 | private final SAXParserFactory saxParserFactory; 19 | 20 | public XMLReaderBuilder() { 21 | this(SAXParserFactory.newInstance()); 22 | } 23 | 24 | XMLReaderBuilder(SAXParserFactory saxParserFactory) { 25 | this.saxParserFactory = saxParserFactory; 26 | } 27 | 28 | /** 29 | * Sets the particular feature in the underlying implementation of org.xml.sax.XMLReader. http://xerces.apache.org/xerces2-j/features.html 30 | * 31 | * @param name The name of the feature to be set. 32 | * @param value The value of the feature to be set. 33 | * @return XMLReaderBuilder 34 | * @throws XMLReaderCreationException 35 | */ 36 | 37 | public XMLReaderBuilder withFeature(String name, boolean value) throws XMLReaderCreationException { 38 | try { 39 | saxParserFactory.setFeature(name, value); 40 | } catch (ParserConfigurationException e) { 41 | throw new XMLReaderCreationException(e); 42 | } catch (SAXNotRecognizedException e) { 43 | throw new XMLReaderCreationException(e); 44 | } catch (SAXNotSupportedException e) { 45 | throw new XMLReaderCreationException(e); 46 | } 47 | return this; 48 | } 49 | 50 | /** 51 | * Perform namespace processing: prefixes will be stripped off element and attribute names and 52 | * replaced with the corresponding namespace URIs. By default, the two will simply be concatenated, 53 | * but the namespace-sep core property allows the application to specify a delimiter string for 54 | * separating the URI part and the local part. 55 | * 56 | * @param allowProcessing enables/disables the processing 57 | * @return XMLReaderBuilder 58 | * @throws XMLReaderCreationException 59 | */ 60 | public XMLReaderBuilder allowNamespaceProcessing(boolean allowProcessing) throws XMLReaderCreationException { 61 | withFeature(FEATURE_NAMESPACE, allowProcessing); 62 | return this; 63 | } 64 | 65 | /** 66 | * Report the original prefixed names and attributes used for namespace declarations. 67 | * 68 | * @param allowProcessing enables/disables the processing 69 | * @return XMLReaderBuilder 70 | * @throws XMLReaderCreationException 71 | */ 72 | public XMLReaderBuilder allowAttributeNamespaceProcessing(boolean allowProcessing) throws XMLReaderCreationException { 73 | withFeature(FEATURE_NAMESPACE_PREFIX, allowProcessing); 74 | return this; 75 | } 76 | 77 | /** 78 | * Specifies that the parser produced by this code will provide support for XML namespaces. 79 | * 80 | * @param isAware enables/disables the processing 81 | * @return XMLReaderBuilder 82 | */ 83 | public XMLReaderBuilder setNamespaceAware(boolean isAware) { 84 | saxParserFactory.setNamespaceAware(isAware); 85 | return this; 86 | } 87 | 88 | /** 89 | * Set state of XInclude processing. 90 | * 91 | * @param includeAware 92 | * @return XMLReaderBuilder 93 | */ 94 | public XMLReaderBuilder setXIncludeAware(boolean includeAware) { 95 | saxParserFactory.setXIncludeAware(includeAware); 96 | return this; 97 | } 98 | 99 | /** 100 | * Set the {@link Schema} to be used by parsers created 101 | * from this factory 102 | * 103 | * @param schema 104 | * @return XMLReaderBuilder 105 | */ 106 | public XMLReaderBuilder setSchema(Schema schema) { 107 | saxParserFactory.setSchema(schema); 108 | return this; 109 | } 110 | 111 | /** 112 | * Create the XMLReader with the specified options 113 | * 114 | * @return XMLReader 115 | * @throws XMLReaderCreationException 116 | */ 117 | public XMLReader build() throws XMLReaderCreationException { 118 | try { 119 | SAXParser saxParser = saxParserFactory.newSAXParser(); 120 | return saxParser.getXMLReader(); 121 | } catch (ParserConfigurationException e) { 122 | throw new XMLReaderCreationException(e); 123 | } catch (SAXException e) { 124 | throw new XMLReaderCreationException(e); 125 | } 126 | } 127 | 128 | public static class XMLReaderCreationException extends Exception { 129 | public XMLReaderCreationException(Exception e) { 130 | super(e); 131 | } 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/XmlParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | import org.xml.sax.InputSource; 8 | import org.xml.sax.SAXException; 9 | import org.xml.sax.XMLReader; 10 | 11 | public class XmlParser { 12 | 13 | public void parse(String xml, XMLReader xmlReader) { 14 | parse(new ByteArrayInputStream(xml.getBytes()), xmlReader); 15 | } 16 | 17 | public void parse(InputStream xml, XMLReader xmlReader) { 18 | try { 19 | InputSource input = new InputSource(xml); 20 | xmlReader.parse(input); 21 | } catch (IOException e) { 22 | throw new RuntimeException(e); 23 | } catch (SAXException e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/finder/BasicElementFinder.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.finder; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sexp.parser.Parser; 5 | 6 | public class BasicElementFinder implements ElementFinder { 7 | 8 | private final Parser parser; 9 | private T result; 10 | 11 | public BasicElementFinder(Parser parser) { 12 | this.parser = parser; 13 | this.result = null; 14 | } 15 | 16 | @Override 17 | public void find(Element from, String tag) { 18 | parser.parse(from.getChild(tag), this); 19 | } 20 | 21 | @Override 22 | public void find(Element from, String uri, String tag) { 23 | parser.parse(from.getChild(uri, tag), this); 24 | } 25 | 26 | @Override 27 | public void onParsed(T body) { 28 | result = body; 29 | } 30 | 31 | @Override 32 | public T getResult() { 33 | return result; 34 | } 35 | 36 | @Override 37 | public T getResultOrThrow() { 38 | validateResult(); 39 | return result; 40 | } 41 | 42 | @Override 43 | public T popResult() { 44 | try { 45 | return result; 46 | } finally { 47 | result = null; 48 | } 49 | } 50 | 51 | @Override 52 | public T popResultOrThrow() { 53 | validateResult(); 54 | return popResult(); 55 | } 56 | 57 | private void validateResult() { 58 | if (result == null) { 59 | throw new ResultNotFoundException(); 60 | } 61 | } 62 | 63 | public static class ResultNotFoundException extends RuntimeException { 64 | public ResultNotFoundException() { 65 | super("Did you call find() with a valid tag and/or wait for the result to be parsed?"); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/finder/ElementFinder.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.finder; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sexp.parser.ParseWatcher; 5 | 6 | public interface ElementFinder extends ParseWatcher { 7 | 8 | /** 9 | * Find the given tag inside the given element 10 | * 11 | * @param from the XML element to search within 12 | * @param tag the XML tag to be searched for 13 | */ 14 | void find(Element from, String tag); 15 | 16 | /** 17 | * Find the given tag inside the given element 18 | * 19 | * @param from the XML element to search within 20 | * @param uri the XML namespace of the searched for tag 21 | * @param tag the XML tag to be searched for 22 | */ 23 | void find(Element from, String uri, String tag); 24 | 25 | @Override 26 | void onParsed(R item); 27 | 28 | /** 29 | * @return the result of the parsing if the parsing was completed and tag was found 30 | * or null if the tag was not found or parsing didn't complete. 31 | */ 32 | R getResult(); 33 | 34 | /** 35 | * @return the result of the parsing if the parsing was completed and tag was found 36 | * or throws an exception if the tag was not found or parsing didn't complete. 37 | */ 38 | R getResultOrThrow(); 39 | 40 | /** 41 | * Same as {@link #getResult} but clears the result instead of keeping it. 42 | * @return the result of the parsing if the parsing was completed and tag was found 43 | * or null if the tag was not found or parsing didn't complete. 44 | */ 45 | R popResult(); 46 | 47 | /** 48 | * Same as {@link #getResultOrThrow} but clears the result instead of keeping it. 49 | * @return the result of the parsing if the parsing was completed and tag was found 50 | * or throws an exception if the tag was not found or parsing didn't complete. 51 | */ 52 | R popResultOrThrow(); 53 | } 54 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/finder/ElementFinderFactory.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.finder; 2 | 3 | import com.novoda.sexp.marshaller.AttributeMarshaller; 4 | import com.novoda.sexp.marshaller.BodyMarshaller; 5 | import com.novoda.sexp.marshaller.BooleanBodyMarshaller; 6 | import com.novoda.sexp.marshaller.IntegerBodyMarshaller; 7 | import com.novoda.sexp.marshaller.IntegerWrapperBodyMarshaller; 8 | import com.novoda.sexp.marshaller.StringBodyMarshaller; 9 | import com.novoda.sexp.marshaller.StringWrapperBodyMarshaller; 10 | import com.novoda.sexp.parser.BasicAttributeParser; 11 | import com.novoda.sexp.parser.BasicParser; 12 | import com.novoda.sexp.parser.ListParser; 13 | import com.novoda.sexp.parser.ParseWatcher; 14 | import com.novoda.sexp.parser.Parser; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * You can retrieve {@link ElementFinder ElementFinder's} here 20 | *

21 | * For example if you have the XML 22 | *

 23 |  * {@code Paul Blundell }
 24 |  * 
25 | * and you want to retrieve it as a {@link String} 26 | * you would use the {@link #getStringFinder()} method 27 | *
 28 |  * {@code ElementFinder finder = getStringFinder();
 29 |  * finder.find(XML, "myTag");
 30 |  * String name = finder.getResultOrThrow();
 31 |  * }
 32 |  * 
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 | *
 91 |      * {@code 
 92 |      *     Paul
 93 |      *     Peter
 94 |      *   
 95 |      *
 96 |      * ElementFinder finder = getListFinder("name", marshallerOfT);
 97 |      * finder.find(element, "names");
 98 |      * List names = finder.getResultOrThrow();
 99 |      * }
100 |      * 
101 | * 102 | * @param tag The tag to parse the body for each list element 103 | * @param bodyMarshaller The marshaller to parse the body into your required type 104 | * @param The type you wish to create from the XML body 105 | * @return {@link ElementFinder} 106 | */ 107 | public ElementFinder> getListFinder(String tag, BodyMarshaller bodyMarshaller) { 108 | return getTypeFinder(new ListParser(tag, this, bodyMarshaller)); 109 | } 110 | 111 | /** 112 | * Will parse the body of all XML tags with the {@code tag} argument 113 | * into a {@link java.util.List} of {@link Object}. This is a simple String wrapper class that 114 | * has a constructor that takes a single {@link String} parameter.
115 | * See {@link #getStringWrapperTypeFinder(Class)} for more info. 116 | * 117 | * @param tag The tag to parse the body for each list element 118 | * @param clazz The class of the wrapper type you wish your List to be made of 119 | * @param The type you wish to create from the XML body 120 | * @return {@link ElementFinder} 121 | */ 122 | public ElementFinder> getStringWrapperTypeListFinder(String tag, Class clazz) { 123 | return getTypeFinder(new ListParser(tag, this, getStringWrapperMarshaller(clazz))); 124 | } 125 | 126 | /** 127 | * Will parse the body of all XML tags with the {@code tag} argument 128 | * into a {@link java.util.List} of {@link Object}. This is a simple Integer wrapper class that 129 | * has a constructor that takes a single {@link Integer} parameter.
130 | * See {@link #getIntegerWrapperTypeFinder(Class)} for more info. 131 | * 132 | * @param tag The tag to parse the body for each list element 133 | * @param clazz The class of the wrapper type you wish your List to be made of 134 | * @param The type you wish to create from the XML body 135 | * @return {@link ElementFinder} 136 | */ 137 | public ElementFinder> getIntegerWrapperTypeListFinder(String tag, Class clazz) { 138 | return getTypeFinder(new ListParser(tag, this, getIntegerWrapperMarshaller(clazz))); 139 | } 140 | 141 | /** 142 | * Will parse the attributes off an XML tag, these are then passed to your {@link AttributeMarshaller} 143 | * to create an object of type {@link Object}. 144 | * 145 | * @param attributeMarshaller The marshaller to parse the attributes into your required type 146 | * @param attrTags The tags of the attributes you wish to parse 147 | * @param The type you wish to create from the attributes 148 | * @return {@link ElementFinder} 149 | */ 150 | public ElementFinder getAttributeFinder(AttributeMarshaller attributeMarshaller, String... attrTags) { 151 | return getTypeFinder(new BasicAttributeParser(attributeMarshaller, attrTags)); 152 | } 153 | 154 | /** 155 | * Will parse the attributes off an XML tag, into {@link Object} then inform the {@link ParseWatcher} 156 | * The idea is to have a callback for a number of elements to create a {@link java.util.List} 157 | * 158 | * @param attributeMarshaller The marshaller to parse the attributes into your required type 159 | * @param watcher The watcher on elements to be informed of object creation 160 | * @param attrTags The tags of the attributes you wish to parse 161 | * @param The type you wish to create from the XML body 162 | * @return {@link ElementFinder} 163 | */ 164 | public ElementFinder getListAttributeFinder(AttributeMarshaller attributeMarshaller, ParseWatcher watcher, String... attrTags) { 165 | return new ListElementFinder(new BasicAttributeParser(attributeMarshaller, attrTags), watcher); 166 | } 167 | 168 | /** 169 | * Will parse the body of an XML tag into {@link Object} using the supplied {@link BodyMarshaller} 170 | * 171 | * @param bodyMarshaller The marshaller to parse the body into your required type 172 | * @param The type you wish to create from the XML body 173 | * @return {@link ElementFinder} 174 | */ 175 | public ElementFinder getTypeFinder(BodyMarshaller bodyMarshaller) { 176 | return getTypeFinder(new BasicParser(bodyMarshaller)); 177 | } 178 | 179 | /** 180 | * Will parse an XML tag using the passed {@link Parser} 181 | * This can be used when you want to parse attributes as well as the body into your object 182 | * 183 | * @param parser The parser you wish to parse the XML with 184 | * @param The type you wish to create from the XML body 185 | * @return {@link ElementFinder} 186 | */ 187 | public ElementFinder getTypeFinder(Parser parser) { 188 | return new BasicElementFinder(parser); 189 | } 190 | 191 | /** 192 | * Will parse the body of an XML tag into {@link Object} then inform the {@link ParseWatcher} 193 | * The idea is to have a callback for a number of elements to create a {@link java.util.List} 194 | * 195 | * @param bodyMarshaller The marshaller to create an object from the XML body 196 | * @param watcher The watcher on elements to be informed of object creation 197 | * @param The type you wish to create from the XML body 198 | * @return {@link ElementFinder} 199 | */ 200 | public ElementFinder getListElementFinder(BodyMarshaller bodyMarshaller, ParseWatcher watcher) { 201 | return getListElementFinder(new BasicParser(bodyMarshaller), watcher); 202 | } 203 | 204 | /** 205 | * Will parse an XML tag into {@link Object} then inform the {@link ParseWatcher} 206 | * The idea is to have a callback for a number of elements to create a {@link java.util.List} 207 | * This can be used when you want to parse attributes as well as the XML body into your object 208 | * 209 | * @param parser The parser you wish to parse the XML with 210 | * @param watcher The watcher on elements to be informed of object creation 211 | * @param The type you wish to create from the XML body 212 | * @return {@link ElementFinder} 213 | */ 214 | public ElementFinder getListElementFinder(Parser parser, ParseWatcher watcher) { 215 | return new ListElementFinder(parser, watcher); 216 | } 217 | 218 | private BodyMarshaller getStringWrapperMarshaller(Class clazz) { 219 | return new StringWrapperBodyMarshaller(clazz); 220 | } 221 | 222 | private BodyMarshaller getIntegerWrapperMarshaller(Class clazz) { 223 | return new IntegerWrapperBodyMarshaller(clazz); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/finder/ListElementFinder.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.finder; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sexp.parser.ParseWatcher; 5 | import com.novoda.sexp.parser.Parser; 6 | 7 | public class ListElementFinder implements ElementFinder { 8 | 9 | private final Parser parser; 10 | private final ParseWatcher parseWatcher; 11 | 12 | public ListElementFinder(Parser parser, ParseWatcher parseWatcher) { 13 | this.parser = parser; 14 | this.parseWatcher = parseWatcher; 15 | } 16 | 17 | @Override 18 | public void find(Element from, String tag) { 19 | parser.parse(from.getChild(tag), this); 20 | } 21 | 22 | @Override 23 | public void find(Element from, String uri, String tag) { 24 | parser.parse(from.getChild(uri, tag), this); 25 | } 26 | 27 | @Override 28 | public void onParsed(T body) { 29 | parseWatcher.onParsed(body); 30 | } 31 | 32 | @Override 33 | public T getResult() { 34 | throw new UnsupportedOperationException("Has a listener to pass each item as parsed, so there is no result."); 35 | } 36 | 37 | @Override 38 | public T getResultOrThrow() { 39 | throw new UnsupportedOperationException("Has a listener to pass each item as parsed, so there is no result."); 40 | } 41 | 42 | @Override 43 | public T popResult() { 44 | throw new UnsupportedOperationException("Has a listener to pass each item as parsed, so there is no result."); 45 | } 46 | 47 | @Override 48 | public T popResultOrThrow() { 49 | throw new UnsupportedOperationException("Has a listener to pass each item as parsed, so there is no result."); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/AttributeMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | public interface AttributeMarshaller { 4 | T marshal(String... input); 5 | } 6 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/BodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | public interface BodyMarshaller { 4 | T marshal(String input); 5 | } 6 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/BooleanBodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | public class BooleanBodyMarshaller implements BodyMarshaller { 4 | @Override 5 | public Boolean marshal(String input) { 6 | if ("true".equalsIgnoreCase(input)) { 7 | return true; 8 | } else if ("false".equalsIgnoreCase(input)) { 9 | return false; 10 | } 11 | throw new IllegalArgumentException("The input '" + input + "' is not a valid boolean (possible values are 'true' and 'false')."); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/DoubleBodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | public class DoubleBodyMarshaller implements BodyMarshaller { 4 | @Override 5 | public Double marshal(String input) { 6 | return Double.valueOf(input); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/DoubleWrapperBodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | public class DoubleWrapperBodyMarshaller implements BodyMarshaller { 6 | 7 | private final Class clazz; 8 | 9 | public DoubleWrapperBodyMarshaller(Class clazz) { 10 | this.clazz = clazz; 11 | } 12 | 13 | @Override 14 | public T marshal(String input) { 15 | try { 16 | return clazz.getDeclaredConstructor(double.class).newInstance(Double.parseDouble(input)); 17 | } catch (InstantiationException e) { 18 | throw new RuntimeException(e); 19 | } catch (IllegalAccessException e) { 20 | throw new RuntimeException(e); 21 | } catch (InvocationTargetException e) { 22 | throw new RuntimeException(e); 23 | } catch (NoSuchMethodException e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/IntegerBodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | public class IntegerBodyMarshaller implements BodyMarshaller { 4 | @Override 5 | public Integer marshal(String input) { 6 | return Integer.valueOf(input); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/IntegerWrapperBodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | public class IntegerWrapperBodyMarshaller implements BodyMarshaller { 6 | 7 | private final Class clazz; 8 | 9 | public IntegerWrapperBodyMarshaller(Class clazz) { 10 | this.clazz = clazz; 11 | } 12 | 13 | @Override 14 | public T marshal(String input) { 15 | try { 16 | return clazz.getDeclaredConstructor(int.class).newInstance(Integer.parseInt(input)); 17 | } catch (InstantiationException e) { 18 | throw new RuntimeException(e); 19 | } catch (IllegalAccessException e) { 20 | throw new RuntimeException(e); 21 | } catch (InvocationTargetException e) { 22 | throw new RuntimeException(e); 23 | } catch (NoSuchMethodException e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/LongBodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | public class LongBodyMarshaller implements BodyMarshaller { 4 | @Override 5 | public Long marshal(String input) { 6 | return Long.valueOf(input); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/LongWrapperBodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | public class LongWrapperBodyMarshaller implements BodyMarshaller { 6 | 7 | private final Class clazz; 8 | 9 | public LongWrapperBodyMarshaller(Class clazz) { 10 | this.clazz = clazz; 11 | } 12 | 13 | @Override 14 | public T marshal(String input) { 15 | try { 16 | return clazz.getDeclaredConstructor(long.class).newInstance(Long.parseLong(input)); 17 | } catch (InstantiationException e) { 18 | throw new RuntimeException(e); 19 | } catch (IllegalAccessException e) { 20 | throw new RuntimeException(e); 21 | } catch (InvocationTargetException e) { 22 | throw new RuntimeException(e); 23 | } catch (NoSuchMethodException e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/StringBodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | public class StringBodyMarshaller implements BodyMarshaller { 4 | 5 | @Override 6 | public String marshal(String input) { 7 | return input; 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/marshaller/StringWrapperBodyMarshaller.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | public class StringWrapperBodyMarshaller implements BodyMarshaller { 6 | 7 | private final Class clazz; 8 | 9 | public StringWrapperBodyMarshaller(Class clazz) { 10 | this.clazz = clazz; 11 | } 12 | 13 | @Override 14 | public T marshal(String input) { 15 | try { 16 | return clazz.getDeclaredConstructor(String.class).newInstance(input); 17 | } catch (InstantiationException e) { 18 | throw new RuntimeException(e); 19 | } catch (IllegalAccessException e) { 20 | throw new RuntimeException(e); 21 | } catch (InvocationTargetException e) { 22 | throw new RuntimeException(e); 23 | } catch (NoSuchMethodException e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/parser/BasicAttributeParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sax.StartElementListener; 5 | import com.novoda.sexp.marshaller.AttributeMarshaller; 6 | 7 | import org.xml.sax.Attributes; 8 | 9 | public class BasicAttributeParser implements Parser, StartElementListener { 10 | 11 | private final AttributeMarshaller attributeMarshaller; 12 | private final String[] attrs; 13 | private ParseWatcher listener; 14 | 15 | public BasicAttributeParser(AttributeMarshaller attributeMarshaller, String... attrs) { 16 | this.attributeMarshaller = attributeMarshaller; 17 | this.attrs = attrs; 18 | } 19 | 20 | @Override 21 | public void parse(Element element, final ParseWatcher listener) { 22 | this.listener = listener; 23 | element.setStartElementListener(this); 24 | } 25 | 26 | @Override 27 | public void start(Attributes attributes) { 28 | String[] values = getAttributeValues(attributes); 29 | listener.onParsed(attributeMarshaller.marshal(values)); 30 | } 31 | 32 | private String[] getAttributeValues(Attributes attributes) { 33 | String[] values = new String[attrs.length]; 34 | for (int i = 0; i < attrs.length; i++) { 35 | values[i] = attributes.getValue(attrs[i]); 36 | } 37 | return values; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/parser/BasicParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sax.EndTextElementListener; 5 | import com.novoda.sexp.marshaller.BodyMarshaller; 6 | 7 | public class BasicParser implements Parser { 8 | 9 | private final BodyMarshaller bodyMarshaller; 10 | 11 | public BasicParser(BodyMarshaller bodyMarshaller) { 12 | this.bodyMarshaller = bodyMarshaller; 13 | } 14 | 15 | @Override 16 | public void parse(Element element, final ParseWatcher listener) { 17 | element.setEndTextElementListener( 18 | new EndTextElementListener() { 19 | @Override 20 | public void end(String body) { 21 | listener.onParsed(bodyMarshaller.marshal(body)); 22 | } 23 | } 24 | ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/parser/ListParseWatcher.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | import com.novoda.sax.ElementListener; 4 | 5 | import org.xml.sax.Attributes; 6 | 7 | public interface ListParseWatcher extends ParseWatcher, ElementListener { 8 | @Override 9 | void start(Attributes attributes); 10 | 11 | @Override 12 | void onParsed(T item); 13 | 14 | @Override 15 | void end(); 16 | } 17 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/parser/ListParser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sexp.finder.ElementFinder; 5 | import com.novoda.sexp.finder.ElementFinderFactory; 6 | import com.novoda.sexp.marshaller.BodyMarshaller; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import org.xml.sax.Attributes; 12 | 13 | public class ListParser implements Parser>, ListParseWatcher { 14 | 15 | private final String tag; 16 | private final ElementFinder listCreator; 17 | 18 | private ParseWatcher> listener; 19 | private List list; 20 | 21 | public ListParser(String tag, ElementFinderFactory factory, BodyMarshaller bodyMarshaller) { 22 | this.tag = tag; 23 | this.listCreator = factory.getListElementFinder(bodyMarshaller, this); 24 | } 25 | 26 | @Override 27 | public void parse(Element element, ParseWatcher> listener) { 28 | this.listener = listener; 29 | element.setElementListener(this); 30 | listCreator.find(element, tag); 31 | } 32 | 33 | @Override 34 | public void start(Attributes attributes) { 35 | list = new ArrayList(); 36 | } 37 | 38 | @Override 39 | public void onParsed(T item) { 40 | list.add(item); 41 | } 42 | 43 | @Override 44 | public void end() { 45 | listener.onParsed(list); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/parser/ParseFinishWatcher.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | public interface ParseFinishWatcher { 4 | void onFinish(); 5 | } 6 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/parser/ParseWatcher.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | public interface ParseWatcher { 4 | void onParsed(T item); 5 | } 6 | -------------------------------------------------------------------------------- /sexp/src/main/java/com/novoda/sexp/parser/Parser.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | import com.novoda.sax.Element; 4 | 5 | public interface Parser { 6 | void parse(Element element, ParseWatcher listener); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sax/ChildrenShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sax; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.fest.assertions.api.Assertions.assertThat; 7 | 8 | public class ChildrenShould { 9 | 10 | private static final String FIRST_STRING_WITH_EQUAL_HASH = "0-42L"; 11 | private static final String SECOND_STRING_WITH_EQUAL_HASH = "0-43-"; 12 | 13 | private Children children; 14 | private Element parent; 15 | 16 | @Before 17 | public void setUp() { 18 | children = new Children(); 19 | parent = new Element(null, "", "test", 0); 20 | } 21 | 22 | @Test 23 | public void create_new_child_with_equal_hash_uri_and_local() throws Exception { 24 | Element element = children.getOrCreate(parent, FIRST_STRING_WITH_EQUAL_HASH, FIRST_STRING_WITH_EQUAL_HASH); 25 | Element anotherElement = children.getOrCreate(parent, SECOND_STRING_WITH_EQUAL_HASH, SECOND_STRING_WITH_EQUAL_HASH); 26 | 27 | assertThat(element).isNotSameAs(anotherElement); 28 | } 29 | 30 | @Test 31 | public void same_child_with_equal_uri_and_local() { 32 | Element element = children.getOrCreate(parent, FIRST_STRING_WITH_EQUAL_HASH, FIRST_STRING_WITH_EQUAL_HASH); 33 | Element anotherElement = children.getOrCreate(parent, FIRST_STRING_WITH_EQUAL_HASH, FIRST_STRING_WITH_EQUAL_HASH); 34 | 35 | assertThat(element).isSameAs(anotherElement); 36 | } 37 | 38 | @Test 39 | public void return_proper_child_with_equal_hash_uri_and_local() { 40 | children.getOrCreate(parent, FIRST_STRING_WITH_EQUAL_HASH, FIRST_STRING_WITH_EQUAL_HASH); 41 | 42 | Element anotherElement = children.getOrCreate(parent, SECOND_STRING_WITH_EQUAL_HASH, SECOND_STRING_WITH_EQUAL_HASH); 43 | 44 | assertThat(children.get(SECOND_STRING_WITH_EQUAL_HASH, SECOND_STRING_WITH_EQUAL_HASH)).isSameAs(anotherElement); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/RootTagShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.fest.assertions.api.Assertions.assertThat; 6 | 7 | public class RootTagShould { 8 | 9 | private static final String ROOT = "root"; 10 | private static final String NAMESPACE = "namespace"; 11 | 12 | @Test 13 | public void setRoot_onCreate() throws Exception { 14 | RootTag rootTag = RootTag.create(ROOT, NAMESPACE); 15 | 16 | assertThat(rootTag.getTag()).isEqualTo(ROOT); 17 | } 18 | 19 | @Test 20 | public void setNamespace_onCreate() throws Exception { 21 | RootTag rootTag = RootTag.create(ROOT, NAMESPACE); 22 | 23 | assertThat(rootTag.getNamespace()).isEqualTo(NAMESPACE); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/XmlParserShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.mockito.Mock; 6 | import org.mockito.Mockito; 7 | import org.xml.sax.InputSource; 8 | import org.xml.sax.XMLReader; 9 | 10 | import static org.mockito.Mockito.verify; 11 | import static org.mockito.MockitoAnnotations.initMocks; 12 | 13 | public class XmlParserShould { 14 | 15 | @Mock 16 | XMLReader xmlReader; 17 | 18 | //language=XML 19 | private static final String xml = 20 | "" + 21 | "Blue" + 22 | ""; 23 | 24 | @Before 25 | public void setUp() throws Exception { 26 | initMocks(this); 27 | } 28 | 29 | @Test 30 | public void call_reader_parse_when_parse_is_called() throws Exception { 31 | XmlParser xmlParser = new XmlParser(); 32 | 33 | xmlParser.parse(xml, xmlReader); 34 | 35 | verify(xmlReader).parse(Mockito.any()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/finder/BasicElementFinderShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.finder; 2 | 3 | import com.novoda.sexp.parser.Parser; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | 9 | import static org.fest.assertions.api.Assertions.assertThat; 10 | import static org.mockito.Mockito.stub; 11 | import static org.mockito.Mockito.verify; 12 | import static org.mockito.MockitoAnnotations.initMocks; 13 | 14 | public class BasicElementFinderShould { 15 | 16 | @Mock 17 | private Parser mockParser; 18 | @Mock 19 | private com.novoda.sax.Element mockElement; 20 | 21 | BasicElementFinder elementCreator; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | initMocks(this); 26 | elementCreator = new BasicElementFinder(mockParser); 27 | } 28 | 29 | @Test 30 | public void callParser_whenCreated() { 31 | String tag = "tag"; 32 | stub(mockElement.getChild(tag)).toReturn(mockElement); 33 | 34 | elementCreator.find(mockElement, tag); 35 | 36 | verify(mockParser).parse(mockElement, elementCreator); 37 | } 38 | 39 | @Test 40 | public void get_child_elements_using_namspace_when_a_namespace_is_provided() throws Exception { 41 | String tag = "tag"; 42 | String namespace = "namespace"; 43 | 44 | elementCreator.find(mockElement, namespace, tag); 45 | 46 | verify(mockElement).getChild(namespace, tag); 47 | } 48 | 49 | @Test 50 | public void create_a_result_when_parsing_finished() throws Exception { 51 | String result = "result"; 52 | 53 | elementCreator.onParsed(result); 54 | 55 | assertThat(elementCreator.getResult()).isEqualTo(result); 56 | } 57 | 58 | @Test(expected = BasicElementFinder.ResultNotFoundException.class) 59 | public void throw_exception_when_result_has_not_been_parsed_or_found_and_a_required_result_is_asked_for() throws Exception { 60 | elementCreator.getResultOrThrow(); 61 | } 62 | 63 | @Test 64 | public void allow_null_results_when_get_result_is_used() throws Exception { 65 | Object result = elementCreator.getResult(); 66 | 67 | assertThat(result).isNull(); 68 | } 69 | 70 | @Test 71 | public void pop_a_result_when_parsing_finished() throws Exception { 72 | String result = "result"; 73 | elementCreator.onParsed(result); 74 | 75 | Object actual = elementCreator.popResult(); 76 | 77 | assertThat(actual).isEqualTo(result); 78 | } 79 | 80 | @Test(expected = BasicElementFinder.ResultNotFoundException.class) 81 | public void throw_exception_when_result_has_not_been_parsed_or_found_and_a_result_is_popped() throws Exception { 82 | elementCreator.popResultOrThrow(); 83 | } 84 | 85 | @Test 86 | public void allow_null_results_when_get_result_is_popped() throws Exception { 87 | Object result = elementCreator.popResult(); 88 | 89 | assertThat(result).isNull(); 90 | } 91 | 92 | @Test 93 | public void not_cache_result_after_a_result_is_popped() throws Exception { 94 | elementCreator.onParsed("ignore"); 95 | elementCreator.popResult(); 96 | 97 | Object actual = elementCreator.popResult(); 98 | 99 | assertThat(actual).isNull(); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/finder/ListElementFinderShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.finder; 2 | 3 | import com.novoda.sexp.parser.ParseWatcher; 4 | import com.novoda.sexp.parser.Parser; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.mockito.Mock; 9 | 10 | import static org.mockito.Mockito.stub; 11 | import static org.mockito.Mockito.verify; 12 | import static org.mockito.MockitoAnnotations.initMocks; 13 | 14 | public class ListElementFinderShould { 15 | 16 | @Mock 17 | private Parser mockParser; 18 | @Mock 19 | private ParseWatcher mockWatcher; 20 | 21 | @Mock 22 | private com.novoda.sax.Element mockElement; 23 | ListElementFinder elementCreator; 24 | 25 | @Before 26 | public void setUp() throws Exception { 27 | initMocks(this); 28 | elementCreator = new ListElementFinder(mockParser, mockWatcher); 29 | } 30 | 31 | @Test 32 | public void callParser_whenCreated() { 33 | String tag = "tag"; 34 | stub(mockElement.getChild(tag)).toReturn(mockElement); 35 | 36 | elementCreator.find(mockElement, tag); 37 | 38 | verify(mockParser).parse(mockElement, elementCreator); 39 | } 40 | 41 | @Test 42 | public void callWatcher_afterEachItemForTheListIsParsed() throws Exception { 43 | String result = "result"; 44 | 45 | elementCreator.onParsed(result); 46 | 47 | verify(mockWatcher).onParsed(result); 48 | } 49 | 50 | @Test(expected = UnsupportedOperationException.class) 51 | public void notSupportGetResult_asListCreatorWillCallbackAfterEveryListItemIsParsed() throws Exception { 52 | elementCreator.getResult(); 53 | } 54 | 55 | @Test(expected = UnsupportedOperationException.class) 56 | public void notSupportGetResultOrThrow_asListCreatorWillCallbackAfterEveryListItemIsParsed() throws Exception { 57 | elementCreator.getResultOrThrow(); 58 | } 59 | 60 | @Test(expected = UnsupportedOperationException.class) 61 | public void notSupportPopResult_asListCreatorWillCallbackAfterEveryListItemIsParsed() throws Exception { 62 | elementCreator.popResult(); 63 | } 64 | 65 | @Test(expected = UnsupportedOperationException.class) 66 | public void notSupportPopResultOrThrow_asListCreatorWillCallbackAfterEveryListItemIsParsed() throws Exception { 67 | elementCreator.popResultOrThrow(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/marshaller/BooleanBodyMarshallerShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.fest.assertions.api.Assertions.assertThat; 7 | 8 | public class BooleanBodyMarshallerShould { 9 | 10 | private BooleanBodyMarshaller booleanBodyMarshaller; 11 | 12 | @Before 13 | public void setUp() throws Exception { 14 | booleanBodyMarshaller = new BooleanBodyMarshaller(); 15 | } 16 | 17 | @Test 18 | public void marshal_string_false_to_boolean_false() throws Exception { 19 | String rawFalse = "false"; 20 | 21 | assertThat(booleanBodyMarshaller.marshal(rawFalse)).isFalse(); 22 | } 23 | 24 | @Test 25 | public void marshal_string_true_to_boolean_true() throws Exception { 26 | String rawTrue = "true"; 27 | 28 | assertThat(booleanBodyMarshaller.marshal(rawTrue)).isTrue(); 29 | } 30 | 31 | @Test(expected = IllegalArgumentException.class) 32 | public void throw_exception_when_input_is_invalid() throws Exception { 33 | String invalidInput = "invalid"; 34 | 35 | booleanBodyMarshaller.marshal(invalidInput); 36 | } 37 | 38 | @Test(expected = IllegalArgumentException.class) 39 | public void throw_exception_when_input_is_null() throws Exception { 40 | booleanBodyMarshaller.marshal(null); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/marshaller/DoubleBodyMarshallerShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.fest.assertions.api.Assertions.assertThat; 7 | 8 | public class DoubleBodyMarshallerShould { 9 | 10 | private DoubleBodyMarshaller doubleBodyMarshaller; 11 | 12 | @Before 13 | public void setUp() throws Exception { 14 | doubleBodyMarshaller = new DoubleBodyMarshaller(); 15 | } 16 | 17 | @Test 18 | public void marshal_integer_strings_to_long() throws Exception { 19 | String validInput = "5"; 20 | double expectedOutput = 5; 21 | 22 | assertThat(doubleBodyMarshaller.marshal(validInput)).isEqualTo(expectedOutput); 23 | } 24 | 25 | @Test 26 | public void marshal_long_strings_to_long() throws Exception { 27 | String validInput = String.valueOf(Long.MAX_VALUE); 28 | double expectedOutput = Long.MAX_VALUE; 29 | 30 | assertThat(doubleBodyMarshaller.marshal(validInput)).isEqualTo(expectedOutput); 31 | } 32 | 33 | @Test 34 | public void marshal_float_strings_to_long() throws Exception { 35 | String invalidInput = "0.5f"; 36 | double expectedOutput = 0.5d; 37 | 38 | assertThat(doubleBodyMarshaller.marshal(invalidInput)).isEqualTo(expectedOutput); 39 | } 40 | 41 | @Test 42 | public void marshal_double_strings_to_long() throws Exception { 43 | String validInput = "120.234d"; 44 | double expectedOutput = 120.234d; 45 | 46 | assertThat(doubleBodyMarshaller.marshal(validInput)).isEqualTo(expectedOutput); 47 | assertThat(doubleBodyMarshaller.marshal(validInput)).isEqualTo(expectedOutput); 48 | } 49 | 50 | @Test(expected = IllegalArgumentException.class) 51 | public void throw_exception_when_input_is_invalid() throws Exception { 52 | String invalidInput = "invalid"; 53 | 54 | doubleBodyMarshaller.marshal(invalidInput); 55 | } 56 | 57 | @Test(expected = NullPointerException.class) 58 | public void throw_exception_when_input_is_null() throws Exception { 59 | doubleBodyMarshaller.marshal(null); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/marshaller/DoubleWrapperBodyMarshallerShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.fest.assertions.api.Assertions.assertThat; 6 | 7 | public class DoubleWrapperBodyMarshallerShould { 8 | 9 | @Test 10 | public void marshalStringInput() throws Exception { 11 | DoubleWrapperBodyMarshaller cut = doubleWrapperBodyMarshaller(DoubleWrapperClass.class); 12 | 13 | DoubleWrapperClass output = cut.marshal("42.42d"); 14 | 15 | assertThat(output).isEqualTo(new DoubleWrapperClass(42.42d)); 16 | } 17 | 18 | @Test(expected = RuntimeException.class) 19 | public void onlyWorkForClassesWithADoubleConstructorArgString() throws Exception { 20 | DoubleWrapperBodyMarshaller cut = doubleWrapperBodyMarshaller(StringWrapperClass.class); 21 | 22 | cut.marshal(""); 23 | } 24 | 25 | @Test(expected = RuntimeException.class) 26 | public void onlyWorkForClassesWithADoubleConstructorArgInteger() throws Exception { 27 | DoubleWrapperBodyMarshaller cut = doubleWrapperBodyMarshaller(IntegerWrapperClass.class); 28 | 29 | cut.marshal(""); 30 | } 31 | 32 | @Test(expected = RuntimeException.class) 33 | public void onlyWorkForClassesWithADoubleConstructorArgLong() throws Exception { 34 | DoubleWrapperBodyMarshaller cut = doubleWrapperBodyMarshaller(LongWrapperClass.class); 35 | 36 | cut.marshal(""); 37 | } 38 | 39 | @Test(expected = RuntimeException.class) 40 | public void onlyWorkForClassesWithADoubleConstructorArgFloat() throws Exception { 41 | DoubleWrapperBodyMarshaller cut = doubleWrapperBodyMarshaller(FloatWrapperClass.class); 42 | 43 | cut.marshal(""); 44 | } 45 | 46 | 47 | @Test(expected = RuntimeException.class) 48 | public void onlyWorkForClassesWithASingleConstructorArg() throws Exception { 49 | DoubleWrapperBodyMarshaller cut = doubleWrapperBodyMarshaller(TwoArgWrapperClass.class); 50 | 51 | cut.marshal(""); 52 | } 53 | 54 | @Test(expected = RuntimeException.class) 55 | public void failForClassesWithNoArgConstructor() throws Exception { 56 | DoubleWrapperBodyMarshaller cut = doubleWrapperBodyMarshaller(NoArgWrapperClass.class); 57 | 58 | cut.marshal(""); 59 | } 60 | 61 | @Test(expected = RuntimeException.class) 62 | public void failForClassesWithPrivateConstructor() throws Exception { 63 | DoubleWrapperBodyMarshaller cut = doubleWrapperBodyMarshaller( 64 | PrivateConstructorWrapperClass.class 65 | ); 66 | 67 | cut.marshal("1"); 68 | } 69 | 70 | private DoubleWrapperBodyMarshaller doubleWrapperBodyMarshaller(Class clazz) { 71 | return new DoubleWrapperBodyMarshaller(clazz); 72 | } 73 | 74 | private static class DoubleWrapperClass { 75 | private final double toWrap; 76 | 77 | public DoubleWrapperClass(double toWrap) { 78 | this.toWrap = toWrap; 79 | } 80 | 81 | @Override 82 | public boolean equals(Object o) { 83 | if (this == o) return true; 84 | if (o == null || getClass() != o.getClass()) return false; 85 | 86 | DoubleWrapperClass that = (DoubleWrapperClass) o; 87 | 88 | return Double.compare(that.toWrap, toWrap) == 0; 89 | 90 | } 91 | 92 | @Override 93 | public int hashCode() { 94 | long temp = Double.doubleToLongBits(toWrap); 95 | return (int) (temp ^ (temp >>> 32)); 96 | } 97 | } 98 | 99 | private static class IntegerWrapperClass { 100 | private IntegerWrapperClass(int one) { 101 | } 102 | } 103 | 104 | private static class LongWrapperClass { 105 | private LongWrapperClass(long one) { 106 | } 107 | } 108 | 109 | private static class FloatWrapperClass { 110 | private FloatWrapperClass(float one) { 111 | } 112 | } 113 | 114 | private static class StringWrapperClass { 115 | private StringWrapperClass(String one) { 116 | } 117 | } 118 | 119 | private static class TwoArgWrapperClass { 120 | private TwoArgWrapperClass(double one, double two) { 121 | } 122 | } 123 | 124 | private static class NoArgWrapperClass { 125 | public NoArgWrapperClass() { 126 | } 127 | } 128 | 129 | private static class PrivateConstructorWrapperClass { 130 | private PrivateConstructorWrapperClass(double toWrap) { 131 | } 132 | } 133 | 134 | } 135 | 136 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/marshaller/IntegerBodyMarshallerShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.fest.assertions.api.Assertions.assertThat; 7 | 8 | public class IntegerBodyMarshallerShould { 9 | 10 | private IntegerBodyMarshaller integerBodyMarshaller; 11 | 12 | @Before 13 | public void setUp() throws Exception { 14 | integerBodyMarshaller = new IntegerBodyMarshaller(); 15 | } 16 | 17 | @Test 18 | public void marshal_strings_to_integers() throws Exception { 19 | String validInput = "5"; 20 | int expectedOutput = 5; 21 | 22 | assertThat(integerBodyMarshaller.marshal(validInput)).isEqualTo(expectedOutput); 23 | } 24 | 25 | @Test(expected = IllegalArgumentException.class) 26 | public void throw_exception_when_input_is_invalid() throws Exception { 27 | String invalidInput = "invalid"; 28 | 29 | integerBodyMarshaller.marshal(invalidInput); 30 | } 31 | 32 | @Test(expected = IllegalArgumentException.class) 33 | public void throw_exception_when_input_is_float() throws Exception { 34 | String invalidInput = "0.5f"; 35 | 36 | integerBodyMarshaller.marshal(invalidInput); 37 | } 38 | 39 | @Test(expected = java.lang.NumberFormatException.class) 40 | public void throw_exception_when_input_is_double() throws Exception { 41 | String invalidInput = "3.5d"; 42 | 43 | integerBodyMarshaller.marshal(invalidInput); 44 | } 45 | 46 | @Test(expected = IllegalArgumentException.class) 47 | public void throw_exception_when_input_is_null() throws Exception { 48 | integerBodyMarshaller.marshal(null); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/marshaller/IntegerWrapperBodyMarshallerShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.fest.assertions.api.Assertions.assertThat; 6 | 7 | public class IntegerWrapperBodyMarshallerShould { 8 | 9 | @Test 10 | public void marshalStringInput() throws Exception { 11 | IntegerWrapperBodyMarshaller cut = integerWrapperBodyMarshaller(IntegerWrapperClass.class); 12 | 13 | IntegerWrapperClass output = cut.marshal("1"); 14 | 15 | assertThat(output).isEqualTo(new IntegerWrapperClass(1)); 16 | } 17 | 18 | @Test(expected = RuntimeException.class) 19 | public void onlyWorkForClassesWithAnIntegerConstructorArg() throws Exception { 20 | IntegerWrapperBodyMarshaller cut = integerWrapperBodyMarshaller(StringWrapperClass.class); 21 | 22 | cut.marshal(""); 23 | } 24 | 25 | @Test(expected = RuntimeException.class) 26 | public void onlyWorkForClassesWithASingleConstructorArg() throws Exception { 27 | IntegerWrapperBodyMarshaller cut = integerWrapperBodyMarshaller(TwoArgWrapperClass.class); 28 | 29 | cut.marshal(""); 30 | } 31 | 32 | @Test(expected = RuntimeException.class) 33 | public void failForClassesWithNoArgConstructor() throws Exception { 34 | IntegerWrapperBodyMarshaller cut = integerWrapperBodyMarshaller(NoArgWrapperClass.class); 35 | 36 | cut.marshal(""); 37 | } 38 | 39 | @Test(expected = RuntimeException.class) 40 | public void failForClassesWithPrivateConstructor() throws Exception { 41 | IntegerWrapperBodyMarshaller cut = integerWrapperBodyMarshaller( 42 | PrivateConstructorWrapperClass.class 43 | ); 44 | 45 | cut.marshal("1"); 46 | } 47 | 48 | private IntegerWrapperBodyMarshaller integerWrapperBodyMarshaller(Class clazz) { 49 | return new IntegerWrapperBodyMarshaller(clazz); 50 | } 51 | 52 | private static class IntegerWrapperClass { 53 | private final int toWrap; 54 | 55 | public IntegerWrapperClass(int toWrap) { 56 | this.toWrap = toWrap; 57 | } 58 | 59 | @Override 60 | public boolean equals(Object o) { 61 | if (this == o) { 62 | return true; 63 | } 64 | if (o == null || getClass() != o.getClass()) { 65 | return false; 66 | } 67 | 68 | IntegerWrapperClass that = (IntegerWrapperClass) o; 69 | 70 | return toWrap == that.toWrap; 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return toWrap; 76 | } 77 | } 78 | 79 | private static class StringWrapperClass { 80 | private StringWrapperClass(String one) { 81 | } 82 | } 83 | 84 | private static class TwoArgWrapperClass { 85 | private TwoArgWrapperClass(int one, int two) { 86 | } 87 | } 88 | 89 | private static class NoArgWrapperClass { 90 | public NoArgWrapperClass() { 91 | } 92 | } 93 | 94 | private static class PrivateConstructorWrapperClass { 95 | private PrivateConstructorWrapperClass(int toWrap) { 96 | } 97 | } 98 | 99 | } 100 | 101 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/marshaller/LongBodyMarshallerShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.fest.assertions.api.Assertions.assertThat; 7 | 8 | public class LongBodyMarshallerShould { 9 | 10 | private LongBodyMarshaller longBodyMarshaller; 11 | 12 | @Before 13 | public void setUp() throws Exception { 14 | longBodyMarshaller = new LongBodyMarshaller(); 15 | } 16 | 17 | @Test 18 | public void marshal_integer_strings_to_long() throws Exception { 19 | String validInput = "5"; 20 | long expectedOutput = 5; 21 | 22 | assertThat(longBodyMarshaller.marshal(validInput)).isEqualTo(expectedOutput); 23 | } 24 | 25 | @Test 26 | public void marshal_long_strings_to_long() throws Exception { 27 | String validInput = String.valueOf(Long.MAX_VALUE); 28 | long expectedOutput = Long.MAX_VALUE; 29 | 30 | assertThat(longBodyMarshaller.marshal(validInput)).isEqualTo(expectedOutput); 31 | } 32 | 33 | @Test(expected = IllegalArgumentException.class) 34 | public void throw_exception_when_input_is_invalid() throws Exception { 35 | String invalidInput = "invalid"; 36 | 37 | longBodyMarshaller.marshal(invalidInput); 38 | } 39 | 40 | @Test(expected = IllegalArgumentException.class) 41 | public void throw_exception_when_input_is_float() throws Exception { 42 | String invalidInput = "0.5f"; 43 | 44 | longBodyMarshaller.marshal(invalidInput); 45 | } 46 | 47 | @Test(expected = NumberFormatException.class) 48 | public void throw_exception_when_input_is_double() throws Exception { 49 | String invalidInput = "3.5d"; 50 | 51 | longBodyMarshaller.marshal(invalidInput); 52 | } 53 | 54 | @Test(expected = IllegalArgumentException.class) 55 | public void throw_exception_when_input_is_null() throws Exception { 56 | longBodyMarshaller.marshal(null); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/marshaller/LongWrapperBodyMarshallerShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.fest.assertions.api.Assertions.assertThat; 6 | 7 | public class LongWrapperBodyMarshallerShould { 8 | 9 | @Test 10 | public void marshalStringInput() throws Exception { 11 | LongWrapperBodyMarshaller cut = longWrapperBodyMarshaller(LongWrapperClass.class); 12 | 13 | LongWrapperClass output = cut.marshal(String.valueOf(Long.MAX_VALUE)); 14 | 15 | assertThat(output).isEqualTo(new LongWrapperClass(Long.MAX_VALUE)); 16 | } 17 | 18 | @Test(expected = RuntimeException.class) 19 | public void onlyWorkForClassesWithALongConstructorArgString() throws Exception { 20 | LongWrapperBodyMarshaller cut = longWrapperBodyMarshaller(StringWrapperClass.class); 21 | 22 | cut.marshal(""); 23 | } 24 | 25 | @Test(expected = RuntimeException.class) 26 | public void onlyWorkForClassesWithALongConstructorArgInteger() throws Exception { 27 | LongWrapperBodyMarshaller cut = longWrapperBodyMarshaller(IntegerWrapperClass.class); 28 | 29 | cut.marshal(""); 30 | } 31 | 32 | @Test(expected = RuntimeException.class) 33 | public void onlyWorkForClassesWithASingleConstructorArg() throws Exception { 34 | LongWrapperBodyMarshaller cut = longWrapperBodyMarshaller(TwoArgWrapperClass.class); 35 | 36 | cut.marshal(""); 37 | } 38 | 39 | @Test(expected = RuntimeException.class) 40 | public void failForClassesWithNoArgConstructor() throws Exception { 41 | LongWrapperBodyMarshaller cut = longWrapperBodyMarshaller(NoArgWrapperClass.class); 42 | 43 | cut.marshal(""); 44 | } 45 | 46 | @Test(expected = RuntimeException.class) 47 | public void failForClassesWithPrivateConstructor() throws Exception { 48 | LongWrapperBodyMarshaller cut = longWrapperBodyMarshaller( 49 | PrivateConstructorWrapperClass.class 50 | ); 51 | 52 | cut.marshal("1"); 53 | } 54 | 55 | private LongWrapperBodyMarshaller longWrapperBodyMarshaller(Class clazz) { 56 | return new LongWrapperBodyMarshaller(clazz); 57 | } 58 | 59 | private static class LongWrapperClass { 60 | private final long toWrap; 61 | 62 | public LongWrapperClass(long toWrap) { 63 | this.toWrap = toWrap; 64 | } 65 | 66 | @Override 67 | public boolean equals(Object o) { 68 | if (this == o) { 69 | return true; 70 | } 71 | if (o == null || getClass() != o.getClass()) { 72 | return false; 73 | } 74 | 75 | LongWrapperClass that = (LongWrapperClass) o; 76 | 77 | return toWrap == that.toWrap; 78 | } 79 | 80 | @Override 81 | public int hashCode() { 82 | return (int) (toWrap ^ (toWrap >>> 32)); 83 | } 84 | } 85 | 86 | private static class IntegerWrapperClass { 87 | private IntegerWrapperClass(int one) { 88 | } 89 | } 90 | 91 | private static class StringWrapperClass { 92 | private StringWrapperClass(String one) { 93 | } 94 | } 95 | 96 | private static class TwoArgWrapperClass { 97 | private TwoArgWrapperClass(long one, long two) { 98 | } 99 | } 100 | 101 | private static class NoArgWrapperClass { 102 | public NoArgWrapperClass() { 103 | } 104 | } 105 | 106 | private static class PrivateConstructorWrapperClass { 107 | private PrivateConstructorWrapperClass(long toWrap) { 108 | } 109 | } 110 | 111 | } 112 | 113 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/marshaller/StringBodyMarshallerShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.fest.assertions.api.Assertions.assertThat; 7 | 8 | public class StringBodyMarshallerShould { 9 | 10 | StringBodyMarshaller stringBodyMarshaller; 11 | 12 | @Before 13 | public void setUp() { 14 | stringBodyMarshaller = new StringBodyMarshaller(); 15 | } 16 | 17 | @Test 18 | public void marshalTheString() throws Exception { 19 | String theInput = "theInput"; 20 | 21 | assertThat(stringBodyMarshaller.marshal(theInput)).isEqualTo(theInput); 22 | } 23 | 24 | @Test 25 | public void marshalNull() throws Exception { 26 | assertThat(stringBodyMarshaller.marshal(null)).isEqualTo(null); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/marshaller/StringWrapperBodyMarshallerShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.marshaller; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.fest.assertions.api.Assertions.assertThat; 6 | 7 | public class StringWrapperBodyMarshallerShould { 8 | 9 | @Test 10 | public void marshalStringInput() throws Exception { 11 | StringWrapperBodyMarshaller cut = stringWrapperBodyMarshaller(StringWrapperClass.class); 12 | 13 | String testInput = "testInput"; 14 | StringWrapperClass output = cut.marshal(testInput); 15 | 16 | assertThat(output).isEqualTo(new StringWrapperClass(testInput)); 17 | } 18 | 19 | @Test(expected = RuntimeException.class) 20 | public void onlyWorkForClassesWithASingleStringConstructorArg() throws Exception { 21 | StringWrapperBodyMarshaller cut = stringWrapperBodyMarshaller(IntegerWrapperClass.class); 22 | 23 | cut.marshal(""); 24 | } 25 | 26 | @Test(expected = RuntimeException.class) 27 | public void onlyWorkForClassesWithASingleConstructorArg() throws Exception { 28 | StringWrapperBodyMarshaller cut = stringWrapperBodyMarshaller(TwoArgWrapperClass.class); 29 | 30 | cut.marshal(""); 31 | } 32 | 33 | private StringWrapperBodyMarshaller stringWrapperBodyMarshaller(Class clazz) { 34 | return new StringWrapperBodyMarshaller(clazz); 35 | } 36 | 37 | private static class StringWrapperClass { 38 | private final String toWrap; 39 | 40 | public StringWrapperClass(String toWrap) { 41 | this.toWrap = toWrap; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) { 47 | return true; 48 | } 49 | if (o == null || getClass() != o.getClass()) { 50 | return false; 51 | } 52 | 53 | StringWrapperClass that = (StringWrapperClass) o; 54 | 55 | return !(toWrap != null ? !toWrap.equals(that.toWrap) : that.toWrap != null); 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | return toWrap != null ? toWrap.hashCode() : 0; 61 | } 62 | } 63 | 64 | private static class IntegerWrapperClass { 65 | private IntegerWrapperClass(Integer one) { 66 | } 67 | } 68 | 69 | private static class TwoArgWrapperClass { 70 | private TwoArgWrapperClass(String one, String two) { 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/parser/BasicAttributeParserShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sexp.marshaller.AttributeMarshaller; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.mockito.Mock; 9 | import org.xml.sax.Attributes; 10 | 11 | import static org.mockito.Mockito.*; 12 | import static org.mockito.MockitoAnnotations.initMocks; 13 | 14 | public class BasicAttributeParserShould { 15 | 16 | private static final String ATTR_TAG = "attrTag"; 17 | private static final String FOUND_ATTRIBUTE = "foundAttribute"; 18 | 19 | @Mock 20 | private AttributeMarshaller mockMarshaller; 21 | @Mock 22 | private ParseWatcher mockParseWatcher; 23 | private BasicAttributeParser cut; 24 | 25 | @Before 26 | public void setUp() { 27 | initMocks(this); 28 | cut = new BasicAttributeParser(mockMarshaller, ATTR_TAG); 29 | } 30 | 31 | @Test 32 | public void setListenerToObtainAttributes() throws Exception { 33 | Element element = mock(Element.class); 34 | cut.parse(element, mockParseWatcher); 35 | 36 | verify(element).setStartElementListener(cut); 37 | } 38 | 39 | @Test 40 | public void informListener_whenAttributesParsed() throws Exception { 41 | Element element = mock(Element.class); 42 | cut.parse(element, mockParseWatcher); 43 | Attributes attributes = mock(Attributes.class); 44 | stub(attributes.getValue(ATTR_TAG)).toReturn(FOUND_ATTRIBUTE); 45 | Object outputObject = new Object(); 46 | stub(mockMarshaller.marshal(FOUND_ATTRIBUTE)).toReturn(outputObject); 47 | cut.start(attributes); 48 | 49 | verify(mockParseWatcher).onParsed(outputObject); 50 | } 51 | 52 | @Test 53 | public void delegateToMarshaller_toConvertToObjectWanted_whenAttributesParsed() throws Exception { 54 | Element element = mock(Element.class); 55 | cut.parse(element, mockParseWatcher); 56 | Attributes attributes = mock(Attributes.class); 57 | stub(attributes.getValue(ATTR_TAG)).toReturn(FOUND_ATTRIBUTE); 58 | cut.start(attributes); 59 | 60 | verify(mockMarshaller).marshal(FOUND_ATTRIBUTE); 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/parser/BasicParserShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | import com.novoda.sax.EndTextElementListener; 4 | import com.novoda.sexp.marshaller.BodyMarshaller; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.mockito.Mock; 9 | 10 | import static com.novoda.sexp.parser.ParserHelper.mockParse; 11 | import static org.mockito.Matchers.any; 12 | import static org.mockito.Mockito.verify; 13 | import static org.mockito.MockitoAnnotations.initMocks; 14 | 15 | public class BasicParserShould { 16 | 17 | @Mock 18 | private BodyMarshaller mockMarshaller; 19 | @Mock 20 | private com.novoda.sax.Element mockElement; 21 | @Mock 22 | private ParseWatcher mockListener; 23 | 24 | @Before 25 | public void setup() { 26 | initMocks(this); 27 | } 28 | 29 | @Test 30 | public void listenForBodyText_whenParsing() throws Exception { 31 | BasicParser basicParser = new BasicParser(mockMarshaller); 32 | 33 | basicParser.parse(mockElement, mockListener); 34 | 35 | verify(mockElement).setEndTextElementListener(any(EndTextElementListener.class)); 36 | } 37 | 38 | @Test 39 | public void informListener_whenElementIsParsed() throws Exception { 40 | BasicParser basicParser = new BasicParser(mockMarshaller); 41 | 42 | mockParse(basicParser, mockListener); 43 | 44 | verify(mockListener).onParsed(any()); 45 | } 46 | 47 | @Test 48 | public void delegateToMarshaller_whenElementIsParsed() throws Exception { 49 | BasicParser basicParser = new BasicParser(mockMarshaller); 50 | 51 | mockParse(basicParser, mockListener); 52 | 53 | verify(mockMarshaller).marshal(any(String.class)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/parser/ListParserShould.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | import com.novoda.sax.Element; 4 | import com.novoda.sexp.finder.ElementFinder; 5 | import com.novoda.sexp.finder.ElementFinderFactory; 6 | import com.novoda.sexp.marshaller.BodyMarshaller; 7 | 8 | import java.util.List; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.mockito.Mock; 13 | import org.mockito.Mockito; 14 | 15 | import static org.mockito.Mockito.*; 16 | import static org.mockito.MockitoAnnotations.initMocks; 17 | 18 | public class ListParserShould { 19 | 20 | @Mock 21 | private ElementFinderFactory mockFactory; 22 | @Mock 23 | private BodyMarshaller mockMarshaller; 24 | @Mock 25 | private ParseWatcher> mockListener; 26 | @Mock 27 | private ElementFinder mockListCreator; 28 | private ListParser listParser; 29 | 30 | @Before 31 | public void setUp() { 32 | initMocks(this); 33 | stub(mockFactory.getListElementFinder(Mockito.>any(), Mockito.>any())).toReturn(mockListCreator); 34 | 35 | listParser = new ListParser("individualItemTag", mockFactory, mockMarshaller); 36 | } 37 | 38 | @Test 39 | public void delegateToElementCreatorToParseEachItem_whenParsing() throws Exception { 40 | Element element = mock(Element.class); 41 | listParser.parse(element, mockListener); 42 | 43 | verify(mockListCreator).find(element, "individualItemTag"); 44 | } 45 | 46 | @Test 47 | public void informListener_whenTheWholeListOfElementsHasBeenParsed() { 48 | Element element = mock(Element.class); 49 | listParser.parse(element, mockListener); 50 | listParser.end(); 51 | 52 | verify(mockListener).onParsed(Mockito.>any()); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /sexp/src/test/java/com/novoda/sexp/parser/ParserHelper.java: -------------------------------------------------------------------------------- 1 | package com.novoda.sexp.parser; 2 | 3 | import com.novoda.sax.RootElement; 4 | 5 | import org.xml.sax.SAXException; 6 | 7 | public class ParserHelper { 8 | public static void mockParse(Parser parser, ParseWatcher mockListener) throws SAXException { 9 | mockParse(parser, mockListener, ""); 10 | } 11 | 12 | public static void mockParse(Parser parser, ParseWatcher mockListener, String tagBody) throws SAXException { 13 | RootElement element = new RootElement(""); 14 | parser.parse(element, mockListener); 15 | element.getContentHandler().startElement("", "", "", null); 16 | element.getContentHandler().characters(tagBody.toCharArray(), 0, tagBody.length()); 17 | element.getContentHandler().endElement("", "", ""); 18 | } 19 | } 20 | --------------------------------------------------------------------------------