blob: 80a9fff834e8d02f846feda20b529075f20a6450 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1997-2007 by ProSyst Software GmbH
* http://www.prosyst.com
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* ProSyst Software GmbH - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.util.xml;
import java.io.*;
import java.util.Vector;
/**
* XmlSerializer is an utility class, that simplifies creation of XML files. It
* is designed to be similar to the XML Pull API serializer.
*
* @author Valentin Valchev
* @author Pavlin Dobrev
* @version 1.0
*/
public class XmlSerializer {
private Writer writer; // underlying writer
private Vector stack; // of XML entity names
private StringWriter attrsWriter; // current attribute writer
private StringBuffer attrs; // current attribute string
private boolean empty; // is the current node empty
private boolean closed; // is the current node closed...
private String indentString;
private boolean prologWritten;
/**
* Opens and initializes XML output on top of existing Writer.
*
* @param writer
* the output writer
* @return self
*/
public XmlSerializer setOutput(Writer writer) {
this.writer = writer;
this.closed = true;
this.prologWritten = false;
if (stack == null) {
stack = new Vector(5);
} else {
stack.removeAllElements();
}
if (attrs != null) {
attrs.setLength(0);
}
return this;
}
/**
* Set to use binary output stream with given encoding.
*
* @param os
* the output stream
* @param encoding
* the output encoding
* @throws IOException
* @see #setOutput(Writer)
* @throws NullPointerException
* is output stream is null
*/
public void setOutput(OutputStream os, String encoding) throws IOException {
if (os == null)
throw new NullPointerException("output stream can not be null");
Writer writer = encoding != null ? //
new OutputStreamWriter(os, encoding)
: //
new OutputStreamWriter(os);
setOutput(writer);
}
/**
* Writes XML prolog code:
*
* <pre>
* &lt;?xml version=&quot;1.0&quot;?&gt;
* </pre>
*
* @param encoding
* the selected encoding. If <code>null</code> encoding
* attribute is not written.
* @param standalone
* if the XML is stand-alone, no DTD or schema
* @return self
* @throws IOException
* on I/O error
* @throws IllegalStateException
* if prolog code is already written
*/
public XmlSerializer startDocument(String encoding, Boolean standalone) throws IOException {
if (prologWritten) {
throw new IllegalStateException("Prolog already written");
}
writer.write("<?xml version=\"1.0\"");
if (encoding != null) {
writer.write(" encoding=\"");
writer.write(encoding);
writer.write("\"");
}
if (standalone != null) {
writer.write(" standalone=\"");
writer.write(standalone.booleanValue() ? "yes" : "no");
writer.write("\"");
}
writer.write("?>");
prologWritten = true;
return this;
}
/**
* Close this writer. It does not close the underlying writer, but does
* throw an exception if there are as yet unclosed tags.
*
* @throws IllegalStateException
* if tags are not closed
*/
public void endDocument() {
if (!stack.isEmpty()) {
throw new IllegalStateException("Tags are not all closed. Possibly, '" + pop() + "' is unclosed. ");
}
}
/**
* Returns the current depth of the element. Outside the root element, the
* depth is 0. The depth is incremented by 1 when startTag() is called. The
* depth is decremented after the call to endTag() event was observed.
*
* <pre>
* &lt;!-- outside --&gt; 0
* &lt;root&gt; 1
* sometext 1
* &lt;foobar&gt; 2
* &lt;/foobar&gt; 2
* &lt;/root&gt; 1
* &lt;!-- outside --&gt; 0
* </pre>
*
* @return the current level
*/
public int getDepth() {
return stack.size();
}
/**
* Begin to output an entity.
*
* @param name
* name of entity.
* @return self
* @throws IOException
* on I/O error
*/
public XmlSerializer startTag(String name) throws IOException {
if (!prologWritten) {
startDocument(null, null);
}
closeOpeningTag();
closed = false;
indent();
writer.write("<");
writer.write(name);
stack.addElement(name);
empty = true;
return this;
}
/**
* End the current entity silently without validating if correct tag is
* closed.
*
* @return self
* @throws IOException
* @deprecated see {@link #endTag(String)}
*/
public XmlSerializer endTag() throws IOException {
return endTag(null);
}
/**
* End the current entity. This will throw an exception if it is called when
* there is not a currently open entity.
*
* @param aName
* tag to close. This is used mostly for validation
* @return self
* @throws IOException
* on I/O error
* @throws IllegalStateException
* if no entity waits to be closed
* @throws IllegalArgumentException
* if expected element name is not the same
*/
public XmlSerializer endTag(String aName) throws IOException {
if (stack.isEmpty()) {
throw new IllegalStateException("Called endEntity too many times.");
}
String name = pop();
if (name != null) {
if (aName != null && !aName.equals(name)) {
throw new IllegalArgumentException("Expected element name '" + name + "', not '" + aName + "'");
}
if (empty) {
writeAttributes();
writer.write("/>");
} else {
indent();
writer.write("</");
writer.write(name);
writer.write(">");
}
empty = false;
}
return this;
}
/**
* Write an attribute out for the current entity. Any XML characters in the
* value are escaped. Calls to attribute() MUST follow a call to startTag()
* immediately.
*
* @param localName
* name of attribute.
* @param value
* value of attribute.
* @return self
* @throws IOException
* on I/O error
* @throws IllegalStateException
* if opening tag is closed
*/
public XmlSerializer attribute(String localName, String value) throws IOException {
if (closed) {
throw new IllegalStateException("Opening tag is already closed");
}
if (localName == null) {
throw new IllegalArgumentException("Attribute name is null");
}
if (value == null)
value = "";
if (attrsWriter == null) {
attrsWriter = new StringWriter();
attrs = attrsWriter.getBuffer();
}
attrs.append(" ");
attrs.append(localName);
attrs.append("=\"");
writeEscape(value, attrsWriter);
attrs.append("\"");
return this;
}
/**
* Output body text. Any XML characters are escaped.
*
* @param text
* the body text
* @return self
* @throws IOException
* on I/O error
*/
public XmlSerializer text(String text) throws IOException {
closeOpeningTag();
if (text != null && text.length() > 0) {
indent();
empty = false;
writeEscape(text, writer);
}
return this;
}
/**
* Writes raw, CDATA section
*
* @param text
* the data
* @return self
* @throws IOException
* on I/O error
*/
public XmlSerializer cdsect(String text) throws IOException {
closeOpeningTag();
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
return this;
}
/**
* Writes XML comment code
*
* @param text
* the comment
* @return self
* @throws IOException
* on I/O error
*/
public XmlSerializer comment(String text) throws IOException {
closeOpeningTag();
writer.write("<!--");
writer.write(text);
writer.write("-->");
return this;
}
/**
* Writes DOCTYPE declaration. The document must be open prior calling that
* method.
*
* @param text
* declaration
* @return self
* @throws IOException
* on I/O error
* @throws IllegalStateException
* if document is not open or start tag is already started
*/
public XmlSerializer docdecl(String text) throws IOException {
if (!prologWritten) {
throw new IllegalStateException("Document is not open");
}
if (getDepth() != 0) {
throw new IllegalStateException("Cannot add DOCTYPE after start tag has been opened!");
}
closeOpeningTag();
writer.write("<!DOCTYPE");
writer.write(text);
writer.write(">");
return this;
}
/**
* Enables/disables indentation
*
* @param indent
* an indentation string or <code>null</code> to disable
* indentation.
*/
public void setIndent(boolean indent) {
this.indentString = indent ? "\t" : null;
}
// close off the opening tag
private void closeOpeningTag() throws IOException {
if (!this.closed) {
writeAttributes();
closed = true;
writer.write(">");
}
}
// write out all current attributes
private void writeAttributes() throws IOException {
if (attrs != null) {
writer.write(attrs.toString());
attrs.setLength(0);
empty = false;
}
}
private final String pop() {
int location = stack.size() - 1;
String ret = (String) stack.elementAt(location);
stack.removeElementAt(location);
return ret;
}
private final void indent() throws IOException {
if (indentString != null) {
writer.write('\n');
for (int i = 0; i < stack.size(); i++) {
writer.write(indentString);
}
}
}
private final void writeEscape(String text, Writer out) throws IOException {
// escape '<', '&', '>', ', "
char[] buf = text.toCharArray();
int len = buf.length;
int pos = 0;
for (int i = 0; i < len; i++) {
final char ch = buf[i];
if (ch == '&') {
if (i > pos)
out.write(buf, pos, i - pos);
out.write("&amp;");
pos = i + 1;
} else if (ch == '<') {
if (i > pos)
out.write(buf, pos, i - pos);
out.write("&lt;");
pos = i + 1;
} else if (ch == '>') {
if (i > pos)
out.write(buf, pos, i - pos);
out.write("&gt;");
pos = i + 1;
} else if (ch == '"') {
if (i > pos)
out.write(buf, pos, i - pos);
out.write("&guot;");
pos = i + 1;
} else if (ch == '\'') {
if (i > pos)
out.write(buf, pos, i - pos);
out.write("&apos;");
pos = i + 1;
} else if (ch < 32) {
// in XML 1.0 only legal character are #x9 | #xA | #xD
if (ch == 9 || ch == 10 || ch == 13) {
// pass through
} else {
throw new IllegalStateException("character " + ch + " (" + Integer.toString(ch) + ") is not allowed in output");
}
}
}
if (len > pos) {
out.write(buf, pos, len - pos);
}
}
/*
* public static void main(String[] args) throws IOException { XmlSerializer
* o = new XmlSerializer(); Writer x = new java.io.StringWriter();
* o.setIndent(true); o.setOutput(x); o.startDocument("UTF-8", null)//
* .startTag("test")// .startTag("inner")// .attribute("attribute",
* "value").text("text & ' < > \\\" ;[")// .comment("this is an embedded
* comment inside the inner body")// .endTag("inner")//
* .startTag("entry").cdsect("testing [] > < & ;").endTag("entry")//
* .endTag("test")// .endDocument(); System.out.println(x); }
*/
}