       1 /*
       2  * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
       3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4  *
       5  * This code is free software; you can redistribute it and/or modify it
       6  * under the terms of the GNU General Public License version 2 only, as
       7  * published by the Free Software Foundation.  Oracle designates this
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25 package javax.swing.text.html;
      26
      27 import javax.swing.text.*;
      28 import java.io.Writer;
      29 import java.util.Stack;
      30 import java.util.Enumeration;
      31 import java.util.Vector;
      32 import java.io.IOException;
      33 import java.util.StringTokenizer;
      34 import java.util.NoSuchElementException;
      35 import java.net.URL;
      36
      37 /**
      38  * This is a writer for HTMLDocuments.
      39  *
      40  * @author  Sunita Mani
      41  */
      42
      43
      44 public class HTMLWriter extends AbstractWriter {
      45     /*
      46      * Stores all elements for which end tags have to
      47      * be emitted.
      48      */
      49     private Stack<Element> blockElementStack = new Stack<Element>();
      50     private boolean inContent = false;
      51     private boolean inPre = false;
      52     /** When inPre is true, this will indicate the end offset of the pre
      53      * element. */
      54     private int preEndOffset;
      55     private boolean inTextArea = false;
      56     private boolean newlineOutputed = false;
      57     private boolean completeDoc;
      58
      59     /*
      60      * Stores all embedded tags. Embedded tags are tags that are
      61      * stored as attributes in other tags. Generally they're
      62      * character level attributes.  Examples include
      63      * &lt;b&gt;, &lt;i&gt;, &lt;font&gt;, and &lt;a&gt;.
      64      */
      65     private Vector<HTML.Tag> tags = new Vector<HTML.Tag>(10);
      66
      67     /**
      68      * Values for the tags.
      69      */
      70     private Vector<Object> tagValues = new Vector<Object>(10);
      71
      72     /**
      73      * Used when writing out content.
      74      */
      75     private Segment segment;
      76
      77     /*
      78      * This is used in closeOutUnwantedEmbeddedTags.
      79      */
      80     private Vector<HTML.Tag> tagsToRemove = new Vector<HTML.Tag>(10);
      81
      82     /**
      83      * Set to true after the head has been output.
      84      */
      85     private boolean wroteHead;
      86
      87     /**
      88      * Set to true when entities (such as &lt;) should be replaced.
      89      */
      90     private boolean replaceEntities;
      91
      92     /**
      93      * Temporary buffer.
      94      */
      95     private char[] tempChars;
      96
      97
      98     /**
      99      * Creates a new HTMLWriter.
     100      *
     101      * @param w   a Writer
     102      * @param doc  an HTMLDocument
     103      *
     104      */
     105     public HTMLWriter(Writer w, HTMLDocument doc) {
     106         this(w, doc, 0, doc.getLength());
     107     }
     108
     109     /**
     110      * Creates a new HTMLWriter.
     111      *
     112      * @param w  a Writer
     113      * @param doc an HTMLDocument
     114      * @param pos the document location from which to fetch the content
     115      * @param len the amount to write out
     116      */
     117     public HTMLWriter(Writer w, HTMLDocument doc, int pos, int len) {
     118         super(w, doc, pos, len);
     119         completeDoc = (pos == 0 && len == doc.getLength());
     120         setLineLength(80);
     121     }
     122
     123     /**
     124      * Iterates over the
     125      * Element tree and controls the writing out of
     126      * all the tags and its attributes.
     127      *
     128      * @exception IOException on any I/O error
     129      * @exception BadLocationException if pos represents an invalid
     130      *            location within the document.
     131      *
     132      */
     133     public void write() throws IOException, BadLocationException {
     134         ElementIterator it = getElementIterator();
     135         Element current = null;
     136         Element next;
     137
     138         wroteHead = false;
     139         setCurrentLineLength(0);
     140         replaceEntities = false;
     141         setCanWrapLines(false);
     142         if (segment == null) {
     143             segment = new Segment();
     144         }
     145         inPre = false;
     146         boolean forcedBody = false;
     147         while ((next = it.next()) != null) {
     148             if (!inRange(next)) {
     149                 if (completeDoc && next.getAttributes().getAttribute(
     150                         StyleConstants.NameAttribute) == HTML.Tag.BODY) {
     151                     forcedBody = true;
     152                 }
     153                 else {
     154                     continue;
     155                 }
     156             }
     157             if (current != null) {
     158
     159                 /*
     160                   if next is child of current increment indent
     161                 */
     162
     163                 if (indentNeedsIncrementing(current, next)) {
     164                     incrIndent();
     165                 } else if (current.getParentElement() != next.getParentElement()) {
     166                     /*
     167                        next and current are not siblings
     168                        so emit end tags for items on the stack until the
     169                        item on top of the stack, is the parent of the
     170                        next.
     171                     */
     172                     Element top = blockElementStack.peek();
     173                     while (top != next.getParentElement()) {
     174                         /*
     175                            pop() will return top.
     176                         */
     177                         blockElementStack.pop();
     178                         if (!synthesizedElement(top)) {
     179                             AttributeSet attrs = top.getAttributes();
     180                             if (!matchNameAttribute(attrs, HTML.Tag.PRE) &&
     181                                 !isFormElementWithContent(attrs) &&
     182                                 !isPreTagWithParagraphTag(attrs)) {
     183                                 decrIndent();
     184                             }
     185                             endTag(top);
     186                         }
     187                         top = blockElementStack.peek();
     188                     }
     189                 } else if (current.getParentElement() == next.getParentElement()) {
     190                     /*
     191                        if next and current are siblings the indent level
     192                        is correct.  But, we need to make sure that if current is
     193                        on the stack, we pop it off, and put out its end tag.
     194                     */
     195                     Element top = blockElementStack.peek();
     196                     if (top == current) {
     197                         blockElementStack.pop();
     198                         endTag(top);
     199                     }
     200                 }
     201             }
     202             if (!next.isLeaf() || isFormElementWithContent(next.getAttributes())) {
     203                 blockElementStack.push(next);
     204                 startTag(next);
     205             } else {
     206                 emptyTag(next);
     207             }
     208             current = next;
     209         }
     210         /* Emit all remaining end tags */
     211
     212         /* A null parameter ensures that all embedded tags
     213            currently in the tags vector have their
     214            corresponding end tags written out.
     215         */
     216         closeOutUnwantedEmbeddedTags(null);
     217
     218         if (forcedBody) {
     219             blockElementStack.pop();
     220             endTag(current);
     221         }
     222         while (!blockElementStack.empty()) {
     223             current = blockElementStack.pop();
     224             if (!synthesizedElement(current)) {
     225                 AttributeSet attrs = current.getAttributes();
     226                 if (!matchNameAttribute(attrs, HTML.Tag.PRE) &&
     227                               !isFormElementWithContent(attrs) &&
     228                               !isPreTagWithParagraphTag(attrs)) {
     229                     decrIndent();
     230                 }
     231                 endTag(current);
     232             }
     233         }
     234
     235         if (completeDoc) {
     236             writeAdditionalComments();
     237         }
     238
     239         segment.array = null;
     240     }
     241
     242
     243     /**
     244      * Writes out the attribute set.  Ignores all
     245      * attributes with a key of type HTML.Tag,
     246      * attributes with a key of type StyleConstants,
     247      * and attributes with a key of type
     248      * HTML.Attribute.ENDTAG.
     249      *
     250      * @param attr   an AttributeSet
     251      * @exception IOException on any I/O error
     252      *
     253      */
     254     protected void writeAttributes(AttributeSet attr) throws IOException {
     255         // translate css attributes to html
     256         convAttr.removeAttributes(convAttr);
     257         convertToHTML32(attr, convAttr);
     258
     259         Enumeration<?> names = convAttr.getAttributeNames();
     260         while (names.hasMoreElements()) {
     261             Object name = names.nextElement();
     262             if (name instanceof HTML.Tag ||
     263                 name instanceof StyleConstants ||
     264                 name == HTML.Attribute.ENDTAG) {
     265                 continue;
     266             }
     267             write(" " + name + "=\"" + convAttr.getAttribute(name) + "\"");
     268         }
     269     }
     270
     271     /**
     272      * Writes out all empty elements (all tags that have no
     273      * corresponding end tag).
     274      *
     275      * @param elem   an Element
     276      * @exception IOException on any I/O error
     277      * @exception BadLocationException if pos represents an invalid
     278      *            location within the document.
     279      */
     280     protected void emptyTag(Element elem) throws BadLocationException, IOException {
     281
     282         if (!inContent && !inPre) {
     283             indentSmart();
     284         }
     285
     286         AttributeSet attr = elem.getAttributes();
     287         closeOutUnwantedEmbeddedTags(attr);
     288         writeEmbeddedTags(attr);
     289
     290         if (matchNameAttribute(attr, HTML.Tag.CONTENT)) {
     291             inContent = true;
     292             text(elem);
     293         } else if (matchNameAttribute(attr, HTML.Tag.COMMENT)) {
     294             comment(elem);
     295         }  else {
     296             boolean isBlock = isBlockTag(elem.getAttributes());
     297             if (inContent && isBlock ) {
     298                 writeLineSeparator();
     299                 indentSmart();
     300             }
     301
     302             Object nameTag = (attr != null) ? attr.getAttribute
     303                               (StyleConstants.NameAttribute) : null;
     304             Object endTag = (attr != null) ? attr.getAttribute
     305                               (HTML.Attribute.ENDTAG) : null;
     306
     307             boolean outputEndTag = false;
     308             // If an instance of an UNKNOWN Tag, or an instance of a
     309             // tag that is only visible during editing
     310             //
     311             if (nameTag != null && endTag != null &&
     312                 (endTag instanceof String) &&
     313                 endTag.equals("true")) {
     314                 outputEndTag = true;
     315             }
     316
     317             if (completeDoc && matchNameAttribute(attr, HTML.Tag.HEAD)) {
     318                 if (outputEndTag) {
     319                     // Write out any styles.
     320                     writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
     321                 }
     322                 wroteHead = true;
     323             }
     324
     325             write('<');
     326             if (outputEndTag) {
     327                 write('/');
     328             }
     329             write(elem.getName());
     330             writeAttributes(attr);
     331             write('>');
     332             if (matchNameAttribute(attr, HTML.Tag.TITLE) && !outputEndTag) {
     333                 Document doc = elem.getDocument();
     334                 String title = (String)doc.getProperty(Document.TitleProperty);
     335                 write(title);
     336             } else if (!inContent || isBlock) {
     337                 writeLineSeparator();
     338                 if (isBlock && inContent) {
     339                     indentSmart();
     340                 }
     341             }
     342         }
     343     }
     344
     345     /**
     346      * Determines if the HTML.Tag associated with the
     347      * element is a block tag.
     348      *
     349      * @param attr  an AttributeSet
     350      * @return  true if tag is block tag, false otherwise.
     351      */
     352     protected boolean isBlockTag(AttributeSet attr) {
     353         Object o = attr.getAttribute(StyleConstants.NameAttribute);
     354         if (o instanceof HTML.Tag) {
     355             HTML.Tag name = (HTML.Tag) o;
     356             return name.isBlock();
     357         }
     358         return false;
     359     }
     360
     361
     362     /**
     363      * Writes out a start tag for the element.
     364      * Ignores all synthesized elements.
     365      *
     366      * @param elem an Element
     367      * @throws IOException on any I/O error
     368      * @throws BadLocationException if pos represents an invalid
     369      *            location within the document.
     370      */
     371     protected void startTag(Element elem) throws IOException, BadLocationException {
     372
     373         if (synthesizedElement(elem)) {
     374             return;
     375         }
     376
     377         // Determine the name, as an HTML.Tag.
     378         AttributeSet attr = elem.getAttributes();
     379         Object nameAttribute = attr.getAttribute(StyleConstants.NameAttribute);
     380         HTML.Tag name;
     381         if (nameAttribute instanceof HTML.Tag) {
     382             name = (HTML.Tag)nameAttribute;
     383         }
     384         else {
     385             name = null;
     386         }
     387
     388         if (name == HTML.Tag.PRE) {
     389             inPre = true;
     390             preEndOffset = elem.getEndOffset();
     391         }
     392
     393         // write out end tags for item on stack
     394         closeOutUnwantedEmbeddedTags(attr);
     395
     396         if (inContent) {
     397             writeLineSeparator();
     398             inContent = false;
     399             newlineOutputed = false;
     400         }
     401
     402         if (completeDoc && name == HTML.Tag.BODY && !wroteHead) {
     403             // If the head has not been output, output it and the styles.
     404             wroteHead = true;
     405             indentSmart();
     406             write("<head>");
     407             writeLineSeparator();
     408             incrIndent();
     409             writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
     410             decrIndent();
     411             writeLineSeparator();
     412             indentSmart();
     413             write("</head>");
     414             writeLineSeparator();
     415         }
     416
     417         indentSmart();
     418         write('<');
     419         write(elem.getName());
     420         writeAttributes(attr);
     421         write('>');
     422         if (name != HTML.Tag.PRE) {
     423             writeLineSeparator();
     424         }
     425
     426         if (name == HTML.Tag.TEXTAREA) {
     427             textAreaContent(elem.getAttributes());
     428         } else if (name == HTML.Tag.SELECT) {
     429             selectContent(elem.getAttributes());
     430         } else if (completeDoc && name == HTML.Tag.BODY) {
     431             // Write out the maps, which is not stored as Elements in
     432             // the Document.
     433             writeMaps(((HTMLDocument)getDocument()).getMaps());
     434         }
     435         else if (name == HTML.Tag.HEAD) {
     436             HTMLDocument document = (HTMLDocument)getDocument();
     437             wroteHead = true;
     438             incrIndent();
     439             writeStyles(document.getStyleSheet());
     440             if (document.hasBaseTag()) {
     441                 indentSmart();
     442                 write("<base href=\"" + document.getBase() + "\">");
     443                 writeLineSeparator();
     444             }
     445             decrIndent();
     446         }
     447
     448     }
     449
     450
     451     /**
     452      * Writes out text that is contained in a TEXTAREA form
     453      * element.
     454      *
     455      * @param attr  an AttributeSet
     456      * @exception IOException on any I/O error
     457      * @exception BadLocationException if pos represents an invalid
     458      *            location within the document.
     459      */
     460     protected void textAreaContent(AttributeSet attr) throws BadLocationException, IOException {
     461         Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
     462         if (doc != null && doc.getLength() > 0) {
     463             if (segment == null) {
     464                 segment = new Segment();
     465             }
     466             doc.getText(0, doc.getLength(), segment);
     467             if (segment.count > 0) {
     468                 inTextArea = true;
     469                 incrIndent();
     470                 indentSmart();
     471                 setCanWrapLines(true);
     472                 replaceEntities = true;
     473                 write(segment.array, segment.offset, segment.count);
     474                 replaceEntities = false;
     475                 setCanWrapLines(false);
     476                 writeLineSeparator();
     477                 inTextArea = false;
     478                 decrIndent();
     479             }
     480         }
     481     }
     482
     483
     484     /**
     485      * Writes out text.  If a range is specified when the constructor
     486      * is invoked, then only the appropriate range of text is written
     487      * out.
     488      *
     489      * @param elem   an Element
     490      * @exception IOException on any I/O error
     491      * @exception BadLocationException if pos represents an invalid
     492      *            location within the document.
     493      */
     494     protected void text(Element elem) throws BadLocationException, IOException {
     495         int start = Math.max(getStartOffset(), elem.getStartOffset());
     496         int end = Math.min(getEndOffset(), elem.getEndOffset());
     497         if (start < end) {
     498             if (segment == null) {
     499                 segment = new Segment();
     500             }
     501             getDocument().getText(start, end - start, segment);
     502             newlineOutputed = false;
     503             if (segment.count > 0) {
     504                 if (segment.array[segment.offset + segment.count - 1] == '\n'){
     505                     newlineOutputed = true;
     506                 }
     507                 if (inPre && end == preEndOffset) {
     508                     if (segment.count > 1) {
     509                         segment.count--;
     510                     }
     511                     else {
     512                         return;
     513                     }
     514                 }
     515                 replaceEntities = true;
     516                 setCanWrapLines(!inPre);
     517                 write(segment.array, segment.offset, segment.count);
     518                 setCanWrapLines(false);
     519                 replaceEntities = false;
     520             }
     521         }
     522     }
     523
     524     /**
     525      * Writes out the content of the SELECT form element.
     526      *
     527      * @param attr the AttributeSet associated with the form element
     528      * @exception IOException on any I/O error
     529      */
     530     protected void selectContent(AttributeSet attr) throws IOException {
     531         Object model = attr.getAttribute(StyleConstants.ModelAttribute);
     532         incrIndent();
     533         if (model instanceof OptionListModel) {
     534             @SuppressWarnings("unchecked")
     535             OptionListModel<Option> listModel = (OptionListModel<Option>) model;
     536             int size = listModel.getSize();
     537             for (int i = 0; i < size; i++) {
     538                 Option option = listModel.getElementAt(i);
     539                 writeOption(option);
     540             }
     541         } else if (model instanceof OptionComboBoxModel) {
     542             @SuppressWarnings("unchecked")
     543             OptionComboBoxModel<Option> comboBoxModel = (OptionComboBoxModel<Option>) model;
     544             int size = comboBoxModel.getSize();
     545             for (int i = 0; i < size; i++) {
     546                 Option option = comboBoxModel.getElementAt(i);
     547                 writeOption(option);
     548             }
     549         }
     550         decrIndent();
     551     }
     552
     553
     554     /**
     555      * Writes out the content of the Option form element.
     556      * @param option  an Option
     557      * @exception IOException on any I/O error
     558      *
     559      */
     560     protected void writeOption(Option option) throws IOException {
     561
     562         indentSmart();
     563         write('<');
     564         write("option");
     565         // PENDING: should this be changed to check for null first?
     566         Object value = option.getAttributes().getAttribute
     567                               (HTML.Attribute.VALUE);
     568         if (value != null) {
     569             write(" value="+ value);
     570         }
     571         if (option.isSelected()) {
     572             write(" selected");
     573         }
     574         write('>');
     575         if (option.getLabel() != null) {
     576             write(option.getLabel());
     577         }
     578         writeLineSeparator();
     579     }
     580
     581     /**
     582      * Writes out an end tag for the element.
     583      *
     584      * @param elem    an Element
     585      * @exception IOException on any I/O error
     586      */
     587     protected void endTag(Element elem) throws IOException {
     588         if (synthesizedElement(elem)) {
     589             return;
     590         }
     591
     592         // write out end tags for item on stack
     593         closeOutUnwantedEmbeddedTags(elem.getAttributes());
     594         if (inContent) {
     595             if (!newlineOutputed && !inPre) {
     596                 writeLineSeparator();
     597             }
     598             newlineOutputed = false;
     599             inContent = false;
     600         }
     601         if (!inPre) {
     602             indentSmart();
     603         }
     604         if (matchNameAttribute(elem.getAttributes(), HTML.Tag.PRE)) {
     605             inPre = false;
     606         }
     607         write('<');
     608         write('/');
     609         write(elem.getName());
     610         write('>');
     611         writeLineSeparator();
     612     }
     613
     614
     615
     616     /**
     617      * Writes out comments.
     618      *
     619      * @param elem    an Element
     620      * @exception IOException on any I/O error
     621      * @exception BadLocationException if pos represents an invalid
     622      *            location within the document.
     623      */
     624     protected void comment(Element elem) throws BadLocationException, IOException {
     625         AttributeSet as = elem.getAttributes();
     626         if (matchNameAttribute(as, HTML.Tag.COMMENT)) {
     627             Object comment = as.getAttribute(HTML.Attribute.COMMENT);
     628             if (comment instanceof String) {
     629                 writeComment((String)comment);
     630             }
     631             else {
     632                 writeComment(null);
     633             }
     634         }
     635     }
     636
     637
     638     /**
     639      * Writes out comment string.
     640      *
     641      * @param string   the comment
     642      * @exception IOException on any I/O error
     643      * @exception BadLocationException if pos represents an invalid
     644      *            location within the document.
     645      */
     646     void writeComment(String string) throws IOException {
     647         write("<!--");
     648         if (string != null) {
     649             write(string);
     650         }
     651         write("-->");
     652         writeLineSeparator();
     653         indentSmart();
     654     }
     655
     656
     657     /**
     658      * Writes out any additional comments (comments outside of the body)
     659      * stored under the property HTMLDocument.AdditionalComments.
     660      */
     661     void writeAdditionalComments() throws IOException {
     662         Object comments = getDocument().getProperty
     663                                         (HTMLDocument.AdditionalComments);
     664
     665         if (comments instanceof Vector) {
     666             Vector<?> v = (Vector)comments;
     667             for (int counter = 0, maxCounter = v.size(); counter < maxCounter;
     668                  counter++) {
     669                 writeComment(v.elementAt(counter).toString());
     670             }
     671         }
     672     }
     673
     674
     675     /**
     676      * Returns {@code true} if the element is a
     677      * synthesized element.  Currently we are only testing
     678      * for the p-implied tag.
     679      *
     680      * @param elem an element
     681      * @return {@code true} if the element is a synthesized element.
     682      */
     683     protected boolean synthesizedElement(Element elem) {
     684         if (matchNameAttribute(elem.getAttributes(), HTML.Tag.IMPLIED)) {
     685             return true;
     686         }
     687         return false;
     688     }
     689
     690
     691     /**
     692      * Returns true if the StyleConstants.NameAttribute is
     693      * equal to the tag that is passed in as a parameter.
     694      *
     695      * @param attr a set of attributes
     696      * @param tag an HTML tag
     697      * @return {@code true} if the StyleConstants.NameAttribute is equal to the tag that is passed in as a parameter.
     698      */
     699     protected boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
     700         Object o = attr.getAttribute(StyleConstants.NameAttribute);
     701         if (o instanceof HTML.Tag) {
     702             HTML.Tag name = (HTML.Tag) o;
     703             if (name == tag) {
     704                 return true;
     705             }
     706         }
     707         return false;
     708     }
     709
     710     /**
     711      * Searches for embedded tags in the AttributeSet
     712      * and writes them out.  It also stores these tags in a vector
     713      * so that when appropriate the corresponding end tags can be
     714      * written out.
     715      *
     716      * @param attr a set of attributes
     717      * @exception IOException on any I/O error
     718      */
     719     protected void writeEmbeddedTags(AttributeSet attr) throws IOException {
     720
     721         // translate css attributes to html
     722         attr = convertToHTML(attr, oConvAttr);
     723
     724         Enumeration<?> names = attr.getAttributeNames();
     725         while (names.hasMoreElements()) {
     726             Object name = names.nextElement();
     727             if (name instanceof HTML.Tag) {
     728                 HTML.Tag tag = (HTML.Tag)name;
     729                 if (tag == HTML.Tag.FORM || tags.contains(tag)) {
     730                     continue;
     731                 }
     732                 write('<');
     733                 write(tag.toString());
     734                 Object o = attr.getAttribute(tag);
     735                 if (o != null && o instanceof AttributeSet) {
     736                     writeAttributes((AttributeSet)o);
     737                 }
     738                 write('>');
     739                 tags.addElement(tag);
     740                 tagValues.addElement(o);
     741             }
     742         }
     743     }
     744
     745
     746     /**
     747      * Searches the attribute set for a tag, both of which
     748      * are passed in as a parameter.  Returns true if no match is found
     749      * and false otherwise.
     750      */
     751     private boolean noMatchForTagInAttributes(AttributeSet attr, HTML.Tag t,
     752                                               Object tagValue) {
     753         if (attr != null && attr.isDefined(t)) {
     754             Object newValue = attr.getAttribute(t);
     755
     756             if ((tagValue == null) ? (newValue == null) :
     757                 (newValue != null && tagValue.equals(newValue))) {
     758                 return false;
     759             }
     760         }
     761         return true;
     762     }
     763
     764
     765     /**
     766      * Searches the attribute set and for each tag
     767      * that is stored in the tag vector.  If the tag is not found,
     768      * then the tag is removed from the vector and a corresponding
     769      * end tag is written out.
     770      *
     771      * @param attr a set of attributes
     772      * @exception IOException on any I/O error
     773      */
     774     protected void closeOutUnwantedEmbeddedTags(AttributeSet attr) throws IOException {
     775
     776         tagsToRemove.removeAllElements();
     777
     778         // translate css attributes to html
     779         attr = convertToHTML(attr, null);
     780
     781         HTML.Tag t;
     782         Object tValue;
     783         int firstIndex = -1;
     784         int size = tags.size();
     785         // First, find all the tags that need to be removed.
     786         for (int i = size - 1; i >= 0; i--) {
     787             t = tags.elementAt(i);
     788             tValue = tagValues.elementAt(i);
     789             if ((attr == null) || noMatchForTagInAttributes(attr, t, tValue)) {
     790                 firstIndex = i;
     791                 tagsToRemove.addElement(t);
     792             }
     793         }
     794         if (firstIndex != -1) {
     795             // Then close them out.
     796             boolean removeAll = ((size - firstIndex) == tagsToRemove.size());
     797             for (int i = size - 1; i >= firstIndex; i--) {
     798                 t = tags.elementAt(i);
     799                 if (removeAll || tagsToRemove.contains(t)) {
     800                     tags.removeElementAt(i);
     801                     tagValues.removeElementAt(i);
     802                 }
     803                 write('<');
     804                 write('/');
     805                 write(t.toString());
     806                 write('>');
     807             }
     808             // Have to output any tags after firstIndex that still remaing,
     809             // as we closed them out, but they should remain open.
     810             size = tags.size();
     811             for (int i = firstIndex; i < size; i++) {
     812                 t = tags.elementAt(i);
     813                 write('<');
     814                 write(t.toString());
     815                 Object o = tagValues.elementAt(i);
     816                 if (o != null && o instanceof AttributeSet) {
     817                     writeAttributes((AttributeSet)o);
     818                 }
     819                 write('>');
     820             }
     821         }
     822     }
     823
     824
     825     /**
     826      * Determines if the element associated with the attributeset
     827      * is a TEXTAREA or SELECT.  If true, returns true else
     828      * false
     829      */
     830     private boolean isFormElementWithContent(AttributeSet attr) {
     831         return matchNameAttribute(attr, HTML.Tag.TEXTAREA) ||
     832                 matchNameAttribute(attr, HTML.Tag.SELECT);
     833     }
     834
     835     /**
     836      * Determines if the element associated with the attributeset
     837      * is a P tag and it is within Pre tag.  If true, returns true else
     838      * false
     839      */
     840     private boolean isPreTagWithParagraphTag(AttributeSet attr) {
     841         return inPre && matchNameAttribute(attr, HTML.Tag.P);
     842     }
     843
     844     /**
     845      * Determines whether a the indentation needs to be
     846      * incremented.  Basically, if next is a child of current, and
     847      * next is NOT a synthesized element, the indent level will be
     848      * incremented.  If there is a parent-child relationship and "next"
     849      * is a synthesized element, then its children must be indented.
     850      * This state is maintained by the indentNext boolean.
     851      *
     852      * @return boolean that's true if indent level
     853      *         needs incrementing.
     854      */
     855     private boolean indentNext = false;
     856     private boolean indentNeedsIncrementing(Element current, Element next) {
     857         if ((next.getParentElement() == current) && !inPre) {
     858             if (indentNext) {
     859                 indentNext = false;
     860                 return true;
     861             } else if (synthesizedElement(next)) {
     862                 indentNext = true;
     863             } else if (!synthesizedElement(current)){
     864                 return true;
     865             }
     866         }
     867         return false;
     868     }
     869
     870     /**
     871      * Outputs the maps as elements. Maps are not stored as elements in
     872      * the document, and as such this is used to output them.
     873      */
     874     void writeMaps(Enumeration<?> maps) throws IOException {
     875         if (maps != null) {
     876             while(maps.hasMoreElements()) {
     877                 Map map = (Map)maps.nextElement();
     878                 String name = map.getName();
     879
     880                 incrIndent();
     881                 indentSmart();
     882                 write("<map");
     883                 if (name != null) {
     884                     write(" name=\"");
     885                     write(name);
     886                     write("\">");
     887                 }
     888                 else {
     889                     write('>');
     890                 }
     891                 writeLineSeparator();
     892                 incrIndent();
     893
     894                 // Output the areas
     895                 AttributeSet[] areas = map.getAreas();
     896                 if (areas != null) {
     897                     for (int counter = 0, maxCounter = areas.length;
     898                          counter < maxCounter; counter++) {
     899                         indentSmart();
     900                         write("<area");
     901                         writeAttributes(areas[counter]);
     902                         write("></area>");
     903                         writeLineSeparator();
     904                     }
     905                 }
     906                 decrIndent();
     907                 indentSmart();
     908                 write("</map>");
     909                 writeLineSeparator();
     910                 decrIndent();
     911             }
     912         }
     913     }
     914
     915     /**
     916      * Outputs the styles as a single element. Styles are not stored as
     917      * elements, but part of the document. For the time being styles are
     918      * written out as a comment, inside a style tag.
     919      */
     920     void writeStyles(StyleSheet sheet) throws IOException {
     921         if (sheet != null) {
     922             Enumeration<?> styles = sheet.getStyleNames();
     923             if (styles != null) {
     924                 boolean outputStyle = false;
     925                 while (styles.hasMoreElements()) {
     926                     String name = (String)styles.nextElement();
     927                     // Don't write out the default style.
     928                     if (!StyleContext.DEFAULT_STYLE.equals(name) &&
     929                         writeStyle(name, sheet.getStyle(name), outputStyle)) {
     930                         outputStyle = true;
     931                     }
     932                 }
     933                 if (outputStyle) {
     934                     writeStyleEndTag();
     935                 }
     936             }
     937         }
     938     }
     939
     940     /**
     941      * Outputs the named style. <code>outputStyle</code> indicates
     942      * whether or not a style has been output yet. This will return
     943      * true if a style is written.
     944      */
     945     boolean writeStyle(String name, Style style, boolean outputStyle)
     946                  throws IOException{
     947         boolean didOutputStyle = false;
     948         Enumeration<?> attributes = style.getAttributeNames();
     949         if (attributes != null) {
     950             while (attributes.hasMoreElements()) {
     951                 Object attribute = attributes.nextElement();
     952                 if (attribute instanceof CSS.Attribute) {
     953                     String value = style.getAttribute(attribute).toString();
     954                     if (value != null) {
     955                         if (!outputStyle) {
     956                             writeStyleStartTag();
     957                             outputStyle = true;
     958                         }
     959                         if (!didOutputStyle) {
     960                             didOutputStyle = true;
     961                             indentSmart();
     962                             write(name);
     963                             write(" {");
     964                         }
     965                         else {
     966                             write(";");
     967                         }
     968                         write(' ');
     969                         write(attribute.toString());
     970                         write(": ");
     971                         write(value);
     972                     }
     973                 }
     974             }
     975         }
     976         if (didOutputStyle) {
     977             write(" }");
     978             writeLineSeparator();
     979         }
     980         return didOutputStyle;
     981     }
     982
     983     void writeStyleStartTag() throws IOException {
     984         indentSmart();
     985         write("<style type=\"text/css\">");
     986         incrIndent();
     987         writeLineSeparator();
     988         indentSmart();
     989         write("<!--");
     990         incrIndent();
     991         writeLineSeparator();
     992     }
     993
     994     void writeStyleEndTag() throws IOException {
     995         decrIndent();
     996         indentSmart();
     997         write("-->");
     998         writeLineSeparator();
     999         decrIndent();
    1000         indentSmart();
    1001         write("</style>");
    1002         writeLineSeparator();
    1003         indentSmart();
    1004     }
    1005
    1006     // --- conversion support ---------------------------
    1007
    1008     /**
    1009      * Convert the give set of attributes to be html for
    1010      * the purpose of writing them out.  Any keys that
    1011      * have been converted will not appear in the resultant
    1012      * set.  Any keys not converted will appear in the
    1013      * resultant set the same as the received set.<p>
    1014      * This will put the converted values into <code>to</code>, unless
    1015      * it is null in which case a temporary AttributeSet will be returned.
    1016      */
    1017     AttributeSet convertToHTML(AttributeSet from, MutableAttributeSet to) {
    1018         if (to == null) {
    1019             to = convAttr;
    1020         }
    1021         to.removeAttributes(to);
    1022         if (writeCSS) {
    1023             convertToHTML40(from, to);
    1024         } else {
    1025             convertToHTML32(from, to);
    1026         }
    1027         return to;
    1028     }
    1029
    1030     /**
    1031      * If true, the writer will emit CSS attributes in preference
    1032      * to HTML tags/attributes (i.e. It will emit an HTML 4.0
    1033      * style).
    1034      */
    1035     private boolean writeCSS = false;
    1036
    1037     /**
    1038      * Buffer for the purpose of attribute conversion
    1039      */
    1040     private MutableAttributeSet convAttr = new SimpleAttributeSet();
    1041
    1042     /**
    1043      * Buffer for the purpose of attribute conversion. This can be
    1044      * used if convAttr is being used.
    1045      */
    1046     private MutableAttributeSet oConvAttr = new SimpleAttributeSet();
    1047
    1048     /**
    1049      * Create an older style of HTML attributes.  This will
    1050      * convert character level attributes that have a StyleConstants
    1051      * mapping over to an HTML tag/attribute.  Other CSS attributes
    1052      * will be placed in an HTML style attribute.
    1053      */
    1054     private static void convertToHTML32(AttributeSet from, MutableAttributeSet to) {
    1055         if (from == null) {
    1056             return;
    1057         }
    1058         Enumeration<?> keys = from.getAttributeNames();
    1059         String value = "";
    1060         while (keys.hasMoreElements()) {
    1061             Object key = keys.nextElement();
    1062             if (key instanceof CSS.Attribute) {
    1063                 if ((key == CSS.Attribute.FONT_FAMILY) ||
    1064                     (key == CSS.Attribute.FONT_SIZE) ||
    1065                     (key == CSS.Attribute.COLOR)) {
    1066
    1067                     createFontAttribute((CSS.Attribute)key, from, to);
    1068                 } else if (key == CSS.Attribute.FONT_WEIGHT) {
    1069                     // add a bold tag is weight is bold
    1070                     CSS.FontWeight weightValue = (CSS.FontWeight)
    1071                         from.getAttribute(CSS.Attribute.FONT_WEIGHT);
    1072                     if ((weightValue != null) && (weightValue.getValue() > 400)) {
    1073                         addAttribute(to, HTML.Tag.B, SimpleAttributeSet.EMPTY);
    1074                     }
    1075                 } else if (key == CSS.Attribute.FONT_STYLE) {
    1076                     String s = from.getAttribute(key).toString();
    1077                     if (s.indexOf("italic") >= 0) {
    1078                         addAttribute(to, HTML.Tag.I, SimpleAttributeSet.EMPTY);
    1079                     }
    1080                 } else if (key == CSS.Attribute.TEXT_DECORATION) {
    1081                     String decor = from.getAttribute(key).toString();
    1082                     if (decor.indexOf("underline") >= 0) {
    1083                         addAttribute(to, HTML.Tag.U, SimpleAttributeSet.EMPTY);
    1084                     }
    1085                     if (decor.indexOf("line-through") >= 0) {
    1086                         addAttribute(to, HTML.Tag.STRIKE, SimpleAttributeSet.EMPTY);
    1087                     }
    1088                 } else if (key == CSS.Attribute.VERTICAL_ALIGN) {
    1089                     String vAlign = from.getAttribute(key).toString();
    1090                     if (vAlign.indexOf("sup") >= 0) {
    1091                         addAttribute(to, HTML.Tag.SUP, SimpleAttributeSet.EMPTY);
    1092                     }
    1093                     if (vAlign.indexOf("sub") >= 0) {
    1094                         addAttribute(to, HTML.Tag.SUB, SimpleAttributeSet.EMPTY);
    1095                     }
    1096                 } else if (key == CSS.Attribute.TEXT_ALIGN) {
    1097                     addAttribute(to, HTML.Attribute.ALIGN,
    1098                                     from.getAttribute(key).toString());
    1099                 } else {
    1100                     // default is to store in a HTML style attribute
    1101                     if (value.length() > 0) {
    1102                         value = value + "; ";
    1103                     }
    1104                     value = value + key + ": " + from.getAttribute(key);
    1105                 }
    1106             } else {
    1107                 Object attr = from.getAttribute(key);
    1108                 if (attr instanceof AttributeSet) {
    1109                     attr = ((AttributeSet)attr).copyAttributes();
    1110                 }
    1111                 addAttribute(to, key, attr);
    1112             }
    1113         }
    1114         if (value.length() > 0) {
    1115             to.addAttribute(HTML.Attribute.STYLE, value);
    1116         }
    1117     }
    1118
    1119     /**
    1120      * Add an attribute only if it doesn't exist so that we don't
    1121      * loose information replacing it with SimpleAttributeSet.EMPTY
    1122      */
    1123     private static void addAttribute(MutableAttributeSet to, Object key, Object value) {
    1124         Object attr = to.getAttribute(key);
    1125         if (attr == null || attr == SimpleAttributeSet.EMPTY) {
    1126             to.addAttribute(key, value);
    1127         } else {
    1128             if (attr instanceof MutableAttributeSet &&
    1129                 value instanceof AttributeSet) {
    1130                 ((MutableAttributeSet)attr).addAttributes((AttributeSet)value);
    1131             }
    1132         }
    1133     }
    1134
    1135     /**
    1136      * Create/update an HTML &lt;font&gt; tag attribute.  The
    1137      * value of the attribute should be a MutableAttributeSet so
    1138      * that the attributes can be updated as they are discovered.
    1139      */
    1140     private static void createFontAttribute(CSS.Attribute a, AttributeSet from,
    1141                                     MutableAttributeSet to) {
    1142         MutableAttributeSet fontAttr = (MutableAttributeSet)
    1143             to.getAttribute(HTML.Tag.FONT);
    1144         if (fontAttr == null) {
    1145             fontAttr = new SimpleAttributeSet();
    1146             to.addAttribute(HTML.Tag.FONT, fontAttr);
    1147         }
    1148         // edit the parameters to the font tag
    1149         String htmlValue = from.getAttribute(a).toString();
    1150         if (a == CSS.Attribute.FONT_FAMILY) {
    1151             fontAttr.addAttribute(HTML.Attribute.FACE, htmlValue);
    1152         } else if (a == CSS.Attribute.FONT_SIZE) {
    1153             fontAttr.addAttribute(HTML.Attribute.SIZE, htmlValue);
    1154         } else if (a == CSS.Attribute.COLOR) {
    1155             fontAttr.addAttribute(HTML.Attribute.COLOR, htmlValue);
    1156         }
    1157     }
    1158
    1159     /**
    1160      * Copies the given AttributeSet to a new set, converting
    1161      * any CSS attributes found to arguments of an HTML style
    1162      * attribute.
    1163      */
    1164     private static void convertToHTML40(AttributeSet from, MutableAttributeSet to) {
    1165         Enumeration<?> keys = from.getAttributeNames();
    1166         String value = "";
    1167         while (keys.hasMoreElements()) {
    1168             Object key = keys.nextElement();
    1169             if (key instanceof CSS.Attribute) {
    1170                 value = value + " " + key + "=" + from.getAttribute(key) + ";";
    1171             } else {
    1172                 to.addAttribute(key, from.getAttribute(key));
    1173             }
    1174         }
    1175         if (value.length() > 0) {
    1176             to.addAttribute(HTML.Attribute.STYLE, value);
    1177         }
    1178     }
    1179
    1180     //
    1181     // Overrides the writing methods to only break a string when
    1182     // canBreakString is true.
    1183     // In a future release it is likely AbstractWriter will get this
    1184     // functionality.
    1185     //
    1186
    1187     /**
    1188      * Writes the line separator. This is overriden to make sure we don't
    1189      * replace the newline content in case it is outside normal ascii.
    1190      * @since 1.3
    1191      */
    1192     protected void writeLineSeparator() throws IOException {
    1193         boolean oldReplace = replaceEntities;
    1194         replaceEntities = false;
    1195         super.writeLineSeparator();
    1196         replaceEntities = oldReplace;
    1197         indented = false;
    1198     }
    1199
    1200     /**
    1201      * This method is overriden to map any character entities, such as
    1202      * &lt; to &amp;lt;. <code>super.output</code> will be invoked to
    1203      * write the content.
    1204      * @since 1.3
    1205      */
    1206     protected void output(char[] chars, int start, int length)
    1207                    throws IOException {
    1208         if (!replaceEntities) {
    1209             super.output(chars, start, length);
    1210             return;
    1211         }
    1212         int last = start;
    1213         length += start;
    1214         for (int counter = start; counter < length; counter++) {
    1215             // This will change, we need better support character level
    1216             // entities.
    1217             switch(chars[counter]) {
    1218                 // Character level entities.
    1219             case '<':
    1220                 if (counter > last) {
    1221                     super.output(chars, last, counter - last);
    1222                 }
    1223                 last = counter + 1;
    1224                 output("&lt;");
    1225                 break;
    1226             case '>':
    1227                 if (counter > last) {
    1228                     super.output(chars, last, counter - last);
    1229                 }
    1230                 last = counter + 1;
    1231                 output("&gt;");
    1232                 break;
    1233             case '&':
    1234                 if (counter > last) {
    1235                     super.output(chars, last, counter - last);
    1236                 }
    1237                 last = counter + 1;
    1238                 output("&amp;");
    1239                 break;
    1240             case '"':
    1241                 if (counter > last) {
    1242                     super.output(chars, last, counter - last);
    1243                 }
    1244                 last = counter + 1;
    1245                 output("&quot;");
    1246                 break;
    1247                 // Special characters
    1248             case '\n':
    1249             case '\t':
    1250             case '\r':
    1251                 break;
    1252             default:
    1253                 if (chars[counter] < ' ' || chars[counter] > 127) {
    1254                     if (counter > last) {
    1255                         super.output(chars, last, counter - last);
    1256                     }
    1257                     last = counter + 1;
    1258                     // If the character is outside of ascii, write the
    1259                     // numeric value.
    1260                     output("&#");
    1261                     output(String.valueOf((int)chars[counter]));
    1262                     output(";");
    1263                 }
    1264                 break;
    1265             }
    1266         }
    1267         if (last < length) {
    1268             super.output(chars, last, length - last);
    1269         }
    1270     }
    1271
    1272     /**
    1273      * This directly invokes super's <code>output</code> after converting
    1274      * <code>string</code> to a char[].
    1275      */
    1276     private void output(String string) throws IOException {
    1277         int length = string.length();
    1278         if (tempChars == null || tempChars.length < length) {
    1279             tempChars = new char[length];
    1280         }
    1281         string.getChars(0, length, tempChars, 0);
    1282         super.output(tempChars, 0, length);
    1283     }
    1284
    1285     private boolean indented = false;
    1286
    1287     /**
    1288      * Writes indent only once per line.
    1289      */
    1290     private void indentSmart() throws IOException {
    1291         if (!indented) {
    1292             indent();
    1293             indented = true;
    1294         }
    1295     }
    1296 }