From 50ec5f5f441d45bca09788049dea5ffb09d3ba5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Fri, 29 Oct 2021 19:08:33 +0200 Subject: [PATCH] begin of xml work --- .../xbib/datastructures/xml/XMLBuilder.java | 275 ++++++++++++++++++ .../xbib/datastructures/xml/XMLElement.java | 52 ++++ .../xbib/datastructures/xml/XMLException.java | 12 + .../org/xbib/datastructures/xml/XMLUtils.java | 53 ++++ 4 files changed, 392 insertions(+) create mode 100644 datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLBuilder.java create mode 100644 datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLElement.java create mode 100644 datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLException.java create mode 100644 datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLUtils.java 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 new file mode 100644 index 0000000..7829f36 --- /dev/null +++ b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLBuilder.java @@ -0,0 +1,275 @@ +package org.xbib.datastructures.xml; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +public class XMLBuilder { + + public static final String XMLNS = "xmlns"; + + public static String XML_HEADER() { + return ""; + } + + 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(Writer writer, String documentEncoding) throws IOException { + this(writer, documentEncoding, true); + } + + 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 { + return startElement(null, null, elementName); + } + + public XMLElement startElement(String nsURI, String elementName) throws IOException { + 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; + } + writer.write('<'); + boolean addNamespace = (nsURI != null); + if (nsURI != null) { + if (nsPrefix == null && element != null) { + nsPrefix = element.getNamespacePrefix(nsURI); + if (nsPrefix != null) { + addNamespace = false; + } + } + } + if (nsPrefix != null) { + elementName = nsPrefix + ':' + elementName; + } + writer.write(elementName); + state = STATE_ELEM_OPENED; + element = createElement(element, elementName); + if (addNamespace) { + 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 { + return addNamespace(nsURI, null); + } + + public XMLBuilder addNamespace(String nsURI, String nsPrefix) throws IOException, IllegalStateException { + if (element == null) { + throw new IllegalStateException("Namespace outside of element"); + } + String attrName = XMLNS; + if (nsPrefix != null) { + attrName = attrName + ':' + nsPrefix; + element.addNamespace(nsURI, nsPrefix); + } + addAttribute(null, attrName, nsURI, true); + return this; + } + + public XMLBuilder addAttribute(String attributeName, String attributeValue) throws IOException { + return addAttribute(null, attributeName, attributeValue, true); + } + + 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: { + if (nsURI != null) { + String nsPrefix = element.getNamespacePrefix(nsURI); + if (nsPrefix == null) { + throw new IllegalStateException("Unknown attribute '" + attributeName + "' namespace URI '" + nsURI + "' in element '" + element.getName() + "'"); + } + attributeName = nsPrefix + ':' + attributeName; + } + writer.write(' '); + writer.write(attributeName); + writer.write("=\""); + writer.write(escape ? XMLUtils.escapeXml(attributeValue) : attributeValue); + writer.write('"'); + break; + } + case STATE_TEXT_ADDED: + case STATE_NOTHING: + throw new IllegalStateException("Attribute outside of element"); + default: + break; + } + return this; + } + + public XMLBuilder addText(CharSequence textValue) throws IOException { + return addText(textValue, true); + } + + 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; + } + 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()); + } + return this; + } + +} 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 new file mode 100644 index 0000000..2f27c46 --- /dev/null +++ b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLElement.java @@ -0,0 +1,52 @@ +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 new file mode 100644 index 0000000..bce08b9 --- /dev/null +++ b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLException.java @@ -0,0 +1,12 @@ +package org.xbib.datastructures.xml; + +public class XMLException extends Exception { + public XMLException(String message) { + super(message); + } + + public XMLException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLUtils.java b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLUtils.java new file mode 100644 index 0000000..071376d --- /dev/null +++ b/datastructures-xml/src/main/java/org/xbib/datastructures/xml/XMLUtils.java @@ -0,0 +1,53 @@ +package org.xbib.datastructures.xml; + +public class XMLUtils { + + public static String escapeXml(CharSequence str) { + if (str == null) { + return null; + } + StringBuilder res = null; + int strLength = str.length(); + for (int i = 0; i < strLength; i++) { + char c = str.charAt(i); + String repl = encodeXMLChar(c); + if (repl == null) { + if (res != null) { + res.append(c); + } + } else { + if (res == null) { + res = new StringBuilder(str.length() + 5); + for (int k = 0; k < i; k++) { + res.append(str.charAt(k)); + } + } + res.append(repl); + } + } + return res == null ? str.toString() : res.toString(); + } + + /** + * Encodes a char to XML-valid form replacing &,',",<,> with special XML encoding. + * + * @param ch char to convert + * @return XML-encoded text + */ + public static String encodeXMLChar(char ch) { + switch (ch) { + case '&': + return "&"; + case '\"': + return """; + case '\'': + return "'"; + case '<': + return "<"; + case '>': + return ">"; + default: + return null; + } + } +}