package er.xiss;
import java.io.File;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.lang3.CharEncoding;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import com.webobjects.foundation.NSTimeZone;
import com.webobjects.foundation.NSTimestamp;
/**
* XML is the container class for all of the XISS XML builder classes and methods.
*
* @author mschrag
*
* http://github.com/mschrag/xiss
*/
public class ERXML {
/** Version ID */
public static final NSTimestamp versionDate = new NSTimestamp(2009, 11, 24, 0, 0, 0, NSTimeZone.systemTimeZone());
/**
* Item is the base class of everything that can appear in an XML document.
*
* @author mschrag
*/
public static abstract class Item {
private Item _parent;
/**
* Constructs a new Item.
*/
public Item() {
}
/**
* Sets the parent of the this item.
*
* @param parent the parent of the this item
*/
protected void setParent(Item parent) {
_parent = parent;
}
/**
* Returns the parent of this item, or null if there isn't one.
*
* @return the parent of this item, or null if there isn't one
*/
public Item parent() {
return _parent;
}
/**
* Returns the XML document, or null if it isn't in a document.
*
* @return the XML document, or null if it isn't in a document
*/
public ERXML.Doc doc() {
ERXML.Item item = this;
ERXML.Item parent = null;
while ((parent = item.parent()) != null) {
item = parent;
}
ERXML.Doc doc = null;
if (item instanceof ERXML.Doc) {
doc = (ERXML.Doc) item;
}
return doc;
}
/**
* Writes this item to the given writer with a certain indentation. All
* items are pretty printed.
*
* @param writer the writer to write to
* @param indent the current indentation
*/
public abstract void write(PrintWriter writer, int indent);
/**
* Visits this item and any of its children (if the visitor allows).
*
* @param visitor the visitor to visit with
*/
public abstract void visit(ERXML.Visitor visitor);
/**
* Writes the given escaped String to the writer.
*
* @param value the string value to escape
* @param writer the writer to write to
*/
protected void writeEscapedString(String value, PrintWriter writer) {
if (value != null) {
int length = value.length();
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
switch (c) {
case '<':
writer.print("<");
break;
case '>':
writer.print(">");
break;
case '&':
writer.print("&");
break;
case '"':
writer.print(""");
break;
default:
writer.print(c);
}
}
}
}
/**
* Writes an indentation to the writer.
*
* @param indent the indentation to write
* @param writer the writer to write to
*/
protected void writeIndent(int indent, PrintWriter writer) {
for (int i = 0; i < indent; i++) {
writer.print(" ");
}
}
/**
* Generates a formatted representation of this item.
*
* @return a formatted representation of this item
*/
@Override
public String toString() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
write(pw, 0);
return sw.toString();
}
}
/**
* Declaration represents an XML declaration (the <? ... ?> part of the XML document).
*
* @author mschrag
*/
public static class Declaration extends Item {
private String _version;
private String _encoding;
/**
* Constructs a new Declaration.
*
* @param version the version of XML used by this document
* @param encoding the encoding used by this document
*/
public Declaration(String version, String encoding) {
_version = version;
_encoding = encoding;
}
/**
* Returns the version of this declaration.
*
* @return the version of this declaration
*/
public String version() {
return _version;
}
/**
* Sets the version of this document.
*
* @param version the version of this document
*/
public void setVersion(String version) {
_version = version;
}
/**
* Returns the encoding for this document.
*
* @return the encoding for this document
*/
public String encoding() {
return _encoding;
}
/**
* Sets the encoding for this document.
*
* @param encoding the encoding for this document
*/
public void setEncoding(String encoding) {
_encoding = encoding;
}
@Override
public void visit(Visitor visitor) {
visitor.visit(this);
}
@Override
public void write(PrintWriter writer, int indent) {
writer.print("<?xml");
if (_version != null) {
writer.print(" version=\"");
writer.print(_version);
writer.print("\"");
}
if (_encoding != null) {
writer.print(" encoding=\"");
writer.print(_encoding);
writer.print("\"");
}
writer.print("?>");
writer.println();
}
}
/**
* <p>
* Doc represents the top level XML Document object, and is typically the first object you will make when building.
* </p>
*
* <pre>
* XML.Doc doc = XML.doc();
* XML.E person = doc.root("person");
* </pre>
*
* @author mschrag
*/
public static class Doc extends Item {
private ERXML.E _root;
private ERXML.Declaration _declaration;
private List<ERXML.Item> _children;
/**
* Constructs a new Document.
*/
public Doc() {
_children = new LinkedList<>();
setDeclaration(new ERXML.Declaration("1.0", CharEncoding.UTF_8));
}
/**
* Checks if there is already a root element and throws if there is.
*/
protected void checkNullRoot() {
if (_root != null) {
throw new IllegalStateException("There is already a root node for this document.");
}
}
/**
* Returns the declaration for this document, or null if there isn't one. Every Document
* gets a version="1.0" encoding="UTF-8" declaration by default, so you should call
* setDeclaration(null) if you want to remove this default.
*
* @return the declaration for this document
*/
public ERXML.Declaration declaration() {
return _declaration;
}
/**
* Sets the declaration for this document.
*
* @param declaration the declaration for this document
*/
public void setDeclaration(ERXML.Declaration declaration) {
if (_declaration != null) {
remove(_declaration);
}
_declaration = declaration;
if (_declaration != null) {
_declaration.setParent(this);
_children.add(0, _declaration);
}
}
/**
* Creates a new root element with the given name and returns it. If there is already
* a root element, this will throw an exception.
*
* @param name the name of the new root element
*
* @return the new root element
*/
public ERXML.E root(String name) {
checkNullRoot();
ERXML.E root = ERXML.e(name);
setRoot(root);
return root;
}
/**
* Creates a new root element with the given name and initial text contents and returns
* it. If there is already a root element, this will throw an exception.
*
* @param name the name of the new root element
* @param value the initial text value of this element
*
* @return the new root element
*/
public ERXML.E root(String name, String value) {
checkNullRoot();
ERXML.E root = ERXML.e(name, value);
setRoot(root);
return root;
}
/**
* Returns the root element (or null if there isn't one). Documents do not start out
* with a root element, so this might be null.
*
* @return the root element
*/
public ERXML.E root() {
return _root;
}
/**
* Sets the root element of this document.
*
* @param root the new root element
*/
public void setRoot(ERXML.E root) {
if (_root != null) {
if (root != null) {
int rootIndex = _children.indexOf(_root);
_children.set(rootIndex, root);
}
else {
_children.remove(_root);
}
_root = root;
}
else if (root != null) {
_root = root;
_add(root);
}
}
/**
* Creates and returns a new comment for this document. If you want comments above
* the root element, you should call doc.comment(..) prior to calling .root(..).
*
* @param comment a new comment for this document
*
* @return this document
*/
public ERXML.Doc comment(String comment) {
_add(ERXML.comment(comment));
return this;
}
/**
* Removes the given child from this document.
*
* @param child the child to remove
*/
public void remove(ERXML.Item child) {
_children.remove(child);
}
/**
* Adds a new child to this document.
*
* @param child the child to add
*/
protected void _add(ERXML.Item child) {
child.setParent(this);
_children.add(child);
}
/**
* Adds a new child to this document. No validation is performed of these items.
*
* @param <T> the type of the item
* @param child the child to add
* @return the newly added item
*/
public <T extends ERXML.Item> T add(T child) {
if (_root == null && child instanceof ERXML.E) {
setRoot((ERXML.E) child);
}
else if (child instanceof ERXML.Declaration) {
setDeclaration((ERXML.Declaration) child);
}
else {
_add(child);
}
return child;
}
/**
* Returns the children of this document.
*
* @return the children of this document
*/
public List<ERXML.Item> children() {
return _children;
}
@Override
public void write(PrintWriter writer, int indent) {
for (ERXML.Item item : _children) {
item.write(writer, indent);
}
}
@Override
public void visit(ERXML.Visitor visitor) {
if (visitor.visit(this)) {
for (ERXML.Item item : _children) {
item.visit(visitor);
}
}
}
public org.w3c.dom.Document w3c() {
try {
org.w3c.dom.Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
if (_children != null) {
for (ERXML.Item child : _children) {
if (child instanceof ERXML.Node) {
org.w3c.dom.Node childNode = ((ERXML.Node) child).w3c(doc);
doc.appendChild(childNode);
}
}
}
return doc;
}
catch (Throwable t) {
throw new IllegalArgumentException("Failed to create a W3C Document from the this Doc.", t);
}
}
}
/**
* Node is the abstract superclass of all Node items in a Document.
*
* @author mschrag
*/
public static abstract class Node extends ERXML.Item {
public abstract org.w3c.dom.Node w3c(Document doc);
}
/**
* Content is the abstract superclass of all Nodes that have text content.
*
* @author mschrag
*/
public static abstract class Content extends ERXML.Node {
private String _text;
/**
* Constructs a new Content.
*
* @param text the text of the node
*/
public Content(String text) {
_text = text;
}
/**
* Returns the text of this node.
*
* @return the text of this node
*/
public String text() {
return _text;
}
/**
* Sets text text of this node.
*
* @param text text text of this node
*/
public void setText(String text) {
_text = text;
}
@Override
public void visit(Visitor visitor) {
visitor.visit(this);
}
/**
* Writes the text of this node to the writer.
*
* @param writer the writer to write to
*/
protected abstract void writeText(PrintWriter writer);
@Override
public void write(PrintWriter writer, int indent) {
if (_text != null) {
writeIndent(indent, writer);
writeText(writer);
}
}
}
/**
* Text represents a bare text node.
*
* @author mschrag
*/
public static class Text extends Content {
/**
* Creates a new text node.
*
* @param text the text of the node
*/
public Text(String text) {
super(text);
}
@Override
protected void writeText(PrintWriter writer) {
writeEscapedString(text(), writer);
}
@Override
public org.w3c.dom.Node w3c(Document doc) {
org.w3c.dom.Text text = doc.createTextNode(text());
return text;
}
}
/**
* CDATA represents a CDATA section of your document.
*
* @author mschrag
*/
public static class CDATA extends Content {
public CDATA(String text) {
super(text);
}
@Override
protected void writeText(PrintWriter writer) {
writer.print("<![CDATA[");
writer.print(text());
writer.println("]]>");
}
@Override
public org.w3c.dom.Node w3c(Document doc) {
org.w3c.dom.CDATASection cdata = doc.createCDATASection(text());
return cdata;
}
}
/**
* Comment represents a Comment section of the document.
*
* @author mschrag
*/
public static class Comment extends Content {
public Comment(String text) {
super(text);
}
@Override
protected void writeText(PrintWriter writer) {
writer.print("<!-- ");
writer.print(text());
writer.println(" -->");
}
@Override
public org.w3c.dom.Node w3c(Document doc) {
org.w3c.dom.Comment comment = doc.createComment(text());
return comment;
}
}
/**
* Attr represents a key-value pair attribute of an element.
*
* @author mschrag
*/
public static class Attr {
private String _name;
private String _value;
/**
* Constructs a new attribute.
*
* @param name the name of the attribute
* @param value the value of the attribute
*/
public Attr(String name, String value) {
_name = name;
_value = value;
}
/**
* Returns the name of this attribute.
*
* @return the name of this attribute
*/
public String name() {
return _name;
}
/**
* Sets the name of this attribute.
*
* @param name the name of this attribute
*/
public void setName(String name) {
_name = name;
}
/**
* Returns the value of this attribute.
*
* @return the value of this attribute
*/
public String value() {
return _value;
}
/**
* Sets the value of this attribute.
*
* @param value the value of this attribute
*/
public void setValue(String value) {
_value = value;
}
}
/**
* E represents an element of the XML document, which can
* have attributes and children nodes.
*
* @author mschrag
*/
public static class E extends ERXML.Node {
private String _name;
private List<Node> _children;
private List<Attr> _attributes;
/**
* Constructs a new element.
*
* @param name the name of this element
*/
public E(String name) {
_name = name;
}
/**
* Constructs a new element.
*
* @param name the name of this element
* @param text the initial text of this element
*/
public E(String name, String text) {
this(name);
text(text);
}
/**
* Sets the name of this element.
*
* @param name the name of this element
*/
public void setName(String name) {
_name = name;
}
/**
* Returns the name of this element.
*
* @return the name of this element
*/
public String name() {
return _name;
}
@Override
public void visit(ERXML.Visitor visitor) {
if (visitor.visit(this) && _children != null) {
for (ERXML.Node node : _children) {
node.visit(visitor);
}
}
}
/**
* Sets the attribute of the given name to the given value. If there is
* already an attribute with this name, it is replaced.
*
* @param name the name of the attribute
* @param value the value of the attribute
*
* @return this element
*/
public ERXML.E set(String name, String value) {
remove(name);
add(new ERXML.Attr(name, value));
return this;
}
/**
* Sets a series of attributes in the format "name1","value1", "name2","value2", ... If any
* name already exists, it will be replaced with the value you provide.
*
* @param nvPairs an array of name-value pairs
*
* @return this element
*/
public ERXML.E set(String... nvPairs) {
for (int i = 0; i < nvPairs.length; i += 2) {
String name = nvPairs[i];
String value = nvPairs[i + 1];
set(name, value);
}
return this;
}
/**
* Adds the given attribute to this element. This does
* not check for duplicates.
*
* @param attribute the attribute to add
*
* @return this element
*/
public ERXML.E add(ERXML.Attr attribute) {
if (_attributes == null) {
_attributes = new LinkedList<>();
}
_attributes.add(attribute);
return this;
}
/**
* Returns the attribute with the given name, or null if there isn't one.
*
* @param attributeName the name of the attribute to look up
*
* @return the attribute with the given name, or null if there isn't one
*/
public ERXML.Attr getAttr(String attributeName) {
if (_attributes != null) {
for (ERXML.Attr attribute : _attributes) {
if (attribute._name.equals(attributeName)) {
return attribute;
}
}
}
return null;
}
/**
* Removes the attribute with the given name.
*
* @param attributeName the name of the attribute to remove
*
* @return the removed attribute, or null if there wasn't one
*/
public ERXML.Attr remove(String attributeName) {
ERXML.Attr attribute = getAttr(attributeName);
if (attribute != null) {
remove(attribute);
}
return attribute;
}
/**
* Removes the given attribute from this element.
*
* @param attribute the attribute to remove
*/
public void remove(ERXML.Attr attribute) {
_attributes.remove(attribute);
if (_attributes.size() == 0) {
_attributes = null;
}
}
/**
* Returns the value of the attribute with the given name, or null if there isn't one.
*
* @param attributeName the name of the attribute to look up
*
* @return the value of the attribute with the given name, or null if there isn't one
*/
public String get(String attributeName) {
ERXML.Attr attribute = getAttr(attributeName);
String value = null;
if (attribute != null) {
value = attribute.value();
}
return value;
}
/**
* Returns the int variant of the value of the attribute with the given name, or null if there isn't one.
*
* @param attributeName the name of the attribute to lookup
* @param defaultValue the default value to return if there is no attribute, or if the attribute value is null
*
* @return the int variant of the value of the attribute with the given name, or null if there isn't one.
*/
public int getInt(String attributeName, int defaultValue) {
int value = defaultValue;
String text = get(attributeName);
if (text != null) {
value = Integer.parseInt(text);
}
return value;
}
/**
* Returns the float variant of the value of the attribute with the given name, or null if there isn't one.
*
* @param attributeName the name of the attribute to lookup
* @param defaultValue the default value to return if there is no attribute, or if the attribute value is null
*
* @return the float variant of the value of the attribute with the given name, or null if there isn't one.
*/
public float getFloat(String attributeName, float defaultValue) {
float value = defaultValue;
String text = get(attributeName);
if (text != null) {
value = Float.parseFloat(text);
}
return value;
}
/**
* Returns the boolean variant of the value of the attribute with the given name, or null if there isn't one.
*
* @param attributeName the name of the attribute to lookup
* @param defaultValue the default value to return if there is no attribute, or if the attribute value is null
*
* @return the boolean variant of the value of the attribute with the given name, or null if there isn't one.
*/
public boolean getBoolean(String attributeName, boolean defaultValue) {
boolean value = defaultValue;
String text = get(attributeName);
if (text != null) {
value = Boolean.parseBoolean(text);
}
return value;
}
/**
* Returns the enum variant of the value of the attribute with the given name, or null if there isn't one.
*
* @param attributeName the name of the attribute to lookup
* @param enumClass the class of the enum to return
* @param defaultValue the default value to return if there is no attribute, or if the attribute value is null
*
* @return the enum variant of the value of the attribute with the given name, or null if there isn't one.
*/
public <T extends Enum<T>> T getEnum(String attributeName, Class<T> enumClass, T defaultValue) {
T value = defaultValue;
String text = get(attributeName);
if (text != null) {
value = Enum.valueOf(enumClass, text);
}
return value;
}
/**
* Returns the attributes for this element.
*
* @return the attributes for this element
*/
public List<ERXML.Attr> attributes() {
return _attributes;
}
/**
* Sets the text value of this element. This will replace an existing Text child with
* the provided string, or create a new one if one doesn't exist. If there is already
* a child in this element that is not a Text node, this will throw an exception.
*
* @param text the new text value for this node
*
* @return this element
*/
public ERXML.E setText(String text) {
if (_children != null) {
if (_children.size() == 1) {
ERXML.Node node = _children.get(0);
if (node instanceof ERXML.Text) {
((ERXML.Text) node).setText(text);
}
else {
throw new IllegalStateException("There was already a non-text child of this element: " + node);
}
}
else {
throw new IllegalStateException("There was more than one child of this element: " + this);
}
}
else {
text(text);
}
return this;
}
/**
* Returns the text value of this element, or null if there isn't one. If there is a non-text
* child of this element, this will throw an exception.
*
* @return the text value of this element
*/
public ERXML.Text textNode() {
ERXML.Text textNode = null;
if (_children != null) {
if (_children.size() == 1) {
ERXML.Node node = _children.get(0);
if (node instanceof ERXML.Text) {
textNode = (ERXML.Text) node;
}
else {
throw new IllegalStateException("There was only a non-text child of this element: " + node);
}
}
else {
throw new IllegalStateException("There was more than one child of this element: " + this);
}
}
return textNode;
}
/**
* Returns the text value of this element, or null if there isn't one. If there is a non-text
* child of this element, this will throw an exception.
*
* @return the text value of this element
*/
public String text() {
ERXML.Text textNode = textNode();
String text = (textNode != null) ? textNode.text() : null;
return text;
}
/**
* Returns the children nodes of this element.
*
* @return the children nodes of this element
*/
public List<ERXML.Node> children() {
return _children;
}
/**
* Returns the text of the child element with the given name from this element, or null if there isn't one.
*
* @param name the name of the element to look up
*
* @return the text of the child element with the given name from this element, or null if there isn't one
*/
public String childText(String name) {
ERXML.E child = child(name);
return (child != null) ? child.text() : null;
}
/**
* Returns the int variant of the child element with the given name from this element, or defaultValue if there isn't one.
*
* @param name the name of the element to look up
* @param defaultValue the default value to return if there is no child node, or if the child node is empty
*
* @return the int variant of the child element with the given name from this element, or defaultValue if there isn't one.
*/
public int childInt(String name, int defaultValue) {
int value = defaultValue;
ERXML.E child = child(name);
if (child != null) {
String text = child.text();
if (text != null) {
value = Integer.parseInt(text);
}
}
return value;
}
/**
* Returns the float variant of the child element with the given name from this element, or defaultValue if there isn't one.
*
* @param name the name of the element to look up
* @param defaultValue the default value to return if there is no child node, or if the child node is empty
*
* @return the float variant of the child element with the given name from this element, or defaultValue if there isn't one.
*/
public float childFloat(String name, float defaultValue) {
float value = defaultValue;
ERXML.E child = child(name);
if (child != null) {
String text = child.text();
if (text != null) {
value = Float.parseFloat(text);
}
}
return value;
}
/**
* Returns the boolean variant of the child element with the given name from this element, or defaultValue if there isn't one.
*
* @param name the name of the element to look up
* @param defaultValue the default value to return if there is no child node, or if the child node is empty
*
* @return the boolean variant of the child element with the given name from this element, or defaultValue if there isn't one.
*/
public boolean childBoolean(String name, boolean defaultValue) {
boolean value = defaultValue;
ERXML.E child = child(name);
if (child != null) {
String text = child.text();
if (text != null) {
value = Boolean.parseBoolean(text);
}
}
return value;
}
/**
* Returns the enum variant of the child element with the given name from this element, or defaultValue if there isn't one.
*
* @param name the name of the element to look up
* @param enumClass the class of the enum to return
* @param defaultValue the default value to return if there is no child node, or if the child node is empty
* @return the enum variant of the child element with the given name from this element, or defaultValue if there isn't one.
*
*/
public <T extends Enum<T>> T childEnum(String name, Class<T> enumClass, T defaultValue) {
T value = defaultValue;
ERXML.E child = child(name);
if (child != null) {
String text = child.text();
if (text != null) {
value = Enum.valueOf(enumClass, text);
}
}
return value;
}
/**
* Returns the child element with the given name from this element, or null if there isn't one.
*
* @param name the name of the element to look up
*
* @return the child element with the given name from this element, or null if there isn't one
*/
public ERXML.E child(String name) {
ERXML.E matchingElement = null;
if (_children != null) {
for (ERXML.Node node : _children) {
if (node instanceof ERXML.E && ((ERXML.E) node)._name.equals(name)) {
if (matchingElement == null) {
matchingElement = (ERXML.E) node;
}
else {
throw new IllegalStateException("There was more than one child named '" + name + "'.");
}
}
}
}
return matchingElement;
}
/**
* Returns a list of direct children of this element that have the given name, or an empty
* list if there aren't any.
*
* @param name the name of the children elements to look up
*
* @return a list of direct children of this element that have the given name, or an empty
* list if there aren't any
*/
public List<ERXML.E> children(String name) {
List<ERXML.E> children = new LinkedList<>();
if (_children != null) {
for (ERXML.Node node : _children) {
if (node instanceof ERXML.E && ((ERXML.E) node)._name.equals(name)) {
children.add((ERXML.E) node);
}
}
}
return children;
}
/**
* Returns a set of the text of the descendent elements of this element that have the given name, or an empty
* list if there aren't any.
*
* @param name the name of the descendent elements to look up
*
* @return a set of the text of the descendents of this element that have the given name, or an empty
* set if there aren't any
*/
public Set<String> descendentsText(String name) {
Set<ERXML.E> descendents = descendents(name);
Set<String> descendentsText = new LinkedHashSet<>();
for (ERXML.E descendent : descendents) {
descendentsText.add(descendent.text());
}
return descendentsText;
}
/**
* Returns a set of descendent elements of this element that have the given name, or an empty
* list if there aren't any.
*
* @param name the name of the descendent elements to look up
*
* @return a set of descendents of this element that have the given name, or an empty
* set if there aren't any
*/
public Set<ERXML.E> descendents(final String name) {
final Set<ERXML.E> descendents = new LinkedHashSet<>();
if (_children != null) {
ERXML.Visitor visitor = new ERXML.Visitor() {
public boolean visit(Item item) {
if (item instanceof ERXML.E && ((ERXML.E) item)._name.equals(name)) {
descendents.add((ERXML.E) item);
}
return true;
}
};
for (ERXML.Node node : _children) {
node.visit(visitor);
}
}
return descendents;
}
/**
* Creates and appends a new element to this element.
*
* @param name the name of the new element
*
* @return the new element
*/
public ERXML.E e(String name) {
return add(ERXML.e(name));
}
/**
* Creates and appends a new element to this element.
*
* @param name the name of the new element
* @param value the text value of the new element
*
* @return the new element
*/
public ERXML.E e(String name, String value) {
return add(ERXML.e(name, value));
}
/**
* Creates and appends a new text node to this element.
*
* @param text the text value to append
*
* @return the new text node
*/
public ERXML.Text text(String text) {
return add(ERXML.text(text));
}
/**
* Creates and appends a new CDATA node to this element.
*
* @param cdata the cdata value to append
*
* @return the new cdata node
*/
public ERXML.CDATA cdata(String cdata) {
return add(ERXML.cdata(cdata));
}
/**
* Creates and appends a new comment node to this element.
*
* @param comment the comment value to append
*
* @return the new comment node
*/
public ERXML.Comment comment(String comment) {
return add(ERXML.comment(comment));
}
/**
* Removes the given node from this element.
*
* @param child the child node to remove
*/
public void remove(ERXML.Node child) {
if (_children != null) {
_children.remove(child);
if (_children.size() == 0) {
_children = null;
}
}
}
/**
* Adds a new node to this element.
*
* @param <T> the type of the node to add
* @param child the child to add
*
* @return the added child
*/
public <T extends ERXML.Node> T add(T child) {
child.setParent(this);
if (_children == null) {
_children = new LinkedList<>();
}
_children.add(child);
return child;
}
/**
* Writes the attributes of this element to the writer.
*
* @param writer the writer to write attributes to
*/
protected void writeAttributes(PrintWriter writer) {
if (_attributes != null) {
for (ERXML.Attr attribute : _attributes) {
writer.print(" ");
writer.print(attribute.name());
writer.print("=\"");
writeEscapedString(attribute.value(), writer);
writer.print("\"");
}
}
}
@Override
public void write(PrintWriter writer, int indent) {
writeIndent(indent, writer);
if (_children != null && _children.size() > 0) {
writer.print("<");
writer.print(_name);
writeAttributes(writer);
writer.print(">");
if (_children.size() == 1 && _children.get(0) instanceof ERXML.Text) {
_children.get(0).write(writer, 0);
}
else {
writer.println();
for (ERXML.Node node : _children) {
node.write(writer, indent + 1);
}
writeIndent(indent, writer);
}
writer.print("</");
writer.print(_name);
writer.println(">");
}
else {
writer.print("<");
writer.print(_name);
writeAttributes(writer);
writer.println(" />");
}
}
@Override
public org.w3c.dom.Node w3c(Document doc) {
Element e = doc.createElement(_name);
if (_attributes != null) {
for (ERXML.Attr attribute : _attributes) {
e.setAttribute(attribute.name(), attribute.value());
}
}
if (_children != null) {
for (ERXML.Node child : _children) {
org.w3c.dom.Node childNode = child.w3c(doc);
e.appendChild(childNode);
}
}
return e;
}
}
/**
* Visitor is an interface that can be passed to the visit
* method of any XML.Item to walk the DOM.
*
* @author mschrag
*/
public static interface Visitor {
/**
* Called by the visit method of each item.
*
* @param item the item being visited
*
* @return true if the children of the given item should be visited, false if it should skip the children
*/
public boolean visit(ERXML.Item item);
}
/**
* Creates and returns a new Document.
*
* @return a new document
*/
public static ERXML.Doc doc() {
return new ERXML.Doc();
}
/**
* Creates and return a document parsed from the given string.
*
* @param documentString the string to parse as XML
*
* @return a new parsed document
*/
public static ERXML.Doc doc(String documentString) {
try {
ERXML.Doc doc;
if (documentString == null || documentString.trim().length() == 0) {
doc = ERXML.doc();
}
else {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(documentString)));
doc = ERXML.doc(document);
}
return doc;
}
catch (Throwable t) {
throw new IllegalArgumentException("Failed to parse a document from the provided string.", t);
}
}
/**
* Creates and return a document parsed from the given reader.
*
* @param reader the reader to parse from
*
* @return a new parsed document
*/
public static ERXML.Doc doc(Reader reader) {
try {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(reader));
return ERXML.doc(document);
}
catch (Throwable t) {
throw new IllegalArgumentException("Failed to parse a document from the provided reader.", t);
}
}
/**
* Creates and return a document parsed from the given file.
*
* @param file the file to parse from
*
* @return a new parsed document
*/
public static ERXML.Doc doc(File file) {
try {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file);
return ERXML.doc(document);
}
catch (Throwable t) {
throw new IllegalArgumentException("Failed to parse a document from the provided file.", t);
}
}
/**
* Converts a W3C Element into an XML.E.
*
* @param w3cElement the W3C Element
*
* @return the equivalent XML.E
*/
public static ERXML.E e(Element w3cElement) {
ERXML.E e = ERXML.e(w3cElement.getNodeName());
org.w3c.dom.NamedNodeMap attributes = w3cElement.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
org.w3c.dom.Node w3cAttribute = attributes.item(i);
String attributeName = w3cAttribute.getNodeName();
String attributeValue = w3cAttribute.getNodeValue();
e.set(attributeName, attributeValue);
}
org.w3c.dom.NodeList w3cChildren = w3cElement.getChildNodes();
for (int i = 0; i < w3cChildren.getLength(); i++) {
org.w3c.dom.Node w3cChild = w3cChildren.item(i);
if (w3cChild instanceof org.w3c.dom.Text) {
e.text(((org.w3c.dom.Text) w3cChild).getNodeValue());
}
else if (w3cChild instanceof org.w3c.dom.CDATASection) {
e.cdata(((org.w3c.dom.CDATASection) w3cChild).getNodeValue());
}
else if (w3cChild instanceof org.w3c.dom.Comment) {
e.comment(((org.w3c.dom.Comment) w3cChild).getNodeValue());
}
else if (w3cChild instanceof org.w3c.dom.Element) {
e.add(ERXML.e((org.w3c.dom.Element) w3cChild));
}
else {
throw new IllegalArgumentException("Unable to handle nodes of type '" + w3cChild + "'.");
}
}
return e;
}
/**
* Converts a W3C Document into an XML.Doc.
*
* @param w3cDocument the W3C Document
*
* @return the equivalent XML.Doc
*/
public static ERXML.Doc doc(org.w3c.dom.Document w3cDocument) {
org.w3c.dom.Element w3cElement = w3cDocument.getDocumentElement();
Doc doc = ERXML.doc();
doc.setRoot(ERXML.e(w3cElement));
return doc;
}
/**
* Creates and returns a new Document. The first item that is an element
* will be set as the root element of the document.
*
* @param children the children to add to the document
*
* @return a new element
*/
public static ERXML.Doc doc(ERXML.Item... children) {
ERXML.Doc doc = ERXML.doc();
for (ERXML.Item child : children) {
doc.add(child);
}
return doc;
}
/**
* Creates and returns a new Declaration.
*
* @param version the version of the declaration
* @param encoding the encoding of the declaration
*
* @return a new declaration
*/
public static ERXML.Declaration declaration(String version, String encoding) {
return new ERXML.Declaration(version, encoding);
}
/**
* Creates and returns a new Element.
*
* @param name the name of the element
*
* @return a new element
*/
public static ERXML.E e(String name) {
return new ERXML.E(name);
}
/**
* Creates and returns a new Element.
*
* @param name the name of the element
* @param children the children to add to this element (String, XML.Node, or XML.Attr)
*
* @return a new element
*/
public static ERXML.E e(String name, Object... children) {
ERXML.E e = ERXML.e(name);
for (Object child : children) {
if (child instanceof String) {
e.text((String) child);
}
else if (child instanceof ERXML.Node) {
e.add((ERXML.Node) child);
}
else if (child instanceof ERXML.Attr) {
e.add((ERXML.Attr) child);
}
else {
throw new IllegalArgumentException("Unable to add the object '" + child + "' to an XML element.");
}
}
return e;
}
/**
* Creates and returns a new Element.
*
* @param name the name of the element
* @param text the text of the element
*
* @return a new element
*/
public static ERXML.E e(String name, String text) {
return new ERXML.E(name, text);
}
/**
* Creates and returns a new attribute.
*
* @param name the name of the attribute
* @param value the value of the attribute
*
* @return a new attribute
*/
public static ERXML.Attr a(String name, String value) {
return new ERXML.Attr(name, value);
}
/**
* Creates and returns a new text node.
*
* @param text the text of the node
*
* @return a new text node
*/
public static ERXML.Text text(String text) {
return new ERXML.Text(text);
}
/**
* Creates and returns a new cdata node.
*
* @param cdata the text of the cdata node
*
* @return a new cdata node
*/
public static ERXML.CDATA cdata(String cdata) {
return new ERXML.CDATA(cdata);
}
/**
* Creates and returns a new comment node.
*
* @param comment the text of the comment node
*
* @return a new comment node
*/
public static ERXML.Comment comment(String comment) {
return new ERXML.Comment(comment);
}
}