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                                 decrIndent();
 183                             }
 184                             endTag(top);
 185                         }
 186                         top = blockElementStack.peek();
 187                     }
 188                 } else if (current.getParentElement() == next.getParentElement()) {
 189                     /*
 190                        if next and current are siblings the indent level
 191                        is correct.  But, we need to make sure that if current is
 192                        on the stack, we pop it off, and put out its end tag.
 193                     */
 194                     Element top = blockElementStack.peek();
 195                     if (top == current) {
 196                         blockElementStack.pop();
 197                         endTag(top);
 198                     }
 199                 }
 200             }
 201             if (!next.isLeaf() || isFormElementWithContent(next.getAttributes())) {
 202                 blockElementStack.push(next);
 203                 startTag(next);
 204             } else {
 205                 emptyTag(next);
 206             }
 207             current = next;
 208         }
 209         /* Emit all remaining end tags */
 210 
 211         /* A null parameter ensures that all embedded tags
 212            currently in the tags vector have their
 213            corresponding end tags written out.
 214         */
 215         closeOutUnwantedEmbeddedTags(null);
 216 
 217         if (forcedBody) {
 218             blockElementStack.pop();
 219             endTag(current);
 220         }
 221         while (!blockElementStack.empty()) {
 222             current = blockElementStack.pop();
 223             if (!synthesizedElement(current)) {
 224                 AttributeSet attrs = current.getAttributes();
 225                 if (!matchNameAttribute(attrs, HTML.Tag.PRE) &&
 226                               !isFormElementWithContent(attrs)) {
 227                     decrIndent();
 228                 }
 229                 endTag(current);
 230             }
 231         }
 232 
 233         if (completeDoc) {
 234             writeAdditionalComments();
 235         }
 236 
 237         segment.array = null;
 238     }
 239 
 240 
 241     /**
 242      * Writes out the attribute set.  Ignores all
 243      * attributes with a key of type HTML.Tag,
 244      * attributes with a key of type StyleConstants,
 245      * and attributes with a key of type
 246      * HTML.Attribute.ENDTAG.
 247      *
 248      * @param attr   an AttributeSet
 249      * @exception IOException on any I/O error
 250      *
 251      */
 252     protected void writeAttributes(AttributeSet attr) throws IOException {
 253         // translate css attributes to html
 254         convAttr.removeAttributes(convAttr);
 255         convertToHTML32(attr, convAttr);
 256 
 257         Enumeration<?> names = convAttr.getAttributeNames();
 258         while (names.hasMoreElements()) {
 259             Object name = names.nextElement();
 260             if (name instanceof HTML.Tag ||
 261                 name instanceof StyleConstants ||
 262                 name == HTML.Attribute.ENDTAG) {
 263                 continue;
 264             }
 265             write(" " + name + "=\"" + convAttr.getAttribute(name) + "\"");
 266         }
 267     }
 268 
 269     /**
 270      * Writes out all empty elements (all tags that have no
 271      * corresponding end tag).
 272      *
 273      * @param elem   an Element
 274      * @exception IOException on any I/O error
 275      * @exception BadLocationException if pos represents an invalid
 276      *            location within the document.
 277      */
 278     protected void emptyTag(Element elem) throws BadLocationException, IOException {
 279 
 280         if (!inContent && !inPre) {
 281             indentSmart();
 282         }
 283 
 284         AttributeSet attr = elem.getAttributes();
 285         closeOutUnwantedEmbeddedTags(attr);
 286         writeEmbeddedTags(attr);
 287 
 288         if (matchNameAttribute(attr, HTML.Tag.CONTENT)) {
 289             inContent = true;
 290             text(elem);
 291         } else if (matchNameAttribute(attr, HTML.Tag.COMMENT)) {
 292             comment(elem);
 293         }  else {
 294             boolean isBlock = isBlockTag(elem.getAttributes());
 295             if (inContent && isBlock ) {
 296                 writeLineSeparator();
 297                 indentSmart();
 298             }
 299 
 300             Object nameTag = (attr != null) ? attr.getAttribute
 301                               (StyleConstants.NameAttribute) : null;
 302             Object endTag = (attr != null) ? attr.getAttribute
 303                               (HTML.Attribute.ENDTAG) : null;
 304 
 305             boolean outputEndTag = false;
 306             // If an instance of an UNKNOWN Tag, or an instance of a
 307             // tag that is only visible during editing
 308             //
 309             if (nameTag != null && endTag != null &&
 310                 (endTag instanceof String) &&
 311                 endTag.equals("true")) {
 312                 outputEndTag = true;
 313             }
 314 
 315             if (completeDoc && matchNameAttribute(attr, HTML.Tag.HEAD)) {
 316                 if (outputEndTag) {
 317                     // Write out any styles.
 318                     writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
 319                 }
 320                 wroteHead = true;
 321             }
 322 
 323             write('<');
 324             if (outputEndTag) {
 325                 write('/');
 326             }
 327             write(elem.getName());
 328             writeAttributes(attr);
 329             write('>');
 330             if (matchNameAttribute(attr, HTML.Tag.TITLE) && !outputEndTag) {
 331                 Document doc = elem.getDocument();
 332                 String title = (String)doc.getProperty(Document.TitleProperty);
 333                 write(title);
 334             } else if (!inContent || isBlock) {
 335                 writeLineSeparator();
 336                 if (isBlock && inContent) {
 337                     indentSmart();
 338                 }
 339             }
 340         }
 341     }
 342 
 343     /**
 344      * Determines if the HTML.Tag associated with the
 345      * element is a block tag.
 346      *
 347      * @param attr  an AttributeSet
 348      * @return  true if tag is block tag, false otherwise.
 349      */
 350     protected boolean isBlockTag(AttributeSet attr) {
 351         Object o = attr.getAttribute(StyleConstants.NameAttribute);
 352         if (o instanceof HTML.Tag) {
 353             HTML.Tag name = (HTML.Tag) o;
 354             return name.isBlock();
 355         }
 356         return false;
 357     }
 358 
 359 
 360     /**
 361      * Writes out a start tag for the element.
 362      * Ignores all synthesized elements.
 363      *
 364      * @param elem an Element
 365      * @throws IOException on any I/O error
 366      * @throws BadLocationException if pos represents an invalid
 367      *            location within the document.
 368      */
 369     protected void startTag(Element elem) throws IOException, BadLocationException {
 370 
 371         if (synthesizedElement(elem)) {
 372             return;
 373         }
 374 
 375         // Determine the name, as an HTML.Tag.
 376         AttributeSet attr = elem.getAttributes();
 377         Object nameAttribute = attr.getAttribute(StyleConstants.NameAttribute);
 378         HTML.Tag name;
 379         if (nameAttribute instanceof HTML.Tag) {
 380             name = (HTML.Tag)nameAttribute;
 381         }
 382         else {
 383             name = null;
 384         }
 385 
 386         if (name == HTML.Tag.PRE) {
 387             inPre = true;
 388             preEndOffset = elem.getEndOffset();
 389         }
 390 
 391         // write out end tags for item on stack
 392         closeOutUnwantedEmbeddedTags(attr);
 393 
 394         if (inContent) {
 395             writeLineSeparator();
 396             inContent = false;
 397             newlineOutputed = false;
 398         }
 399 
 400         if (completeDoc && name == HTML.Tag.BODY && !wroteHead) {
 401             // If the head has not been output, output it and the styles.
 402             wroteHead = true;
 403             indentSmart();
 404             write("<head>");
 405             writeLineSeparator();
 406             incrIndent();
 407             writeStyles(((HTMLDocument)getDocument()).getStyleSheet());
 408             decrIndent();
 409             writeLineSeparator();
 410             indentSmart();
 411             write("</head>");
 412             writeLineSeparator();
 413         }
 414 
 415         indentSmart();
 416         write('<');
 417         write(elem.getName());
 418         writeAttributes(attr);
 419         write('>');
 420         if (name != HTML.Tag.PRE) {
 421             writeLineSeparator();
 422         }
 423 
 424         if (name == HTML.Tag.TEXTAREA) {
 425             textAreaContent(elem.getAttributes());
 426         } else if (name == HTML.Tag.SELECT) {
 427             selectContent(elem.getAttributes());
 428         } else if (completeDoc && name == HTML.Tag.BODY) {
 429             // Write out the maps, which is not stored as Elements in
 430             // the Document.
 431             writeMaps(((HTMLDocument)getDocument()).getMaps());
 432         }
 433         else if (name == HTML.Tag.HEAD) {
 434             HTMLDocument document = (HTMLDocument)getDocument();
 435             wroteHead = true;
 436             incrIndent();
 437             writeStyles(document.getStyleSheet());
 438             if (document.hasBaseTag()) {
 439                 indentSmart();
 440                 write("<base href=\"" + document.getBase() + "\">");
 441                 writeLineSeparator();
 442             }
 443             decrIndent();
 444         }
 445 
 446     }
 447 
 448 
 449     /**
 450      * Writes out text that is contained in a TEXTAREA form
 451      * element.
 452      *
 453      * @param attr  an AttributeSet
 454      * @exception IOException on any I/O error
 455      * @exception BadLocationException if pos represents an invalid
 456      *            location within the document.
 457      */
 458     protected void textAreaContent(AttributeSet attr) throws BadLocationException, IOException {
 459         Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
 460         if (doc != null && doc.getLength() > 0) {
 461             if (segment == null) {
 462                 segment = new Segment();
 463             }
 464             doc.getText(0, doc.getLength(), segment);
 465             if (segment.count > 0) {
 466                 inTextArea = true;
 467                 incrIndent();
 468                 indentSmart();
 469                 setCanWrapLines(true);
 470                 replaceEntities = true;
 471                 write(segment.array, segment.offset, segment.count);
 472                 replaceEntities = false;
 473                 setCanWrapLines(false);
 474                 writeLineSeparator();
 475                 inTextArea = false;
 476                 decrIndent();
 477             }
 478         }
 479     }
 480 
 481 
 482     /**
 483      * Writes out text.  If a range is specified when the constructor
 484      * is invoked, then only the appropriate range of text is written
 485      * out.
 486      *
 487      * @param elem   an Element
 488      * @exception IOException on any I/O error
 489      * @exception BadLocationException if pos represents an invalid
 490      *            location within the document.
 491      */
 492     protected void text(Element elem) throws BadLocationException, IOException {
 493         int start = Math.max(getStartOffset(), elem.getStartOffset());
 494         int end = Math.min(getEndOffset(), elem.getEndOffset());
 495         if (start < end) {
 496             if (segment == null) {
 497                 segment = new Segment();
 498             }
 499             getDocument().getText(start, end - start, segment);
 500             newlineOutputed = false;
 501             if (segment.count > 0) {
 502                 if (segment.array[segment.offset + segment.count - 1] == '\n'){
 503                     newlineOutputed = true;
 504                 }
 505                 if (inPre && end == preEndOffset) {
 506                     if (segment.count > 1) {
 507                         segment.count--;
 508                     }
 509                     else {
 510                         return;
 511                     }
 512                 }
 513                 replaceEntities = true;
 514                 setCanWrapLines(!inPre);
 515                 write(segment.array, segment.offset, segment.count);
 516                 setCanWrapLines(false);
 517                 replaceEntities = false;
 518             }
 519         }
 520     }
 521 
 522     /**
 523      * Writes out the content of the SELECT form element.
 524      *
 525      * @param attr the AttributeSet associated with the form element
 526      * @exception IOException on any I/O error
 527      */
 528     protected void selectContent(AttributeSet attr) throws IOException {
 529         Object model = attr.getAttribute(StyleConstants.ModelAttribute);
 530         incrIndent();
 531         if (model instanceof OptionListModel) {
 532             @SuppressWarnings("unchecked")
 533             OptionListModel<Option> listModel = (OptionListModel<Option>) model;
 534             int size = listModel.getSize();
 535             for (int i = 0; i < size; i++) {
 536                 Option option = listModel.getElementAt(i);
 537                 writeOption(option);
 538             }
 539         } else if (model instanceof OptionComboBoxModel) {
 540             @SuppressWarnings("unchecked")
 541             OptionComboBoxModel<Option> comboBoxModel = (OptionComboBoxModel<Option>) 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     /**
 835      * Determines whether a the indentation needs to be
 836      * incremented.  Basically, if next is a child of current, and
 837      * next is NOT a synthesized element, the indent level will be
 838      * incremented.  If there is a parent-child relationship and "next"
 839      * is a synthesized element, then its children must be indented.
 840      * This state is maintained by the indentNext boolean.
 841      *
 842      * @return boolean that's true if indent level
 843      *         needs incrementing.
 844      */
 845     private boolean indentNext = false;
 846     private boolean indentNeedsIncrementing(Element current, Element next) {
 847         if ((next.getParentElement() == current) && !inPre) {
 848             if (indentNext) {
 849                 indentNext = false;
 850                 return true;
 851             } else if (synthesizedElement(next)) {
 852                 indentNext = true;
 853             } else if (!synthesizedElement(current)){
 854                 return true;
 855             }
 856         }
 857         return false;
 858     }
 859 
 860     /**
 861      * Outputs the maps as elements. Maps are not stored as elements in
 862      * the document, and as such this is used to output them.
 863      */
 864     void writeMaps(Enumeration<?> maps) throws IOException {
 865         if (maps != null) {
 866             while(maps.hasMoreElements()) {
 867                 Map map = (Map)maps.nextElement();
 868                 String name = map.getName();
 869 
 870                 incrIndent();
 871                 indentSmart();
 872                 write("<map");
 873                 if (name != null) {
 874                     write(" name=\"");
 875                     write(name);
 876                     write("\">");
 877                 }
 878                 else {
 879                     write('>');
 880                 }
 881                 writeLineSeparator();
 882                 incrIndent();
 883 
 884                 // Output the areas
 885                 AttributeSet[] areas = map.getAreas();
 886                 if (areas != null) {
 887                     for (int counter = 0, maxCounter = areas.length;
 888                          counter < maxCounter; counter++) {
 889                         indentSmart();
 890                         write("<area");
 891                         writeAttributes(areas[counter]);
 892                         write("></area>");
 893                         writeLineSeparator();
 894                     }
 895                 }
 896                 decrIndent();
 897                 indentSmart();
 898                 write("</map>");
 899                 writeLineSeparator();
 900                 decrIndent();
 901             }
 902         }
 903     }
 904 
 905     /**
 906      * Outputs the styles as a single element. Styles are not stored as
 907      * elements, but part of the document. For the time being styles are
 908      * written out as a comment, inside a style tag.
 909      */
 910     void writeStyles(StyleSheet sheet) throws IOException {
 911         if (sheet != null) {
 912             Enumeration<?> styles = sheet.getStyleNames();
 913             if (styles != null) {
 914                 boolean outputStyle = false;
 915                 while (styles.hasMoreElements()) {
 916                     String name = (String)styles.nextElement();
 917                     // Don't write out the default style.
 918                     if (!StyleContext.DEFAULT_STYLE.equals(name) &&
 919                         writeStyle(name, sheet.getStyle(name), outputStyle)) {
 920                         outputStyle = true;
 921                     }
 922                 }
 923                 if (outputStyle) {
 924                     writeStyleEndTag();
 925                 }
 926             }
 927         }
 928     }
 929 
 930     /**
 931      * Outputs the named style. <code>outputStyle</code> indicates
 932      * whether or not a style has been output yet. This will return
 933      * true if a style is written.
 934      */
 935     boolean writeStyle(String name, Style style, boolean outputStyle)
 936                  throws IOException{
 937         boolean didOutputStyle = false;
 938         Enumeration<?> attributes = style.getAttributeNames();
 939         if (attributes != null) {
 940             while (attributes.hasMoreElements()) {
 941                 Object attribute = attributes.nextElement();
 942                 if (attribute instanceof CSS.Attribute) {
 943                     String value = style.getAttribute(attribute).toString();
 944                     if (value != null) {
 945                         if (!outputStyle) {
 946                             writeStyleStartTag();
 947                             outputStyle = true;
 948                         }
 949                         if (!didOutputStyle) {
 950                             didOutputStyle = true;
 951                             indentSmart();
 952                             write(name);
 953                             write(" {");
 954                         }
 955                         else {
 956                             write(";");
 957                         }
 958                         write(' ');
 959                         write(attribute.toString());
 960                         write(": ");
 961                         write(value);
 962                     }
 963                 }
 964             }
 965         }
 966         if (didOutputStyle) {
 967             write(" }");
 968             writeLineSeparator();
 969         }
 970         return didOutputStyle;
 971     }
 972 
 973     void writeStyleStartTag() throws IOException {
 974         indentSmart();
 975         write("<style type=\"text/css\">");
 976         incrIndent();
 977         writeLineSeparator();
 978         indentSmart();
 979         write("<!--");
 980         incrIndent();
 981         writeLineSeparator();
 982     }
 983 
 984     void writeStyleEndTag() throws IOException {
 985         decrIndent();
 986         indentSmart();
 987         write("-->");
 988         writeLineSeparator();
 989         decrIndent();
 990         indentSmart();
 991         write("</style>");
 992         writeLineSeparator();
 993         indentSmart();
 994     }
 995 
 996     // --- conversion support ---------------------------
 997 
 998     /**
 999      * Convert the give set of attributes to be html for
1000      * the purpose of writing them out.  Any keys that
1001      * have been converted will not appear in the resultant
1002      * set.  Any keys not converted will appear in the
1003      * resultant set the same as the received set.<p>
1004      * This will put the converted values into <code>to</code>, unless
1005      * it is null in which case a temporary AttributeSet will be returned.
1006      */
1007     AttributeSet convertToHTML(AttributeSet from, MutableAttributeSet to) {
1008         if (to == null) {
1009             to = convAttr;
1010         }
1011         to.removeAttributes(to);
1012         if (writeCSS) {
1013             convertToHTML40(from, to);
1014         } else {
1015             convertToHTML32(from, to);
1016         }
1017         return to;
1018     }
1019 
1020     /**
1021      * If true, the writer will emit CSS attributes in preference
1022      * to HTML tags/attributes (i.e. It will emit an HTML 4.0
1023      * style).
1024      */
1025     private boolean writeCSS = false;
1026 
1027     /**
1028      * Buffer for the purpose of attribute conversion
1029      */
1030     private MutableAttributeSet convAttr = new SimpleAttributeSet();
1031 
1032     /**
1033      * Buffer for the purpose of attribute conversion. This can be
1034      * used if convAttr is being used.
1035      */
1036     private MutableAttributeSet oConvAttr = new SimpleAttributeSet();
1037 
1038     /**
1039      * Create an older style of HTML attributes.  This will
1040      * convert character level attributes that have a StyleConstants
1041      * mapping over to an HTML tag/attribute.  Other CSS attributes
1042      * will be placed in an HTML style attribute.
1043      */
1044     private static void convertToHTML32(AttributeSet from, MutableAttributeSet to) {
1045         if (from == null) {
1046             return;
1047         }
1048         Enumeration<?> keys = from.getAttributeNames();
1049         String value = "";
1050         while (keys.hasMoreElements()) {
1051             Object key = keys.nextElement();
1052             if (key instanceof CSS.Attribute) {
1053                 if ((key == CSS.Attribute.FONT_FAMILY) ||
1054                     (key == CSS.Attribute.FONT_SIZE) ||
1055                     (key == CSS.Attribute.COLOR)) {
1056 
1057                     createFontAttribute((CSS.Attribute)key, from, to);
1058                 } else if (key == CSS.Attribute.FONT_WEIGHT) {
1059                     // add a bold tag is weight is bold
1060                     CSS.FontWeight weightValue = (CSS.FontWeight)
1061                         from.getAttribute(CSS.Attribute.FONT_WEIGHT);
1062                     if ((weightValue != null) && (weightValue.getValue() > 400)) {
1063                         addAttribute(to, HTML.Tag.B, SimpleAttributeSet.EMPTY);
1064                     }
1065                 } else if (key == CSS.Attribute.FONT_STYLE) {
1066                     String s = from.getAttribute(key).toString();
1067                     if (s.indexOf("italic") >= 0) {
1068                         addAttribute(to, HTML.Tag.I, SimpleAttributeSet.EMPTY);
1069                     }
1070                 } else if (key == CSS.Attribute.TEXT_DECORATION) {
1071                     String decor = from.getAttribute(key).toString();
1072                     if (decor.indexOf("underline") >= 0) {
1073                         addAttribute(to, HTML.Tag.U, SimpleAttributeSet.EMPTY);
1074                     }
1075                     if (decor.indexOf("line-through") >= 0) {
1076                         addAttribute(to, HTML.Tag.STRIKE, SimpleAttributeSet.EMPTY);
1077                     }
1078                 } else if (key == CSS.Attribute.VERTICAL_ALIGN) {
1079                     String vAlign = from.getAttribute(key).toString();
1080                     if (vAlign.indexOf("sup") >= 0) {
1081                         addAttribute(to, HTML.Tag.SUP, SimpleAttributeSet.EMPTY);
1082                     }
1083                     if (vAlign.indexOf("sub") >= 0) {
1084                         addAttribute(to, HTML.Tag.SUB, SimpleAttributeSet.EMPTY);
1085                     }
1086                 } else if (key == CSS.Attribute.TEXT_ALIGN) {
1087                     addAttribute(to, HTML.Attribute.ALIGN,
1088                                     from.getAttribute(key).toString());
1089                 } else {
1090                     // default is to store in a HTML style attribute
1091                     if (value.length() > 0) {
1092                         value = value + "; ";
1093                     }
1094                     value = value + key + ": " + from.getAttribute(key);
1095                 }
1096             } else {
1097                 Object attr = from.getAttribute(key);
1098                 if (attr instanceof AttributeSet) {
1099                     attr = ((AttributeSet)attr).copyAttributes();
1100                 }
1101                 addAttribute(to, key, attr);
1102             }
1103         }
1104         if (value.length() > 0) {
1105             to.addAttribute(HTML.Attribute.STYLE, value);
1106         }
1107     }
1108 
1109     /**
1110      * Add an attribute only if it doesn't exist so that we don't
1111      * loose information replacing it with SimpleAttributeSet.EMPTY
1112      */
1113     private static void addAttribute(MutableAttributeSet to, Object key, Object value) {
1114         Object attr = to.getAttribute(key);
1115         if (attr == null || attr == SimpleAttributeSet.EMPTY) {
1116             to.addAttribute(key, value);
1117         } else {
1118             if (attr instanceof MutableAttributeSet &&
1119                 value instanceof AttributeSet) {
1120                 ((MutableAttributeSet)attr).addAttributes((AttributeSet)value);
1121             }
1122         }
1123     }
1124 
1125     /**
1126      * Create/update an HTML &lt;font&gt; tag attribute.  The
1127      * value of the attribute should be a MutableAttributeSet so
1128      * that the attributes can be updated as they are discovered.
1129      */
1130     private static void createFontAttribute(CSS.Attribute a, AttributeSet from,
1131                                     MutableAttributeSet to) {
1132         MutableAttributeSet fontAttr = (MutableAttributeSet)
1133             to.getAttribute(HTML.Tag.FONT);
1134         if (fontAttr == null) {
1135             fontAttr = new SimpleAttributeSet();
1136             to.addAttribute(HTML.Tag.FONT, fontAttr);
1137         }
1138         // edit the parameters to the font tag
1139         String htmlValue = from.getAttribute(a).toString();
1140         if (a == CSS.Attribute.FONT_FAMILY) {
1141             fontAttr.addAttribute(HTML.Attribute.FACE, htmlValue);
1142         } else if (a == CSS.Attribute.FONT_SIZE) {
1143             fontAttr.addAttribute(HTML.Attribute.SIZE, htmlValue);
1144         } else if (a == CSS.Attribute.COLOR) {
1145             fontAttr.addAttribute(HTML.Attribute.COLOR, htmlValue);
1146         }
1147     }
1148 
1149     /**
1150      * Copies the given AttributeSet to a new set, converting
1151      * any CSS attributes found to arguments of an HTML style
1152      * attribute.
1153      */
1154     private static void convertToHTML40(AttributeSet from, MutableAttributeSet to) {
1155         Enumeration<?> keys = from.getAttributeNames();
1156         String value = "";
1157         while (keys.hasMoreElements()) {
1158             Object key = keys.nextElement();
1159             if (key instanceof CSS.Attribute) {
1160                 value = value + " " + key + "=" + from.getAttribute(key) + ";";
1161             } else {
1162                 to.addAttribute(key, from.getAttribute(key));
1163             }
1164         }
1165         if (value.length() > 0) {
1166             to.addAttribute(HTML.Attribute.STYLE, value);
1167         }
1168     }
1169 
1170     //
1171     // Overrides the writing methods to only break a string when
1172     // canBreakString is true.
1173     // In a future release it is likely AbstractWriter will get this
1174     // functionality.
1175     //
1176 
1177     /**
1178      * Writes the line separator. This is overriden to make sure we don't
1179      * replace the newline content in case it is outside normal ascii.
1180      * @since 1.3
1181      */
1182     protected void writeLineSeparator() throws IOException {
1183         boolean oldReplace = replaceEntities;
1184         replaceEntities = false;
1185         super.writeLineSeparator();
1186         replaceEntities = oldReplace;
1187         indented = false;
1188     }
1189 
1190     /**
1191      * This method is overriden to map any character entities, such as
1192      * &lt; to &amp;lt;. <code>super.output</code> will be invoked to
1193      * write the content.
1194      * @since 1.3
1195      */
1196     protected void output(char[] chars, int start, int length)
1197                    throws IOException {
1198         if (!replaceEntities) {
1199             super.output(chars, start, length);
1200             return;
1201         }
1202         int last = start;
1203         length += start;
1204         for (int counter = start; counter < length; counter++) {
1205             // This will change, we need better support character level
1206             // entities.
1207             switch(chars[counter]) {
1208                 // Character level entities.
1209             case '<':
1210                 if (counter > last) {
1211                     super.output(chars, last, counter - last);
1212                 }
1213                 last = counter + 1;
1214                 output("&lt;");
1215                 break;
1216             case '>':
1217                 if (counter > last) {
1218                     super.output(chars, last, counter - last);
1219                 }
1220                 last = counter + 1;
1221                 output("&gt;");
1222                 break;
1223             case '&':
1224                 if (counter > last) {
1225                     super.output(chars, last, counter - last);
1226                 }
1227                 last = counter + 1;
1228                 output("&amp;");
1229                 break;
1230             case '"':
1231                 if (counter > last) {
1232                     super.output(chars, last, counter - last);
1233                 }
1234                 last = counter + 1;
1235                 output("&quot;");
1236                 break;
1237                 // Special characters
1238             case '\n':
1239             case '\t':
1240             case '\r':
1241                 break;
1242             default:
1243                 if (chars[counter] < ' ' || chars[counter] > 127) {
1244                     if (counter > last) {
1245                         super.output(chars, last, counter - last);
1246                     }
1247                     last = counter + 1;
1248                     // If the character is outside of ascii, write the
1249                     // numeric value.
1250                     output("&#");
1251                     output(String.valueOf((int)chars[counter]));
1252                     output(";");
1253                 }
1254                 break;
1255             }
1256         }
1257         if (last < length) {
1258             super.output(chars, last, length - last);
1259         }
1260     }
1261 
1262     /**
1263      * This directly invokes super's <code>output</code> after converting
1264      * <code>string</code> to a char[].
1265      */
1266     private void output(String string) throws IOException {
1267         int length = string.length();
1268         if (tempChars == null || tempChars.length < length) {
1269             tempChars = new char[length];
1270         }
1271         string.getChars(0, length, tempChars, 0);
1272         super.output(tempChars, 0, length);
1273     }
1274 
1275     private boolean indented = false;
1276 
1277     /**
1278      * Writes indent only once per line.
1279      */
1280     private void indentSmart() throws IOException {
1281         if (!indented) {
1282             indent();
1283             indented = true;
1284         }
1285     }
1286 }