/* * Copyright (c) 1998, 1999, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing.text.html; import java.io.Writer; import java.io.IOException; import java.util.*; import java.awt.Color; import javax.swing.text.*; /** * MinimalHTMLWriter is a fallback writer used by the * HTMLEditorKit to write out HTML for a document that * is a not produced by the EditorKit. * * The format for the document is: * <pre> * <html> * <head> * <style> * <!-- list of named styles * p.normal { * font-family: SansSerif; * margin-height: 0; * font-size: 14 * } * --> * </style> * </head> * <body> * <p style=normal> * <b>Bold, italic, and underline attributes * of the run are emitted as HTML tags. * The remaining attributes are emitted as * part of the style attribute of a <span> tag. * The syntax is similar to inline styles.</b> * </p> * </body> * </html> * </pre> * * @author Sunita Mani */ public class MinimalHTMLWriter extends AbstractWriter { /** * These static finals are used to * tweak and query the fontMask about which * of these tags need to be generated or * terminated. */ private static final int BOLD = 0x01; private static final int ITALIC = 0x02; private static final int UNDERLINE = 0x04; // Used to map StyleConstants to CSS. private static final CSS css = new CSS(); private int fontMask = 0; int startOffset = 0; int endOffset = 0; /** * Stores the attributes of the previous run. * Used to compare with the current run's * attributeset. If identical, then a * <span> tag is not emitted. */ private AttributeSet fontAttributes; /** * Maps from style name as held by the Document, to the archived * style name (style name written out). These may differ. */ private Hashtable styleNameMapping; /** * Creates a new MinimalHTMLWriter. * * @param w Writer * @param doc StyledDocument * */ public MinimalHTMLWriter(Writer w, StyledDocument doc) { super(w, doc); } /** * Creates a new MinimalHTMLWriter. * * @param w Writer * @param doc StyledDocument * @param pos The location in the document to fetch the * content. * @param len The amount to write out. * */ public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len) { super(w, doc, pos, len); } /** * Generates HTML output * from a StyledDocument. * * @exception IOException on any I/O error * @exception BadLocationException if pos represents an invalid * location within the document. * */ public void write() throws IOException, BadLocationException { styleNameMapping = new Hashtable(); writeStartTag("<html>"); writeHeader(); writeBody(); writeEndTag("</html>"); } /** * Writes out all the attributes for the * following types: * StyleConstants.ParagraphConstants, * StyleConstants.CharacterConstants, * StyleConstants.FontConstants, * StyleConstants.ColorConstants. * The attribute name and value are separated by a colon. * Each pair is separated by a semicolon. * * @exception IOException on any I/O error */ protected void writeAttributes(AttributeSet attr) throws IOException { Enumeration attributeNames = attr.getAttributeNames(); while (attributeNames.hasMoreElements()) { Object name = attributeNames.nextElement(); if ((name instanceof StyleConstants.ParagraphConstants) || (name instanceof StyleConstants.CharacterConstants) || (name instanceof StyleConstants.FontConstants) || (name instanceof StyleConstants.ColorConstants)) { indent(); write(name.toString()); write(':'); write(css.styleConstantsValueToCSSValue ((StyleConstants)name, attr.getAttribute(name)). toString()); write(';'); write(NEWLINE); } } } /** * Writes out text. * * @exception IOException on any I/O error */ protected void text(Element elem) throws IOException, BadLocationException { String contentStr = getText(elem); if ((contentStr.length() > 0) && (contentStr.charAt(contentStr.length()-1) == NEWLINE)) { contentStr = contentStr.substring(0, contentStr.length()-1); } if (contentStr.length() > 0) { write(contentStr); } } /** * Writes out a start tag appropriately * indented. Also increments the indent level. * * @exception IOException on any I/O error */ protected void writeStartTag(String tag) throws IOException { indent(); write(tag); write(NEWLINE); incrIndent(); } /** * Writes out an end tag appropriately * indented. Also decrements the indent level. * * @exception IOException on any I/O error */ protected void writeEndTag(String endTag) throws IOException { decrIndent(); indent(); write(endTag); write(NEWLINE); } /** * Writes out the <head> and <style> * tags, and then invokes writeStyles() to write * out all the named styles as the content of the * <style> tag. The content is surrounded by * valid HTML comment markers to ensure that the * document is viewable in applications/browsers * that do not support the tag. * * @exception IOException on any I/O error */ protected void writeHeader() throws IOException { writeStartTag("<head>"); writeStartTag("<style>"); writeStartTag("<!--"); writeStyles(); writeEndTag("-->"); writeEndTag("</style>"); writeEndTag("</head>"); } /** * Writes out all the named styles as the * content of the <style> tag. * * @exception IOException on any I/O error */ protected void writeStyles() throws IOException { /* * Access to DefaultStyledDocument done to workaround * a missing API in styled document to access the * stylenames. */ DefaultStyledDocument styledDoc = ((DefaultStyledDocument)getDocument()); Enumeration styleNames = styledDoc.getStyleNames(); while (styleNames.hasMoreElements()) { Style s = styledDoc.getStyle((String)styleNames.nextElement()); /** PENDING: Once the name attribute is removed from the list we check check for 0. **/ if (s.getAttributeCount() == 1 && s.isDefined(StyleConstants.NameAttribute)) { continue; } indent(); write("p." + addStyleName(s.getName())); write(" {\n"); incrIndent(); writeAttributes(s); decrIndent(); indent(); write("}\n"); } } /** * Iterates over the elements in the document * and processes elements based on whether they are * branch elements or leaf elements. This method specially handles * leaf elements that are text. * * @exception IOException on any I/O error */ protected void writeBody() throws IOException, BadLocationException { ElementIterator it = getElementIterator(); /* This will be a section element for a styled document. We represent this element in HTML as the body tags. Therefore we ignore it. */ it.current(); Element next = null; writeStartTag("<body>"); boolean inContent = false; while((next = it.next()) != null) { if (!inRange(next)) { continue; } if (next instanceof AbstractDocument.BranchElement) { if (inContent) { writeEndParagraph(); inContent = false; fontMask = 0; } writeStartParagraph(next); } else if (isText(next)) { writeContent(next, !inContent); inContent = true; } else { writeLeaf(next); inContent = true; } } if (inContent) { writeEndParagraph(); } writeEndTag("</body>"); } /** * Emits an end tag for a <p> * tag. Before writing out the tag, this method ensures * that all other tags that have been opened are * appropriately closed off. * * @exception IOException on any I/O error */ protected void writeEndParagraph() throws IOException { writeEndMask(fontMask); if (inFontTag()) { endSpanTag(); } else { write(NEWLINE); } writeEndTag("</p>"); } /** * Emits the start tag for a paragraph. If * the paragraph has a named style associated with it, * then this method also generates a class attribute for the * <p> tag and sets its value to be the name of the * style. * * @exception IOException on any I/O error */ protected void writeStartParagraph(Element elem) throws IOException { AttributeSet attr = elem.getAttributes(); Object resolveAttr = attr.getAttribute(StyleConstants.ResolveAttribute); if (resolveAttr instanceof StyleContext.NamedStyle) { writeStartTag("<p class=" + mapStyleName(((StyleContext.NamedStyle)resolveAttr).getName()) + ">"); } else { writeStartTag("<p>"); } } /** * Responsible for writing out other non-text leaf * elements. * * @exception IOException on any I/O error */ protected void writeLeaf(Element elem) throws IOException { indent(); if (elem.getName() == StyleConstants.IconElementName) { writeImage(elem); } else if (elem.getName() == StyleConstants.ComponentElementName) { writeComponent(elem); } } /** * Responsible for handling Icon Elements; * deliberately unimplemented. How to implement this method is * an issue of policy. For example, if you're generating * an <img> tag, how should you * represent the src attribute (the location of the image)? * In certain cases it could be a URL, in others it could * be read from a stream. * * @param elem element of type StyleConstants.IconElementName */ protected void writeImage(Element elem) throws IOException { } /** * Responsible for handling Component Elements; * deliberately unimplemented. * How this method is implemented is a matter of policy. */ protected void writeComponent(Element elem) throws IOException { } /** * Returns true if the element is a text element. * */ protected boolean isText(Element elem) { return (elem.getName() == AbstractDocument.ContentElementName); } /** * Writes out the attribute set * in an HTML-compliant manner. * * @exception IOException on any I/O error * @exception BadLocationException if pos represents an invalid * location within the document. */ protected void writeContent(Element elem, boolean needsIndenting) throws IOException, BadLocationException { AttributeSet attr = elem.getAttributes(); writeNonHTMLAttributes(attr); if (needsIndenting) { indent(); } writeHTMLTags(attr); text(elem); } /** * Generates * bold <b>, italic <i>, and <u> tags for the * text based on its attribute settings. * * @exception IOException on any I/O error */ protected void writeHTMLTags(AttributeSet attr) throws IOException { int oldMask = fontMask; setFontMask(attr); int endMask = 0; int startMask = 0; if ((oldMask & BOLD) != 0) { if ((fontMask & BOLD) == 0) { endMask |= BOLD; } } else if ((fontMask & BOLD) != 0) { startMask |= BOLD; } if ((oldMask & ITALIC) != 0) { if ((fontMask & ITALIC) == 0) { endMask |= ITALIC; } } else if ((fontMask & ITALIC) != 0) { startMask |= ITALIC; } if ((oldMask & UNDERLINE) != 0) { if ((fontMask & UNDERLINE) == 0) { endMask |= UNDERLINE; } } else if ((fontMask & UNDERLINE) != 0) { startMask |= UNDERLINE; } writeEndMask(endMask); writeStartMask(startMask); } /** * Tweaks the appropriate bits of fontMask * to reflect whether the text is to be displayed in * bold, italic, and/or with an underline. * */ private void setFontMask(AttributeSet attr) { if (StyleConstants.isBold(attr)) { fontMask |= BOLD; } if (StyleConstants.isItalic(attr)) { fontMask |= ITALIC; } if (StyleConstants.isUnderline(attr)) { fontMask |= UNDERLINE; } } /** * Writes out start tags <u>, <i>, and <b> based on * the mask settings. * * @exception IOException on any I/O error */ private void writeStartMask(int mask) throws IOException { if (mask != 0) { if ((mask & UNDERLINE) != 0) { write("<u>"); } if ((mask & ITALIC) != 0) { write("<i>"); } if ((mask & BOLD) != 0) { write("<b>"); } } } /** * Writes out end tags for <u>, <i>, and <b> based on * the mask settings. * * @exception IOException on any I/O error */ private void writeEndMask(int mask) throws IOException { if (mask != 0) { if ((mask & BOLD) != 0) { write("</b>"); } if ((mask & ITALIC) != 0) { write("</i>"); } if ((mask & UNDERLINE) != 0) { write("</u>"); } } } /** * Writes out the remaining * character-level attributes (attributes other than bold, * italic, and underline) in an HTML-compliant way. Given that * attributes such as font family and font size have no direct * mapping to HTML tags, a <span> tag is generated and its * style attribute is set to contain the list of remaining * attributes just like inline styles. * * @exception IOException on any I/O error */ protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException { String style = ""; String separator = "; "; if (inFontTag() && fontAttributes.isEqual(attr)) { return; } boolean first = true; Color color = (Color)attr.getAttribute(StyleConstants.Foreground); if (color != null) { style += "color: " + css.styleConstantsValueToCSSValue ((StyleConstants)StyleConstants.Foreground, color); first = false; } Integer size = (Integer)attr.getAttribute(StyleConstants.FontSize); if (size != null) { if (!first) { style += separator; } style += "font-size: " + size.intValue() + "pt"; first = false; } String family = (String)attr.getAttribute(StyleConstants.FontFamily); if (family != null) { if (!first) { style += separator; } style += "font-family: " + family; first = false; } if (style.length() > 0) { if (fontMask != 0) { writeEndMask(fontMask); fontMask = 0; } startSpanTag(style); fontAttributes = attr; } else if (fontAttributes != null) { writeEndMask(fontMask); fontMask = 0; endSpanTag(); } } /** * Returns true if we are currently in a <font> tag. */ protected boolean inFontTag() { return (fontAttributes != null); } /** * This is no longer used, instead <span> will be written out. * <p> * Writes out an end tag for the <font> tag. * * @exception IOException on any I/O error */ protected void endFontTag() throws IOException { write(NEWLINE); writeEndTag("</font>"); fontAttributes = null; } /** * This is no longer used, instead <span> will be written out. * <p> * Writes out a start tag for the <font> tag. * Because font tags cannot be nested, * this method closes out * any enclosing font tag before writing out a * new start tag. * * @exception IOException on any I/O error */ protected void startFontTag(String style) throws IOException { boolean callIndent = false; if (inFontTag()) { endFontTag(); callIndent = true; } writeStartTag("<font style=\"" + style + "\">"); if (callIndent) { indent(); } } /** * Writes out a start tag for the <font> tag. * Because font tags cannot be nested, * this method closes out * any enclosing font tag before writing out a * new start tag. * * @exception IOException on any I/O error */ private void startSpanTag(String style) throws IOException { boolean callIndent = false; if (inFontTag()) { endSpanTag(); callIndent = true; } writeStartTag("<span style=\"" + style + "\">"); if (callIndent) { indent(); } } /** * Writes out an end tag for the <span> tag. * * @exception IOException on any I/O error */ private void endSpanTag() throws IOException { write(NEWLINE); writeEndTag("</span>"); fontAttributes = null; } /** * Adds the style named <code>style</code> to the style mapping. This * returns the name that should be used when outputting. CSS does not * allow the full Unicode set to be used as a style name. */ private String addStyleName(String style) { if (styleNameMapping == null) { return style; } StringBuffer sb = null; for (int counter = style.length() - 1; counter >= 0; counter--) { if (!isValidCharacter(style.charAt(counter))) { if (sb == null) { sb = new StringBuffer(style); } sb.setCharAt(counter, 'a'); } } String mappedName = (sb != null) ? sb.toString() : style; while (styleNameMapping.get(mappedName) != null) { mappedName = mappedName + 'x'; } styleNameMapping.put(style, mappedName); return mappedName; } /** * Returns the mapped style name corresponding to <code>style</code>. */ private String mapStyleName(String style) { if (styleNameMapping == null) { return style; } String retValue = (String)styleNameMapping.get(style); return (retValue == null) ? style : retValue; } private boolean isValidCharacter(char character) { return ((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z')); } }