working on XML builder

This commit is contained in:
Jörg Prante 2021-12-01 17:35:24 +01:00
parent c48f9db95a
commit faf7cf2a83
10 changed files with 421 additions and 287 deletions

View file

@ -82,19 +82,12 @@ public class Json implements DataStructure {
@Override @Override
public Builder createBuilder() { public Builder createBuilder() {
return new JsonBuilder(new StringWriter()); return JsonBuilder.builder();
} }
@Override @Override
public Builder createBuilder(Consumer<String> consumer) { public Builder createBuilder(Consumer<String> consumer) {
return new JsonBuilder(new StringWriter()) { return new ConsumingJsonBuilder(consumer);
@Override
public String toString() {
String string = super.toString();
consumer.accept(string);
return string;
}
};
} }
@Override @Override
@ -401,4 +394,21 @@ public class Json implements DataStructure {
} }
return string; return string;
} }
static class ConsumingJsonBuilder extends JsonBuilder {
private final Consumer<String> consumer;
ConsumingJsonBuilder(Consumer<String> consumer) {
super();
this.consumer = consumer;
}
@Override
public String build() {
String s = super.build();
consumer.accept(s);
return s;
}
}
} }

View file

@ -16,11 +16,11 @@ public class JsonBuilder implements Builder {
private State state; private State state;
public JsonBuilder() { protected JsonBuilder() {
this(new StringBuilder()); this(new StringBuilder());
} }
public JsonBuilder(Appendable appendable) { protected JsonBuilder(Appendable appendable) {
this.appendable = appendable; this.appendable = appendable;
this.state = new State(null, 0, Structure.DOCSTART, true); this.state = new State(null, 0, Structure.DOCSTART, true);
} }

View file

@ -21,7 +21,7 @@ public class JsonGenerator implements Generator {
@Override @Override
public void generate(Writer writer) throws IOException { public void generate(Writer writer) throws IOException {
this.builder = new JsonBuilder(writer); this.builder = JsonBuilder.builder(writer);
try (writer) { try (writer) {
if (root != null) { if (root != null) {
internalWrite(root); internalWrite(root);

View file

@ -19,35 +19,35 @@ public class JsonBuilderTest {
@Test @Test
public void testUmlautEncoding() throws IOException{ public void testUmlautEncoding() throws IOException{
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.buildMap(Map.of("Hello", "Jörg")); jsonBuilder.buildMap(Map.of("Hello", "Jörg"));
assertEquals("{\"Hello\":\"Jörg\"}", jsonBuilder.build()); assertEquals("{\"Hello\":\"Jörg\"}", jsonBuilder.build());
} }
@Test @Test
public void testObjectStrFromMap() throws IOException { public void testObjectStrFromMap() throws IOException {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.buildMap(Map.of("a", "b")); jsonBuilder.buildMap(Map.of("a", "b"));
assertEquals("{\"a\":\"b\"}", jsonBuilder.build()); assertEquals("{\"a\":\"b\"}", jsonBuilder.build());
} }
@Test @Test
public void testObjectNumFromMap() throws IOException { public void testObjectNumFromMap() throws IOException {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.buildMap(Map.of("a", 2)); jsonBuilder.buildMap(Map.of("a", 2));
assertEquals("{\"a\":2}", jsonBuilder.build()); assertEquals("{\"a\":2}", jsonBuilder.build());
} }
@Test @Test
public void testArrayFromCollection() throws IOException { public void testArrayFromCollection() throws IOException {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.buildCollection(List.of("a", "b", "c")); jsonBuilder.buildCollection(List.of("a", "b", "c"));
assertEquals("[\"a\",\"b\",\"c\"]", jsonBuilder.build()); assertEquals("[\"a\",\"b\",\"c\"]", jsonBuilder.build());
} }
@Test @Test
public void testArrayStr() throws IOException { public void testArrayStr() throws IOException {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginCollection() jsonBuilder.beginCollection()
.buildValue("a") .buildValue("a")
.buildValue("b") .buildValue("b")
@ -61,7 +61,7 @@ public class JsonBuilderTest {
@Test @Test
public void testArrayNum() throws IOException { public void testArrayNum() throws IOException {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginCollection() jsonBuilder.beginCollection()
.buildValue(1) .buildValue(1)
.buildValue(2) .buildValue(2)
@ -75,7 +75,7 @@ public class JsonBuilderTest {
@Test @Test
public void testArrayFloat() throws IOException { public void testArrayFloat() throws IOException {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginCollection() jsonBuilder.beginCollection()
.buildValue(1.0) .buildValue(1.0)
.buildValue(2.0) .buildValue(2.0)
@ -89,7 +89,7 @@ public class JsonBuilderTest {
@Test @Test
public void testObjectStr() throws IOException { public void testObjectStr() throws IOException {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap() jsonBuilder.beginMap()
.buildKey("a") .buildKey("a")
.buildValue("b") .buildValue("b")
@ -102,7 +102,7 @@ public class JsonBuilderTest {
@Test @Test
public void testObjectNum() throws IOException { public void testObjectNum() throws IOException {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap() jsonBuilder.beginMap()
.buildKey("a") .buildKey("a")
.buildValue(1) .buildValue(1)
@ -115,14 +115,14 @@ public class JsonBuilderTest {
@Test @Test
public void testBuild() throws Exception { public void testBuild() throws Exception {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.buildMap(Map.of("a", "b")); jsonBuilder.buildMap(Map.of("a", "b"));
assertEquals("{\"a\":\"b\"}", jsonBuilder.build()); assertEquals("{\"a\":\"b\"}", jsonBuilder.build());
} }
@Test @Test
public void testKeyValue() throws Exception { public void testKeyValue() throws Exception {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap(); jsonBuilder.beginMap();
jsonBuilder.buildKey("a"); jsonBuilder.buildKey("a");
jsonBuilder.buildValue("b"); jsonBuilder.buildValue("b");
@ -135,7 +135,7 @@ public class JsonBuilderTest {
@Test @Test
public void testMapBuild() throws Exception { public void testMapBuild() throws Exception {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap(); jsonBuilder.beginMap();
jsonBuilder.buildKey("map"); jsonBuilder.buildKey("map");
// buildMap is wrapped with '{' and '}' // buildMap is wrapped with '{' and '}'
@ -146,7 +146,7 @@ public class JsonBuilderTest {
@Test @Test
public void testBeginMapBuild() throws Exception { public void testBeginMapBuild() throws Exception {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap(); jsonBuilder.beginMap();
jsonBuilder.beginMap("map"); jsonBuilder.beginMap("map");
// buildMap is not wrapped with '{' and '}' // buildMap is not wrapped with '{' and '}'
@ -158,7 +158,7 @@ public class JsonBuilderTest {
@Test @Test
public void testMapOfCollections() throws Exception { public void testMapOfCollections() throws Exception {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap(); jsonBuilder.beginMap();
jsonBuilder.beginMap("map"); jsonBuilder.beginMap("map");
jsonBuilder.collection("a", Arrays.asList("b", "c")); jsonBuilder.collection("a", Arrays.asList("b", "c"));
@ -171,7 +171,7 @@ public class JsonBuilderTest {
@Test @Test
public void testMapOfEmptyCollections() throws Exception { public void testMapOfEmptyCollections() throws Exception {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap(); jsonBuilder.beginMap();
jsonBuilder.beginMap("map"); jsonBuilder.beginMap("map");
jsonBuilder.collection("a", List.of()); jsonBuilder.collection("a", List.of());
@ -184,7 +184,7 @@ public class JsonBuilderTest {
@Test @Test
public void testCollectionOfMaps() throws Exception { public void testCollectionOfMaps() throws Exception {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap(); jsonBuilder.beginMap();
jsonBuilder.beginCollection("collection"); jsonBuilder.beginCollection("collection");
jsonBuilder.buildMap(Map.of("a", "b")); jsonBuilder.buildMap(Map.of("a", "b"));
@ -197,7 +197,7 @@ public class JsonBuilderTest {
@Test @Test
public void testListOfMapAsField() throws Exception { public void testListOfMapAsField() throws Exception {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap(); jsonBuilder.beginMap();
jsonBuilder.field("collection", List.of(Map.of("a","b"), Map.of("c", "d"))); jsonBuilder.field("collection", List.of(Map.of("a","b"), Map.of("c", "d")));
jsonBuilder.endMap(); jsonBuilder.endMap();
@ -206,7 +206,7 @@ public class JsonBuilderTest {
@Test @Test
public void testCollectionOfEmptyMaps() throws Exception { public void testCollectionOfEmptyMaps() throws Exception {
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginMap(); jsonBuilder.beginMap();
jsonBuilder.beginCollection("collection"); jsonBuilder.beginCollection("collection");
jsonBuilder.buildMap(Map.of()); jsonBuilder.buildMap(Map.of());
@ -219,11 +219,11 @@ public class JsonBuilderTest {
@Test @Test
public void testCopy() throws Exception { public void testCopy() throws Exception {
JsonBuilder jsonBuilder1 = new JsonBuilder(); JsonBuilder jsonBuilder1 = JsonBuilder.builder();
jsonBuilder1.buildMap(Map.of("a", "b")); jsonBuilder1.buildMap(Map.of("a", "b"));
JsonBuilder jsonBuilder2 = new JsonBuilder(); JsonBuilder jsonBuilder2 = JsonBuilder.builder();
jsonBuilder2.buildMap(Map.of("c", "d")); jsonBuilder2.buildMap(Map.of("c", "d"));
JsonBuilder jsonBuilder = new JsonBuilder(); JsonBuilder jsonBuilder = JsonBuilder.builder();
jsonBuilder.beginCollection(); jsonBuilder.beginCollection();
jsonBuilder.copy(jsonBuilder1); jsonBuilder.copy(jsonBuilder1);
// test comma separation // test comma separation
@ -242,7 +242,7 @@ public class JsonBuilderTest {
@Test @Test
public void testMapInList() throws IOException { public void testMapInList() throws IOException {
JsonBuilder builder = new JsonBuilder(); JsonBuilder builder = JsonBuilder.builder();
String field = "name"; String field = "name";
String value = "Jörg"; String value = "Jörg";
builder.beginMap(). builder.beginMap().

View file

@ -0,0 +1,3 @@
dependencies {
api project(':datastructures-api')
}

View file

@ -0,0 +1,6 @@
module org.xbib.datastructures.xml {
exports org.xbib.datastructures.xml;
requires org.xbib.datastructures.api;
requires java.logging;
}

View file

@ -1,191 +1,141 @@
package org.xbib.datastructures.xml; package org.xbib.datastructures.xml;
import java.io.BufferedWriter; import org.xbib.datastructures.api.Builder;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.UncheckedIOException;
import java.io.OutputStreamWriter; import java.util.Collection;
import java.io.Writer; import java.util.HashMap;
import java.util.ArrayList; import java.util.Map;
import java.util.List; 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() { private static final String PREFIX_XML = "xml";
return "<?xml version=\"1.0\"?>";
private static final String NS_XML = "http://www.w3.org/TR/REC-xml";
private final Map<String, String> 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) { public XMLBuilder(Appendable appendable, String version, String encoding) throws IOException {
return "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>"; this.appendable = appendable;
} if (version != null && encoding != null) {
appendable.append("<?xml version=\"").append(version).append("\" encoding=\"").append(encoding).append("\"?>");
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<XMLElement> 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);
} }
this.state = new State(null, "root", 0, Structure.DOCSTART, Xml.ELEMENT_CLOSED);
this.namespaces = new HashMap<>();
} }
public XMLBuilder(Writer writer, String documentEncoding) throws IOException { public static XMLBuilder builder() throws IOException {
this(writer, documentEncoding, true); return new XMLBuilder(new StringBuilder(), null, null);
} }
public XMLBuilder(Writer writer, String documentEncoding, boolean printHeader) throws IOException { public static XMLBuilder builder(Appendable appendable) throws IOException {
init(writer, documentEncoding, printHeader); return new XMLBuilder(appendable);
} }
private XMLElement createElement(XMLElement parent, String name) { public static XMLBuilder builder(Appendable appendable, String version, String encoding) throws IOException {
if (trashElements.isEmpty()) { return new XMLBuilder(appendable, version, encoding);
return new XMLElement(parent, name);
} else {
XMLElement element = trashElements.remove(trashElements.size() - 1);
element.init(parent, name);
return element;
}
} }
private void deleteElement(XMLElement element) { public XMLBuilder startElement(CharSequence elementName) throws IOException, XMLException {
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 {
return startElement(null, null, elementName); 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); return startElement(nsURI, null, elementName);
} }
public XMLElement startElement(String nsURI, String nsPrefix, String elementName) throws IOException { public XMLBuilder startElement(String nsURI, String nsPrefix, CharSequence elementName) throws IOException, XMLException {
switch (state) { if (state.xml == Xml.ELEMENT_OPENED) {
case STATE_ELEM_OPENED: appendable.append('>');
writer.write('>');
case STATE_NOTHING:
break;
default:
break;
} }
writer.write('<'); appendable.append('<');
boolean addNamespace = (nsURI != null); boolean add = nsURI != null;
if (nsURI != null) { if (nsURI != null) {
if (nsPrefix == null && element != null) { if (nsPrefix == null) {
nsPrefix = element.getNamespacePrefix(nsURI); nsPrefix = getNamespacePrefix(nsURI);
if (nsPrefix != null) { if (nsPrefix != null) {
addNamespace = false; add = false;
} }
} }
} }
if (nsPrefix != null) { if (nsPrefix != null) {
elementName = nsPrefix + ':' + elementName; elementName = nsPrefix + ':' + elementName;
} }
writer.write(elementName); appendable.append(elementName);
state = STATE_ELEM_OPENED; state = new State(state, elementName, state.level + 1, Structure.NONE, Xml.ELEMENT_OPENED);
element = createElement(element, elementName); if (add) {
if (addNamespace) {
addNamespace(nsURI, nsPrefix); 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("</");
writer.write(element.getName());
writer.write('>');
default:
break;
}
deleteElement(element);
element = element.getParent();
state = STATE_NOTHING;
return this; 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("</").append(state.name).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); return addNamespace(nsURI, null);
} }
public XMLBuilder addNamespace(String nsURI, String nsPrefix) throws IOException, IllegalStateException { public XMLBuilder addNamespace(String nsURI, String nsPrefix) throws IOException, XMLException {
if (element == null) { if (state.xml != Xml.ELEMENT_OPENED) {
throw new IllegalStateException("Namespace outside of element"); throw new XMLException("namespace outside of element");
} }
String attrName = XMLNS; String attrName = XMLNS;
if (nsPrefix != null) { if (nsPrefix != null) {
attrName = attrName + ':' + nsPrefix; attrName = attrName + ':' + nsPrefix;
element.addNamespace(nsURI, nsPrefix); namespaces.put(nsURI, nsPrefix);
} }
addAttribute(null, attrName, nsURI, true); addAttribute(null, attrName, nsURI);
return this; return this;
} }
public XMLBuilder addAttribute(String attributeName, String attributeValue) throws IOException { public XMLBuilder addAttribute(String attributeName, String attributeValue) throws IOException, XMLException {
return addAttribute(null, attributeName, attributeValue, true); return addAttribute(null, attributeName, attributeValue);
} }
public XMLBuilder addAttribute(String nsURI, String attributeName, String attributeValue) throws IOException { public XMLBuilder addAttribute(String nsURI, String attributeName, String attributeValue) throws IOException, XMLException {
return addAttribute(nsURI, attributeName, attributeValue, true); switch (state.xml) {
} case ELEMENT_OPENED: {
private XMLBuilder addAttribute(String nsURI, String attributeName, String attributeValue, boolean escape) throws IOException, IllegalStateException {
switch (state) {
case STATE_ELEM_OPENED: {
if (nsURI != null) { if (nsURI != null) {
String nsPrefix = element.getNamespacePrefix(nsURI); String nsPrefix = getNamespacePrefix(nsURI);
if (nsPrefix == null) { 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; attributeName = nsPrefix + ':' + attributeName;
} }
writer.write(' '); appendable.append(' ').append(attributeName).append("=\"").append(XMLUtils.escapeXml(attributeValue)).append('"');
writer.write(attributeName);
writer.write("=\"");
writer.write(escape ? XMLUtils.escapeXml(attributeValue) : attributeValue);
writer.write('"');
break; break;
} }
case STATE_TEXT_ADDED: case TEXT_ADDED:
case STATE_NOTHING: throw new XMLException("attribute outside of element");
throw new IllegalStateException("Attribute outside of element");
default: default:
break; break;
} }
@ -197,79 +147,186 @@ public class XMLBuilder {
} }
public XMLBuilder addText(CharSequence textValue, boolean escape) throws IOException { public XMLBuilder addText(CharSequence textValue, boolean escape) throws IOException {
switch (state) { if (state.xml == Xml.ELEMENT_OPENED) {
case STATE_ELEM_OPENED: appendable.append('>');
writer.write('>');
case STATE_TEXT_ADDED:
case STATE_NOTHING:
break;
default:
break;
} }
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("<![CDATA[");
writer.write(text);
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("<!--");
writer.write(commentValue);
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) { 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("<![CDATA[").append(text).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("<!--").append(commentValue).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; 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<String, Object> 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<String, Object>) 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;
}
}
} }

View file

@ -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<String, String> 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);
}
}

View file

@ -1,6 +1,7 @@
package org.xbib.datastructures.xml; package org.xbib.datastructures.xml;
public class XMLException extends Exception { public class XMLException extends Exception {
public XMLException(String message) { public XMLException(String message) {
super(message); super(message);
} }
@ -8,5 +9,4 @@ public class XMLException extends Exception {
public XMLException(String message, Throwable cause) { public XMLException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
} }

View file

@ -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("<root/>", builder.build());
}
@Test
public void nestedTest() throws Exception {
XMLBuilder builder = XMLBuilder.builder();
builder.startElement("a").startElement("b").endElement().endElement();
assertEquals("<a><b/></a>", builder.build());
}
@Test
public void nestedTextTest() throws Exception {
XMLBuilder builder = XMLBuilder.builder();
builder.startElement("a")
.startElement("b").addText("Hello world")
.endElement()
.endElement();
assertEquals("<a><b>Hello world</b></a>", builder.build());
}
@Test
public void sequenceTest() throws Exception {
XMLBuilder builder = XMLBuilder.builder();
builder.startElement("a").endElement().startElement("b").endElement().startElement("c").endElement();
assertEquals("<a/><b/><c/>", builder.build());
}
@Test
public void attributeTest() throws Exception {
XMLBuilder builder = XMLBuilder.builder();
builder.startElement("a").addAttribute("b", "c").endElement();
assertEquals("<a b=\"c\"/>", builder.build());
}
@Test
public void emptyMapTest() throws Exception {
XMLBuilder builder = XMLBuilder.builder();
builder.beginMap().endMap();
assertEquals("<root/>", builder.build());
}
@Test
public void simpleMapTest() throws Exception {
XMLBuilder builder = XMLBuilder.builder();
builder.beginMap()
.buildKey("a")
.buildValue("b")
.endMap();
assertEquals("<root><a>b</a></root>", builder.build());
}
@Test
public void doubleMapTest() throws Exception {
XMLBuilder builder = XMLBuilder.builder();
builder.beginMap()
.buildKey("a")
.buildValue("b")
.buildKey("c")
.buildValue("d")
.endMap();
assertEquals("<root><a>b</a><c>d</c></root>", builder.build());
}
@Test
public void mapTest() throws Exception {
XMLBuilder builder = XMLBuilder.builder();
Map<String, Object> map = Map.of("a", "b");
builder.buildMap(map);
assertEquals("<root><a>b</a></root>", builder.build());
}
@Test
public void collectionTest() throws Exception {
XMLBuilder builder = XMLBuilder.builder();
List<Object> list = List.of("a", "b", "c");
builder.buildCollection(list);
assertEquals("<root>a</root><root>b</root><root>c</root>", 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("<root><a>b</a></root><root><c>d</c></root>", builder.build());
}
}