- /*
- * @(#)MinimalHTMLWriter.java 1.14 03/12/19
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
- 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
- * @version 1.14, 12/19/03
- */
-
- 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'));
- }
- }