begin of xml work
This commit is contained in:
parent
cce92c775b
commit
50ec5f5f44
4 changed files with 392 additions and 0 deletions
|
@ -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 "<?xml version=\"1.0\"?>";
|
||||
}
|
||||
|
||||
public static String XML_HEADER(String encoding) {
|
||||
return "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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("</");
|
||||
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 {
|
||||
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("<![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());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue