package com.freeone3000.xml; public class XmlFormattingException extends IOException { public XmlFormattingException(String str) { super(str); } } --- 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 openNodes = new Stack(); private boolean documentIsOpen = false; private boolean inTag = false; private boolean inAttribute = false; private Map replacements = new LinkedHashMap(); /** * 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("&", "&"); replacements.put("<", "<"); replacements.put(">", ">"); } /** * 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(""); } /** * 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(""); 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(""); } /** * 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(""); } /** * 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(""); } /** * 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(); } }