1 /* 2 * Copyright (c) 1998, 2010, 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 26 package javax.swing.text.html; 27 28 import java.io.Writer; 29 import java.io.IOException; 30 import java.util.*; 31 import java.awt.Color; 32 import javax.swing.text.*; 33 34 /** 35 * MinimalHTMLWriter is a fallback writer used by the 36 * HTMLEditorKit to write out HTML for a document that 37 * is a not produced by the EditorKit. 38 * 39 * The format for the document is: 40 * <pre> 41 * <html> 42 * <head> 43 * <style> 44 * <!-- list of named styles 45 * p.normal { 46 * font-family: SansSerif; 47 * margin-height: 0; 48 * font-size: 14 49 * } 50 * --> 51 * </style> 52 * </head> 53 * <body> 54 * <p style=normal> 55 * <b>Bold, italic, and underline attributes 56 * of the run are emitted as HTML tags. 57 * The remaining attributes are emitted as 58 * part of the style attribute of a <span> tag. 59 * The syntax is similar to inline styles.</b> 60 * </p> 61 * </body> 62 * </html> 63 * </pre> 64 * 65 * @author Sunita Mani 66 */ 67 68 public class MinimalHTMLWriter extends AbstractWriter { 69 70 /** 71 * These static finals are used to 72 * tweak and query the fontMask about which 73 * of these tags need to be generated or 74 * terminated. 75 */ 76 private static final int BOLD = 0x01; 77 private static final int ITALIC = 0x02; 78 private static final int UNDERLINE = 0x04; 79 80 // Used to map StyleConstants to CSS. 81 private static final CSS css = new CSS(); 82 83 private int fontMask = 0; 84 85 int startOffset = 0; 86 int endOffset = 0; 87 88 /** 89 * Stores the attributes of the previous run. 90 * Used to compare with the current run's 91 * attributeset. If identical, then a 92 * <span> tag is not emitted. 93 */ 94 private AttributeSet fontAttributes; 95 96 /** 97 * Maps from style name as held by the Document, to the archived 98 * style name (style name written out). These may differ. 99 */ 100 private Hashtable<String, String> styleNameMapping; 101 102 /** 103 * Creates a new MinimalHTMLWriter. 104 * 105 * @param w Writer 106 * @param doc StyledDocument 107 * 108 */ MinimalHTMLWriter(Writer w, StyledDocument doc)109 public MinimalHTMLWriter(Writer w, StyledDocument doc) { 110 super(w, doc); 111 } 112 113 /** 114 * Creates a new MinimalHTMLWriter. 115 * 116 * @param w Writer 117 * @param doc StyledDocument 118 * @param pos The location in the document to fetch the 119 * content. 120 * @param len The amount to write out. 121 * 122 */ MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len)123 public MinimalHTMLWriter(Writer w, StyledDocument doc, int pos, int len) { 124 super(w, doc, pos, len); 125 } 126 127 /** 128 * Generates HTML output 129 * from a StyledDocument. 130 * 131 * @exception IOException on any I/O error 132 * @exception BadLocationException if pos represents an invalid 133 * location within the document. 134 * 135 */ write()136 public void write() throws IOException, BadLocationException { 137 styleNameMapping = new Hashtable<String, String>(); 138 writeStartTag("<html>"); 139 writeHeader(); 140 writeBody(); 141 writeEndTag("</html>"); 142 } 143 144 145 /** 146 * Writes out all the attributes for the 147 * following types: 148 * StyleConstants.ParagraphConstants, 149 * StyleConstants.CharacterConstants, 150 * StyleConstants.FontConstants, 151 * StyleConstants.ColorConstants. 152 * The attribute name and value are separated by a colon. 153 * Each pair is separated by a semicolon. 154 * 155 * @exception IOException on any I/O error 156 */ writeAttributes(AttributeSet attr)157 protected void writeAttributes(AttributeSet attr) throws IOException { 158 Enumeration<?> attributeNames = attr.getAttributeNames(); 159 while (attributeNames.hasMoreElements()) { 160 Object name = attributeNames.nextElement(); 161 if ((name instanceof StyleConstants.ParagraphConstants) || 162 (name instanceof StyleConstants.CharacterConstants) || 163 (name instanceof StyleConstants.FontConstants) || 164 (name instanceof StyleConstants.ColorConstants)) { 165 indent(); 166 write(name.toString()); 167 write(':'); 168 write(css.styleConstantsValueToCSSValue 169 ((StyleConstants)name, attr.getAttribute(name)). 170 toString()); 171 write(';'); 172 write(NEWLINE); 173 } 174 } 175 } 176 177 178 /** 179 * Writes out text. 180 * 181 * @exception IOException on any I/O error 182 */ text(Element elem)183 protected void text(Element elem) throws IOException, BadLocationException { 184 String contentStr = getText(elem); 185 if ((contentStr.length() > 0) && 186 (contentStr.charAt(contentStr.length()-1) == NEWLINE)) { 187 contentStr = contentStr.substring(0, contentStr.length()-1); 188 } 189 if (contentStr.length() > 0) { 190 write(contentStr); 191 } 192 } 193 194 /** 195 * Writes out a start tag appropriately 196 * indented. Also increments the indent level. 197 * 198 * @param tag a start tag 199 * @exception IOException on any I/O error 200 */ writeStartTag(String tag)201 protected void writeStartTag(String tag) throws IOException { 202 indent(); 203 write(tag); 204 write(NEWLINE); 205 incrIndent(); 206 } 207 208 209 /** 210 * Writes out an end tag appropriately 211 * indented. Also decrements the indent level. 212 * 213 * @param endTag an end tag 214 * @exception IOException on any I/O error 215 */ writeEndTag(String endTag)216 protected void writeEndTag(String endTag) throws IOException { 217 decrIndent(); 218 indent(); 219 write(endTag); 220 write(NEWLINE); 221 } 222 223 224 /** 225 * Writes out the <head> and <style> 226 * tags, and then invokes writeStyles() to write 227 * out all the named styles as the content of the 228 * <style> tag. The content is surrounded by 229 * valid HTML comment markers to ensure that the 230 * document is viewable in applications/browsers 231 * that do not support the tag. 232 * 233 * @exception IOException on any I/O error 234 */ writeHeader()235 protected void writeHeader() throws IOException { 236 writeStartTag("<head>"); 237 writeStartTag("<style>"); 238 writeStartTag("<!--"); 239 writeStyles(); 240 writeEndTag("-->"); 241 writeEndTag("</style>"); 242 writeEndTag("</head>"); 243 } 244 245 246 247 /** 248 * Writes out all the named styles as the 249 * content of the <style> tag. 250 * 251 * @exception IOException on any I/O error 252 */ writeStyles()253 protected void writeStyles() throws IOException { 254 /* 255 * Access to DefaultStyledDocument done to workaround 256 * a missing API in styled document to access the 257 * stylenames. 258 */ 259 DefaultStyledDocument styledDoc = ((DefaultStyledDocument)getDocument()); 260 Enumeration<?> styleNames = styledDoc.getStyleNames(); 261 262 while (styleNames.hasMoreElements()) { 263 Style s = styledDoc.getStyle((String)styleNames.nextElement()); 264 265 /** PENDING: Once the name attribute is removed 266 from the list we check check for 0. **/ 267 if (s.getAttributeCount() == 1 && 268 s.isDefined(StyleConstants.NameAttribute)) { 269 continue; 270 } 271 indent(); 272 write("p." + addStyleName(s.getName())); 273 write(" {\n"); 274 incrIndent(); 275 writeAttributes(s); 276 decrIndent(); 277 indent(); 278 write("}\n"); 279 } 280 } 281 282 283 /** 284 * Iterates over the elements in the document 285 * and processes elements based on whether they are 286 * branch elements or leaf elements. This method specially handles 287 * leaf elements that are text. 288 * 289 * @throws IOException on any I/O error 290 * @throws BadLocationException if we are in an invalid 291 * location within the document. 292 */ writeBody()293 protected void writeBody() throws IOException, BadLocationException { 294 ElementIterator it = getElementIterator(); 295 296 /* 297 This will be a section element for a styled document. 298 We represent this element in HTML as the body tags. 299 Therefore we ignore it. 300 */ 301 it.current(); 302 303 Element next; 304 305 writeStartTag("<body>"); 306 307 boolean inContent = false; 308 309 while((next = it.next()) != null) { 310 if (!inRange(next)) { 311 continue; 312 } 313 if (next instanceof AbstractDocument.BranchElement) { 314 if (inContent) { 315 writeEndParagraph(); 316 inContent = false; 317 fontMask = 0; 318 } 319 writeStartParagraph(next); 320 } else if (isText(next)) { 321 writeContent(next, !inContent); 322 inContent = true; 323 } else { 324 writeLeaf(next); 325 inContent = true; 326 } 327 } 328 if (inContent) { 329 writeEndParagraph(); 330 } 331 writeEndTag("</body>"); 332 } 333 334 335 /** 336 * Emits an end tag for a <p> 337 * tag. Before writing out the tag, this method ensures 338 * that all other tags that have been opened are 339 * appropriately closed off. 340 * 341 * @exception IOException on any I/O error 342 */ writeEndParagraph()343 protected void writeEndParagraph() throws IOException { 344 writeEndMask(fontMask); 345 if (inFontTag()) { 346 endSpanTag(); 347 } else { 348 write(NEWLINE); 349 } 350 writeEndTag("</p>"); 351 } 352 353 354 /** 355 * Emits the start tag for a paragraph. If 356 * the paragraph has a named style associated with it, 357 * then this method also generates a class attribute for the 358 * <p> tag and sets its value to be the name of the 359 * style. 360 * 361 * @param elem an element 362 * @exception IOException on any I/O error 363 */ writeStartParagraph(Element elem)364 protected void writeStartParagraph(Element elem) throws IOException { 365 AttributeSet attr = elem.getAttributes(); 366 Object resolveAttr = attr.getAttribute(StyleConstants.ResolveAttribute); 367 if (resolveAttr instanceof StyleContext.NamedStyle) { 368 writeStartTag("<p class=" + mapStyleName(((StyleContext.NamedStyle)resolveAttr).getName()) + ">"); 369 } else { 370 writeStartTag("<p>"); 371 } 372 } 373 374 375 /** 376 * Responsible for writing out other non-text leaf 377 * elements. 378 * 379 * @param elem an element 380 * @exception IOException on any I/O error 381 */ writeLeaf(Element elem)382 protected void writeLeaf(Element elem) throws IOException { 383 indent(); 384 if (elem.getName() == StyleConstants.IconElementName) { 385 writeImage(elem); 386 } else if (elem.getName() == StyleConstants.ComponentElementName) { 387 writeComponent(elem); 388 } 389 } 390 391 392 /** 393 * Responsible for handling Icon Elements; 394 * deliberately unimplemented. How to implement this method is 395 * an issue of policy. For example, if you're generating 396 * an <img> tag, how should you 397 * represent the src attribute (the location of the image)? 398 * In certain cases it could be a URL, in others it could 399 * be read from a stream. 400 * 401 * @param elem an element of type StyleConstants.IconElementName 402 * @throws IOException if I/O error occured. 403 */ writeImage(Element elem)404 protected void writeImage(Element elem) throws IOException { 405 } 406 407 408 /** 409 * Responsible for handling Component Elements; 410 * deliberately unimplemented. 411 * How this method is implemented is a matter of policy. 412 * 413 * @param elem an element of type StyleConstants.ComponentElementName 414 * @throws IOException if I/O error occured. 415 */ writeComponent(Element elem)416 protected void writeComponent(Element elem) throws IOException { 417 } 418 419 420 /** 421 * Returns true if the element is a text element. 422 * 423 * @param elem an element 424 * @return {@code true} if the element is a text element. 425 */ isText(Element elem)426 protected boolean isText(Element elem) { 427 return (elem.getName() == AbstractDocument.ContentElementName); 428 } 429 430 431 /** 432 * Writes out the attribute set 433 * in an HTML-compliant manner. 434 * 435 * @param elem an element 436 * @param needsIndenting indention will be added if {@code needsIndenting} is {@code true} 437 * @exception IOException on any I/O error 438 * @exception BadLocationException if pos represents an invalid 439 * location within the document. 440 */ writeContent(Element elem, boolean needsIndenting)441 protected void writeContent(Element elem, boolean needsIndenting) 442 throws IOException, BadLocationException { 443 444 AttributeSet attr = elem.getAttributes(); 445 writeNonHTMLAttributes(attr); 446 if (needsIndenting) { 447 indent(); 448 } 449 writeHTMLTags(attr); 450 text(elem); 451 } 452 453 454 /** 455 * Generates 456 * bold <b>, italic <i>, and <u> tags for the 457 * text based on its attribute settings. 458 * 459 * @param attr a set of attributes 460 * @exception IOException on any I/O error 461 */ 462 writeHTMLTags(AttributeSet attr)463 protected void writeHTMLTags(AttributeSet attr) throws IOException { 464 465 int oldMask = fontMask; 466 setFontMask(attr); 467 468 int endMask = 0; 469 int startMask = 0; 470 if ((oldMask & BOLD) != 0) { 471 if ((fontMask & BOLD) == 0) { 472 endMask |= BOLD; 473 } 474 } else if ((fontMask & BOLD) != 0) { 475 startMask |= BOLD; 476 } 477 478 if ((oldMask & ITALIC) != 0) { 479 if ((fontMask & ITALIC) == 0) { 480 endMask |= ITALIC; 481 } 482 } else if ((fontMask & ITALIC) != 0) { 483 startMask |= ITALIC; 484 } 485 486 if ((oldMask & UNDERLINE) != 0) { 487 if ((fontMask & UNDERLINE) == 0) { 488 endMask |= UNDERLINE; 489 } 490 } else if ((fontMask & UNDERLINE) != 0) { 491 startMask |= UNDERLINE; 492 } 493 writeEndMask(endMask); 494 writeStartMask(startMask); 495 } 496 497 498 /** 499 * Tweaks the appropriate bits of fontMask 500 * to reflect whether the text is to be displayed in 501 * bold, italic, and/or with an underline. 502 * 503 */ setFontMask(AttributeSet attr)504 private void setFontMask(AttributeSet attr) { 505 if (StyleConstants.isBold(attr)) { 506 fontMask |= BOLD; 507 } 508 509 if (StyleConstants.isItalic(attr)) { 510 fontMask |= ITALIC; 511 } 512 513 if (StyleConstants.isUnderline(attr)) { 514 fontMask |= UNDERLINE; 515 } 516 } 517 518 519 520 521 /** 522 * Writes out start tags <u>, <i>, and <b> based on 523 * the mask settings. 524 * 525 * @exception IOException on any I/O error 526 */ writeStartMask(int mask)527 private void writeStartMask(int mask) throws IOException { 528 if (mask != 0) { 529 if ((mask & UNDERLINE) != 0) { 530 write("<u>"); 531 } 532 if ((mask & ITALIC) != 0) { 533 write("<i>"); 534 } 535 if ((mask & BOLD) != 0) { 536 write("<b>"); 537 } 538 } 539 } 540 541 /** 542 * Writes out end tags for <u>, <i>, and <b> based on 543 * the mask settings. 544 * 545 * @exception IOException on any I/O error 546 */ writeEndMask(int mask)547 private void writeEndMask(int mask) throws IOException { 548 if (mask != 0) { 549 if ((mask & BOLD) != 0) { 550 write("</b>"); 551 } 552 if ((mask & ITALIC) != 0) { 553 write("</i>"); 554 } 555 if ((mask & UNDERLINE) != 0) { 556 write("</u>"); 557 } 558 } 559 } 560 561 562 /** 563 * Writes out the remaining 564 * character-level attributes (attributes other than bold, 565 * italic, and underline) in an HTML-compliant way. Given that 566 * attributes such as font family and font size have no direct 567 * mapping to HTML tags, a <span> tag is generated and its 568 * style attribute is set to contain the list of remaining 569 * attributes just like inline styles. 570 * 571 * @param attr a set of attributes 572 * @exception IOException on any I/O error 573 */ writeNonHTMLAttributes(AttributeSet attr)574 protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException { 575 576 String style = ""; 577 String separator = "; "; 578 579 if (inFontTag() && fontAttributes.isEqual(attr)) { 580 return; 581 } 582 583 boolean first = true; 584 Color color = (Color)attr.getAttribute(StyleConstants.Foreground); 585 if (color != null) { 586 style += "color: " + css.styleConstantsValueToCSSValue 587 ((StyleConstants)StyleConstants.Foreground, 588 color); 589 first = false; 590 } 591 Integer size = (Integer)attr.getAttribute(StyleConstants.FontSize); 592 if (size != null) { 593 if (!first) { 594 style += separator; 595 } 596 style += "font-size: " + size.intValue() + "pt"; 597 first = false; 598 } 599 600 String family = (String)attr.getAttribute(StyleConstants.FontFamily); 601 if (family != null) { 602 if (!first) { 603 style += separator; 604 } 605 style += "font-family: " + family; 606 first = false; 607 } 608 609 if (style.length() > 0) { 610 if (fontMask != 0) { 611 writeEndMask(fontMask); 612 fontMask = 0; 613 } 614 startSpanTag(style); 615 fontAttributes = attr; 616 } 617 else if (fontAttributes != null) { 618 writeEndMask(fontMask); 619 fontMask = 0; 620 endSpanTag(); 621 } 622 } 623 624 625 /** 626 * Returns true if we are currently in a <font> tag. 627 * 628 * @return {@code true} if we are currently in a <font> tag. 629 */ inFontTag()630 protected boolean inFontTag() { 631 return (fontAttributes != null); 632 } 633 634 /** 635 * This is no longer used, instead <span> will be written out. 636 * <p> 637 * Writes out an end tag for the <font> tag. 638 * 639 * @exception IOException on any I/O error 640 */ endFontTag()641 protected void endFontTag() throws IOException { 642 write(NEWLINE); 643 writeEndTag("</font>"); 644 fontAttributes = null; 645 } 646 647 648 /** 649 * This is no longer used, instead <span> will be written out. 650 * <p> 651 * Writes out a start tag for the <font> tag. 652 * Because font tags cannot be nested, 653 * this method closes out 654 * any enclosing font tag before writing out a 655 * new start tag. 656 * 657 * @param style a font style 658 * @exception IOException on any I/O error 659 */ startFontTag(String style)660 protected void startFontTag(String style) throws IOException { 661 boolean callIndent = false; 662 if (inFontTag()) { 663 endFontTag(); 664 callIndent = true; 665 } 666 writeStartTag("<font style=\"" + style + "\">"); 667 if (callIndent) { 668 indent(); 669 } 670 } 671 672 /** 673 * Writes out a start tag for the <font> tag. 674 * Because font tags cannot be nested, 675 * this method closes out 676 * any enclosing font tag before writing out a 677 * new start tag. 678 * 679 * @exception IOException on any I/O error 680 */ startSpanTag(String style)681 private void startSpanTag(String style) throws IOException { 682 boolean callIndent = false; 683 if (inFontTag()) { 684 endSpanTag(); 685 callIndent = true; 686 } 687 writeStartTag("<span style=\"" + style + "\">"); 688 if (callIndent) { 689 indent(); 690 } 691 } 692 693 /** 694 * Writes out an end tag for the <span> tag. 695 * 696 * @exception IOException on any I/O error 697 */ endSpanTag()698 private void endSpanTag() throws IOException { 699 write(NEWLINE); 700 writeEndTag("</span>"); 701 fontAttributes = null; 702 } 703 704 /** 705 * Adds the style named <code>style</code> to the style mapping. This 706 * returns the name that should be used when outputting. CSS does not 707 * allow the full Unicode set to be used as a style name. 708 */ addStyleName(String style)709 private String addStyleName(String style) { 710 if (styleNameMapping == null) { 711 return style; 712 } 713 StringBuilder sb = null; 714 for (int counter = style.length() - 1; counter >= 0; counter--) { 715 if (!isValidCharacter(style.charAt(counter))) { 716 if (sb == null) { 717 sb = new StringBuilder(style); 718 } 719 sb.setCharAt(counter, 'a'); 720 } 721 } 722 String mappedName = (sb != null) ? sb.toString() : style; 723 while (styleNameMapping.get(mappedName) != null) { 724 mappedName = mappedName + 'x'; 725 } 726 styleNameMapping.put(style, mappedName); 727 return mappedName; 728 } 729 730 /** 731 * Returns the mapped style name corresponding to <code>style</code>. 732 */ mapStyleName(String style)733 private String mapStyleName(String style) { 734 if (styleNameMapping == null) { 735 return style; 736 } 737 String retValue = styleNameMapping.get(style); 738 return (retValue == null) ? style : retValue; 739 } 740 isValidCharacter(char character)741 private boolean isValidCharacter(char character) { 742 return ((character >= 'a' && character <= 'z') || 743 (character >= 'A' && character <= 'Z')); 744 } 745 } 746