From faf7cf2a83dba5b1d4b1590a80383229f3f0f3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Wed, 1 Dec 2021 17:35:24 +0100 Subject: [PATCH] working on XML builder --- .../xbib/datastructures/json/tiny/Json.java | 28 +- .../datastructures/json/tiny/JsonBuilder.java | 4 +- .../json/tiny/JsonGenerator.java | 2 +- .../json/tiny/test/JsonBuilderTest.java | 44 +- datastructures-xml/build.gradle | 3 + .../src/main/java/module-info.java | 6 + .../xbib/datastructures/xml/XMLBuilder.java | 457 ++++++++++-------- .../xbib/datastructures/xml/XMLElement.java | 52 -- .../xbib/datastructures/xml/XMLException.java | 2 +- .../xml/test/XMLBuilderTest.java | 110 +++++ 10 files changed, 421 insertions(+), 287 deletions(-) create mode 100644 datastructures-xml/build.gradle create mode 100644 datastructures-xml/src/main/java/module-info.java delete mode 100644 datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLElement.java create mode 100644 datastructures-xml/src/test/java/org/xbib/datatstructures/xml/test/XMLBuilderTest.java diff --git a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/Json.java b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/Json.java index 76eb654..067bb0f 100644 --- a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/Json.java +++ b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/Json.java @@ -82,19 +82,12 @@ public class Json implements DataStructure { @Override public Builder createBuilder() { - return new JsonBuilder(new StringWriter()); + return JsonBuilder.builder(); } @Override public Builder createBuilder(Consumer consumer) { - return new JsonBuilder(new StringWriter()) { - @Override - public String toString() { - String string = super.toString(); - consumer.accept(string); - return string; - } - }; + return new ConsumingJsonBuilder(consumer); } @Override @@ -401,4 +394,21 @@ public class Json implements DataStructure { } return string; } + + static class ConsumingJsonBuilder extends JsonBuilder { + + private final Consumer consumer; + + ConsumingJsonBuilder(Consumer consumer) { + super(); + this.consumer = consumer; + } + + @Override + public String build() { + String s = super.build(); + consumer.accept(s); + return s; + } + } } diff --git a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonBuilder.java b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonBuilder.java index b413264..7327479 100644 --- a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonBuilder.java +++ b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonBuilder.java @@ -16,11 +16,11 @@ public class JsonBuilder implements Builder { private State state; - public JsonBuilder() { + protected JsonBuilder() { this(new StringBuilder()); } - public JsonBuilder(Appendable appendable) { + protected JsonBuilder(Appendable appendable) { this.appendable = appendable; this.state = new State(null, 0, Structure.DOCSTART, true); } diff --git a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonGenerator.java b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonGenerator.java index bde92bb..b1f820e 100644 --- a/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonGenerator.java +++ b/datastructures-json-tiny/src/main/java/org/xbib/datastructures/json/tiny/JsonGenerator.java @@ -21,7 +21,7 @@ public class JsonGenerator implements Generator { @Override public void generate(Writer writer) throws IOException { - this.builder = new JsonBuilder(writer); + this.builder = JsonBuilder.builder(writer); try (writer) { if (root != null) { internalWrite(root); diff --git a/datastructures-json-tiny/src/test/java/org/xbib/datastructures/json/tiny/test/JsonBuilderTest.java b/datastructures-json-tiny/src/test/java/org/xbib/datastructures/json/tiny/test/JsonBuilderTest.java index 1ae5ab9..6b5cc3f 100644 --- a/datastructures-json-tiny/src/test/java/org/xbib/datastructures/json/tiny/test/JsonBuilderTest.java +++ b/datastructures-json-tiny/src/test/java/org/xbib/datastructures/json/tiny/test/JsonBuilderTest.java @@ -19,35 +19,35 @@ public class JsonBuilderTest { @Test public void testUmlautEncoding() throws IOException{ - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.buildMap(Map.of("Hello", "Jörg")); assertEquals("{\"Hello\":\"Jörg\"}", jsonBuilder.build()); } @Test public void testObjectStrFromMap() throws IOException { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.buildMap(Map.of("a", "b")); assertEquals("{\"a\":\"b\"}", jsonBuilder.build()); } @Test public void testObjectNumFromMap() throws IOException { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.buildMap(Map.of("a", 2)); assertEquals("{\"a\":2}", jsonBuilder.build()); } @Test public void testArrayFromCollection() throws IOException { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.buildCollection(List.of("a", "b", "c")); assertEquals("[\"a\",\"b\",\"c\"]", jsonBuilder.build()); } @Test public void testArrayStr() throws IOException { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginCollection() .buildValue("a") .buildValue("b") @@ -61,7 +61,7 @@ public class JsonBuilderTest { @Test public void testArrayNum() throws IOException { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginCollection() .buildValue(1) .buildValue(2) @@ -75,7 +75,7 @@ public class JsonBuilderTest { @Test public void testArrayFloat() throws IOException { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginCollection() .buildValue(1.0) .buildValue(2.0) @@ -89,7 +89,7 @@ public class JsonBuilderTest { @Test public void testObjectStr() throws IOException { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap() .buildKey("a") .buildValue("b") @@ -102,7 +102,7 @@ public class JsonBuilderTest { @Test public void testObjectNum() throws IOException { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap() .buildKey("a") .buildValue(1) @@ -115,14 +115,14 @@ public class JsonBuilderTest { @Test public void testBuild() throws Exception { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.buildMap(Map.of("a", "b")); assertEquals("{\"a\":\"b\"}", jsonBuilder.build()); } @Test public void testKeyValue() throws Exception { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap(); jsonBuilder.buildKey("a"); jsonBuilder.buildValue("b"); @@ -135,7 +135,7 @@ public class JsonBuilderTest { @Test public void testMapBuild() throws Exception { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap(); jsonBuilder.buildKey("map"); // buildMap is wrapped with '{' and '}' @@ -146,7 +146,7 @@ public class JsonBuilderTest { @Test public void testBeginMapBuild() throws Exception { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap(); jsonBuilder.beginMap("map"); // buildMap is not wrapped with '{' and '}' @@ -158,7 +158,7 @@ public class JsonBuilderTest { @Test public void testMapOfCollections() throws Exception { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap(); jsonBuilder.beginMap("map"); jsonBuilder.collection("a", Arrays.asList("b", "c")); @@ -171,7 +171,7 @@ public class JsonBuilderTest { @Test public void testMapOfEmptyCollections() throws Exception { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap(); jsonBuilder.beginMap("map"); jsonBuilder.collection("a", List.of()); @@ -184,7 +184,7 @@ public class JsonBuilderTest { @Test public void testCollectionOfMaps() throws Exception { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap(); jsonBuilder.beginCollection("collection"); jsonBuilder.buildMap(Map.of("a", "b")); @@ -197,7 +197,7 @@ public class JsonBuilderTest { @Test public void testListOfMapAsField() throws Exception { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap(); jsonBuilder.field("collection", List.of(Map.of("a","b"), Map.of("c", "d"))); jsonBuilder.endMap(); @@ -206,7 +206,7 @@ public class JsonBuilderTest { @Test public void testCollectionOfEmptyMaps() throws Exception { - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginMap(); jsonBuilder.beginCollection("collection"); jsonBuilder.buildMap(Map.of()); @@ -219,11 +219,11 @@ public class JsonBuilderTest { @Test public void testCopy() throws Exception { - JsonBuilder jsonBuilder1 = new JsonBuilder(); + JsonBuilder jsonBuilder1 = JsonBuilder.builder(); jsonBuilder1.buildMap(Map.of("a", "b")); - JsonBuilder jsonBuilder2 = new JsonBuilder(); + JsonBuilder jsonBuilder2 = JsonBuilder.builder(); jsonBuilder2.buildMap(Map.of("c", "d")); - JsonBuilder jsonBuilder = new JsonBuilder(); + JsonBuilder jsonBuilder = JsonBuilder.builder(); jsonBuilder.beginCollection(); jsonBuilder.copy(jsonBuilder1); // test comma separation @@ -242,7 +242,7 @@ public class JsonBuilderTest { @Test public void testMapInList() throws IOException { - JsonBuilder builder = new JsonBuilder(); + JsonBuilder builder = JsonBuilder.builder(); String field = "name"; String value = "Jörg"; builder.beginMap(). diff --git a/datastructures-xml/build.gradle b/datastructures-xml/build.gradle new file mode 100644 index 0000000..6a1c6eb --- /dev/null +++ b/datastructures-xml/build.gradle @@ -0,0 +1,3 @@ +dependencies { + api project(':datastructures-api') +} diff --git a/datastructures-xml/src/main/java/module-info.java b/datastructures-xml/src/main/java/module-info.java new file mode 100644 index 0000000..23136ba --- /dev/null +++ b/datastructures-xml/src/main/java/module-info.java @@ -0,0 +1,6 @@ + +module org.xbib.datastructures.xml { + exports org.xbib.datastructures.xml; + requires org.xbib.datastructures.api; + requires java.logging; +} diff --git a/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLBuilder.java b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLBuilder.java index 7829f36..8232183 100644 --- a/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLBuilder.java +++ b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLBuilder.java @@ -1,191 +1,141 @@ package org.xbib.datastructures.xml; -import java.io.BufferedWriter; +import org.xbib.datastructures.api.Builder; + import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.util.ArrayList; -import java.util.List; +import java.io.UncheckedIOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; -public class XMLBuilder { +public class XMLBuilder implements Builder { - public static final String XMLNS = "xmlns"; + private static final String XMLNS = "xmlns"; - public static String XML_HEADER() { - return ""; + private static final String PREFIX_XML = "xml"; + + private static final String NS_XML = "http://www.w3.org/TR/REC-xml"; + + private final Map namespaces; + + private final Appendable appendable; + + private State state; + + private XMLBuilder(Appendable appendable) throws IOException { + this(appendable, "1.0", "UTF-8"); } - public static String XML_HEADER(String encoding) { - return ""; - } - - private static final int STATE_NOTHING = 0; - private static final int STATE_ELEM_OPENED = 1; - private static final int STATE_TEXT_ADDED = 2; - private static final int IO_BUFFER_SIZE = 8192; - - private Writer writer; - - private int state = STATE_NOTHING; - - private XMLElement element = null; - - private final List trashElements = new ArrayList<>(); - - public XMLBuilder(OutputStream stream, String documentEncoding) throws IOException { - this(stream, documentEncoding, true); - } - - public XMLBuilder(OutputStream stream, String documentEncoding, boolean printHeader) throws IOException { - if (documentEncoding == null) { - init(new OutputStreamWriter(stream), null, printHeader); - } else { - init(new OutputStreamWriter(stream, documentEncoding), documentEncoding, printHeader); + public XMLBuilder(Appendable appendable, String version, String encoding) throws IOException { + this.appendable = appendable; + if (version != null && encoding != null) { + appendable.append(""); } + this.state = new State(null, "root", 0, Structure.DOCSTART, Xml.ELEMENT_CLOSED); + this.namespaces = new HashMap<>(); } - public XMLBuilder(Writer writer, String documentEncoding) throws IOException { - this(writer, documentEncoding, true); + public static XMLBuilder builder() throws IOException { + return new XMLBuilder(new StringBuilder(), null, null); } - public XMLBuilder(Writer writer, String documentEncoding, boolean printHeader) throws IOException { - init(writer, documentEncoding, printHeader); + public static XMLBuilder builder(Appendable appendable) throws IOException { + return new XMLBuilder(appendable); } - private XMLElement createElement(XMLElement parent, String name) { - if (trashElements.isEmpty()) { - return new XMLElement(parent, name); - } else { - XMLElement element = trashElements.remove(trashElements.size() - 1); - element.init(parent, name); - return element; - } + public static XMLBuilder builder(Appendable appendable, String version, String encoding) throws IOException { + return new XMLBuilder(appendable, version, encoding); } - private void deleteElement(XMLElement element) { - trashElements.add(element); - } - - private void init(Writer writer, String documentEncoding, boolean printHeader) throws IOException { - writer = new BufferedWriter(writer, IO_BUFFER_SIZE); - if (printHeader) { - if (documentEncoding != null) { - writer.write(XML_HEADER(documentEncoding)); - } else { - writer.write(XML_HEADER()); - } - } - } - - public XMLElement startElement(String elementName) throws IOException { + public XMLBuilder startElement(CharSequence elementName) throws IOException, XMLException { return startElement(null, null, elementName); } - public XMLElement startElement(String nsURI, String elementName) throws IOException { + public XMLBuilder startElement(String nsURI, CharSequence elementName) throws IOException, XMLException { return startElement(nsURI, null, elementName); } - public XMLElement startElement(String nsURI, String nsPrefix, String elementName) throws IOException { - switch (state) { - case STATE_ELEM_OPENED: - writer.write('>'); - case STATE_NOTHING: - break; - default: - break; + public XMLBuilder startElement(String nsURI, String nsPrefix, CharSequence elementName) throws IOException, XMLException { + if (state.xml == Xml.ELEMENT_OPENED) { + appendable.append('>'); } - writer.write('<'); - boolean addNamespace = (nsURI != null); + appendable.append('<'); + boolean add = nsURI != null; if (nsURI != null) { - if (nsPrefix == null && element != null) { - nsPrefix = element.getNamespacePrefix(nsURI); + if (nsPrefix == null) { + nsPrefix = getNamespacePrefix(nsURI); if (nsPrefix != null) { - addNamespace = false; + add = false; } } } if (nsPrefix != null) { elementName = nsPrefix + ':' + elementName; } - writer.write(elementName); - state = STATE_ELEM_OPENED; - element = createElement(element, elementName); - if (addNamespace) { + appendable.append(elementName); + state = new State(state, elementName, state.level + 1, Structure.NONE, Xml.ELEMENT_OPENED); + if (add) { addNamespace(nsURI, nsPrefix); - element.addNamespace(nsURI, nsPrefix); } - - return element; - } - - public XMLBuilder endElement() throws IOException, IllegalStateException { - if (element == null) { - throw new IllegalStateException("Close tag without open"); - } - switch (state) { - case STATE_ELEM_OPENED: - writer.write("/>"); - break; - case STATE_NOTHING: - case STATE_TEXT_ADDED: - writer.write("'); - default: - break; - } - deleteElement(element); - element = element.getParent(); - state = STATE_NOTHING; return this; } - public XMLBuilder addNamespace(String nsURI) throws IOException { + public XMLBuilder endElement() throws IOException, XMLException { + if (state.xml != Xml.ELEMENT_OPENED && state.xml != Xml.ELEMENT_CLOSED && state.xml != Xml.TEXT_ADDED) { + throw new XMLException("close tag without open"); + } + switch (state.xml) { + case ELEMENT_OPENED: + appendable.append("/>"); + break; + case ELEMENT_CLOSED: + case TEXT_ADDED: + appendable.append("'); + default: + break; + } + state = state.parent; + state.xml = Xml.ELEMENT_CLOSED; + return this; + } + + public XMLBuilder addNamespace(String nsURI) throws IOException, XMLException { return addNamespace(nsURI, null); } - public XMLBuilder addNamespace(String nsURI, String nsPrefix) throws IOException, IllegalStateException { - if (element == null) { - throw new IllegalStateException("Namespace outside of element"); + public XMLBuilder addNamespace(String nsURI, String nsPrefix) throws IOException, XMLException { + if (state.xml != Xml.ELEMENT_OPENED) { + throw new XMLException("namespace outside of element"); } String attrName = XMLNS; if (nsPrefix != null) { attrName = attrName + ':' + nsPrefix; - element.addNamespace(nsURI, nsPrefix); + namespaces.put(nsURI, nsPrefix); } - addAttribute(null, attrName, nsURI, true); + addAttribute(null, attrName, nsURI); return this; } - public XMLBuilder addAttribute(String attributeName, String attributeValue) throws IOException { - return addAttribute(null, attributeName, attributeValue, true); + public XMLBuilder addAttribute(String attributeName, String attributeValue) throws IOException, XMLException { + return addAttribute(null, attributeName, attributeValue); } - public XMLBuilder addAttribute(String nsURI, String attributeName, String attributeValue) throws IOException { - return addAttribute(nsURI, attributeName, attributeValue, true); - } - - private XMLBuilder addAttribute(String nsURI, String attributeName, String attributeValue, boolean escape) throws IOException, IllegalStateException { - switch (state) { - case STATE_ELEM_OPENED: { + public XMLBuilder addAttribute(String nsURI, String attributeName, String attributeValue) throws IOException, XMLException { + switch (state.xml) { + case ELEMENT_OPENED: { if (nsURI != null) { - String nsPrefix = element.getNamespacePrefix(nsURI); + String nsPrefix = getNamespacePrefix(nsURI); if (nsPrefix == null) { - throw new IllegalStateException("Unknown attribute '" + attributeName + "' namespace URI '" + nsURI + "' in element '" + element.getName() + "'"); + throw new XMLException("unknown attribute '" + attributeName + "' namespace URI '" + nsURI + "' in element '" + state.name + "'"); } attributeName = nsPrefix + ':' + attributeName; } - writer.write(' '); - writer.write(attributeName); - writer.write("=\""); - writer.write(escape ? XMLUtils.escapeXml(attributeValue) : attributeValue); - writer.write('"'); + appendable.append(' ').append(attributeName).append("=\"").append(XMLUtils.escapeXml(attributeValue)).append('"'); break; } - case STATE_TEXT_ADDED: - case STATE_NOTHING: - throw new IllegalStateException("Attribute outside of element"); + case TEXT_ADDED: + throw new XMLException("attribute outside of element"); default: break; } @@ -197,79 +147,186 @@ public class XMLBuilder { } public XMLBuilder addText(CharSequence textValue, boolean escape) throws IOException { - switch (state) { - case STATE_ELEM_OPENED: - writer.write('>'); - case STATE_TEXT_ADDED: - case STATE_NOTHING: - break; - default: - break; + if (state.xml == Xml.ELEMENT_OPENED) { + appendable.append('>'); } - writeText(textValue, escape); - - state = STATE_TEXT_ADDED; - - return this; - } - - public XMLBuilder addTextData(String text) throws IOException { - switch (state) { - case STATE_ELEM_OPENED: - writer.write('>'); - case STATE_TEXT_ADDED: - case STATE_NOTHING: - break; - default: - break; - } - writer.write(""); - state = STATE_TEXT_ADDED; - return this; - } - - public XMLBuilder addContent(CharSequence textValue) throws IOException { - writer.write(textValue.toString()); - return this; - } - - public XMLBuilder addComment(String commentValue) throws IOException { - switch (state) { - case STATE_ELEM_OPENED: - writer.write('>'); - case STATE_NOTHING: - break; - default: - break; - } - writer.write(""); - state = STATE_TEXT_ADDED; - return this; - } - - public XMLBuilder addElement(String elementName, String elementValue) throws IOException { - startElement(elementName); - addText(elementValue); - endElement(); - return this; - } - - public XMLBuilder addElementText(String elementName, String elementValue) throws IOException { - startElement(elementName); - addTextData(elementValue); - endElement(); - return this; - } - - private XMLBuilder writeText(CharSequence textValue, boolean escape) throws IOException { if (textValue != null) { - writer.write(escape ? XMLUtils.escapeXml(textValue) : textValue.toString()); + appendable.append(escape ? XMLUtils.escapeXml(textValue) : textValue.toString()); + } + state.xml = Xml.TEXT_ADDED; + return this; + } + + public XMLBuilder addTextData(CharSequence text) throws IOException { + if (state.xml == Xml.ELEMENT_OPENED) { + appendable.append('>'); + } + appendable.append(""); + state.xml = Xml.TEXT_ADDED; + return this; + } + + public XMLBuilder addComment(CharSequence commentValue) throws IOException { + if (state.xml == Xml.ELEMENT_OPENED) { + appendable.append('>'); + } + appendable.append(""); + state.xml = Xml.TEXT_ADDED; + return this; + } + + public String getNamespacePrefix(String nsURI) { + if (nsURI.equals(NS_XML)) { + return PREFIX_XML; + } + return namespaces == null ? null : namespaces.get(nsURI); + } + + @Override + public Builder beginCollection() { + state.structure = Structure.COLLECTION; + return this; + } + + @Override + public Builder endCollection() { + return this; + } + + @Override + public Builder beginMap() throws IOException { + try { + startElement(state.name); + state.structure = Structure.MAP; + } catch (XMLException e) { + throw new IOException(e); } return this; } + @Override + public Builder endMap() throws IOException { + if (state.structure == Structure.MAP || + state.structure == Structure.KEY || + state.structure == Structure.DOCSTART) { + try { + endElement(); + } catch (XMLException e) { + throw new IOException(e); + } + } + return this; + } + + @Override + public Builder buildMap(Map map) throws IOException { + Objects.requireNonNull(map); + boolean wrap = state.structure != Structure.MAP; + if (wrap) { + beginMap(); + } + map.forEach((k, v) -> { + try { + buildKey(k); + buildValue(v); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + if (wrap) { + endMap(); + } + return this; + } + + @Override + public Builder buildCollection(Collection collection) throws IOException { + Objects.requireNonNull(collection); + beginCollection(); + collection.forEach(v -> { + try { + startElement(state.name); + buildValue(v); + endElement(); + } catch (XMLException e) { + throw new UncheckedIOException(new IOException(e)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + endCollection(); + return this; + } + + @Override + public Builder buildKey(CharSequence key) throws IOException { + if (state.structure == Structure.MAP || state.structure == Structure.KEY) { + try { + startElement(key); + } catch (XMLException e) { + throw new IOException(e); + } + } + state.structure = Structure.KEY; + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Builder buildValue(Object value) throws IOException { + if (value instanceof Map) { + buildMap((Map) value); + } else if (value instanceof Collection) { + buildCollection((Collection) value); + } else if (value == null) { + buildNull(); + } else { + addText(value.toString(), true); + if (state.structure == Structure.KEY) { + try { + endElement(); + } catch (XMLException e) { + throw new IOException(e); + } + } + } + return this; + } + + @Override + public Builder buildNull() throws IOException { + addText("null", false); + return this; + } + + @Override + public Builder copy(Builder builder) throws IOException { + appendable.append(builder.build()); + return this; + } + + @Override + public String build() { + return appendable.toString(); + } + + private enum Structure { DOCSTART, NONE, MAP, KEY, COLLECTION }; + + private enum Xml { ELEMENT_OPENED, ELEMENT_CLOSED, TEXT_ADDED }; + + private static class State { + State parent; + CharSequence name; + int level; + Structure structure; + Xml xml; + + State(State parent, CharSequence name, int level, Structure structure, Xml xml) { + this.parent = parent; + this.name = name; + this.level = level; + this.structure = structure; + this.xml = xml; + } + } } diff --git a/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLElement.java b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLElement.java deleted file mode 100644 index 2f27c46..0000000 --- a/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLElement.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.xbib.datastructures.xml; - -import java.util.HashMap; -import java.util.Map; - -public final class XMLElement { - public static final String NS_XML = "http://www.w3.org/TR/REC-xml"; - public static final String PREFIX_XML = "xml"; - - private XMLElement parent; - private String name; - private Map namespaces = null; - private int level; - - XMLElement(XMLElement parent, String name) { - this.init(parent, name); - } - - void init(XMLElement parent, String name) { - this.parent = parent; - this.name = name; - this.namespaces = null; - this.level = parent == null ? 0 : parent.level + 1; - } - - public String getName() { - return name; - } - - public XMLElement getParent() { - return parent; - } - - public int getLevel() { - return level; - } - - public void addNamespace(String nsURI, String nsPrefix) { - if (namespaces == null) { - namespaces = new HashMap<>(); - } - namespaces.put(nsURI, nsPrefix); - } - - public String getNamespacePrefix(String nsURI) { - if (nsURI.equals(NS_XML)) { - return PREFIX_XML; - } - String prefix = namespaces == null ? null : namespaces.get(nsURI); - return prefix != null ? prefix : (parent != null ? parent.getNamespacePrefix(nsURI) : null); - } -} diff --git a/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLException.java b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLException.java index bce08b9..dd0a6b9 100644 --- a/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLException.java +++ b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLException.java @@ -1,6 +1,7 @@ package org.xbib.datastructures.xml; public class XMLException extends Exception { + public XMLException(String message) { super(message); } @@ -8,5 +9,4 @@ public class XMLException extends Exception { public XMLException(String message, Throwable cause) { super(message, cause); } - } diff --git a/datastructures-xml/src/test/java/org/xbib/datatstructures/xml/test/XMLBuilderTest.java b/datastructures-xml/src/test/java/org/xbib/datatstructures/xml/test/XMLBuilderTest.java new file mode 100644 index 0000000..e6f8bd4 --- /dev/null +++ b/datastructures-xml/src/test/java/org/xbib/datatstructures/xml/test/XMLBuilderTest.java @@ -0,0 +1,110 @@ +package org.xbib.datatstructures.xml.test; + +import org.junit.jupiter.api.Test; +import org.xbib.datastructures.xml.XMLBuilder; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class XMLBuilderTest { + + @Test + public void simpleTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + builder.startElement("root").endElement(); + assertEquals("", builder.build()); + } + + @Test + public void nestedTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + builder.startElement("a").startElement("b").endElement().endElement(); + assertEquals("", builder.build()); + } + + @Test + public void nestedTextTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + builder.startElement("a") + .startElement("b").addText("Hello world") + .endElement() + .endElement(); + assertEquals("Hello world", builder.build()); + } + + @Test + public void sequenceTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + builder.startElement("a").endElement().startElement("b").endElement().startElement("c").endElement(); + assertEquals("", builder.build()); + } + + @Test + public void attributeTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + builder.startElement("a").addAttribute("b", "c").endElement(); + assertEquals("", builder.build()); + } + + @Test + public void emptyMapTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + builder.beginMap().endMap(); + assertEquals("", builder.build()); + } + + @Test + public void simpleMapTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + builder.beginMap() + .buildKey("a") + .buildValue("b") + .endMap(); + assertEquals("b", builder.build()); + } + + @Test + public void doubleMapTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + builder.beginMap() + .buildKey("a") + .buildValue("b") + .buildKey("c") + .buildValue("d") + .endMap(); + assertEquals("bd", builder.build()); + } + + @Test + public void mapTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + Map map = Map.of("a", "b"); + builder.buildMap(map); + assertEquals("b", builder.build()); + } + + @Test + public void collectionTest() throws Exception { + XMLBuilder builder = XMLBuilder.builder(); + List list = List.of("a", "b", "c"); + builder.buildCollection(list); + assertEquals("abc", builder.build()); + } + + @Test + public void copyTest() throws Exception { + XMLBuilder builder1 = XMLBuilder.builder(); + builder1.buildMap(Map.of("a", "b")); + XMLBuilder builder2 = XMLBuilder.builder(); + builder2.buildMap(Map.of("c", "d")); + XMLBuilder builder = XMLBuilder.builder(); + builder.beginCollection(); + builder.copy(builder1); + builder.copy(builder2); + builder.endCollection(); + assertEquals("bd", builder.build()); + } + +}