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 */ 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 */ 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 */ 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 */ 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 */ 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 * @exception IOException on any I/O error 199 */ 200 protected void writeStartTag(String tag) throws IOException { 201 indent(); 202 write(tag); 203 write(NEWLINE); 204 incrIndent(); 205 } 206 207 208 /** 209 * Writes out an end tag appropriately 210 * indented. Also decrements the indent level. 211 * 212 * @exception IOException on any I/O error 213 */ 214 protected void writeEndTag(String endTag) throws IOException { 215 decrIndent(); 216 indent(); 217 write(endTag); 218 write(NEWLINE); 219 } 220 221 222 /** 223 * Writes out the <head> and <style> 224 * tags, and then invokes writeStyles() to write 225 * out all the named styles as the content of the 226 * <style> tag. The content is surrounded by 227 * valid HTML comment markers to ensure that the 228 * document is viewable in applications/browsers 229 * that do not support the tag. 230 * 231 * @exception IOException on any I/O error 232 */ 233 protected void writeHeader() throws IOException { 234 writeStartTag("<head>"); 235 writeStartTag("<style>"); 236 writeStartTag("<!--"); 237 writeStyles(); 238 writeEndTag("-->"); 239 writeEndTag("</style>"); 240 writeEndTag("</head>"); 241 } 242 243 244 245 /** 246 * Writes out all the named styles as the 247 * content of the <style> tag. 248 * 249 * @exception IOException on any I/O error 250 */ 251 protected void writeStyles() throws IOException { 252 /* 253 * Access to DefaultStyledDocument done to workaround 254 * a missing API in styled document to access the 255 * stylenames. 256 */ 257 DefaultStyledDocument styledDoc = ((DefaultStyledDocument)getDocument()); 258 Enumeration styleNames = styledDoc.getStyleNames(); 259 260 while (styleNames.hasMoreElements()) { 261 Style s = styledDoc.getStyle((String)styleNames.nextElement()); 262 263 /** PENDING: Once the name attribute is removed 264 from the list we check check for 0. **/ 265 if (s.getAttributeCount() == 1 && 266 s.isDefined(StyleConstants.NameAttribute)) { 267 continue; 268 } 269 indent(); 270 write("p." + addStyleName(s.getName())); 271 write(" {\n"); 272 incrIndent(); 273 writeAttributes(s); 274 decrIndent(); 275 indent(); 276 write("}\n"); 277 } 278 } 279 280 281 /** 282 * Iterates over the elements in the document 283 * and processes elements based on whether they are 284 * branch elements or leaf elements. This method specially handles 285 * leaf elements that are text. 286 * 287 * @exception IOException on any I/O error 288 */ 289 protected void writeBody() throws IOException, BadLocationException { 290 ElementIterator it = getElementIterator(); 291 292 /* 293 This will be a section element for a styled document. 294 We represent this element in HTML as the body tags. 295 Therefore we ignore it. 296 */ 297 it.current(); 298 299 Element next; 300 301 writeStartTag("<body>"); 302 303 boolean inContent = false; 304 305 while((next = it.next()) != null) { 306 if (!inRange(next)) { 307 continue; 308 } 309 if (next instanceof AbstractDocument.BranchElement) { 310 if (inContent) { 311 writeEndParagraph(); 312 inContent = false; 313 fontMask = 0; 314 } 315 writeStartParagraph(next); 316 } else if (isText(next)) { 317 writeContent(next, !inContent); 318 inContent = true; 319 } else { 320 writeLeaf(next); 321 inContent = true; 322 } 323 } 324 if (inContent) { 325 writeEndParagraph(); 326 } 327 writeEndTag("</body>"); 328 } 329 330 331 /** 332 * Emits an end tag for a <p> 333 * tag. Before writing out the tag, this method ensures 334 * that all other tags that have been opened are 335 * appropriately closed off. 336 * 337 * @exception IOException on any I/O error 338 */ 339 protected void writeEndParagraph() throws IOException { 340 writeEndMask(fontMask); 341 if (inFontTag()) { 342 endSpanTag(); 343 } else { 344 write(NEWLINE); 345 } 346 writeEndTag("</p>"); 347 } 348 349 350 /** 351 * Emits the start tag for a paragraph. If 352 * the paragraph has a named style associated with it, 353 * then this method also generates a class attribute for the 354 * <p> tag and sets its value to be the name of the 355 * style. 356 * 357 * @exception IOException on any I/O error 358 */ 359 protected void writeStartParagraph(Element elem) throws IOException { 360 AttributeSet attr = elem.getAttributes(); 361 Object resolveAttr = attr.getAttribute(StyleConstants.ResolveAttribute); 362 if (resolveAttr instanceof StyleContext.NamedStyle) { 363 writeStartTag("<p class=" + mapStyleName(((StyleContext.NamedStyle)resolveAttr).getName()) + ">"); 364 } else { 365 writeStartTag("<p>"); 366 } 367 } 368 369 370 /** 371 * Responsible for writing out other non-text leaf 372 * elements. 373 * 374 * @exception IOException on any I/O error 375 */ 376 protected void writeLeaf(Element elem) throws IOException { 377 indent(); 378 if (elem.getName() == StyleConstants.IconElementName) { 379 writeImage(elem); 380 } else if (elem.getName() == StyleConstants.ComponentElementName) { 381 writeComponent(elem); 382 } 383 } 384 385 386 /** 387 * Responsible for handling Icon Elements; 388 * deliberately unimplemented. How to implement this method is 389 * an issue of policy. For example, if you're generating 390 * an <img> tag, how should you 391 * represent the src attribute (the location of the image)? 392 * In certain cases it could be a URL, in others it could 393 * be read from a stream. 394 * 395 * @param elem element of type StyleConstants.IconElementName 396 */ 397 protected void writeImage(Element elem) throws IOException { 398 } 399 400 401 /** 402 * Responsible for handling Component Elements; 403 * deliberately unimplemented. 404 * How this method is implemented is a matter of policy. 405 */ 406 protected void writeComponent(Element elem) throws IOException { 407 } 408 409 410 /** 411 * Returns true if the element is a text element. 412 * 413 */ 414 protected boolean isText(Element elem) { 415 return (elem.getName() == AbstractDocument.ContentElementName); 416 } 417 418 419 /** 420 * Writes out the attribute set 421 * in an HTML-compliant manner. 422 * 423 * @exception IOException on any I/O error 424 * @exception BadLocationException if pos represents an invalid 425 * location within the document. 426 */ 427 protected void writeContent(Element elem, boolean needsIndenting) 428 throws IOException, BadLocationException { 429 430 AttributeSet attr = elem.getAttributes(); 431 writeNonHTMLAttributes(attr); 432 if (needsIndenting) { 433 indent(); 434 } 435 writeHTMLTags(attr); 436 text(elem); 437 } 438 439 440 /** 441 * Generates 442 * bold <b>, italic <i>, and <u> tags for the 443 * text based on its attribute settings. 444 * 445 * @exception IOException on any I/O error 446 */ 447 448 protected void writeHTMLTags(AttributeSet attr) throws IOException { 449 450 int oldMask = fontMask; 451 setFontMask(attr); 452 453 int endMask = 0; 454 int startMask = 0; 455 if ((oldMask & BOLD) != 0) { 456 if ((fontMask & BOLD) == 0) { 457 endMask |= BOLD; 458 } 459 } else if ((fontMask & BOLD) != 0) { 460 startMask |= BOLD; 461 } 462 463 if ((oldMask & ITALIC) != 0) { 464 if ((fontMask & ITALIC) == 0) { 465 endMask |= ITALIC; 466 } 467 } else if ((fontMask & ITALIC) != 0) { 468 startMask |= ITALIC; 469 } 470 471 if ((oldMask & UNDERLINE) != 0) { 472 if ((fontMask & UNDERLINE) == 0) { 473 endMask |= UNDERLINE; 474 } 475 } else if ((fontMask & UNDERLINE) != 0) { 476 startMask |= UNDERLINE; 477 } 478 writeEndMask(endMask); 479 writeStartMask(startMask); 480 } 481 482 483 /** 484 * Tweaks the appropriate bits of fontMask 485 * to reflect whether the text is to be displayed in 486 * bold, italic, and/or with an underline. 487 * 488 */ 489 private void setFontMask(AttributeSet attr) { 490 if (StyleConstants.isBold(attr)) { 491 fontMask |= BOLD; 492 } 493 494 if (StyleConstants.isItalic(attr)) { 495 fontMask |= ITALIC; 496 } 497 498 if (StyleConstants.isUnderline(attr)) { 499 fontMask |= UNDERLINE; 500 } 501 } 502 503 504 505 506 /** 507 * Writes out start tags <u>, <i>, and <b> based on 508 * the mask settings. 509 * 510 * @exception IOException on any I/O error 511 */ 512 private void writeStartMask(int mask) throws IOException { 513 if (mask != 0) { 514 if ((mask & UNDERLINE) != 0) { 515 write("<u>"); 516 } 517 if ((mask & ITALIC) != 0) { 518 write("<i>"); 519 } 520 if ((mask & BOLD) != 0) { 521 write("<b>"); 522 } 523 } 524 } 525 526 /** 527 * Writes out end tags for <u>, <i>, and <b> based on 528 * the mask settings. 529 * 530 * @exception IOException on any I/O error 531 */ 532 private void writeEndMask(int mask) throws IOException { 533 if (mask != 0) { 534 if ((mask & BOLD) != 0) { 535 write("</b>"); 536 } 537 if ((mask & ITALIC) != 0) { 538 write("</i>"); 539 } 540 if ((mask & UNDERLINE) != 0) { 541 write("</u>"); 542 } 543 } 544 } 545 546 547 /** 548 * Writes out the remaining 549 * character-level attributes (attributes other than bold, 550 * italic, and underline) in an HTML-compliant way. Given that 551 * attributes such as font family and font size have no direct 552 * mapping to HTML tags, a <span> tag is generated and its 553 * style attribute is set to contain the list of remaining 554 * attributes just like inline styles. 555 * 556 * @exception IOException on any I/O error 557 */ 558 protected void writeNonHTMLAttributes(AttributeSet attr) throws IOException { 559 560 String style = ""; 561 String separator = "; "; 562 563 if (inFontTag() && fontAttributes.isEqual(attr)) { 564 return; 565 } 566 567 boolean first = true; 568 Color color = (Color)attr.getAttribute(StyleConstants.Foreground); 569 if (color != null) { 570 style += "color: " + css.styleConstantsValueToCSSValue 571 ((StyleConstants)StyleConstants.Foreground, 572 color); 573 first = false; 574 } 575 Integer size = (Integer)attr.getAttribute(StyleConstants.FontSize); 576 if (size != null) { 577 if (!first) { 578 style += separator; 579 } 580 style += "font-size: " + size.intValue() + "pt"; 581 first = false; 582 } 583 584 String family = (String)attr.getAttribute(StyleConstants.FontFamily); 585 if (family != null) { 586 if (!first) { 587 style += separator; 588 } 589 style += "font-family: " + family; 590 first = false; 591 } 592 593 if (style.length() > 0) { 594 if (fontMask != 0) { 595 writeEndMask(fontMask); 596 fontMask = 0; 597 } 598 startSpanTag(style); 599 fontAttributes = attr; 600 } 601 else if (fontAttributes != null) { 602 writeEndMask(fontMask); 603 fontMask = 0; 604 endSpanTag(); 605 } 606 } 607 608 609 /** 610 * Returns true if we are currently in a <font> tag. 611 */ 612 protected boolean inFontTag() { 613 return (fontAttributes != null); 614 } 615 616 /** 617 * This is no longer used, instead <span> will be written out. 618 * <p> 619 * Writes out an end tag for the <font> tag. 620 * 621 * @exception IOException on any I/O error 622 */ 623 protected void endFontTag() throws IOException { 624 write(NEWLINE); 625 writeEndTag("</font>"); 626 fontAttributes = null; 627 } 628 629 630 /** 631 * This is no longer used, instead <span> will be written out. 632 * <p> 633 * Writes out a start tag for the <font> tag. 634 * Because font tags cannot be nested, 635 * this method closes out 636 * any enclosing font tag before writing out a 637 * new start tag. 638 * 639 * @exception IOException on any I/O error 640 */ 641 protected void startFontTag(String style) throws IOException { 642 boolean callIndent = false; 643 if (inFontTag()) { 644 endFontTag(); 645 callIndent = true; 646 } 647 writeStartTag("<font style=\"" + style + "\">"); 648 if (callIndent) { 649 indent(); 650 } 651 } 652 653 /** 654 * Writes out a start tag for the <font> tag. 655 * Because font tags cannot be nested, 656 * this method closes out 657 * any enclosing font tag before writing out a 658 * new start tag. 659 * 660 * @exception IOException on any I/O error 661 */ 662 private void startSpanTag(String style) throws IOException { 663 boolean callIndent = false; 664 if (inFontTag()) { 665 endSpanTag(); 666 callIndent = true; 667 } 668 writeStartTag("<span style=\"" + style + "\">"); 669 if (callIndent) { 670 indent(); 671 } 672 } 673 674 /** 675 * Writes out an end tag for the <span> tag. 676 * 677 * @exception IOException on any I/O error 678 */ 679 private void endSpanTag() throws IOException { 680 write(NEWLINE); 681 writeEndTag("</span>"); 682 fontAttributes = null; 683 } 684 685 /** 686 * Adds the style named <code>style</code> to the style mapping. This 687 * returns the name that should be used when outputting. CSS does not 688 * allow the full Unicode set to be used as a style name. 689 */ 690 private String addStyleName(String style) { 691 if (styleNameMapping == null) { 692 return style; 693 } 694 StringBuilder sb = null; 695 for (int counter = style.length() - 1; counter >= 0; counter--) { 696 if (!isValidCharacter(style.charAt(counter))) { 697 if (sb == null) { 698 sb = new StringBuilder(style); 699 } 700 sb.setCharAt(counter, 'a'); 701 } 702 } 703 String mappedName = (sb != null) ? sb.toString() : style; 704 while (styleNameMapping.get(mappedName) != null) { 705 mappedName = mappedName + 'x'; 706 } 707 styleNameMapping.put(style, mappedName); 708 return mappedName; 709 } 710 711 /** 712 * Returns the mapped style name corresponding to <code>style</code>. 713 */ 714 private String mapStyleName(String style) { 715 if (styleNameMapping == null) { 716 return style; 717 } 718 String retValue = styleNameMapping.get(style); 719 return (retValue == null) ? style : retValue; 720 } 721 722 private boolean isValidCharacter(char character) { 723 return ((character >= 'a' && character <= 'z') || 724 (character >= 'A' && character <= 'Z')); 725 } 726 }