diff options
| author | Michael Williamson <mike@zwobble.org> | 2016-02-24 08:16:16 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2016-02-24 08:16:16 +0000 |
| commit | 3e2786a004eddf968a5fa7d5a92dff5b1b5f7734 (patch) | |
| tree | 7fa1bcc1e484cd0ef782f64baf504ee83333d397 /src/main/java/org/zwobble/mammoth/internal/xml | |
| parent | 0dc3a12affc2e46c1a0df2899cf51a1ae3fdbd61 (diff) | |
Create internal package
Diffstat (limited to 'src/main/java/org/zwobble/mammoth/internal/xml')
13 files changed, 421 insertions, 0 deletions
diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/NullXmlElement.java b/src/main/java/org/zwobble/mammoth/internal/xml/NullXmlElement.java new file mode 100644 index 0000000..ecc7971 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/NullXmlElement.java @@ -0,0 +1,25 @@ +package org.zwobble.mammoth.internal.xml; + +import java.util.Optional; + +public class NullXmlElement implements XmlElementLike { + public static final XmlElementLike INSTANCE = new NullXmlElement(); + + private NullXmlElement() { + } + + @Override + public boolean hasChild(String name) { + return false; + } + + @Override + public XmlElementLike findChildOrEmpty(String name) { + return this; + } + + @Override + public Optional<String> getAttributeOrNone(String name) { + return Optional.empty(); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/XmlElement.java b/src/main/java/org/zwobble/mammoth/internal/xml/XmlElement.java new file mode 100644 index 0000000..157d638 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/XmlElement.java @@ -0,0 +1,89 @@ +package org.zwobble.mammoth.internal.xml; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.google.common.collect.Iterables.*; + +public class XmlElement implements XmlNode, XmlElementLike { + private final String name; + private final Map<String, String> attributes; + private final List<XmlNode> children; + + public XmlElement(String name, Map<String, String> attributes, List<XmlNode> children) { + this.name = name; + this.attributes = attributes; + this.children = children; + } + + public String getName() { + return name; + } + + public Map<String, String> getAttributes() { + return attributes; + } + + public String getAttribute(String name) { + // TODO: throw more informative error message + return getAttributeOrNone(name).get(); + } + + @Override + public Optional<String> getAttributeOrNone(String name) { + return Optional.ofNullable(attributes.get(name)); + } + + public List<XmlNode> getChildren() { + return children; + } + + @Override + public String innerText() { + return Joiner.on("").join(transform(children, XmlNode::innerText)); + } + + @Override + public <T> T accept(XmlNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + public Iterable<XmlNode> children() { + return children; + } + + @Override + public String toString() { + return "XmlElement(name=" + name + ", attributes=" + attributes + + ", children=" + children + ")"; + } + + public XmlElementList findChildren(String name) { + Iterable<XmlElement> filtered = findChildrenIterable(name); + return new XmlElementList(ImmutableList.copyOf(filtered)); + } + + public XmlElement findChild(String name) { + return findChildrenIterable(name).iterator().next(); + } + + @Override + public boolean hasChild(String name) { + return findChildrenIterable(name).iterator().hasNext(); + } + + @Override + public XmlElementLike findChildOrEmpty(String name) { + return getFirst(findChildrenIterable(name), NullXmlElement.INSTANCE); + } + + private Iterable<XmlElement> findChildrenIterable(String name) { + return filter( + filter(children, XmlElement.class), + child -> child.getName().equals(name)); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/XmlElementLike.java b/src/main/java/org/zwobble/mammoth/internal/xml/XmlElementLike.java new file mode 100644 index 0000000..bdb3cc8 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/XmlElementLike.java @@ -0,0 +1,9 @@ +package org.zwobble.mammoth.internal.xml; + +import java.util.Optional; + +public interface XmlElementLike { + boolean hasChild(String name); + XmlElementLike findChildOrEmpty(String name); + Optional<String> getAttributeOrNone(String name); +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/XmlElementList.java b/src/main/java/org/zwobble/mammoth/internal/xml/XmlElementList.java new file mode 100644 index 0000000..3facde4 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/XmlElementList.java @@ -0,0 +1,28 @@ +package org.zwobble.mammoth.internal.xml; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +import java.util.Iterator; +import java.util.List; + +public class XmlElementList implements Iterable<XmlElement> { + private final List<XmlElement> elements; + + public XmlElementList(List<XmlElement> elements) { + this.elements = elements; + } + + @Override + public Iterator<XmlElement> iterator() { + return elements.iterator(); + } + + public XmlElementList findChildren(String name) { + Iterable<XmlElement> children = Iterables.concat( + Iterables.transform( + elements, + element -> element.findChildren(name))); + return new XmlElementList(ImmutableList.copyOf(children)); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/XmlNode.java b/src/main/java/org/zwobble/mammoth/internal/xml/XmlNode.java new file mode 100644 index 0000000..6f6e7fa --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/XmlNode.java @@ -0,0 +1,6 @@ +package org.zwobble.mammoth.internal.xml; + +public interface XmlNode { + String innerText(); + <T> T accept(XmlNodeVisitor<T> visitor); +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/XmlNodeVisitor.java b/src/main/java/org/zwobble/mammoth/internal/xml/XmlNodeVisitor.java new file mode 100644 index 0000000..5bbef54 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/XmlNodeVisitor.java @@ -0,0 +1,6 @@ +package org.zwobble.mammoth.internal.xml; + +public interface XmlNodeVisitor<T> { + T visit(XmlElement element); + T visit(XmlTextNode textNode); +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/XmlNodes.java b/src/main/java/org/zwobble/mammoth/internal/xml/XmlNodes.java new file mode 100644 index 0000000..3b4bab7 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/XmlNodes.java @@ -0,0 +1,29 @@ +package org.zwobble.mammoth.internal.xml; + +import java.util.List; +import java.util.Map; + +import static org.zwobble.mammoth.internal.util.MammothLists.list; +import static org.zwobble.mammoth.internal.util.MammothMaps.map; + +public class XmlNodes { + public static XmlElement element(String name) { + return element(name, list()); + } + + public static XmlElement element(String name, Map<String, String> attributes) { + return element(name, attributes, list()); + } + + public static XmlElement element(String name, List<XmlNode> children) { + return element(name, map(), children); + } + + public static XmlElement element(String name, Map<String, String> attributes, List<XmlNode> children) { + return new XmlElement(name, attributes, children); + } + + public static XmlTextNode text(String value) { + return new XmlTextNode(value); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/XmlTextNode.java b/src/main/java/org/zwobble/mammoth/internal/xml/XmlTextNode.java new file mode 100644 index 0000000..02380ee --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/XmlTextNode.java @@ -0,0 +1,28 @@ +package org.zwobble.mammoth.internal.xml; + +public class XmlTextNode implements XmlNode { + private final String value; + + public XmlTextNode(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String innerText() { + return value; + } + + @Override + public <T> T accept(XmlNodeVisitor<T> visitor) { + return visitor.visit(this); + } + + @Override + public String toString() { + return "XmlTextNode(value=" + value + ")"; + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/parsing/ElementName.java b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/ElementName.java new file mode 100644 index 0000000..a97a683 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/ElementName.java @@ -0,0 +1,19 @@ +package org.zwobble.mammoth.internal.xml.parsing; + +class ElementName { + private final String uri; + private final String localName; + + ElementName(String uri, String localName) { + this.uri = uri; + this.localName = localName; + } + + String getUri() { + return uri; + } + + String getLocalName() { + return localName; + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/parsing/SimpleSax.java b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/SimpleSax.java new file mode 100644 index 0000000..047e02d --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/SimpleSax.java @@ -0,0 +1,64 @@ +package org.zwobble.mammoth.internal.xml.parsing; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.util.Map; +import java.util.stream.IntStream; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +import static java.util.stream.Collectors.toMap; + +class SimpleSax { + static void parseStream(InputStream input, SimpleSaxHandler handler) { + parseInputSource(new InputSource(input), handler); + } + + static void parseString(String value, SimpleSaxHandler handler) { + parseInputSource(new InputSource(new StringReader(value)), handler); + } + + private static void parseInputSource(InputSource inputSource, SimpleSaxHandler handler) { + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parserFactory.setNamespaceAware(true); + try { + SAXParser saxParser = parserFactory.newSAXParser(); + XMLReader xmlReader = saxParser.getXMLReader(); + xmlReader.setContentHandler(new DefaultHandler() { + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + ElementName name = new ElementName(uri, localName); + Map<ElementName, String> attributesMap = IntStream.range(0, attributes.getLength()) + .boxed() + .collect(toMap( + index -> new ElementName(attributes.getURI(index), attributes.getLocalName(index)), + attributes::getValue + )); + handler.startElement(name, attributesMap); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + handler.endElement(); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + handler.characters(new String(ch, start, length)); + } + }); + xmlReader.parse(inputSource); + } catch (ParserConfigurationException | SAXException | IOException exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/parsing/SimpleSaxHandler.java b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/SimpleSaxHandler.java new file mode 100644 index 0000000..8358346 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/SimpleSaxHandler.java @@ -0,0 +1,9 @@ +package org.zwobble.mammoth.internal.xml.parsing; + +import java.util.Map; + +interface SimpleSaxHandler { + void startElement(ElementName name, Map<ElementName, String> attributes); + void endElement(); + void characters(String string); +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/parsing/XmlElementBuilder.java b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/XmlElementBuilder.java new file mode 100644 index 0000000..231299e --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/XmlElementBuilder.java @@ -0,0 +1,28 @@ +package org.zwobble.mammoth.internal.xml.parsing; + +import org.zwobble.mammoth.internal.xml.XmlElement; +import org.zwobble.mammoth.internal.xml.XmlNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +class XmlElementBuilder { + private final String name; + private final Map<String, String> attributes; + private final List<XmlNode> children; + + XmlElementBuilder(String name, Map<String, String> attributes) { + this.name = name; + this.attributes = attributes; + this.children = new ArrayList<>(); + } + + XmlElement build() { + return new XmlElement(name, attributes, children); + } + + void addChild(XmlNode node) { + children.add(node); + } +} diff --git a/src/main/java/org/zwobble/mammoth/internal/xml/parsing/XmlParser.java b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/XmlParser.java new file mode 100644 index 0000000..566dbd6 --- /dev/null +++ b/src/main/java/org/zwobble/mammoth/internal/xml/parsing/XmlParser.java @@ -0,0 +1,81 @@ +package org.zwobble.mammoth.internal.xml.parsing; + +import java.io.InputStream; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Map; + +import com.google.common.base.Strings; +import com.google.common.collect.BiMap; +import org.zwobble.mammoth.internal.xml.XmlElement; +import org.zwobble.mammoth.internal.xml.XmlTextNode; + +import static java.util.stream.Collectors.toMap; + +public class XmlParser { + private final BiMap<String, String> namespaces; + + public XmlParser(BiMap<String, String> namespaces) { + this.namespaces = namespaces; + } + + public XmlElement parseStream(InputStream inputStream) { + NodeGenerator nodeGenerator = new NodeGenerator(); + SimpleSax.parseStream(inputStream, nodeGenerator); + return nodeGenerator.getRoot(); + } + + public XmlElement parseString(String value) { + NodeGenerator nodeGenerator = new NodeGenerator(); + SimpleSax.parseString(value, nodeGenerator); + return nodeGenerator.getRoot(); + } + + private class NodeGenerator implements SimpleSaxHandler { + private final Deque<XmlElementBuilder> elementStack; + + public NodeGenerator() { + elementStack = new ArrayDeque<>(); + } + + public XmlElement getRoot() { + return elementStack.getFirst().build(); + } + + @Override + public void startElement(ElementName name, Map<ElementName, String> attributes) { + Map<String, String> simpleAttributes = attributes + .entrySet() + .stream() + .collect(toMap( + entry -> readName(entry.getKey()), + entry -> entry.getValue())); + XmlElementBuilder element = new XmlElementBuilder(readName(name), simpleAttributes); + elementStack.add(element); + } + + private String readName(ElementName name) { + if (Strings.isNullOrEmpty(name.getUri())) { + return name.getLocalName(); + } else if (namespaces.containsValue(name.getUri())) { + return namespaces.inverse().get(name.getUri()) + ":" + name.getLocalName(); + } else { + return "{" + name.getUri() + "}" + name.getLocalName(); + } + + } + + @Override + public void endElement() { + if (elementStack.size() > 1) { + XmlElementBuilder element = elementStack.removeLast(); + elementStack.getLast().addChild(element.build()); + } + } + + @Override + public void characters(String string) { + elementStack.getLast().addChild(new XmlTextNode(string)); + } + } +} |
