       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             OptionListModel listModel = (OptionListModel) model;
     535             int size = listModel.getSize();
     536             for (int i = 0; i < size; i++) {
     537                 Option option = listModel.getElementAt(i);
     538                 writeOption(option);
     539             }
     540         } else if (model instanceof OptionComboBoxModel) {
     541             OptionComboBoxModel comboBoxModel = (OptionComboBoxModel) model;
     542             int size = comboBoxModel.getSize();
     543             for (int i = 0; i < size; i++) {
     544                 Option option = comboBoxModel.getElementAt(i);
     545                 writeOption(option);
     546             }
     547         }
     548         decrIndent();
     549     }
     550
     551
     552     /**
     553      * Writes out the content of the Option form element.
     554      * @param option  an Option
     555      * @exception IOException on any I/O error
     556      *
     557      */
     558     protected void writeOption(Option option) throws IOException {
     559
     560         indentSmart();
     561         write('<');
     562         write("option");
     563         // PENDING: should this be changed to check for null first?
     564         Object value = option.getAttributes().getAttribute
     565                               (HTML.Attribute.VALUE);
     566         if (value != null) {
     567             write(" value="+ value);
     568         }
     569         if (option.isSelected()) {
     570             write(" selected");
     571         }
     572         write('>');
     573         if (option.getLabel() != null) {
     574             write(option.getLabel());
     575         }
     576         writeLineSeparator();
     577     }
     578
     579     /**
     580      * Writes out an end tag for the element.
     581      *
     582      * @param elem    an Element
     583      * @exception IOException on any I/O error
     584      */
     585     protected void endTag(Element elem) throws IOException {
     586         if (synthesizedElement(elem)) {
     587             return;
     588         }
     589
     590         // write out end tags for item on stack
     591         closeOutUnwantedEmbeddedTags(elem.getAttributes());
     592         if (inContent) {
     593             if (!newlineOutputed && !inPre) {
     594                 writeLineSeparator();
     595             }
     596             newlineOutputed = false;
     597             inContent = false;
     598         }
     599         if (!inPre) {
     600             indentSmart();
     601         }
     602         if (matchNameAttribute(elem.getAttributes(), HTML.Tag.PRE)) {
     603             inPre = false;
     604         }
     605         write('<');
     606         write('/');
     607         write(elem.getName());
     608         write('>');
     609         writeLineSeparator();
     610     }
     611
     612
     613
     614     /**
     615      * Writes out comments.
     616      *
     617      * @param elem    an Element
     618      * @exception IOException on any I/O error
     619      * @exception BadLocationException if pos represents an invalid
     620      *            location within the document.
     621      */
     622     protected void comment(Element elem) throws BadLocationException, IOException {
     623         AttributeSet as = elem.getAttributes();
     624         if (matchNameAttribute(as, HTML.Tag.COMMENT)) {
     625             Object comment = as.getAttribute(HTML.Attribute.COMMENT);
     626             if (comment instanceof String) {
     627                 writeComment((String)comment);
     628             }
     629             else {
     630                 writeComment(null);
     631             }
     632         }
     633     }
     634
     635
     636     /**
     637      * Writes out comment string.
     638      *
     639      * @param string   the comment
     640      * @exception IOException on any I/O error
     641      * @exception BadLocationException if pos represents an invalid
     642      *            location within the document.
     643      */
     644     void writeComment(String string) throws IOException {
     645         write("<!--");
     646         if (string != null) {
     647             write(string);
     648         }
     649         write("-->");
     650         writeLineSeparator();
     651         indentSmart();
     652     }
     653
     654
     655     /**
     656      * Writes out any additional comments (comments outside of the body)
     657      * stored under the property HTMLDocument.AdditionalComments.
     658      */
     659     void writeAdditionalComments() throws IOException {
     660         Object comments = getDocument().getProperty
     661                                         (HTMLDocument.AdditionalComments);
     662
     663         if (comments instanceof Vector) {
     664             Vector<?> v = (Vector)comments;
     665             for (int counter = 0, maxCounter = v.size(); counter < maxCounter;
     666                  counter++) {
     667                 writeComment(v.elementAt(counter).toString());
     668             }
     669         }
     670     }
     671
     672
     673     /**
     674      * Returns {@code true} if the element is a
     675      * synthesized element.  Currently we are only testing
     676      * for the p-implied tag.
     677      *
     678      * @param elem an element
     679      * @return {@code true} if the element is a synthesized element.
     680      */
     681     protected boolean synthesizedElement(Element elem) {
     682         if (matchNameAttribute(elem.getAttributes(), HTML.Tag.IMPLIED)) {
     683             return true;
     684         }
     685         return false;
     686     }
     687
     688
     689     /**
     690      * Returns true if the StyleConstants.NameAttribute is
     691      * equal to the tag that is passed in as a parameter.
     692      *
     693      * @param attr a set of attributes
     694      * @param tag an HTML tag
     695      * @return {@code true} if the StyleConstants.NameAttribute is equal to the tag that is passed in as a parameter.
     696      */
     697     protected boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
     698         Object o = attr.getAttribute(StyleConstants.NameAttribute);
     699         if (o instanceof HTML.Tag) {
     700             HTML.Tag name = (HTML.Tag) o;
     701             if (name == tag) {
     702                 return true;
     703             }
     704         }
     705         return false;
     706     }
     707
     708     /**
     709      * Searches for embedded tags in the AttributeSet
     710      * and writes them out.  It also stores these tags in a vector
     711      * so that when appropriate the corresponding end tags can be
     712      * written out.
     713      *
     714      * @param attr a set of attributes
     715      * @exception IOException on any I/O error
     716      */
     717     protected void writeEmbeddedTags(AttributeSet attr) throws IOException {
     718
     719         // translate css attributes to html
     720         attr = convertToHTML(attr, oConvAttr);
     721
     722         Enumeration<?> names = attr.getAttributeNames();
     723         while (names.hasMoreElements()) {
     724             Object name = names.nextElement();
     725             if (name instanceof HTML.Tag) {
     726                 HTML.Tag tag = (HTML.Tag)name;
     727                 if (tag == HTML.Tag.FORM || tags.contains(tag)) {
     728                     continue;
     729                 }
     730                 write('<');
     731                 write(tag.toString());
     732                 Object o = attr.getAttribute(tag);
     733                 if (o != null && o instanceof AttributeSet) {
     734                     writeAttributes((AttributeSet)o);
     735                 }
     736                 write('>');
     737                 tags.addElement(tag);
     738                 tagValues.addElement(o);
     739             }
     740         }
     741     }
     742
     743
     744     /**
     745      * Searches the attribute set for a tag, both of which
     746      * are passed in as a parameter.  Returns true if no match is found
     747      * and false otherwise.
     748      */
     749     private boolean noMatchForTagInAttributes(AttributeSet attr, HTML.Tag t,
     750                                               Object tagValue) {
     751         if (attr != null && attr.isDefined(t)) {
     752             Object newValue = attr.getAttribute(t);
     753
     754             if ((tagValue == null) ? (newValue == null) :
     755                 (newValue != null && tagValue.equals(newValue))) {
     756                 return false;
     757             }
     758         }
     759         return true;
     760     }
     761
     762
     763     /**
     764      * Searches the attribute set and for each tag
     765      * that is stored in the tag vector.  If the tag is not found,
     766      * then the tag is removed from the vector and a corresponding
     767      * end tag is written out.
     768      *
     769      * @param attr a set of attributes
     770      * @exception IOException on any I/O error
     771      */
     772     protected void closeOutUnwantedEmbeddedTags(AttributeSet attr) throws IOException {
     773
     774         tagsToRemove.removeAllElements();
     775
     776         // translate css attributes to html
     777         attr = convertToHTML(attr, null);
     778
     779         HTML.Tag t;
     780         Object tValue;
     781         int firstIndex = -1;
     782         int size = tags.size();
     783         // First, find all the tags that need to be removed.
     784         for (int i = size - 1; i >= 0; i--) {
     785             t = tags.elementAt(i);
     786             tValue = tagValues.elementAt(i);
     787             if ((attr == null) || noMatchForTagInAttributes(attr, t, tValue)) {
     788                 firstIndex = i;
     789                 tagsToRemove.addElement(t);
     790             }
     791         }
     792         if (firstIndex != -1) {
     793             // Then close them out.
     794             boolean removeAll = ((size - firstIndex) == tagsToRemove.size());
     795             for (int i = size - 1; i >= firstIndex; i--) {
     796                 t = tags.elementAt(i);
     797                 if (removeAll || tagsToRemove.contains(t)) {
     798                     tags.removeElementAt(i);
     799                     tagValues.removeElementAt(i);
     800                 }
     801                 write('<');
     802                 write('/');
     803                 write(t.toString());
     804                 write('>');
     805             }
     806             // Have to output any tags after firstIndex that still remaing,
     807             // as we closed them out, but they should remain open.
     808             size = tags.size();
     809             for (int i = firstIndex; i < size; i++) {
     810                 t = tags.elementAt(i);
     811                 write('<');
     812                 write(t.toString());
     813                 Object o = tagValues.elementAt(i);
     814                 if (o != null && o instanceof AttributeSet) {
     815                     writeAttributes((AttributeSet)o);
     816                 }
     817                 write('>');
     818             }
     819         }
     820     }
     821
     822
     823     /**
     824      * Determines if the element associated with the attributeset
     825      * is a TEXTAREA or SELECT.  If true, returns true else
     826      * false
     827      */
     828     private boolean isFormElementWithContent(AttributeSet attr) {
     829         return matchNameAttribute(attr, HTML.Tag.TEXTAREA) ||
     830                 matchNameAttribute(attr, HTML.Tag.SELECT);
     831     }
     832
     833     /**
     834      * Determines if the element associated with the attributeset
     835      * is a P tag and it is within Pre tag.  If true, returns true else
     836      * false
     837      */
     838     private boolean isPreTagWithParagraphTag(AttributeSet attr) {
     839         return inPre && matchNameAttribute(attr, HTML.Tag.P);
     840     }
     841
     842     /**
     843      * Determines whether a the indentation needs to be
     844      * incremented.  Basically, if next is a child of current, and
     845      * next is NOT a synthesized element, the indent level will be
     846      * incremented.  If there is a parent-child relationship and "next"
     847      * is a synthesized element, then its children must be indented.
     848      * This state is maintained by the indentNext boolean.
     849      *
     850      * @return boolean that's true if indent level
     851      *         needs incrementing.
     852      */
     853     private boolean indentNext = false;
     854     private boolean indentNeedsIncrementing(Element current, Element next) {
     855         if ((next.getParentElement() == current) && !inPre) {
     856             if (indentNext) {
     857                 indentNext = false;
     858                 return true;
     859             } else if (synthesizedElement(next)) {
     860                 indentNext = true;
     861             } else if (!synthesizedElement(current)){
     862                 return true;
     863             }
     864         }
     865         return false;
     866     }
     867
     868     /**
     869      * Outputs the maps as elements. Maps are not stored as elements in
     870      * the document, and as such this is used to output them.
     871      */
     872     void writeMaps(Enumeration<?> maps) throws IOException {
     873         if (maps != null) {
     874             while(maps.hasMoreElements()) {
     875                 Map map = (Map)maps.nextElement();
     876                 String name = map.getName();
     877
     878                 incrIndent();
     879                 indentSmart();
     880                 write("<map");
     881                 if (name != null) {
     882                     write(" name=\"");
     883                     write(name);
     884                     write("\">");
     885                 }
     886                 else {
     887                     write('>');
     888                 }
     889                 writeLineSeparator();
     890                 incrIndent();
     891
     892                 // Output the areas
     893                 AttributeSet[] areas = map.getAreas();
     894                 if (areas != null) {
     895                     for (int counter = 0, maxCounter = areas.length;
     896                          counter < maxCounter; counter++) {
     897                         indentSmart();
     898                         write("<area");
     899                         writeAttributes(areas[counter]);
     900                         write("></area>");
     901                         writeLineSeparator();
     902                     }
     903                 }
     904                 decrIndent();
     905                 indentSmart();
     906                 write("</map>");
     907                 writeLineSeparator();
     908                 decrIndent();
     909             }
     910         }
     911     }
     912
     913     /**
     914      * Outputs the styles as a single element. Styles are not stored as
     915      * elements, but part of the document. For the time being styles are
     916      * written out as a comment, inside a style tag.
     917      */
     918     void writeStyles(StyleSheet sheet) throws IOException {
     919         if (sheet != null) {
     920             Enumeration<?> styles = sheet.getStyleNames();
     921             if (styles != null) {
     922                 boolean outputStyle = false;
     923                 while (styles.hasMoreElements()) {
     924                     String name = (String)styles.nextElement();
     925                     // Don't write out the default style.
     926                     if (!StyleContext.DEFAULT_STYLE.equals(name) &&
     927                         writeStyle(name, sheet.getStyle(name), outputStyle)) {
     928                         outputStyle = true;
     929                     }
     930                 }
     931                 if (outputStyle) {
     932                     writeStyleEndTag();
     933                 }
     934             }
     935         }
     936     }
     937
     938     /**
     939      * Outputs the named style. <code>outputStyle</code> indicates
     940      * whether or not a style has been output yet. This will return
     941      * true if a style is written.
     942      */
     943     boolean writeStyle(String name, Style style, boolean outputStyle)
     944                  throws IOException{
     945         boolean didOutputStyle = false;
     946         Enumeration<?> attributes = style.getAttributeNames();
     947         if (attributes != null) {
     948             while (attributes.hasMoreElements()) {
     949                 Object attribute = attributes.nextElement();
     950                 if (attribute instanceof CSS.Attribute) {
     951                     String value = style.getAttribute(attribute).toString();
     952                     if (value != null) {
     953                         if (!outputStyle) {
     954                             writeStyleStartTag();
     955                             outputStyle = true;
     956                         }
     957                         if (!didOutputStyle) {
     958                             didOutputStyle = true;
     959                             indentSmart();
     960                             write(name);
     961                             write(" {");
     962                         }
     963                         else {
     964                             write(";");
     965                         }
     966                         write(' ');
     967                         write(attribute.toString());
     968                         write(": ");
     969                         write(value);
     970                     }
     971                 }
     972             }
     973         }
     974         if (didOutputStyle) {
     975             write(" }");
     976             writeLineSeparator();
     977         }
     978         return didOutputStyle;
     979     }
     980
     981     void writeStyleStartTag() throws IOException {
     982         indentSmart();
     983         write("<style type=\"text/css\">");
     984         incrIndent();
     985         writeLineSeparator();
     986         indentSmart();
     987         write("<!--");
     988         incrIndent();
     989         writeLineSeparator();
     990     }
     991
     992     void writeStyleEndTag() throws IOException {
     993         decrIndent();
     994         indentSmart();
     995         write("-->");
     996         writeLineSeparator();
     997         decrIndent();
     998         indentSmart();
     999         write("</style>");
    1000         writeLineSeparator();
    1001         indentSmart();
    1002     }
    1003
    1004     // --- conversion support ---------------------------
    1005
    1006     /**
    1007      * Convert the give set of attributes to be html for
    1008      * the purpose of writing them out.  Any keys that
    1009      * have been converted will not appear in the resultant
    1010      * set.  Any keys not converted will appear in the
    1011      * resultant set the same as the received set.<p>
    1012      * This will put the converted values into <code>to</code>, unless
    1013      * it is null in which case a temporary AttributeSet will be returned.
    1014      */
    1015     AttributeSet convertToHTML(AttributeSet from, MutableAttributeSet to) {
    1016         if (to == null) {
    1017             to = convAttr;
    1018         }
    1019         to.removeAttributes(to);
    1020         if (writeCSS) {
    1021             convertToHTML40(from, to);
    1022         } else {
    1023             convertToHTML32(from, to);
    1024         }
    1025         return to;
    1026     }
    1027
    1028     /**
    1029      * If true, the writer will emit CSS attributes in preference
    1030      * to HTML tags/attributes (i.e. It will emit an HTML 4.0
    1031      * style).
    1032      */
    1033     private boolean writeCSS = false;
    1034
    1035     /**
    1036      * Buffer for the purpose of attribute conversion
    1037      */
    1038     private MutableAttributeSet convAttr = new SimpleAttributeSet();
    1039
    1040     /**
    1041      * Buffer for the purpose of attribute conversion. This can be
    1042      * used if convAttr is being used.
    1043      */
    1044     private MutableAttributeSet oConvAttr = new SimpleAttributeSet();
    1045
    1046     /**
    1047      * Create an older style of HTML attributes.  This will
    1048      * convert character level attributes that have a StyleConstants
    1049      * mapping over to an HTML tag/attribute.  Other CSS attributes
    1050      * will be placed in an HTML style attribute.
    1051      */
    1052     private static void convertToHTML32(AttributeSet from, MutableAttributeSet to) {
    1053         if (from == null) {
    1054             return;
    1055         }
    1056         Enumeration<?> keys = from.getAttributeNames();
    1057         String value = "";
    1058         while (keys.hasMoreElements()) {
    1059             Object key = keys.nextElement();
    1060             if (key instanceof CSS.Attribute) {
    1061                 if ((key == CSS.Attribute.FONT_FAMILY) ||
    1062                     (key == CSS.Attribute.FONT_SIZE) ||
    1063                     (key == CSS.Attribute.COLOR)) {
    1064
    1065                     createFontAttribute((CSS.Attribute)key, from, to);
    1066                 } else if (key == CSS.Attribute.FONT_WEIGHT) {
    1067                     // add a bold tag is weight is bold
    1068                     CSS.FontWeight weightValue = (CSS.FontWeight)
    1069                         from.getAttribute(CSS.Attribute.FONT_WEIGHT);
    1070                     if ((weightValue != null) && (weightValue.getValue() > 400)) {
    1071                         addAttribute(to, HTML.Tag.B, SimpleAttributeSet.EMPTY);
    1072                     }
    1073                 } else if (key == CSS.Attribute.FONT_STYLE) {
    1074                     String s = from.getAttribute(key).toString();
    1075                     if (s.indexOf("italic") >= 0) {
    1076                         addAttribute(to, HTML.Tag.I, SimpleAttributeSet.EMPTY);
    1077                     }
    1078                 } else if (key == CSS.Attribute.TEXT_DECORATION) {
    1079                     String decor = from.getAttribute(key).toString();
    1080                     if (decor.indexOf("underline") >= 0) {
    1081                         addAttribute(to, HTML.Tag.U, SimpleAttributeSet.EMPTY);
    1082                     }
    1083                     if (decor.indexOf("line-through") >= 0) {
    1084                         addAttribute(to, HTML.Tag.STRIKE, SimpleAttributeSet.EMPTY);
    1085                     }
    1086                 } else if (key == CSS.Attribute.VERTICAL_ALIGN) {
    1087                     String vAlign = from.getAttribute(key).toString();
    1088                     if (vAlign.indexOf("sup") >= 0) {
    1089                         addAttribute(to, HTML.Tag.SUP, SimpleAttributeSet.EMPTY);
    1090                     }
    1091                     if (vAlign.indexOf("sub") >= 0) {
    1092                         addAttribute(to, HTML.Tag.SUB, SimpleAttributeSet.EMPTY);
    1093                     }
    1094                 } else if (key == CSS.Attribute.TEXT_ALIGN) {
    1095                     addAttribute(to, HTML.Attribute.ALIGN,
    1096                                     from.getAttribute(key).toString());
    1097                 } else {
    1098                     // default is to store in a HTML style attribute
    1099                     if (value.length() > 0) {
    1100                         value = value + "; ";
    1101                     }
    1102                     value = value + key + ": " + from.getAttribute(key);
    1103                 }
    1104             } else {
    1105                 Object attr = from.getAttribute(key);
    1106                 if (attr instanceof AttributeSet) {
    1107                     attr = ((AttributeSet)attr).copyAttributes();
    1108                 }
    1109                 addAttribute(to, key, attr);
    1110             }
    1111         }
    1112         if (value.length() > 0) {
    1113             to.addAttribute(HTML.Attribute.STYLE, value);
    1114         }
    1115     }
    1116
    1117     /**
    1118      * Add an attribute only if it doesn't exist so that we don't
    1119      * loose information replacing it with SimpleAttributeSet.EMPTY
    1120      */
    1121     private static void addAttribute(MutableAttributeSet to, Object key, Object value) {
    1122         Object attr = to.getAttribute(key);
    1123         if (attr == null || attr == SimpleAttributeSet.EMPTY) {
    1124             to.addAttribute(key, value);
    1125         } else {
    1126             if (attr instanceof MutableAttributeSet &&
    1127                 value instanceof AttributeSet) {
    1128                 ((MutableAttributeSet)attr).addAttributes((AttributeSet)value);
    1129             }
    1130         }
    1131     }
    1132
    1133     /**
    1134      * Create/update an HTML &lt;font&gt; tag attribute.  The
    1135      * value of the attribute should be a MutableAttributeSet so
    1136      * that the attributes can be updated as they are discovered.
    1137      */
    1138     private static void createFontAttribute(CSS.Attribute a, AttributeSet from,
    1139                                     MutableAttributeSet to) {
    1140         MutableAttributeSet fontAttr = (MutableAttributeSet)
    1141             to.getAttribute(HTML.Tag.FONT);
    1142         if (fontAttr == null) {
    1143             fontAttr = new SimpleAttributeSet();
    1144             to.addAttribute(HTML.Tag.FONT, fontAttr);
    1145         }
    1146         // edit the parameters to the font tag
    1147         String htmlValue = from.getAttribute(a).toString();
    1148         if (a == CSS.Attribute.FONT_FAMILY) {
    1149             fontAttr.addAttribute(HTML.Attribute.FACE, htmlValue);
    1150         } else if (a == CSS.Attribute.FONT_SIZE) {
    1151             fontAttr.addAttribute(HTML.Attribute.SIZE, htmlValue);
    1152         } else if (a == CSS.Attribute.COLOR) {
    1153             fontAttr.addAttribute(HTML.Attribute.COLOR, htmlValue);
    1154         }
    1155     }
    1156
    1157     /**
    1158      * Copies the given AttributeSet to a new set, converting
    1159      * any CSS attributes found to arguments of an HTML style
    1160      * attribute.
    1161      */
    1162     private static void convertToHTML40(AttributeSet from, MutableAttributeSet to) {
    1163         Enumeration<?> keys = from.getAttributeNames();
    1164         String value = "";
    1165         while (keys.hasMoreElements()) {
    1166             Object key = keys.nextElement();
    1167             if (key instanceof CSS.Attribute) {
    1168                 value = value + " " + key + "=" + from.getAttribute(key) + ";";
    1169             } else {
    1170                 to.addAttribute(key, from.getAttribute(key));
    1171             }
    1172         }
    1173         if (value.length() > 0) {
    1174             to.addAttribute(HTML.Attribute.STYLE, value);
    1175         }
    1176     }
    1177
    1178     //
    1179     // Overrides the writing methods to only break a string when
    1180     // canBreakString is true.
    1181     // In a future release it is likely AbstractWriter will get this
    1182     // functionality.
    1183     //
    1184
    1185     /**
    1186      * Writes the line separator. This is overriden to make sure we don't
    1187      * replace the newline content in case it is outside normal ascii.
    1188      * @since 1.3
    1189      */
    1190     protected void writeLineSeparator() throws IOException {
    1191         boolean oldReplace = replaceEntities;
    1192         replaceEntities = false;
    1193         super.writeLineSeparator();
    1194         replaceEntities = oldReplace;
    1195         indented = false;
    1196     }
    1197
    1198     /**
    1199      * This method is overriden to map any character entities, such as
    1200      * &lt; to &amp;lt;. <code>super.output</code> will be invoked to
    1201      * write the content.
    1202      * @since 1.3
    1203      */
    1204     protected void output(char[] chars, int start, int length)
    1205                    throws IOException {
    1206         if (!replaceEntities) {
    1207             super.output(chars, start, length);
    1208             return;
    1209         }
    1210         int last = start;
    1211         length += start;
    1212         for (int counter = start; counter < length; counter++) {
    1213             // This will change, we need better support character level
    1214             // entities.
    1215             switch(chars[counter]) {
    1216                 // Character level entities.
    1217             case '<':
    1218                 if (counter > last) {
    1219                     super.output(chars, last, counter - last);
    1220                 }
    1221                 last = counter + 1;
    1222                 output("&lt;");
    1223                 break;
    1224             case '>':
    1225                 if (counter > last) {
    1226                     super.output(chars, last, counter - last);
    1227                 }
    1228                 last = counter + 1;
    1229                 output("&gt;");
    1230                 break;
    1231             case '&':
    1232                 if (counter > last) {
    1233                     super.output(chars, last, counter - last);
    1234                 }
    1235                 last = counter + 1;
    1236                 output("&amp;");
    1237                 break;
    1238             case '"':
    1239                 if (counter > last) {
    1240                     super.output(chars, last, counter - last);
    1241                 }
    1242                 last = counter + 1;
    1243                 output("&quot;");
    1244                 break;
    1245                 // Special characters
    1246             case '\n':
    1247             case '\t':
    1248             case '\r':
    1249                 break;
    1250             default:
    1251                 if (chars[counter] < ' ' || chars[counter] > 127) {
    1252                     if (counter > last) {
    1253                         super.output(chars, last, counter - last);
    1254                     }
    1255                     last = counter + 1;
    1256                     // If the character is outside of ascii, write the
    1257                     // numeric value.
    1258                     output("&#");
    1259                     output(String.valueOf((int)chars[counter]));
    1260                     output(";");
    1261                 }
    1262                 break;
    1263             }
    1264         }
    1265         if (last < length) {
    1266             super.output(chars, last, length - last);
    1267         }
    1268     }
    1269
    1270     /**
    1271      * This directly invokes super's <code>output</code> after converting
    1272      * <code>string</code> to a char[].
    1273      */
    1274     private void output(String string) throws IOException {
    1275         int length = string.length();
    1276         if (tempChars == null || tempChars.length < length) {
    1277             tempChars = new char[length];
    1278         }
    1279         string.getChars(0, length, tempChars, 0);
    1280         super.output(tempChars, 0, length);
    1281     }
    1282
    1283     private boolean indented = false;
    1284
    1285     /**
    1286      * Writes indent only once per line.
    1287      */
    1288     private void indentSmart() throws IOException {
    1289         if (!indented) {
    1290             indent();
    1291             indented = true;
    1292         }
    1293     }
    1294 }