package org.freeplane.core.util; /* * XHTMLWriter -- A simple XHTML document writer (C) 2004 Richard "Shred" * Koerber http://www.shredzone.net/ This is free software. You can modify and * use it at will. */ import java.io.FileReader; import java.io.FileWriter; import java.io.FilterWriter; import java.io.IOException; import java.io.Reader; import java.io.Writer; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.StyleConstants; import javax.swing.text.html.HTML; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.Option; /** * Create a new XHTMLWriter which is able to write out a HTMLDocument as XHTML. * <p> * The result will be a valid XML file, but it is not granted that the file will * really be XHTML 1.0 transitional conformous. The basic purpose of this class * is to give an XSL processor access to plain HTML files. * * @author Richard "Shred" Koerber */ class XHTMLWriter extends FixedHTMLWriter { /** * This FilterWriter will convert the output of Swing's HTMLWriter to XHTML * format. This is done by converting tags like <br> to * <br />. Also, special characters in tag attributes are * escaped. * <p> * This filter relies on known flaws of the HTMLWriter. It is known to work * with Java 1.4, but might not work with future Java releases. */ public static class XHTMLFilterWriter extends FilterWriter { private boolean insideTag = false; private boolean insideValue = false; /** * Create a new XHTMLFilterWriter. * * @param writer * Writer to write to */ public XHTMLFilterWriter(final Writer writer) { super(writer); } /** * Write a char array to the Writer. * * @param cbuf * Char array to be written * @param off * Start offset within the array * @param len * Number of chars to be written */ @Override public void write(final char[] cbuf, int off, int len) throws IOException { while (len-- > 0) { write(cbuf[off++]); } } /** * Write a single char to the Writer. * * @param c * Char to be written */ @Override public void write(final int c) throws IOException { if (insideValue) { if (c == '&') { super.write("&", 0, 5); return; } else if (c == '<') { super.write("<", 0, 4); return; } else if (c == '>') { super.write(">", 0, 4); return; } else if (c == '"') { insideValue = false; } } else if (insideTag) { if (c == '"') { insideValue = true; } else if (c == '>') { insideTag = false; } } else if (c == '<') { insideTag = true; } if (c > 126) { super.write("&#x", 0, 3); super.write(Integer.toString(c, 16)); super.write(';'); return; } super.write(c); } /** * Write a String to the Writer. * * @param str * String to be written * @param off * Start offset within the String * @param len * Number of chars to be written */ @Override public void write(final String str, final int off, final int len) throws IOException { write(str.toCharArray(), off, len); } } /** * Read HTML from the Reader, and send XHTML to the writer. Common mistakes * in the HTML code will also be corrected. The result is pretty-printed. * * @param reader * HTML source * @param writer * XHTML target */ public static void html2xhtml(final Reader reader, final Writer writer) throws IOException, BadLocationException { final HTMLEditorKit kit = new HTMLEditorKit(); final Document doc = kit.createDefaultDocument(); kit.read(reader, doc, doc.getLength()); final XHTMLWriter xhw = new XHTMLWriter(writer, (HTMLDocument) doc); xhw.write(); } /** * External call to convert a source HTML file to a target XHTML file. * <p> * Usage: <tt>java XHTMLWriter <source file> <target file></tt> * * @param args * Shell arguments */ public static void main(final String[] args) { try { final FileReader reader = new FileReader(args[0]); final FileWriter writer = new FileWriter(args[1]); XHTMLWriter.html2xhtml(reader, writer); writer.close(); reader.close(); } catch (final Exception e) { LogUtils.severe(e); } } private boolean writeLineSeparatorEnabled = true; private boolean insideEmptyTag; /** * Create a new XHTMLWriter that will write the entire HTMLDocument. * * @param writer * Writer to write to * @param doc * Source document */ public XHTMLWriter(final Writer writer, final HTMLDocument doc) { this(writer, doc, 0, doc.getLength()); } /** * Create a new XHTMLWriter that will write a part of a HTMLDocument. * * @param writer * Writer to write to * @param doc * Source document * @param pos * Starting position * @param len * Length */ public XHTMLWriter(final Writer writer, final HTMLDocument doc, final int pos, final int len) { super(new XHTMLFilterWriter(writer), doc, pos, len); setLineLength(Integer.MAX_VALUE); } /** * Start the writing process. An XML and DOCTYPE header will be written * prior to the XHTML output. */ @Override public void write() throws IOException, BadLocationException { super.write(); } @Override protected void writeLineSeparator() throws IOException { if (writeLineSeparatorEnabled) { super.writeLineSeparator(); } } @Override protected void writeOption(final Option option) throws IOException { writeLineSeparatorEnabled = false; super.writeOption(option); writeLineSeparatorEnabled = true; write("</option>"); writeLineSeparator(); } /* (non-Javadoc) * @see javax.swing.text.html.HTMLWriter#writeAttributes(javax.swing.text.AttributeSet) */ @Override protected void writeAttributes(final AttributeSet attributeSet) throws IOException { final Object nameTag = (attributeSet != null) ? attributeSet.getAttribute(StyleConstants.NameAttribute) : null; final Object endTag = (attributeSet != null) ? attributeSet.getAttribute(HTML.Attribute.ENDTAG) : null; // write no attributes for end tags if (nameTag != null && endTag != null && (endTag instanceof String) && ((String) endTag).equals("true")) { return; } super.writeAttributes(attributeSet); if (insideEmptyTag) { write('/'); } } // remove invalid characters @Override protected void output(final char[] chars, final int start, final int length) throws IOException { for (int i = start; i < start + length; i++) { final char c = chars[i]; if (c < 32 && c != '\r' && c != '\n' && c != '\t') { chars[i] = ' '; } } super.output(chars, start, length); } @Override protected void emptyTag(final Element elem) throws BadLocationException, IOException { try { final boolean isEndtag = isEndtag(elem); final int balance = balance(elem, isEndtag); if (balance == 0 || balance > 0 && isEndtag || balance < 0 && !isEndtag) { super.emptyTag(elem); return; } if (isEndtag) { write('<'); write(elem.getName()); write('/'); write('>'); return; } insideEmptyTag = true; super.emptyTag(elem); } finally { insideEmptyTag = false; } } private int balance(final Element elem, final boolean isEndtag) { final Element parentElement = elem.getParentElement(); final int elementCount = parentElement.getElementCount(); int balance = 0; final String elemName = elem.getName(); for (int i = 0; i < elementCount; i++) { final Element childElement = parentElement.getElement(i); if (isEndtag) { if (childElement.equals(elem)) { balance--; break; } } else { if (childElement.equals(elem)) { balance = 1; continue; } if (balance == 0) { continue; } } if (!elemName.equals(childElement.getName())) { continue; } if (isEndtag(childElement)) { if (balance > 0) { balance--; continue; } } else { balance++; } } return balance; } private boolean isEndtag(final Element elem) { final AttributeSet attributes = elem.getAttributes(); final Object endTag = attributes.getAttribute(HTML.Attribute.ENDTAG); final boolean isEndtag = (endTag instanceof String) && ((String) endTag).equals("true"); return isEndtag; } @Override protected void closeOutUnwantedEmbeddedTags(final AttributeSet attr) throws IOException { final boolean insideEmptyTag = this.insideEmptyTag; this.insideEmptyTag = false; try { super.closeOutUnwantedEmbeddedTags(attr); } finally { this.insideEmptyTag = insideEmptyTag; } } @Override protected void writeEmbeddedTags(final AttributeSet attr) throws IOException { final boolean insideEmptyTag = this.insideEmptyTag; this.insideEmptyTag = false; try { super.writeEmbeddedTags(attr); } finally { this.insideEmptyTag = insideEmptyTag; } } }