working on XML builder
This commit is contained in:
parent
c48f9db95a
commit
faf7cf2a83
10 changed files with 421 additions and 287 deletions
|
@ -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<String> 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<String> consumer;
|
||||
|
||||
ConsumingJsonBuilder(Consumer<String> consumer) {
|
||||
super();
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String build() {
|
||||
String s = super.build();
|
||||
consumer.accept(s);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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().
|
||||
|
|
3
datastructures-xml/build.gradle
Normal file
3
datastructures-xml/build.gradle
Normal file
|
@ -0,0 +1,3 @@
|
|||
dependencies {
|
||||
api project(':datastructures-api')
|
||||
}
|
6
datastructures-xml/src/main/java/module-info.java
Normal file
6
datastructures-xml/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
module org.xbib.datastructures.xml {
|
||||
exports org.xbib.datastructures.xml;
|
||||
requires org.xbib.datastructures.api;
|
||||
requires java.logging;
|
||||
}
|
|
@ -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 "<?xml version=\"1.0\"?>";
|
||||
private static final String PREFIX_XML = "xml";
|
||||
|
||||
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) {
|
||||
return "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>";
|
||||
public XMLBuilder(Appendable appendable, String version, String encoding) throws IOException {
|
||||
this.appendable = appendable;
|
||||
if (version != null && encoding != null) {
|
||||
appendable.append("<?xml version=\"").append(version).append("\" encoding=\"").append(encoding).append("\"?>");
|
||||
}
|
||||
this.state = new State(null, "root", 0, Structure.DOCSTART, Xml.ELEMENT_CLOSED);
|
||||
this.namespaces = new HashMap<>();
|
||||
}
|
||||
|
||||
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 static XMLBuilder builder() throws IOException {
|
||||
return new XMLBuilder(new StringBuilder(), null, null);
|
||||
}
|
||||
|
||||
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 static XMLBuilder builder(Appendable appendable) throws IOException {
|
||||
return new XMLBuilder(appendable);
|
||||
}
|
||||
|
||||
public XMLBuilder(Writer writer, String documentEncoding) throws IOException {
|
||||
this(writer, documentEncoding, true);
|
||||
public static XMLBuilder builder(Appendable appendable, String version, String encoding) throws IOException {
|
||||
return new XMLBuilder(appendable, version, encoding);
|
||||
}
|
||||
|
||||
public XMLBuilder(Writer writer, String documentEncoding, boolean printHeader) throws IOException {
|
||||
init(writer, documentEncoding, printHeader);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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("</");
|
||||
writer.write(element.getName());
|
||||
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("</").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);
|
||||
}
|
||||
|
||||
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("<![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) {
|
||||
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;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue