package nl.helixsoft.xml;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Simple API for reading / writing Xml. The API was designed to be as brief as possible.
* For example, to generate the following xml:
*
*
<pre>
<book author="douglas adams">
<title>Hitchhikers guide to the galaxy</title>
</book>
</pre>
<p>
All you have to do is this:
<p>
<code>
Xml.elt("book").setAttr("author", "douglas adams").add(Xml.elt("title", "Hitchhikers guide to the galaxy")).toString();
</code>
*/
public class Xml
{
private Map<String, String> attributes = new HashMap<String, String>();
private List<String> attributeOrder = new ArrayList<String>();
private String name;
private String namespace;
private List<Object> contents = new ArrayList<Object>();
/**
* Implement this interface for use in the mapList method
*/
public interface XmlMapper<T>
{
public Xml asXml (Object T);
}
/**
* Quickly add a list of items. Each item will be converted to Xml using the specified mapper.
*/
public <T> Xml mapList (final XmlMapper<T> mapper, final Collection<T> data)
{
for (T t : data)
{
addContents(mapper.asXml(t));
}
return this;
}
/**
* Add contents to an element. This contents can be either an Xml child element, or an arbitrary Object that is converted to String text.
*/
private void addContents(Object o)
{
contents.add(o);
}
/** Private constructor, not for external use. Use the Xml.elt() static method instead */
private Xml (String name, Object... os)
{
this.name = name;
for (Object o : os)
addContents (o);
}
/**
* Generate an Xml element with given name. Zero or more subelements or text contents (but not attributes) can be specified optionally.
*/
public static Xml elt (String name, Object... os)
{
return new Xml(name, os);
}
/**
* Set the XML namespace of this element
*/
public Xml setNs (String namespace)
{
this.namespace = namespace;
return this;
}
/**
* Get the XML namespace of this element
*/
public String getNs ()
{
return namespace;
}
/**
* Set an attribute on an element. Returns this, so that methods can be chained. For example:
* book.setAttr("author", "Douglas Adams").setAttr("genre", "science fiction comedy");
*/
public Xml setAttr (String key, Object val)
{
attributes.put(key, "" + val);
attributeOrder.add(key);
return this;
}
/**
* Add contents to a node. Contents can be either Xml children, or text content. Text content can be any object that implements toString().
*/
public Xml add (Object... os)
{
for (Object o : os)
addContents(o);
return this;
}
/**
* get the value of attribute with the given key.
*/
public String getAttr(String key)
{
return attributes.get(key);
}
/**
* Get the first child with the given element name.
*/
public Xml getFirst(String name)
{
for (Object o : contents)
{
if (o instanceof Xml && name.equals (((Xml)o).name))
{
return ((Xml)o);
}
}
return null;
}
/**
* Get all childr elements with the given element name
*/
public Iterable<Xml> getChildren(String name)
{
List<Xml> result = new ArrayList<Xml>();
for (Object o : contents)
{
if (o instanceof Xml && name.equals (((Xml)o).name))
{
result.add((Xml)o);
}
}
return result;
}
/**
* Get all child elements.
*/
public Iterable<Xml> getChildren()
{
List<Xml> result = new ArrayList<Xml>();
for (Object o : contents)
{
if (o instanceof Xml)
{
result.add((Xml)o);
}
}
return result;
}
/**
* Write this node out to file.
*/
public void writeToFile(File out) throws IOException
{
FileWriter writer = new FileWriter (out);
Context context = new Context();
context.indentation = 0;
flush (writer, context);
writer.close();
}
/**
* Convert this Xml to String.
*/
@Override
public String toString()
{
StringWriter writer = new StringWriter();
try
{
Context context = new Context();
context.indentation = 0;
flush (writer, context);
}
catch (IOException e)
{
e.printStackTrace();
}
return writer.toString();
}
private static void newLineWithIndentation (Writer writer, int level) throws IOException
{
writer.write('\n');
for (int i = 0; i < level; ++i)
{
writer.write(" ");
}
}
/** Internal. Append this node and it's children to the writer */
private void flush(Writer writer, Context context) throws IOException
{
if (context.indentation == 0)
writer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
else
newLineWithIndentation(writer, context.indentation);
writer.write('<');
writer.write(name);
for (String key : attributeOrder)
{
writer.write (' ');
writer.write (key);
writer.write ("=\"");
writer.write (attributes.get(key));
writer.write ('"');
}
if (contents.size() == 0)
{
writer.write ("/>");
}
else
{
writer.write (">");
boolean sameLine = true;
for (Object o : contents)
{
if (o instanceof Xml)
{
Context childContext = new Context();
childContext.indentation = context.indentation + 1;
((Xml) o).flush(writer, childContext);
sameLine = false;
}
else
{
writer.write ("" + o);
}
}
if (!sameLine)
newLineWithIndentation(writer, context.indentation);
writer.write("</");
writer.write(name);
writer.write (">");
}
}
/** Internal. For use by flush */
private static class Context
{
int indentation;
}
}