All pastes #165676 Raw Edit

Anonymous

public text v1 · immutable
·published 2006-09-10 01:55 UTC
rendered paste body
package com.freeone3000.xml;

import java.io.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack;

/**
 * This is an XML writer, more or less compliant with Microsoft's
 * .NET framework's XML writer. Note that this class is *not*
 * thread safe.
 *
 * @author freeone3000
 * @version Aug 26, 2006
 */
public class XmlWriter extends Writer {
    private final BufferedWriter write;
    private Stack<String> openNodes = new Stack<String>();
    private boolean documentIsOpen = false;
    private boolean inTag = false;
    private boolean inAttribute = false;

    private Map<String, String> replacements = new LinkedHashMap<String, String>();

    /**
     * Opens a new XmlWriter to the filename given, wrapping in a buffered
     * writer.
     * @param fname The filename to open.
     * @throws IOException If opening the stream causes an IOException.
     * @throws FileNotFoundException If the filename cannot be found.
     */
    public XmlWriter(String fname) throws IOException, FileNotFoundException {
        this(new File(fname));
    }

    /**
     * Opens a new XmlWriter to the File given, wrapping in a buffered
     * writer.
     * @param f The file to use.
     * @throws IOException If opening the stream causes an IOException.
     * @throws FileNotFoundException If the filename cannot be found.
     */
    public XmlWriter(File f) throws IOException, FileNotFoundException {
        this(new FileOutputStream(f));
    }

    /**
     * Opens a new XmlWriter to the OutputStream given, wrapping in a
     * buffered writer.
     * @param str The stream to use.
     */
    public XmlWriter(OutputStream str) {
        this(new OutputStreamWriter(str));
    }

    /**
     * Opens a new XmlWriter to the writer given, wrapping in a buffered
     * writer.
     * @param w The writer to use.
     */
    public XmlWriter(Writer w) {
        this.write = new BufferedWriter(w);
        setupReplacements();
    }
    private void setupReplacements() {
        replacements.put("&", "&amp;");
        replacements.put("<", "&lt;");
        replacements.put(">", "&gt;");
    }

    /**
     * Writes any number of raw strings to the document, with NO
     * CHECKING of any kind. For a checked manner, please see writeString.
     * @param str Any number of strings.
     * @throws IOException If the underlying writer throws an exception.
     * @see XmlWriter#writeString(java.lang.String)
     */
    public void writeRaw(String ... str) throws IOException {
        StringBuilder s = new StringBuilder();
        for(String s2 : str)
            s.append(s2);
        write.write(s.toString());
    }
    /**
     * Writes the start of an element. If a tag is open, it closes it.
     * @param elem The name of the element to write.
     * @throws IOException If the underlying writer throws an exception.
     * @throws XmlFormattingException If closing the tag throws an exception.
     */
    public void writeStartElement(String elem) throws IOException, XmlFormattingException {
        if(inTag) {
            if(inAttribute)
                writeEndAttribute();
            writeRaw(">");
        }
        inTag = true;
        elem = replaceEntities(elem);
        openNodes.push(elem);
        writeRaw("<" + elem);
    }

    /**
     * Ends one element, closing all other tags and attribuates if neccecary.
     * If we are in a tag, the tag is self-closed, otherwise it is tag closed.
     * @throws IOException If the writer throws an exception.
     * @throws XmlFormattingException If all nodes are already closed.
     */
    public void writeEndElement() throws IOException, XmlFormattingException {
        if(openNodes.empty())
            throw new XmlFormattingException("All nodes are closed!");
        if(inAttribute)
            writeEndAttribute();

        if(inTag) {
            writeRaw("/>");
            inTag = false;
        } else
            writeRaw("</" + replaceEntities(openNodes.pop()) + ">");
    }

    /**
     * If pubid and sysid are both non-null, it writes `PUBLIC "pubid" "sysid"'.
     * If pubid is null but sysid is not null, it writes `SYSTEM "sysid"'.
     * If subset is not null, it writes `[subset]'.
     * This results in a Doctype conformant to the .NET implementation.
     * @param name The name of the attribute to write. Must NOT be null or empty.
     * @param pubid Public ID. May be null.
     * @param sysid System ID. May be null.
     * @param subset Subset. May be null. No clue what it does.
     * @throws IOException If writing throws an exception.
     * @throws XmlFormattingException If this results in invalid Xml.
     */
    public void writeDocumentType(String name, String pubid, String sysid, String subset)
            throws IOException, XmlFormattingException {
        if(documentIsOpen)
            throw new XmlFormattingException("Cannot write the document type after the document is open!");
        writeRaw("<!DOCTYPE " + replaceEntities(name));
        writeWhitespace(" ");
        if(pubid!=null && sysid!=null)
            writeRaw("PUBLIC \"" + pubid + "\" \"" + sysid + "\"");
        else if(sysid!=null)
            writeRaw("SYSTEM \"" + sysid + "\"");
        writeWhitespace(" ");
        if(subset!=null)
            writeRaw("[" + subset + "] ");
        writeRaw(">");
        documentIsOpen = true;
    }

    /**
     * Writes a comment literal with the specified contents. If there is a tag open, the
     * tag is closed.
     * @param comment The comment to write.
     * @throws IOException If the underlying writing throws an exception.
     * @throws XmlFormattingException If ending the element throws a formatting exception.
     */
    public void writeComment(String comment) throws IOException, XmlFormattingException {
        if(inTag)
            writeEndElement();
        writeRaw("<!-- ");
        writeString(comment);
        writeRaw(" -->");
    }

    /**
     * Replaces all restricted characters in the string 's' with XML-complaint
     * characters.
     * @param s The string to replace.
     * @return The string after replacement.
     */
    private String replaceEntities(String s) {
        int pos;
        for(String str : replacements.keySet()) {
            pos = s.indexOf(str);
            if(pos!=-1) {
                s = s.replaceAll(str, replacements.get(str));
            }
        }
        return s;
    }

    /**
     * Writes an amount of whitespace, which must conform to the regex
     * "^\\s+$". It then uses writeRaw() to get around any other restrictions,
     * because whitespace cannot invalidate documents. This should be preferred
     * over writeString() to write whitespace.
     * @param whitespace A string of whitespace to write.
     * @throws IOException If writing throws an exception.
     * @throws IllegalArgumentException If the argument given does not conform to the regex.
     * @see XmlWriter#writeRaw(String...)
     */
    public void writeWhitespace(String whitespace) throws IOException {
        if(!whitespace.matches("^\\s+$"))
            throw new IllegalArgumentException("The string given was not whitespace!");
        writeRaw(whitespace);
    }

    /**
     * Writes the start of an attribute, with the name attributeName.
     * If it is not in a tag, it throws an XmlFormattingException, if it
     * is already in an attribute, it closes the old one.
     * @param attributeName The name of the attribute.
     * @throws IOException If writing throws an exception.
     * @throws XmlFormattingException If the XML becomes invalid.
     */
    public void writeStartAttribute(String attributeName) throws IOException, XmlFormattingException {
        if(inAttribute)
            writeEndAttribute();
        if(!inTag)
            throw new XmlFormattingException("You're not in a tag!");
        inAttribute = true;
        writeWhitespace(" ");
        writeRaw(replaceEntities(attributeName));
        writeRaw("=\"");
    }

    /**
     * Writes the end of an attribute. If there is no attribute open,
     * it fails silently.
     * @throws IOException If writing to the stream throws an exception.
     */
    public void writeEndAttribute() throws IOException {
        if(!inAttribute)
            return;
        writeRaw("\"");
        inAttribute = false;
    }

    /**
     * Writes a processing instruction.
     * @param name The name of the instruction.
     * @param parts An array, 1->2, 3->4... think perl.
     * @throws IOException If writing throws an exception.
     * @throws XmlFormattingException If an exception occors.
     */
    public void writeProcessingInstruction(String name, String ... parts) throws IOException,
            XmlFormattingException {
        writeRaw("<?");
        writeString(name);
        inTag = true;
        for(int i=0; i<parts.length; i+=2) {
            writeAttribute(parts[i], parts[i+1]);
        }
        inTag = false;
        writeRaw("?>");
    }

    /**
     * Writes an attribute, all at once.
     * @param name The name of the attribute.
     * @param value The value to write.
     * @throws IOException If writing throws an exception.
     * @throws XmlFormattingException If any of the methods called would invlaidate
     * the formatting.
     * @see XmlWriter#writeStartAttribute(java.lang.String)
     * @see XmlWriter#writeEndAttribute()
     */
    public void writeAttribute(String name, String value) throws IOException, XmlFormattingException {
        writeStartAttribute(name);
        writeString(value);
        writeEndAttribute();
    }

    /**
     * Writes data inside a parser-free block. As such, it uses the writeRaw() method,
     * as opposed to the writeString() method. If it is inside an element but not inside
     * an attribute, it closes the attribute.
     * @param text The text to write.
     * @throws IOException If writing throws an exception.
     * @throws XmlFormattingException If closing the tag would cause a formatting error.
     */
    public void writeCData(String text) throws IOException, XmlFormattingException {
        if(inTag && !inAttribute)
            writeEndElement();
        writeRaw("<![CDATA[");
        writeRaw(text);
        writeRaw("]]>");
    }

    /**
     * Writes the start of an XML1 document.
     * @throws IOException If the writer throws an exception.
     * @throws XmlFormattingException If the document has already started.
     */
    public void writeStartDocument() throws IOException, XmlFormattingException {
        if(documentIsOpen)
            throw new XmlFormattingException("The document is already started!");
        writeProcessingInstruction("xml", "version", "1.0", "encoding", "UTF-16");
    }

    /**
     * Closes each element in turn, then closes the stream. All
     * XmlFormattingExceptions are nonhandled.
     * @throws IOException If the writer throws an exception.
     * @see XmlWriter#writeEndElement()
     */
    @SuppressWarnings({"EmptyCatchBlock"})
    public void writeEndDocument() throws IOException {
        try {
            while(!openNodes.empty())
                writeEndElement();
        } catch(XmlFormattingException e) {
        }
        close();
    }

    /**
     * Safely writes a string, replacing all bad entities, or enclosing
     * in a CDATA block, and making sure it's not a direct write inside
     * an unclosed tag. If it would be, it automatically closes the tag.
     * @param str The string to write.
     * @throws IOException If writing throws an IOException.
     * @throws XmlFormattingException If closing the current tag would cause a XmlFormattingException
     */
    public void writeString(String str) throws IOException, XmlFormattingException {
        str = replaceEntities(str);
        if(inTag && !inAttribute)
            writeEndElement();
        writeRaw(str);
    }

    /**
     * Creates a new String from the character array, and passes it to
     * writeString.
     * @param chars The characters to write.
     * @param off The offset from the start.
     * @param len The length of the array to write.
     * @throws IOException If the writer throws an IOException.
     * @see XmlWriter#writeString
     * @see XmlWriter#writeRaw
     */
    public void write(char[] chars, int off, int len) throws IOException {
        char[] c2 = new char[len];
        System.arraycopy(chars, off, c2, 0, len);
        try {
            writeString(new String(c2));
        } catch(XmlFormattingException xfe) {
            throw new IOException("XmlFormattingException: " + xfe.getMessage());
        }
    }

    /**
     * Flushes the internal writer.
     * @throws IOException If the writer throws an exception.
     */
    public void flush() throws IOException {
        write.flush();
    }

    /**
     * Closes the internal writer.
     * @throws IOException If the writer throws an exception.
     */
    public void close() throws IOException {
        write.close();
    }
}