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 * <b>, <i>, <font>, and <a>. 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 <) 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} 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}, 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 <font> 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 * < to &lt;. {@code super.output} 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("<"); 1215 break; 1216 case '>': 1217 if (counter > last) { 1218 super.output(chars, last, counter - last); 1219 } 1220 last = counter + 1; 1221 output(">"); 1222 break; 1223 case '&': 1224 if (counter > last) { 1225 super.output(chars, last, counter - last); 1226 } 1227 last = counter + 1; 1228 output("&"); 1229 break; 1230 case '"': 1231 if (counter > last) { 1232 super.output(chars, last, counter - last); 1233 } 1234 last = counter + 1; 1235 output("""); 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} after converting 1264 * {@code string} 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 } --- EOF ---