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