1 /* 2 * Copyright (c) 1997, 2014, 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 java.awt.font.TextAttribute; 28 import java.util.*; 29 import java.net.URL; 30 import java.net.MalformedURLException; 31 import java.io.*; 32 import javax.swing.*; 33 import javax.swing.event.*; 34 import javax.swing.text.*; 35 import javax.swing.undo.*; 36 import sun.swing.SwingUtilities2; 37 import static sun.swing.SwingUtilities2.IMPLIED_CR; 38 39 /** 40 * A document that models HTML. The purpose of this model is to 41 * support both browsing and editing. As a result, the structure 42 * described by an HTML document is not exactly replicated by default. 43 * The element structure that is modeled by default, is built by the 44 * class {@code HTMLDocument.HTMLReader}, which implements the 45 * {@code HTMLEditorKit.ParserCallback} protocol that the parser 46 * expects. To change the structure one can subclass 47 * {@code HTMLReader}, and reimplement the method {@link 48 * #getReader(int)} to return the new reader implementation. The 49 * documentation for {@code HTMLReader} should be consulted for 50 * the details of the default structure created. The intent is that 51 * the document be non-lossy (although reproducing the HTML format may 52 * result in a different format). 53 * 54 * <p>The document models only HTML, and makes no attempt to store 55 * view attributes in it. The elements are identified by the 56 * {@code StyleContext.NameAttribute} attribute, which should 57 * always have a value of type {@code HTML.Tag} that identifies 58 * the kind of element. Some of the elements (such as comments) are 59 * synthesized. The {@code HTMLFactory} uses this attribute to 60 * determine what kind of view to build.</p> 61 * 62 * <p>This document supports incremental loading. The 63 * {@code TokenThreshold} property controls how much of the parse 64 * is buffered before trying to update the element structure of the 65 * document. This property is set by the {@code EditorKit} so 66 * that subclasses can disable it.</p> 67 * 68 * <p>The {@code Base} property determines the URL against which 69 * relative URLs are resolved. By default, this will be the 70 * {@code Document.StreamDescriptionProperty} if the value of the 71 * property is a URL. If a <BASE> tag is encountered, the base 72 * will become the URL specified by that tag. Because the base URL is 73 * a property, it can of course be set directly.</p> 74 * 75 * <p>The default content storage mechanism for this document is a gap 76 * buffer ({@code GapContent}). Alternatives can be supplied by 77 * using the constructor that takes a {@code Content} 78 * implementation.</p> 79 * 80 * <h2>Modifying HTMLDocument</h2> 81 * 82 * <p>In addition to the methods provided by Document and 83 * StyledDocument for mutating an HTMLDocument, HTMLDocument provides 84 * a number of convenience methods. The following methods can be used 85 * to insert HTML content into an existing document.</p> 86 * 87 * <ul> 88 * <li>{@link #setInnerHTML(Element, String)}</li> 89 * <li>{@link #setOuterHTML(Element, String)}</li> 90 * <li>{@link #insertBeforeStart(Element, String)}</li> 91 * <li>{@link #insertAfterStart(Element, String)}</li> 92 * <li>{@link #insertBeforeEnd(Element, String)}</li> 93 * <li>{@link #insertAfterEnd(Element, String)}</li> 94 * </ul> 95 * 96 * <p>The following examples illustrate using these methods. Each 97 * example assumes the HTML document is initialized in the following 98 * way:</p> 99 * 100 * <pre> 101 * JEditorPane p = new JEditorPane(); 102 * p.setContentType("text/html"); 103 * p.setText("..."); // Document text is provided below. 104 * HTMLDocument d = (HTMLDocument) p.getDocument(); 105 * </pre> 106 * 107 * <p>With the following HTML content:</p> 108 * 109 * <pre>{@code 110 * <html> 111 * <head> 112 * <title>An example HTMLDocument</title> 113 * <style type="text/css"> 114 * div { background-color: silver; } 115 * ul { color: red; } 116 * </style> 117 * </head> 118 * <body> 119 * <div id="BOX"> 120 * <p>Paragraph 1</p> 121 * <p>Paragraph 2</p> 122 * </div> 123 * </body> 124 * </html> 125 * }</pre> 126 * 127 * <p>All the methods for modifying an HTML document require an {@link 128 * Element}. Elements can be obtained from an HTML document by using 129 * the method {@link #getElement(Element e, Object attribute, Object 130 * value)}. It returns the first descendant element that contains the 131 * specified attribute with the given value, in depth-first order. 132 * For example, {@code d.getElement(d.getDefaultRootElement(), 133 * StyleConstants.NameAttribute, HTML.Tag.P)} returns the first 134 * paragraph element.</p> 135 * 136 * <p>A convenient shortcut for locating elements is the method {@link 137 * #getElement(String)}; returns an element whose {@code ID} 138 * attribute matches the specified value. For example, 139 * {@code d.getElement("BOX")} returns the {@code DIV} 140 * element.</p> 141 * 142 * <p>The {@link #getIterator(HTML.Tag t)} method can also be used for 143 * finding all occurrences of the specified HTML tag in the 144 * document.</p> 145 * 146 * <h3>Inserting elements</h3> 147 * 148 * <p>Elements can be inserted before or after the existing children 149 * of any non-leaf element by using the methods 150 * {@code insertAfterStart} and {@code insertBeforeEnd}. 151 * For example, if {@code e} is the {@code DIV} element, 152 * {@code d.insertAfterStart(e, "<ul><li>List Item</li></ul>")} 153 * inserts the list before the first 154 * paragraph, and 155 * {@code d.insertBeforeEnd(e, "<ul><li>List Item</li></ul>")} 156 * inserts the list after the last 157 * paragraph. The {@code DIV} block becomes the parent of the 158 * newly inserted elements.</p> 159 * 160 * <p>Sibling elements can be inserted before or after any element by 161 * using the methods {@code insertBeforeStart} and 162 * {@code insertAfterEnd}. For example, if {@code e} is the 163 * {@code DIV} element, 164 * {@code d.insertBeforeStart(e, "<ul><li>List Item</li></ul>")} 165 * inserts the list before the {@code DIV} element, and 166 * {@code d.insertAfterEnd(e, "<ul><li>List Item</li></ul>")} 167 * inserts the list after the {@code DIV} element. 168 * The newly inserted elements 169 * become siblings of the {@code DIV} element.</p> 170 * 171 * <h3>Replacing elements</h3> 172 * 173 * <p>Elements and all their descendants can be replaced by using the 174 * methods {@code setInnerHTML} and {@code setOuterHTML}. 175 * For example, if {@code e} is the {@code DIV} element, 176 * {@code d.setInnerHTML(e, "<ul><li>List Item</li></ul>")} 177 * replaces all children paragraphs with 178 * the list, and 179 * {@code d.setOuterHTML(e, "<ul><li>List Item</li></ul>")} 180 * replaces the {@code DIV} element 181 * itself. In latter case the parent of the list is the 182 * {@code BODY} element. 183 * 184 * <h3>Summary</h3> 185 * 186 * <p>The following table shows the example document and the results 187 * of various methods described above.</p> 188 * 189 * <table border=1 cellspacing=0 summary="HTML Content of example above"> 190 * <tr> 191 * <th>Example</th> 192 * <th>{@code insertAfterStart}</th> 193 * <th>{@code insertBeforeEnd}</th> 194 * <th>{@code insertBeforeStart}</th> 195 * <th>{@code insertAfterEnd}</th> 196 * <th>{@code setInnerHTML}</th> 197 * <th>{@code setOuterHTML}</th> 198 * </tr> 199 * <tr valign="top"> 200 * <td style="white-space:nowrap"> 201 * <div style="background-color: silver;"> 202 * <p>Paragraph 1</p> 203 * <p>Paragraph 2</p> 204 * </div> 205 * </td> 206 * <!--insertAfterStart--> 207 * <td style="white-space:nowrap"> 208 * <div style="background-color: silver;"> 209 * <ul style="color: red;"> 210 * <li>List Item</li> 211 * </ul> 212 * <p>Paragraph 1</p> 213 * <p>Paragraph 2</p> 214 * </div> 215 * </td> 216 * <!--insertBeforeEnd--> 217 * <td style="white-space:nowrap"> 218 * <div style="background-color: silver;"> 219 * <p>Paragraph 1</p> 220 * <p>Paragraph 2</p> 221 * <ul style="color: red;"> 222 * <li>List Item</li> 223 * </ul> 224 * </div> 225 * </td> 226 * <!--insertBeforeStart--> 227 * <td style="white-space:nowrap"> 228 * <ul style="color: red;"> 229 * <li>List Item</li> 230 * </ul> 231 * <div style="background-color: silver;"> 232 * <p>Paragraph 1</p> 233 * <p>Paragraph 2</p> 234 * </div> 235 * </td> 236 * <!--insertAfterEnd--> 237 * <td style="white-space:nowrap"> 238 * <div style="background-color: silver;"> 239 * <p>Paragraph 1</p> 240 * <p>Paragraph 2</p> 241 * </div> 242 * <ul style="color: red;"> 243 * <li>List Item</li> 244 * </ul> 245 * </td> 246 * <!--setInnerHTML--> 247 * <td style="white-space:nowrap"> 248 * <div style="background-color: silver;"> 249 * <ul style="color: red;"> 250 * <li>List Item</li> 251 * </ul> 252 * </div> 253 * </td> 254 * <!--setOuterHTML--> 255 * <td style="white-space:nowrap"> 256 * <ul style="color: red;"> 257 * <li>List Item</li> 258 * </ul> 259 * </td> 260 * </tr> 261 * </table> 262 * 263 * <p><strong>Warning:</strong> Serialized objects of this class will 264 * not be compatible with future Swing releases. The current 265 * serialization support is appropriate for short term storage or RMI 266 * between applications running the same version of Swing. As of 1.4, 267 * support for long term storage of all JavaBeans™ 268 * has been added to the 269 * {@code java.beans} package. Please see {@link 270 * java.beans.XMLEncoder}.</p> 271 * 272 * @author Timothy Prinzing 273 * @author Scott Violet 274 * @author Sunita Mani 275 */ 276 @SuppressWarnings("serial") // Same-version serialization only 277 public class HTMLDocument extends DefaultStyledDocument { 278 /** 279 * Constructs an HTML document using the default buffer size 280 * and a default {@code StyleSheet}. This is a convenience 281 * method for the constructor 282 * {@code HTMLDocument(Content, StyleSheet)}. 283 */ 284 public HTMLDocument() { 285 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet()); 286 } 287 288 /** 289 * Constructs an HTML document with the default content 290 * storage implementation and the specified style/attribute 291 * storage mechanism. This is a convenience method for the 292 * constructor 293 * {@code HTMLDocument(Content, StyleSheet)}. 294 * 295 * @param styles the styles 296 */ 297 public HTMLDocument(StyleSheet styles) { 298 this(new GapContent(BUFFER_SIZE_DEFAULT), styles); 299 } 300 301 /** 302 * Constructs an HTML document with the given content 303 * storage implementation and the given style/attribute 304 * storage mechanism. 305 * 306 * @param c the container for the content 307 * @param styles the styles 308 */ 309 public HTMLDocument(Content c, StyleSheet styles) { 310 super(c, styles); 311 } 312 313 /** 314 * Fetches the reader for the parser to use when loading the document 315 * with HTML. This is implemented to return an instance of 316 * {@code HTMLDocument.HTMLReader}. 317 * Subclasses can reimplement this 318 * method to change how the document gets structured if desired. 319 * (For example, to handle custom tags, or structurally represent character 320 * style elements.) 321 * 322 * @param pos the starting position 323 * @return the reader used by the parser to load the document 324 */ 325 public HTMLEditorKit.ParserCallback getReader(int pos) { 326 Object desc = getProperty(Document.StreamDescriptionProperty); 327 if (desc instanceof URL) { 328 setBase((URL)desc); 329 } 330 HTMLReader reader = new HTMLReader(pos); 331 return reader; 332 } 333 334 /** 335 * Returns the reader for the parser to use to load the document 336 * with HTML. This is implemented to return an instance of 337 * {@code HTMLDocument.HTMLReader}. 338 * Subclasses can reimplement this 339 * method to change how the document gets structured if desired. 340 * (For example, to handle custom tags, or structurally represent character 341 * style elements.) 342 * <p>This is a convenience method for 343 * {@code getReader(int, int, int, HTML.Tag, TRUE)}. 344 * 345 * @param pos the starting position 346 * @param popDepth the number of {@code ElementSpec.EndTagTypes} 347 * to generate before inserting 348 * @param pushDepth the number of {@code ElementSpec.StartTagTypes} 349 * with a direction of {@code ElementSpec.JoinNextDirection} 350 * that should be generated before inserting, 351 * but after the end tags have been generated 352 * @param insertTag the first tag to start inserting into document 353 * @return the reader used by the parser to load the document 354 */ 355 public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth, 356 int pushDepth, 357 HTML.Tag insertTag) { 358 return getReader(pos, popDepth, pushDepth, insertTag, true); 359 } 360 361 /** 362 * Fetches the reader for the parser to use to load the document 363 * with HTML. This is implemented to return an instance of 364 * HTMLDocument.HTMLReader. Subclasses can reimplement this 365 * method to change how the document get structured if desired 366 * (e.g. to handle custom tags, structurally represent character 367 * style elements, etc.). 368 * 369 * @param popDepth the number of {@code ElementSpec.EndTagTypes} 370 * to generate before inserting 371 * @param pushDepth the number of {@code ElementSpec.StartTagTypes} 372 * with a direction of {@code ElementSpec.JoinNextDirection} 373 * that should be generated before inserting, 374 * but after the end tags have been generated 375 * @param insertTag the first tag to start inserting into document 376 * @param insertInsertTag false if all the Elements after insertTag should 377 * be inserted; otherwise insertTag will be inserted 378 * @return the reader used by the parser to load the document 379 */ 380 HTMLEditorKit.ParserCallback getReader(int pos, int popDepth, 381 int pushDepth, 382 HTML.Tag insertTag, 383 boolean insertInsertTag) { 384 Object desc = getProperty(Document.StreamDescriptionProperty); 385 if (desc instanceof URL) { 386 setBase((URL)desc); 387 } 388 HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth, 389 insertTag, insertInsertTag, false, 390 true); 391 return reader; 392 } 393 394 /** 395 * Returns the location to resolve relative URLs against. By 396 * default this will be the document's URL if the document 397 * was loaded from a URL. If a base tag is found and 398 * can be parsed, it will be used as the base location. 399 * 400 * @return the base location 401 */ 402 public URL getBase() { 403 return base; 404 } 405 406 /** 407 * Sets the location to resolve relative URLs against. By 408 * default this will be the document's URL if the document 409 * was loaded from a URL. If a base tag is found and 410 * can be parsed, it will be used as the base location. 411 * <p>This also sets the base of the {@code StyleSheet} 412 * to be {@code u} as well as the base of the document. 413 * 414 * @param u the desired base URL 415 */ 416 public void setBase(URL u) { 417 base = u; 418 getStyleSheet().setBase(u); 419 } 420 421 /** 422 * Inserts new elements in bulk. This is how elements get created 423 * in the document. The parsing determines what structure is needed 424 * and creates the specification as a set of tokens that describe the 425 * edit while leaving the document free of a write-lock. This method 426 * can then be called in bursts by the reader to acquire a write-lock 427 * for a shorter duration (i.e. while the document is actually being 428 * altered). 429 * 430 * @param offset the starting offset 431 * @param data the element data 432 * @exception BadLocationException if the given position does not 433 * represent a valid location in the associated document. 434 */ 435 protected void insert(int offset, ElementSpec[] data) throws BadLocationException { 436 super.insert(offset, data); 437 } 438 439 /** 440 * Updates document structure as a result of text insertion. This 441 * will happen within a write lock. This implementation simply 442 * parses the inserted content for line breaks and builds up a set 443 * of instructions for the element buffer. 444 * 445 * @param chng a description of the document change 446 * @param attr the attributes 447 */ 448 protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) { 449 if(attr == null) { 450 attr = contentAttributeSet; 451 } 452 453 // If this is the composed text element, merge the content attribute to it 454 else if (attr.isDefined(StyleConstants.ComposedTextAttribute)) { 455 ((MutableAttributeSet)attr).addAttributes(contentAttributeSet); 456 } 457 458 if (attr.isDefined(IMPLIED_CR)) { 459 ((MutableAttributeSet)attr).removeAttribute(IMPLIED_CR); 460 } 461 462 super.insertUpdate(chng, attr); 463 } 464 465 /** 466 * Replaces the contents of the document with the given 467 * element specifications. This is called before insert if 468 * the loading is done in bursts. This is the only method called 469 * if loading the document entirely in one burst. 470 * 471 * @param data the new contents of the document 472 */ 473 protected void create(ElementSpec[] data) { 474 super.create(data); 475 } 476 477 /** 478 * Sets attributes for a paragraph. 479 * <p> 480 * This method is thread safe, although most Swing methods 481 * are not. Please see 482 * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency 483 * in Swing</A> for more information. 484 * 485 * @param offset the offset into the paragraph (must be at least 0) 486 * @param length the number of characters affected (must be at least 0) 487 * @param s the attributes 488 * @param replace whether to replace existing attributes, or merge them 489 */ 490 public void setParagraphAttributes(int offset, int length, AttributeSet s, 491 boolean replace) { 492 try { 493 writeLock(); 494 // Make sure we send out a change for the length of the paragraph. 495 int end = Math.min(offset + length, getLength()); 496 Element e = getParagraphElement(offset); 497 offset = e.getStartOffset(); 498 e = getParagraphElement(end); 499 length = Math.max(0, e.getEndOffset() - offset); 500 DefaultDocumentEvent changes = 501 new DefaultDocumentEvent(offset, length, 502 DocumentEvent.EventType.CHANGE); 503 AttributeSet sCopy = s.copyAttributes(); 504 int lastEnd = Integer.MAX_VALUE; 505 for (int pos = offset; pos <= end; pos = lastEnd) { 506 Element paragraph = getParagraphElement(pos); 507 if (lastEnd == paragraph.getEndOffset()) { 508 lastEnd++; 509 } 510 else { 511 lastEnd = paragraph.getEndOffset(); 512 } 513 MutableAttributeSet attr = 514 (MutableAttributeSet) paragraph.getAttributes(); 515 changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace)); 516 if (replace) { 517 attr.removeAttributes(attr); 518 } 519 attr.addAttributes(s); 520 } 521 changes.end(); 522 fireChangedUpdate(changes); 523 fireUndoableEditUpdate(new UndoableEditEvent(this, changes)); 524 } finally { 525 writeUnlock(); 526 } 527 } 528 529 /** 530 * Fetches the {@code StyleSheet} with the document-specific display 531 * rules (CSS) that were specified in the HTML document itself. 532 * 533 * @return the {@code StyleSheet} 534 */ 535 public StyleSheet getStyleSheet() { 536 return (StyleSheet) getAttributeContext(); 537 } 538 539 /** 540 * Fetches an iterator for the specified HTML tag. 541 * This can be used for things like iterating over the 542 * set of anchors contained, or iterating over the input 543 * elements. 544 * 545 * @param t the requested {@code HTML.Tag} 546 * @return the {@code Iterator} for the given HTML tag 547 * @see javax.swing.text.html.HTML.Tag 548 */ 549 public Iterator getIterator(HTML.Tag t) { 550 if (t.isBlock()) { 551 // TBD 552 return null; 553 } 554 return new LeafIterator(t, this); 555 } 556 557 /** 558 * Creates a document leaf element that directly represents 559 * text (doesn't have any children). This is implemented 560 * to return an element of type 561 * {@code HTMLDocument.RunElement}. 562 * 563 * @param parent the parent element 564 * @param a the attributes for the element 565 * @param p0 the beginning of the range (must be at least 0) 566 * @param p1 the end of the range (must be at least p0) 567 * @return the new element 568 */ 569 protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) { 570 return new RunElement(parent, a, p0, p1); 571 } 572 573 /** 574 * Creates a document branch element, that can contain other elements. 575 * This is implemented to return an element of type 576 * {@code HTMLDocument.BlockElement}. 577 * 578 * @param parent the parent element 579 * @param a the attributes 580 * @return the element 581 */ 582 protected Element createBranchElement(Element parent, AttributeSet a) { 583 return new BlockElement(parent, a); 584 } 585 586 /** 587 * Creates the root element to be used to represent the 588 * default document structure. 589 * 590 * @return the element base 591 */ 592 protected AbstractElement createDefaultRoot() { 593 // grabs a write-lock for this initialization and 594 // abandon it during initialization so in normal 595 // operation we can detect an illegitimate attempt 596 // to mutate attributes. 597 writeLock(); 598 MutableAttributeSet a = new SimpleAttributeSet(); 599 a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.HTML); 600 BlockElement html = new BlockElement(null, a.copyAttributes()); 601 a.removeAttributes(a); 602 a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.BODY); 603 BlockElement body = new BlockElement(html, a.copyAttributes()); 604 a.removeAttributes(a); 605 a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.P); 606 getStyleSheet().addCSSAttributeFromHTML(a, CSS.Attribute.MARGIN_TOP, "0"); 607 BlockElement paragraph = new BlockElement(body, a.copyAttributes()); 608 a.removeAttributes(a); 609 a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT); 610 RunElement brk = new RunElement(paragraph, a, 0, 1); 611 Element[] buff = new Element[1]; 612 buff[0] = brk; 613 paragraph.replace(0, 0, buff); 614 buff[0] = paragraph; 615 body.replace(0, 0, buff); 616 buff[0] = body; 617 html.replace(0, 0, buff); 618 writeUnlock(); 619 return html; 620 } 621 622 /** 623 * Sets the number of tokens to buffer before trying to update 624 * the documents element structure. 625 * 626 * @param n the number of tokens to buffer 627 */ 628 public void setTokenThreshold(int n) { 629 putProperty(TokenThreshold, n); 630 } 631 632 /** 633 * Gets the number of tokens to buffer before trying to update 634 * the documents element structure. The default value is 635 * {@code Integer.MAX_VALUE}. 636 * 637 * @return the number of tokens to buffer 638 */ 639 public int getTokenThreshold() { 640 Integer i = (Integer) getProperty(TokenThreshold); 641 if (i != null) { 642 return i.intValue(); 643 } 644 return Integer.MAX_VALUE; 645 } 646 647 /** 648 * Determines how unknown tags are handled by the parser. 649 * If set to true, unknown 650 * tags are put in the model, otherwise they are dropped. 651 * 652 * @param preservesTags true if unknown tags should be 653 * saved in the model, otherwise tags are dropped 654 * @see javax.swing.text.html.HTML.Tag 655 */ 656 public void setPreservesUnknownTags(boolean preservesTags) { 657 preservesUnknownTags = preservesTags; 658 } 659 660 /** 661 * Returns the behavior the parser observes when encountering 662 * unknown tags. 663 * 664 * @see javax.swing.text.html.HTML.Tag 665 * @return true if unknown tags are to be preserved when parsing 666 */ 667 public boolean getPreservesUnknownTags() { 668 return preservesUnknownTags; 669 } 670 671 /** 672 * Processes {@code HyperlinkEvents} that 673 * are generated by documents in an HTML frame. 674 * The {@code HyperlinkEvent} type, as the parameter suggests, 675 * is {@code HTMLFrameHyperlinkEvent}. 676 * In addition to the typical information contained in a 677 * {@code HyperlinkEvent}, 678 * this event contains the element that corresponds to the frame in 679 * which the click happened (the source element) and the 680 * target name. The target name has 4 possible values: 681 * <ul> 682 * <li> _self 683 * <li> _parent 684 * <li> _top 685 * <li> a named frame 686 * </ul> 687 * 688 * If target is _self, the action is to change the value of the 689 * {@code HTML.Attribute.SRC} attribute and fires a 690 * {@code ChangedUpdate} event. 691 *<p> 692 * If the target is _parent, then it deletes the parent element, 693 * which is a <FRAMESET> element, and inserts a new <FRAME> 694 * element, and sets its {@code HTML.Attribute.SRC} attribute 695 * to have a value equal to the destination URL and fire a 696 * {@code RemovedUpdate} and {@code InsertUpdate}. 697 *<p> 698 * If the target is _top, this method does nothing. In the implementation 699 * of the view for a frame, namely the {@code FrameView}, 700 * the processing of _top is handled. Given that _top implies 701 * replacing the entire document, it made sense to handle this outside 702 * of the document that it will replace. 703 *<p> 704 * If the target is a named frame, then the element hierarchy is searched 705 * for an element with a name equal to the target, its 706 * {@code HTML.Attribute.SRC} attribute is updated and a 707 * {@code ChangedUpdate} event is fired. 708 * 709 * @param e the event 710 */ 711 public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) { 712 String frameName = e.getTarget(); 713 Element element = e.getSourceElement(); 714 String urlStr = e.getURL().toString(); 715 716 if (frameName.equals("_self")) { 717 /* 718 The source and destination elements 719 are the same. 720 */ 721 updateFrame(element, urlStr); 722 } else if (frameName.equals("_parent")) { 723 /* 724 The destination is the parent of the frame. 725 */ 726 updateFrameSet(element.getParentElement(), urlStr); 727 } else { 728 /* 729 locate a named frame 730 */ 731 Element targetElement = findFrame(frameName); 732 if (targetElement != null) { 733 updateFrame(targetElement, urlStr); 734 } 735 } 736 } 737 738 739 /** 740 * Searches the element hierarchy for an FRAME element 741 * that has its name attribute equal to the {@code frameName}. 742 * 743 * @param frameName 744 * @return the element whose NAME attribute has a value of 745 * {@code frameName}; returns {@code null} 746 * if not found 747 */ 748 private Element findFrame(String frameName) { 749 ElementIterator it = new ElementIterator(this); 750 Element next; 751 752 while ((next = it.next()) != null) { 753 AttributeSet attr = next.getAttributes(); 754 if (matchNameAttribute(attr, HTML.Tag.FRAME)) { 755 String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME); 756 if (frameTarget != null && frameTarget.equals(frameName)) { 757 break; 758 } 759 } 760 } 761 return next; 762 } 763 764 /** 765 * Returns true if {@code StyleConstants.NameAttribute} is 766 * equal to the tag that is passed in as a parameter. 767 * 768 * @param attr the attributes to be matched 769 * @param tag the value to be matched 770 * @return true if there is a match, false otherwise 771 * @see javax.swing.text.html.HTML.Attribute 772 */ 773 static boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) { 774 Object o = attr.getAttribute(StyleConstants.NameAttribute); 775 if (o instanceof HTML.Tag) { 776 HTML.Tag name = (HTML.Tag) o; 777 if (name == tag) { 778 return true; 779 } 780 } 781 return false; 782 } 783 784 /** 785 * Replaces a frameset branch Element with a frame leaf element. 786 * 787 * @param element the frameset element to remove 788 * @param url the value for the SRC attribute for the 789 * new frame that will replace the frameset 790 */ 791 private void updateFrameSet(Element element, String url) { 792 try { 793 int startOffset = element.getStartOffset(); 794 int endOffset = Math.min(getLength(), element.getEndOffset()); 795 String html = "<frame"; 796 if (url != null) { 797 html += " src=\"" + url + "\""; 798 } 799 html += ">"; 800 installParserIfNecessary(); 801 setOuterHTML(element, html); 802 } catch (BadLocationException e1) { 803 // Should handle this better 804 } catch (IOException ioe) { 805 // Should handle this better 806 } 807 } 808 809 810 /** 811 * Updates the Frame elements {@code HTML.Attribute.SRC attribute} 812 * and fires a {@code ChangedUpdate} event. 813 * 814 * @param element a FRAME element whose SRC attribute will be updated 815 * @param url a string specifying the new value for the SRC attribute 816 */ 817 private void updateFrame(Element element, String url) { 818 819 try { 820 writeLock(); 821 DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(), 822 1, 823 DocumentEvent.EventType.CHANGE); 824 AttributeSet sCopy = element.getAttributes().copyAttributes(); 825 MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes(); 826 changes.addEdit(new AttributeUndoableEdit(element, sCopy, false)); 827 attr.removeAttribute(HTML.Attribute.SRC); 828 attr.addAttribute(HTML.Attribute.SRC, url); 829 changes.end(); 830 fireChangedUpdate(changes); 831 fireUndoableEditUpdate(new UndoableEditEvent(this, changes)); 832 } finally { 833 writeUnlock(); 834 } 835 } 836 837 838 /** 839 * Returns true if the document will be viewed in a frame. 840 * @return true if document will be viewed in a frame, otherwise false 841 */ 842 boolean isFrameDocument() { 843 return frameDocument; 844 } 845 846 /** 847 * Sets a boolean state about whether the document will be 848 * viewed in a frame. 849 * @param frameDoc true if the document will be viewed in a frame, 850 * otherwise false 851 */ 852 void setFrameDocumentState(boolean frameDoc) { 853 this.frameDocument = frameDoc; 854 } 855 856 /** 857 * Adds the specified map, this will remove a Map that has been 858 * previously registered with the same name. 859 * 860 * @param map the {@code Map} to be registered 861 */ 862 void addMap(Map map) { 863 String name = map.getName(); 864 865 if (name != null) { 866 Object maps = getProperty(MAP_PROPERTY); 867 868 if (maps == null) { 869 maps = new Hashtable<>(11); 870 putProperty(MAP_PROPERTY, maps); 871 } 872 if (maps instanceof Hashtable) { 873 @SuppressWarnings("unchecked") 874 Hashtable<Object, Object> tmp = (Hashtable)maps; 875 tmp.put("#" + name, map); 876 } 877 } 878 } 879 880 /** 881 * Removes a previously registered map. 882 * @param map the {@code Map} to be removed 883 */ 884 void removeMap(Map map) { 885 String name = map.getName(); 886 887 if (name != null) { 888 Object maps = getProperty(MAP_PROPERTY); 889 890 if (maps instanceof Hashtable) { 891 ((Hashtable)maps).remove("#" + name); 892 } 893 } 894 } 895 896 /** 897 * Returns the Map associated with the given name. 898 * @param name the name of the desired {@code Map} 899 * @return the {@code Map} or {@code null} if it can't 900 * be found, or if {@code name} is {@code null} 901 */ 902 Map getMap(String name) { 903 if (name != null) { 904 Object maps = getProperty(MAP_PROPERTY); 905 906 if (maps != null && (maps instanceof Hashtable)) { 907 return (Map)((Hashtable)maps).get(name); 908 } 909 } 910 return null; 911 } 912 913 /** 914 * Returns an {@code Enumeration} of the possible Maps. 915 * @return the enumerated list of maps, or {@code null} 916 * if the maps are not an instance of {@code Hashtable} 917 */ 918 Enumeration<Object> getMaps() { 919 Object maps = getProperty(MAP_PROPERTY); 920 921 if (maps instanceof Hashtable) { 922 @SuppressWarnings("unchecked") 923 Hashtable<Object, Object> tmp = (Hashtable) maps; 924 return tmp.elements(); 925 } 926 return null; 927 } 928 929 /** 930 * Sets the content type language used for style sheets that do not 931 * explicitly specify the type. The default is text/css. 932 * @param contentType the content type language for the style sheets 933 */ 934 /* public */ 935 void setDefaultStyleSheetType(String contentType) { 936 putProperty(StyleType, contentType); 937 } 938 939 /** 940 * Returns the content type language used for style sheets. The default 941 * is text/css. 942 * @return the content type language used for the style sheets 943 */ 944 /* public */ 945 String getDefaultStyleSheetType() { 946 String retValue = (String)getProperty(StyleType); 947 if (retValue == null) { 948 return "text/css"; 949 } 950 return retValue; 951 } 952 953 /** 954 * Sets the parser that is used by the methods that insert html 955 * into the existing document, such as {@code setInnerHTML}, 956 * and {@code setOuterHTML}. 957 * <p> 958 * {@code HTMLEditorKit.createDefaultDocument} will set the parser 959 * for you. If you create an {@code HTMLDocument} by hand, 960 * be sure and set the parser accordingly. 961 * @param parser the parser to be used for text insertion 962 * 963 * @since 1.3 964 */ 965 public void setParser(HTMLEditorKit.Parser parser) { 966 this.parser = parser; 967 putProperty("__PARSER__", null); 968 } 969 970 /** 971 * Returns the parser that is used when inserting HTML into the existing 972 * document. 973 * @return the parser used for text insertion 974 * 975 * @since 1.3 976 */ 977 public HTMLEditorKit.Parser getParser() { 978 Object p = getProperty("__PARSER__"); 979 980 if (p instanceof HTMLEditorKit.Parser) { 981 return (HTMLEditorKit.Parser)p; 982 } 983 return parser; 984 } 985 986 /** 987 * Replaces the children of the given element with the contents 988 * specified as an HTML string. 989 * 990 * <p>This will be seen as at least two events, n inserts followed by 991 * a remove.</p> 992 * 993 * <p>Consider the following structure (the {@code elem} 994 * parameter is <b>in bold</b>).</p> 995 * 996 * <pre> 997 * <body> 998 * | 999 * <b><div></b> 1000 * / \ 1001 * <p> <p> 1002 * </pre> 1003 * 1004 * <p>Invoking {@code setInnerHTML(elem, "<ul><li>")} 1005 * results in the following structure (new elements are <font 1006 * style="color: red;">in red</font>).</p> 1007 * 1008 * <pre> 1009 * <body> 1010 * | 1011 * <b><div></b> 1012 * \ 1013 * <font style="color: red;"><ul></font> 1014 * \ 1015 * <font style="color: red;"><li></font> 1016 * </pre> 1017 * 1018 * <p>Parameter {@code elem} must not be a leaf element, 1019 * otherwise an {@code IllegalArgumentException} is thrown. 1020 * If either {@code elem} or {@code htmlText} parameter 1021 * is {@code null}, no changes are made to the document.</p> 1022 * 1023 * <p>For this to work correctly, the document must have an 1024 * {@code HTMLEditorKit.Parser} set. This will be the case 1025 * if the document was created from an HTMLEditorKit via the 1026 * {@code createDefaultDocument} method.</p> 1027 * 1028 * @param elem the branch element whose children will be replaced 1029 * @param htmlText the string to be parsed and assigned to {@code elem} 1030 * @throws IllegalArgumentException if {@code elem} is a leaf 1031 * @throws IllegalStateException if an {@code HTMLEditorKit.Parser} 1032 * has not been defined 1033 * @throws BadLocationException if replacement is impossible because of 1034 * a structural issue 1035 * @throws IOException if an I/O exception occurs 1036 * @since 1.3 1037 */ 1038 public void setInnerHTML(Element elem, String htmlText) throws 1039 BadLocationException, IOException { 1040 verifyParser(); 1041 if (elem != null && elem.isLeaf()) { 1042 throw new IllegalArgumentException 1043 ("Can not set inner HTML of a leaf"); 1044 } 1045 if (elem != null && htmlText != null) { 1046 int oldCount = elem.getElementCount(); 1047 int insertPosition = elem.getStartOffset(); 1048 insertHTML(elem, elem.getStartOffset(), htmlText, true); 1049 if (elem.getElementCount() > oldCount) { 1050 // Elements were inserted, do the cleanup. 1051 removeElements(elem, elem.getElementCount() - oldCount, 1052 oldCount); 1053 } 1054 } 1055 } 1056 1057 /** 1058 * Replaces the given element in the parent with the contents 1059 * specified as an HTML string. 1060 * 1061 * <p>This will be seen as at least two events, n inserts followed by 1062 * a remove.</p> 1063 * 1064 * <p>When replacing a leaf this will attempt to make sure there is 1065 * a newline present if one is needed. This may result in an additional 1066 * element being inserted. Consider, if you were to replace a character 1067 * element that contained a newline with <img> this would create 1068 * two elements, one for the image, and one for the newline.</p> 1069 * 1070 * <p>If you try to replace the element at length you will most 1071 * likely end up with two elements, eg 1072 * {@code setOuterHTML(getCharacterElement (getLength()), "blah")} 1073 * will result in two leaf elements at the end, one 1074 * representing 'blah', and the other representing the end 1075 * element.</p> 1076 * 1077 * <p>Consider the following structure (the {@code elem} 1078 * parameter is <b>in bold</b>).</p> 1079 * 1080 * <pre> 1081 * <body> 1082 * | 1083 * <b><div></b> 1084 * / \ 1085 * <p> <p> 1086 * </pre> 1087 * 1088 * <p>Invoking {@code setOuterHTML(elem, "<ul><li>")} 1089 * results in the following structure (new elements are <font 1090 * style="color: red;">in red</font>).</p> 1091 * 1092 * <pre> 1093 * <body> 1094 * | 1095 * <font style="color: red;"><ul></font> 1096 * \ 1097 * <font style="color: red;"><li></font> 1098 * </pre> 1099 * 1100 * <p>If either {@code elem} or {@code htmlText} 1101 * parameter is {@code null}, no changes are made to the 1102 * document.</p> 1103 * 1104 * <p>For this to work correctly, the document must have an 1105 * HTMLEditorKit.Parser set. This will be the case if the document 1106 * was created from an HTMLEditorKit via the 1107 * {@code createDefaultDocument} method.</p> 1108 * 1109 * @param elem the element to replace 1110 * @param htmlText the string to be parsed and inserted in place of {@code elem} 1111 * @throws IllegalStateException if an HTMLEditorKit.Parser has not 1112 * been set 1113 * @throws BadLocationException if replacement is impossible because of 1114 * a structural issue 1115 * @throws IOException if an I/O exception occurs 1116 * @since 1.3 1117 */ 1118 public void setOuterHTML(Element elem, String htmlText) throws 1119 BadLocationException, IOException { 1120 verifyParser(); 1121 if (elem != null && elem.getParentElement() != null && 1122 htmlText != null) { 1123 int start = elem.getStartOffset(); 1124 int end = elem.getEndOffset(); 1125 int startLength = getLength(); 1126 // We don't want a newline if elem is a leaf, and doesn't contain 1127 // a newline. 1128 boolean wantsNewline = !elem.isLeaf(); 1129 if (!wantsNewline && (end > startLength || 1130 getText(end - 1, 1).charAt(0) == NEWLINE[0])){ 1131 wantsNewline = true; 1132 } 1133 Element parent = elem.getParentElement(); 1134 int oldCount = parent.getElementCount(); 1135 insertHTML(parent, start, htmlText, wantsNewline); 1136 // Remove old. 1137 int newLength = getLength(); 1138 if (oldCount != parent.getElementCount()) { 1139 int removeIndex = parent.getElementIndex(start + newLength - 1140 startLength); 1141 removeElements(parent, removeIndex, 1); 1142 } 1143 } 1144 } 1145 1146 /** 1147 * Inserts the HTML specified as a string at the start 1148 * of the element. 1149 * 1150 * <p>Consider the following structure (the {@code elem} 1151 * parameter is <b>in bold</b>).</p> 1152 * 1153 * <pre> 1154 * <body> 1155 * | 1156 * <b><div></b> 1157 * / \ 1158 * <p> <p> 1159 * </pre> 1160 * 1161 * <p>Invoking 1162 * {@code insertAfterStart(elem, "<ul><li>")} 1163 * results in the following structure 1164 * (new elements are <font style="color: red;">in red</font>).</p> 1165 * 1166 * <pre> 1167 * <body> 1168 * | 1169 * <b><div></b> 1170 * / | \ 1171 * <font style="color: red;"><ul></font> <p> <p> 1172 * / 1173 * <font style="color: red;"><li></font> 1174 * </pre> 1175 * 1176 * <p>Unlike the {@code insertBeforeStart} method, new 1177 * elements become <em>children</em> of the specified element, 1178 * not siblings.</p> 1179 * 1180 * <p>Parameter {@code elem} must not be a leaf element, 1181 * otherwise an {@code IllegalArgumentException} is thrown. 1182 * If either {@code elem} or {@code htmlText} parameter 1183 * is {@code null}, no changes are made to the document.</p> 1184 * 1185 * <p>For this to work correctly, the document must have an 1186 * {@code HTMLEditorKit.Parser} set. This will be the case 1187 * if the document was created from an HTMLEditorKit via the 1188 * {@code createDefaultDocument} method.</p> 1189 * 1190 * @param elem the branch element to be the root for the new text 1191 * @param htmlText the string to be parsed and assigned to {@code elem} 1192 * @throws IllegalArgumentException if {@code elem} is a leaf 1193 * @throws IllegalStateException if an HTMLEditorKit.Parser has not 1194 * been set on the document 1195 * @throws BadLocationException if insertion is impossible because of 1196 * a structural issue 1197 * @throws IOException if an I/O exception occurs 1198 * @since 1.3 1199 */ 1200 public void insertAfterStart(Element elem, String htmlText) throws 1201 BadLocationException, IOException { 1202 verifyParser(); 1203 1204 if (elem == null || htmlText == null) { 1205 return; 1206 } 1207 1208 if (elem.isLeaf()) { 1209 throw new IllegalArgumentException 1210 ("Can not insert HTML after start of a leaf"); 1211 } 1212 insertHTML(elem, elem.getStartOffset(), htmlText, false); 1213 } 1214 1215 /** 1216 * Inserts the HTML specified as a string at the end of 1217 * the element. 1218 * 1219 * <p> If {@code elem}'s children are leaves, and the 1220 * character at a {@code elem.getEndOffset() - 1} is a newline, 1221 * this will insert before the newline so that there isn't text after 1222 * the newline.</p> 1223 * 1224 * <p>Consider the following structure (the {@code elem} 1225 * parameter is <b>in bold</b>).</p> 1226 * 1227 * <pre> 1228 * <body> 1229 * | 1230 * <b><div></b> 1231 * / \ 1232 * <p> <p> 1233 * </pre> 1234 * 1235 * <p>Invoking {@code insertBeforeEnd(elem, "<ul><li>")} 1236 * results in the following structure (new elements are <font 1237 * style="color: red;">in red</font>).</p> 1238 * 1239 * <pre> 1240 * <body> 1241 * | 1242 * <b><div></b> 1243 * / | \ 1244 * <p> <p> <font style="color: red;"><ul></font> 1245 * \ 1246 * <font style="color: red;"><li></font> 1247 * </pre> 1248 * 1249 * <p>Unlike the {@code insertAfterEnd} method, new elements 1250 * become <em>children</em> of the specified element, not 1251 * siblings.</p> 1252 * 1253 * <p>Parameter {@code elem} must not be a leaf element, 1254 * otherwise an {@code IllegalArgumentException} is thrown. 1255 * If either {@code elem} or {@code htmlText} parameter 1256 * is {@code null}, no changes are made to the document.</p> 1257 * 1258 * <p>For this to work correctly, the document must have an 1259 * {@code HTMLEditorKit.Parser} set. This will be the case 1260 * if the document was created from an HTMLEditorKit via the 1261 * {@code createDefaultDocument} method.</p> 1262 * 1263 * @param elem the element to be the root for the new text 1264 * @param htmlText the string to be parsed and assigned to {@code elem} 1265 * @throws IllegalArgumentException if {@code elem} is a leaf 1266 * @throws IllegalStateException if an HTMLEditorKit.Parser has not 1267 * been set on the document 1268 * @throws BadLocationException if insertion is impossible because of 1269 * a structural issue 1270 * @throws IOException if an I/O exception occurs 1271 * @since 1.3 1272 */ 1273 public void insertBeforeEnd(Element elem, String htmlText) throws 1274 BadLocationException, IOException { 1275 verifyParser(); 1276 if (elem != null && elem.isLeaf()) { 1277 throw new IllegalArgumentException 1278 ("Can not set inner HTML before end of leaf"); 1279 } 1280 if (elem != null) { 1281 int offset = elem.getEndOffset(); 1282 if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() && 1283 getText(offset - 1, 1).charAt(0) == NEWLINE[0]) { 1284 offset--; 1285 } 1286 insertHTML(elem, offset, htmlText, false); 1287 } 1288 } 1289 1290 /** 1291 * Inserts the HTML specified as a string before the start of 1292 * the given element. 1293 * 1294 * <p>Consider the following structure (the {@code elem} 1295 * parameter is <b>in bold</b>).</p> 1296 * 1297 * <pre> 1298 * <body> 1299 * | 1300 * <b><div></b> 1301 * / \ 1302 * <p> <p> 1303 * </pre> 1304 * 1305 * <p>Invoking 1306 * {@code insertBeforeStart(elem, "<ul><li>")} 1307 * results in the following structure 1308 * (new elements are <font style="color: red;">in red</font>).</p> 1309 * 1310 * <pre> 1311 * <body> 1312 * / \ 1313 * <font style="color: red;"><ul></font> <b><div></b> 1314 * / / \ 1315 * <font style="color: red;"><li></font> <p> <p> 1316 * </pre> 1317 * 1318 * <p>Unlike the {@code insertAfterStart} method, new 1319 * elements become <em>siblings</em> of the specified element, not 1320 * children.</p> 1321 * 1322 * <p>If either {@code elem} or {@code htmlText} 1323 * parameter is {@code null}, no changes are made to the 1324 * document.</p> 1325 * 1326 * <p>For this to work correctly, the document must have an 1327 * {@code HTMLEditorKit.Parser} set. This will be the case 1328 * if the document was created from an HTMLEditorKit via the 1329 * {@code createDefaultDocument} method.</p> 1330 * 1331 * @param elem the element the content is inserted before 1332 * @param htmlText the string to be parsed and inserted before {@code elem} 1333 * @throws IllegalStateException if an HTMLEditorKit.Parser has not 1334 * been set on the document 1335 * @throws BadLocationException if insertion is impossible because of 1336 * a structural issue 1337 * @throws IOException if an I/O exception occurs 1338 * @since 1.3 1339 */ 1340 public void insertBeforeStart(Element elem, String htmlText) throws 1341 BadLocationException, IOException { 1342 verifyParser(); 1343 if (elem != null) { 1344 Element parent = elem.getParentElement(); 1345 1346 if (parent != null) { 1347 insertHTML(parent, elem.getStartOffset(), htmlText, false); 1348 } 1349 } 1350 } 1351 1352 /** 1353 * Inserts the HTML specified as a string after the end of the 1354 * given element. 1355 * 1356 * <p>Consider the following structure (the {@code elem} 1357 * parameter is <b>in bold</b>).</p> 1358 * 1359 * <pre> 1360 * <body> 1361 * | 1362 * <b><div></b> 1363 * / \ 1364 * <p> <p> 1365 * </pre> 1366 * 1367 * <p>Invoking {@code insertAfterEnd(elem, "<ul><li>")} 1368 * results in the following structure (new elements are <font 1369 * style="color: red;">in red</font>).</p> 1370 * 1371 * <pre> 1372 * <body> 1373 * / \ 1374 * <b><div></b> <font style="color: red;"><ul></font> 1375 * / \ \ 1376 * <p> <p> <font style="color: red;"><li></font> 1377 * </pre> 1378 * 1379 * <p>Unlike the {@code insertBeforeEnd} method, new elements 1380 * become <em>siblings</em> of the specified element, not 1381 * children.</p> 1382 * 1383 * <p>If either {@code elem} or {@code htmlText} 1384 * parameter is {@code null}, no changes are made to the 1385 * document.</p> 1386 * 1387 * <p>For this to work correctly, the document must have an 1388 * {@code HTMLEditorKit.Parser} set. This will be the case 1389 * if the document was created from an HTMLEditorKit via the 1390 * {@code createDefaultDocument} method.</p> 1391 * 1392 * @param elem the element the content is inserted after 1393 * @param htmlText the string to be parsed and inserted after {@code elem} 1394 * @throws IllegalStateException if an HTMLEditorKit.Parser has not 1395 * been set on the document 1396 * @throws BadLocationException if insertion is impossible because of 1397 * a structural issue 1398 * @throws IOException if an I/O exception occurs 1399 * @since 1.3 1400 */ 1401 public void insertAfterEnd(Element elem, String htmlText) throws 1402 BadLocationException, IOException { 1403 verifyParser(); 1404 if (elem != null) { 1405 Element parent = elem.getParentElement(); 1406 1407 if (parent != null) { 1408 // If we are going to insert the string into the body 1409 // section, it is necessary to set the corrsponding flag. 1410 if (HTML.Tag.BODY.name.equals(parent.getName())) { 1411 insertInBody = true; 1412 } 1413 int offset = elem.getEndOffset(); 1414 if (offset > (getLength() + 1)) { 1415 offset--; 1416 } 1417 else if (elem.isLeaf() && getText(offset - 1, 1). 1418 charAt(0) == NEWLINE[0]) { 1419 offset--; 1420 } 1421 insertHTML(parent, offset, htmlText, false); 1422 // Cleanup the flag, if any. 1423 if (insertInBody) { 1424 insertInBody = false; 1425 } 1426 } 1427 } 1428 } 1429 1430 /** 1431 * Returns the element that has the given id {@code Attribute}. 1432 * If the element can't be found, {@code null} is returned. 1433 * Note that this method works on an {@code Attribute}, 1434 * <i>not</i> a character tag. In the following HTML snippet: 1435 * {@code <a id="HelloThere">} the attribute is 1436 * 'id' and the character tag is 'a'. 1437 * This is a convenience method for 1438 * {@code getElement(RootElement, HTML.Attribute.id, id)}. 1439 * This is not thread-safe. 1440 * 1441 * @param id the string representing the desired {@code Attribute} 1442 * @return the element with the specified {@code Attribute} 1443 * or {@code null} if it can't be found, 1444 * or {@code null} if {@code id} is {@code null} 1445 * @see javax.swing.text.html.HTML.Attribute 1446 * @since 1.3 1447 */ 1448 public Element getElement(String id) { 1449 if (id == null) { 1450 return null; 1451 } 1452 return getElement(getDefaultRootElement(), HTML.Attribute.ID, id, 1453 true); 1454 } 1455 1456 /** 1457 * Returns the child element of {@code e} that contains the 1458 * attribute, {@code attribute} with value {@code value}, or 1459 * {@code null} if one isn't found. This is not thread-safe. 1460 * 1461 * @param e the root element where the search begins 1462 * @param attribute the desired {@code Attribute} 1463 * @param value the values for the specified {@code Attribute} 1464 * @return the element with the specified {@code Attribute} 1465 * and the specified {@code value}, or {@code null} 1466 * if it can't be found 1467 * @see javax.swing.text.html.HTML.Attribute 1468 * @since 1.3 1469 */ 1470 public Element getElement(Element e, Object attribute, Object value) { 1471 return getElement(e, attribute, value, true); 1472 } 1473 1474 /** 1475 * Returns the child element of {@code e} that contains the 1476 * attribute, {@code attribute} with value {@code value}, or 1477 * {@code null} if one isn't found. This is not thread-safe. 1478 * <p> 1479 * If {@code searchLeafAttributes} is true, and {@code e} is 1480 * a leaf, any attributes that are instances of {@code HTML.Tag} 1481 * with a value that is an {@code AttributeSet} will also be checked. 1482 * 1483 * @param e the root element where the search begins 1484 * @param attribute the desired {@code Attribute} 1485 * @param value the values for the specified {@code Attribute} 1486 * @return the element with the specified {@code Attribute} 1487 * and the specified {@code value}, or {@code null} 1488 * if it can't be found 1489 * @see javax.swing.text.html.HTML.Attribute 1490 */ 1491 private Element getElement(Element e, Object attribute, Object value, 1492 boolean searchLeafAttributes) { 1493 AttributeSet attr = e.getAttributes(); 1494 1495 if (attr != null && attr.isDefined(attribute)) { 1496 if (value.equals(attr.getAttribute(attribute))) { 1497 return e; 1498 } 1499 } 1500 if (!e.isLeaf()) { 1501 for (int counter = 0, maxCounter = e.getElementCount(); 1502 counter < maxCounter; counter++) { 1503 Element retValue = getElement(e.getElement(counter), attribute, 1504 value, searchLeafAttributes); 1505 1506 if (retValue != null) { 1507 return retValue; 1508 } 1509 } 1510 } 1511 else if (searchLeafAttributes && attr != null) { 1512 // For some leaf elements we store the actual attributes inside 1513 // the AttributeSet of the Element (such as anchors). 1514 Enumeration<?> names = attr.getAttributeNames(); 1515 if (names != null) { 1516 while (names.hasMoreElements()) { 1517 Object name = names.nextElement(); 1518 if ((name instanceof HTML.Tag) && 1519 (attr.getAttribute(name) instanceof AttributeSet)) { 1520 1521 AttributeSet check = (AttributeSet)attr. 1522 getAttribute(name); 1523 if (check.isDefined(attribute) && 1524 value.equals(check.getAttribute(attribute))) { 1525 return e; 1526 } 1527 } 1528 } 1529 } 1530 } 1531 return null; 1532 } 1533 1534 /** 1535 * Verifies the document has an {@code HTMLEditorKit.Parser} set. 1536 * If {@code getParser} returns {@code null}, this will throw an 1537 * IllegalStateException. 1538 * 1539 * @throws IllegalStateException if the document does not have a Parser 1540 */ 1541 private void verifyParser() { 1542 if (getParser() == null) { 1543 throw new IllegalStateException("No HTMLEditorKit.Parser"); 1544 } 1545 } 1546 1547 /** 1548 * Installs a default Parser if one has not been installed yet. 1549 */ 1550 private void installParserIfNecessary() { 1551 if (getParser() == null) { 1552 setParser(new HTMLEditorKit().getParser()); 1553 } 1554 } 1555 1556 /** 1557 * Inserts a string of HTML into the document at the given position. 1558 * {@code parent} is used to identify the location to insert the 1559 * {@code html}. If {@code parent} is a leaf this can have 1560 * unexpected results. 1561 */ 1562 private void insertHTML(Element parent, int offset, String html, 1563 boolean wantsTrailingNewline) 1564 throws BadLocationException, IOException { 1565 if (parent != null && html != null) { 1566 HTMLEditorKit.Parser parser = getParser(); 1567 if (parser != null) { 1568 int lastOffset = Math.max(0, offset - 1); 1569 Element charElement = getCharacterElement(lastOffset); 1570 Element commonParent = parent; 1571 int pop = 0; 1572 int push = 0; 1573 1574 if (parent.getStartOffset() > lastOffset) { 1575 while (commonParent != null && 1576 commonParent.getStartOffset() > lastOffset) { 1577 commonParent = commonParent.getParentElement(); 1578 push++; 1579 } 1580 if (commonParent == null) { 1581 throw new BadLocationException("No common parent", 1582 offset); 1583 } 1584 } 1585 while (charElement != null && charElement != commonParent) { 1586 pop++; 1587 charElement = charElement.getParentElement(); 1588 } 1589 if (charElement != null) { 1590 // Found it, do the insert. 1591 HTMLReader reader = new HTMLReader(offset, pop - 1, push, 1592 null, false, true, 1593 wantsTrailingNewline); 1594 1595 parser.parse(new StringReader(html), reader, true); 1596 reader.flush(); 1597 } 1598 } 1599 } 1600 } 1601 1602 /** 1603 * Removes child Elements of the passed in Element {@code e}. This 1604 * will do the necessary cleanup to ensure the element representing the 1605 * end character is correctly created. 1606 * <p>This is not a general purpose method, it assumes that {@code e} 1607 * will still have at least one child after the remove, and it assumes 1608 * the character at {@code e.getStartOffset() - 1} is a newline and 1609 * is of length 1. 1610 */ 1611 private void removeElements(Element e, int index, int count) throws BadLocationException { 1612 writeLock(); 1613 try { 1614 int start = e.getElement(index).getStartOffset(); 1615 int end = e.getElement(index + count - 1).getEndOffset(); 1616 if (end > getLength()) { 1617 removeElementsAtEnd(e, index, count, start, end); 1618 } 1619 else { 1620 removeElements(e, index, count, start, end); 1621 } 1622 } finally { 1623 writeUnlock(); 1624 } 1625 } 1626 1627 /** 1628 * Called to remove child elements of {@code e} when one of the 1629 * elements to remove is representing the end character. 1630 * <p>Since the Content will not allow a removal to the end character 1631 * this will do a remove from {@code start - 1} to {@code end}. 1632 * The end Element(s) will be removed, and the element representing 1633 * {@code start - 1} to {@code start} will be recreated. This 1634 * Element has to be recreated as after the content removal its offsets 1635 * become {@code start - 1} to {@code start - 1}. 1636 */ 1637 private void removeElementsAtEnd(Element e, int index, int count, 1638 int start, int end) throws BadLocationException { 1639 // index must be > 0 otherwise no insert would have happened. 1640 boolean isLeaf = (e.getElement(index - 1).isLeaf()); 1641 DefaultDocumentEvent dde = new DefaultDocumentEvent( 1642 start - 1, end - start + 1, DocumentEvent. 1643 EventType.REMOVE); 1644 1645 if (isLeaf) { 1646 Element endE = getCharacterElement(getLength()); 1647 // e.getElement(index - 1) should represent the newline. 1648 index--; 1649 if (endE.getParentElement() != e) { 1650 // The hiearchies don't match, we'll have to manually 1651 // recreate the leaf at e.getElement(index - 1) 1652 replace(dde, e, index, ++count, start, end, true, true); 1653 } 1654 else { 1655 // The hierarchies for the end Element and 1656 // e.getElement(index - 1), match, we can safely remove 1657 // the Elements and the end content will be aligned 1658 // appropriately. 1659 replace(dde, e, index, count, start, end, true, false); 1660 } 1661 } 1662 else { 1663 // Not a leaf, descend until we find the leaf representing 1664 // start - 1 and remove it. 1665 Element newLineE = e.getElement(index - 1); 1666 while (!newLineE.isLeaf()) { 1667 newLineE = newLineE.getElement(newLineE.getElementCount() - 1); 1668 } 1669 newLineE = newLineE.getParentElement(); 1670 replace(dde, e, index, count, start, end, false, false); 1671 replace(dde, newLineE, newLineE.getElementCount() - 1, 1, start, 1672 end, true, true); 1673 } 1674 postRemoveUpdate(dde); 1675 dde.end(); 1676 fireRemoveUpdate(dde); 1677 fireUndoableEditUpdate(new UndoableEditEvent(this, dde)); 1678 } 1679 1680 /** 1681 * This is used by {@code removeElementsAtEnd}, it removes 1682 * {@code count} elements starting at {@code start} from 1683 * {@code e}. If {@code remove} is true text of length 1684 * {@code start - 1} to {@code end - 1} is removed. If 1685 * {@code create} is true a new leaf is created of length 1. 1686 */ 1687 private void replace(DefaultDocumentEvent dde, Element e, int index, 1688 int count, int start, int end, boolean remove, 1689 boolean create) throws BadLocationException { 1690 Element[] added; 1691 AttributeSet attrs = e.getElement(index).getAttributes(); 1692 Element[] removed = new Element[count]; 1693 1694 for (int counter = 0; counter < count; counter++) { 1695 removed[counter] = e.getElement(counter + index); 1696 } 1697 if (remove) { 1698 UndoableEdit u = getContent().remove(start - 1, end - start); 1699 if (u != null) { 1700 dde.addEdit(u); 1701 } 1702 } 1703 if (create) { 1704 added = new Element[1]; 1705 added[0] = createLeafElement(e, attrs, start - 1, start); 1706 } 1707 else { 1708 added = new Element[0]; 1709 } 1710 dde.addEdit(new ElementEdit(e, index, removed, added)); 1711 ((AbstractDocument.BranchElement)e).replace( 1712 index, removed.length, added); 1713 } 1714 1715 /** 1716 * Called to remove child Elements when the end is not touched. 1717 */ 1718 private void removeElements(Element e, int index, int count, 1719 int start, int end) throws BadLocationException { 1720 Element[] removed = new Element[count]; 1721 Element[] added = new Element[0]; 1722 for (int counter = 0; counter < count; counter++) { 1723 removed[counter] = e.getElement(counter + index); 1724 } 1725 DefaultDocumentEvent dde = new DefaultDocumentEvent 1726 (start, end - start, DocumentEvent.EventType.REMOVE); 1727 ((AbstractDocument.BranchElement)e).replace(index, removed.length, 1728 added); 1729 dde.addEdit(new ElementEdit(e, index, removed, added)); 1730 UndoableEdit u = getContent().remove(start, end - start); 1731 if (u != null) { 1732 dde.addEdit(u); 1733 } 1734 postRemoveUpdate(dde); 1735 dde.end(); 1736 fireRemoveUpdate(dde); 1737 if (u != null) { 1738 fireUndoableEditUpdate(new UndoableEditEvent(this, dde)); 1739 } 1740 } 1741 1742 1743 // These two are provided for inner class access. The are named different 1744 // than the super class as the super class implementations are final. 1745 void obtainLock() { 1746 writeLock(); 1747 } 1748 1749 void releaseLock() { 1750 writeUnlock(); 1751 } 1752 1753 // 1754 // Provided for inner class access. 1755 // 1756 1757 /** 1758 * Notifies all listeners that have registered interest for 1759 * notification on this event type. The event instance 1760 * is lazily created using the parameters passed into 1761 * the fire method. 1762 * 1763 * @param e the event 1764 * @see EventListenerList 1765 */ 1766 protected void fireChangedUpdate(DocumentEvent e) { 1767 super.fireChangedUpdate(e); 1768 } 1769 1770 /** 1771 * Notifies all listeners that have registered interest for 1772 * notification on this event type. The event instance 1773 * is lazily created using the parameters passed into 1774 * the fire method. 1775 * 1776 * @param e the event 1777 * @see EventListenerList 1778 */ 1779 protected void fireUndoableEditUpdate(UndoableEditEvent e) { 1780 super.fireUndoableEditUpdate(e); 1781 } 1782 1783 boolean hasBaseTag() { 1784 return hasBaseTag; 1785 } 1786 1787 String getBaseTarget() { 1788 return baseTarget; 1789 } 1790 1791 /* 1792 * state defines whether the document is a frame document 1793 * or not. 1794 */ 1795 private boolean frameDocument = false; 1796 private boolean preservesUnknownTags = true; 1797 1798 /* 1799 * Used to store button groups for radio buttons in 1800 * a form. 1801 */ 1802 private HashMap<String, ButtonGroup> radioButtonGroupsMap; 1803 1804 /** 1805 * Document property for the number of tokens to buffer 1806 * before building an element subtree to represent them. 1807 */ 1808 static final String TokenThreshold = "token threshold"; 1809 1810 private static final int MaxThreshold = 10000; 1811 1812 private static final int StepThreshold = 5; 1813 1814 1815 /** 1816 * Document property key value. The value for the key will be a Vector 1817 * of Strings that are comments not found in the body. 1818 */ 1819 public static final String AdditionalComments = "AdditionalComments"; 1820 1821 /** 1822 * Document property key value. The value for the key will be a 1823 * String indicating the default type of stylesheet links. 1824 */ 1825 /* public */ static final String StyleType = "StyleType"; 1826 1827 /** 1828 * The location to resolve relative URLs against. By 1829 * default this will be the document's URL if the document 1830 * was loaded from a URL. If a base tag is found and 1831 * can be parsed, it will be used as the base location. 1832 */ 1833 URL base; 1834 1835 /** 1836 * does the document have base tag 1837 */ 1838 boolean hasBaseTag = false; 1839 1840 /** 1841 * BASE tag's TARGET attribute value 1842 */ 1843 private String baseTarget = null; 1844 1845 /** 1846 * The parser that is used when inserting html into the existing 1847 * document. 1848 */ 1849 private HTMLEditorKit.Parser parser; 1850 1851 /** 1852 * Used for inserts when a null AttributeSet is supplied. 1853 */ 1854 private static AttributeSet contentAttributeSet; 1855 1856 /** 1857 * Property Maps are registered under, will be a Hashtable. 1858 */ 1859 static String MAP_PROPERTY = "__MAP__"; 1860 1861 private static char[] NEWLINE; 1862 1863 /** 1864 * Indicates that direct insertion to body section takes place. 1865 */ 1866 private boolean insertInBody = false; 1867 1868 /** 1869 * I18N property key. 1870 * 1871 * @see AbstractDocument#I18NProperty 1872 */ 1873 private static final String I18NProperty = "i18n"; 1874 1875 static { 1876 contentAttributeSet = new SimpleAttributeSet(); 1877 ((MutableAttributeSet)contentAttributeSet). 1878 addAttribute(StyleConstants.NameAttribute, 1879 HTML.Tag.CONTENT); 1880 NEWLINE = new char[1]; 1881 NEWLINE[0] = '\n'; 1882 } 1883 1884 1885 /** 1886 * An iterator to iterate over a particular type of 1887 * tag. The iterator is not thread safe. If reliable 1888 * access to the document is not already ensured by 1889 * the context under which the iterator is being used, 1890 * its use should be performed under the protection of 1891 * Document.render. 1892 */ 1893 public abstract static class Iterator { 1894 1895 /** 1896 * Return the attributes for this tag. 1897 * @return the {@code AttributeSet} for this tag, or 1898 * {@code null} if none can be found 1899 */ 1900 public abstract AttributeSet getAttributes(); 1901 1902 /** 1903 * Returns the start of the range for which the current occurrence of 1904 * the tag is defined and has the same attributes. 1905 * 1906 * @return the start of the range, or -1 if it can't be found 1907 */ 1908 public abstract int getStartOffset(); 1909 1910 /** 1911 * Returns the end of the range for which the current occurrence of 1912 * the tag is defined and has the same attributes. 1913 * 1914 * @return the end of the range 1915 */ 1916 public abstract int getEndOffset(); 1917 1918 /** 1919 * Move the iterator forward to the next occurrence 1920 * of the tag it represents. 1921 */ 1922 public abstract void next(); 1923 1924 /** 1925 * Indicates if the iterator is currently 1926 * representing an occurrence of a tag. If 1927 * false there are no more tags for this iterator. 1928 * @return true if the iterator is currently representing an 1929 * occurrence of a tag, otherwise returns false 1930 */ 1931 public abstract boolean isValid(); 1932 1933 /** 1934 * Type of tag this iterator represents. 1935 * @return the tag 1936 */ 1937 public abstract HTML.Tag getTag(); 1938 } 1939 1940 /** 1941 * An iterator to iterate over a particular type of tag. 1942 */ 1943 static class LeafIterator extends Iterator { 1944 1945 LeafIterator(HTML.Tag t, Document doc) { 1946 tag = t; 1947 pos = new ElementIterator(doc); 1948 endOffset = 0; 1949 next(); 1950 } 1951 1952 /** 1953 * Returns the attributes for this tag. 1954 * @return the {@code AttributeSet} for this tag, 1955 * or {@code null} if none can be found 1956 */ 1957 public AttributeSet getAttributes() { 1958 Element elem = pos.current(); 1959 if (elem != null) { 1960 AttributeSet a = (AttributeSet) 1961 elem.getAttributes().getAttribute(tag); 1962 if (a == null) { 1963 a = elem.getAttributes(); 1964 } 1965 return a; 1966 } 1967 return null; 1968 } 1969 1970 /** 1971 * Returns the start of the range for which the current occurrence of 1972 * the tag is defined and has the same attributes. 1973 * 1974 * @return the start of the range, or -1 if it can't be found 1975 */ 1976 public int getStartOffset() { 1977 Element elem = pos.current(); 1978 if (elem != null) { 1979 return elem.getStartOffset(); 1980 } 1981 return -1; 1982 } 1983 1984 /** 1985 * Returns the end of the range for which the current occurrence of 1986 * the tag is defined and has the same attributes. 1987 * 1988 * @return the end of the range 1989 */ 1990 public int getEndOffset() { 1991 return endOffset; 1992 } 1993 1994 /** 1995 * Moves the iterator forward to the next occurrence 1996 * of the tag it represents. 1997 */ 1998 public void next() { 1999 for (nextLeaf(pos); isValid(); nextLeaf(pos)) { 2000 Element elem = pos.current(); 2001 if (elem.getStartOffset() >= endOffset) { 2002 AttributeSet a = pos.current().getAttributes(); 2003 2004 if (a.isDefined(tag) || 2005 a.getAttribute(StyleConstants.NameAttribute) == tag) { 2006 2007 // we found the next one 2008 setEndOffset(); 2009 break; 2010 } 2011 } 2012 } 2013 } 2014 2015 /** 2016 * Returns the type of tag this iterator represents. 2017 * 2018 * @return the {@code HTML.Tag} that this iterator represents. 2019 * @see javax.swing.text.html.HTML.Tag 2020 */ 2021 public HTML.Tag getTag() { 2022 return tag; 2023 } 2024 2025 /** 2026 * Returns true if the current position is not {@code null}. 2027 * @return true if current position is not {@code null}, 2028 * otherwise returns false 2029 */ 2030 public boolean isValid() { 2031 return (pos.current() != null); 2032 } 2033 2034 /** 2035 * Moves the given iterator to the next leaf element. 2036 * @param iter the iterator to be scanned 2037 */ 2038 void nextLeaf(ElementIterator iter) { 2039 for (iter.next(); iter.current() != null; iter.next()) { 2040 Element e = iter.current(); 2041 if (e.isLeaf()) { 2042 break; 2043 } 2044 } 2045 } 2046 2047 /** 2048 * Marches a cloned iterator forward to locate the end 2049 * of the run. This sets the value of {@code endOffset}. 2050 */ 2051 void setEndOffset() { 2052 AttributeSet a0 = getAttributes(); 2053 endOffset = pos.current().getEndOffset(); 2054 ElementIterator fwd = (ElementIterator) pos.clone(); 2055 for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) { 2056 Element e = fwd.current(); 2057 AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag); 2058 if ((a1 == null) || (! a1.equals(a0))) { 2059 break; 2060 } 2061 endOffset = e.getEndOffset(); 2062 } 2063 } 2064 2065 private int endOffset; 2066 private HTML.Tag tag; 2067 private ElementIterator pos; 2068 2069 } 2070 2071 /** 2072 * An HTML reader to load an HTML document with an HTML 2073 * element structure. This is a set of callbacks from 2074 * the parser, implemented to create a set of elements 2075 * tagged with attributes. The parse builds up tokens 2076 * (ElementSpec) that describe the element subtree desired, 2077 * and burst it into the document under the protection of 2078 * a write lock using the insert method on the document 2079 * outer class. 2080 * <p> 2081 * The reader can be configured by registering actions 2082 * (of type {@code HTMLDocument.HTMLReader.TagAction}) 2083 * that describe how to handle the action. The idea behind 2084 * the actions provided is that the most natural text editing 2085 * operations can be provided if the element structure boils 2086 * down to paragraphs with runs of some kind of style 2087 * in them. Some things are more naturally specified 2088 * structurally, so arbitrary structure should be allowed 2089 * above the paragraphs, but will need to be edited with structural 2090 * actions. The implication of this is that some of the 2091 * HTML elements specified in the stream being parsed will 2092 * be collapsed into attributes, and in some cases paragraphs 2093 * will be synthesized. When HTML elements have been 2094 * converted to attributes, the attribute key will be of 2095 * type HTML.Tag, and the value will be of type AttributeSet 2096 * so that no information is lost. This enables many of the 2097 * existing actions to work so that the user can type input, 2098 * hit the return key, backspace, delete, etc and have a 2099 * reasonable result. Selections can be created, and attributes 2100 * applied or removed, etc. With this in mind, the work done 2101 * by the reader can be categorized into the following kinds 2102 * of tasks: 2103 * <dl> 2104 * <dt>Block 2105 * <dd>Build the structure like it's specified in the stream. 2106 * This produces elements that contain other elements. 2107 * <dt>Paragraph 2108 * <dd>Like block except that it's expected that the element 2109 * will be used with a paragraph view so a paragraph element 2110 * won't need to be synthesized. 2111 * <dt>Character 2112 * <dd>Contribute the element as an attribute that will start 2113 * and stop at arbitrary text locations. This will ultimately 2114 * be mixed into a run of text, with all of the currently 2115 * flattened HTML character elements. 2116 * <dt>Special 2117 * <dd>Produce an embedded graphical element. 2118 * <dt>Form 2119 * <dd>Produce an element that is like the embedded graphical 2120 * element, except that it also has a component model associated 2121 * with it. 2122 * <dt>Hidden 2123 * <dd>Create an element that is hidden from view when the 2124 * document is being viewed read-only, and visible when the 2125 * document is being edited. This is useful to keep the 2126 * model from losing information, and used to store things 2127 * like comments and unrecognized tags. 2128 * 2129 * </dl> 2130 * <p> 2131 * Currently, <APPLET>, <PARAM>, <MAP>, <AREA>, <LINK>, 2132 * <SCRIPT> and <STYLE> are unsupported. 2133 * 2134 * <p> 2135 * The assignment of the actions described is shown in the 2136 * following table for the tags defined in {@code HTML.Tag}. 2137 * <table border=1 summary="HTML tags and assigned actions"> 2138 * <tr><th>Tag</th><th>Action</th></tr> 2139 * <tr><td>{@code HTML.Tag.A} <td>CharacterAction 2140 * <tr><td>{@code HTML.Tag.ADDRESS} <td>CharacterAction 2141 * <tr><td>{@code HTML.Tag.APPLET} <td>HiddenAction 2142 * <tr><td>{@code HTML.Tag.AREA} <td>AreaAction 2143 * <tr><td>{@code HTML.Tag.B} <td>CharacterAction 2144 * <tr><td>{@code HTML.Tag.BASE} <td>BaseAction 2145 * <tr><td>{@code HTML.Tag.BASEFONT} <td>CharacterAction 2146 * <tr><td>{@code HTML.Tag.BIG} <td>CharacterAction 2147 * <tr><td>{@code HTML.Tag.BLOCKQUOTE}<td>BlockAction 2148 * <tr><td>{@code HTML.Tag.BODY} <td>BlockAction 2149 * <tr><td>{@code HTML.Tag.BR} <td>SpecialAction 2150 * <tr><td>{@code HTML.Tag.CAPTION} <td>BlockAction 2151 * <tr><td>{@code HTML.Tag.CENTER} <td>BlockAction 2152 * <tr><td>{@code HTML.Tag.CITE} <td>CharacterAction 2153 * <tr><td>{@code HTML.Tag.CODE} <td>CharacterAction 2154 * <tr><td>{@code HTML.Tag.DD} <td>BlockAction 2155 * <tr><td>{@code HTML.Tag.DFN} <td>CharacterAction 2156 * <tr><td>{@code HTML.Tag.DIR} <td>BlockAction 2157 * <tr><td>{@code HTML.Tag.DIV} <td>BlockAction 2158 * <tr><td>{@code HTML.Tag.DL} <td>BlockAction 2159 * <tr><td>{@code HTML.Tag.DT} <td>ParagraphAction 2160 * <tr><td>{@code HTML.Tag.EM} <td>CharacterAction 2161 * <tr><td>{@code HTML.Tag.FONT} <td>CharacterAction 2162 * <tr><td>{@code HTML.Tag.FORM} <td>As of 1.4 a BlockAction 2163 * <tr><td>{@code HTML.Tag.FRAME} <td>SpecialAction 2164 * <tr><td>{@code HTML.Tag.FRAMESET} <td>BlockAction 2165 * <tr><td>{@code HTML.Tag.H1} <td>ParagraphAction 2166 * <tr><td>{@code HTML.Tag.H2} <td>ParagraphAction 2167 * <tr><td>{@code HTML.Tag.H3} <td>ParagraphAction 2168 * <tr><td>{@code HTML.Tag.H4} <td>ParagraphAction 2169 * <tr><td>{@code HTML.Tag.H5} <td>ParagraphAction 2170 * <tr><td>{@code HTML.Tag.H6} <td>ParagraphAction 2171 * <tr><td>{@code HTML.Tag.HEAD} <td>HeadAction 2172 * <tr><td>{@code HTML.Tag.HR} <td>SpecialAction 2173 * <tr><td>{@code HTML.Tag.HTML} <td>BlockAction 2174 * <tr><td>{@code HTML.Tag.I} <td>CharacterAction 2175 * <tr><td>{@code HTML.Tag.IMG} <td>SpecialAction 2176 * <tr><td>{@code HTML.Tag.INPUT} <td>FormAction 2177 * <tr><td>{@code HTML.Tag.ISINDEX} <td>IsndexAction 2178 * <tr><td>{@code HTML.Tag.KBD} <td>CharacterAction 2179 * <tr><td>{@code HTML.Tag.LI} <td>BlockAction 2180 * <tr><td>{@code HTML.Tag.LINK} <td>LinkAction 2181 * <tr><td>{@code HTML.Tag.MAP} <td>MapAction 2182 * <tr><td>{@code HTML.Tag.MENU} <td>BlockAction 2183 * <tr><td>{@code HTML.Tag.META} <td>MetaAction 2184 * <tr><td>{@code HTML.Tag.NOFRAMES} <td>BlockAction 2185 * <tr><td>{@code HTML.Tag.OBJECT} <td>SpecialAction 2186 * <tr><td>{@code HTML.Tag.OL} <td>BlockAction 2187 * <tr><td>{@code HTML.Tag.OPTION} <td>FormAction 2188 * <tr><td>{@code HTML.Tag.P} <td>ParagraphAction 2189 * <tr><td>{@code HTML.Tag.PARAM} <td>HiddenAction 2190 * <tr><td>{@code HTML.Tag.PRE} <td>PreAction 2191 * <tr><td>{@code HTML.Tag.SAMP} <td>CharacterAction 2192 * <tr><td>{@code HTML.Tag.SCRIPT} <td>HiddenAction 2193 * <tr><td>{@code HTML.Tag.SELECT} <td>FormAction 2194 * <tr><td>{@code HTML.Tag.SMALL} <td>CharacterAction 2195 * <tr><td>{@code HTML.Tag.STRIKE} <td>CharacterAction 2196 * <tr><td>{@code HTML.Tag.S} <td>CharacterAction 2197 * <tr><td>{@code HTML.Tag.STRONG} <td>CharacterAction 2198 * <tr><td>{@code HTML.Tag.STYLE} <td>StyleAction 2199 * <tr><td>{@code HTML.Tag.SUB} <td>CharacterAction 2200 * <tr><td>{@code HTML.Tag.SUP} <td>CharacterAction 2201 * <tr><td>{@code HTML.Tag.TABLE} <td>BlockAction 2202 * <tr><td>{@code HTML.Tag.TD} <td>BlockAction 2203 * <tr><td>{@code HTML.Tag.TEXTAREA} <td>FormAction 2204 * <tr><td>{@code HTML.Tag.TH} <td>BlockAction 2205 * <tr><td>{@code HTML.Tag.TITLE} <td>TitleAction 2206 * <tr><td>{@code HTML.Tag.TR} <td>BlockAction 2207 * <tr><td>{@code HTML.Tag.TT} <td>CharacterAction 2208 * <tr><td>{@code HTML.Tag.U} <td>CharacterAction 2209 * <tr><td>{@code HTML.Tag.UL} <td>BlockAction 2210 * <tr><td>{@code HTML.Tag.VAR} <td>CharacterAction 2211 * </table> 2212 * <p> 2213 * Once </html> is encountered, the Actions are no longer notified. 2214 */ 2215 public class HTMLReader extends HTMLEditorKit.ParserCallback { 2216 2217 /** 2218 * Constructs an HTMLReader using default pop and push depth and no tag to insert. 2219 * 2220 * @param offset the starting offset 2221 */ 2222 public HTMLReader(int offset) { 2223 this(offset, 0, 0, null); 2224 } 2225 2226 /** 2227 * Constructs an HTMLReader. 2228 * 2229 * @param offset the starting offset 2230 * @param popDepth how many parents to ascend before insert new element 2231 * @param pushDepth how many parents to descend (relative to popDepth) before 2232 * inserting 2233 * @param insertTag a tag to insert (may be null) 2234 */ 2235 public HTMLReader(int offset, int popDepth, int pushDepth, 2236 HTML.Tag insertTag) { 2237 this(offset, popDepth, pushDepth, insertTag, true, false, true); 2238 } 2239 2240 /** 2241 * Generates a RuntimeException (will eventually generate 2242 * a BadLocationException when API changes are alloced) if inserting 2243 * into non empty document, {@code insertTag} is 2244 * non-{@code null}, and {@code offset} is not in the body. 2245 */ 2246 // PENDING(sky): Add throws BadLocationException and remove 2247 // RuntimeException 2248 HTMLReader(int offset, int popDepth, int pushDepth, 2249 HTML.Tag insertTag, boolean insertInsertTag, 2250 boolean insertAfterImplied, boolean wantsTrailingNewline) { 2251 emptyDocument = (getLength() == 0); 2252 isStyleCSS = "text/css".equals(getDefaultStyleSheetType()); 2253 this.offset = offset; 2254 threshold = HTMLDocument.this.getTokenThreshold(); 2255 tagMap = new Hashtable<HTML.Tag, TagAction>(57); 2256 TagAction na = new TagAction(); 2257 TagAction ba = new BlockAction(); 2258 TagAction pa = new ParagraphAction(); 2259 TagAction ca = new CharacterAction(); 2260 TagAction sa = new SpecialAction(); 2261 TagAction fa = new FormAction(); 2262 TagAction ha = new HiddenAction(); 2263 TagAction conv = new ConvertAction(); 2264 2265 // register handlers for the well known tags 2266 tagMap.put(HTML.Tag.A, new AnchorAction()); 2267 tagMap.put(HTML.Tag.ADDRESS, ca); 2268 tagMap.put(HTML.Tag.APPLET, ha); 2269 tagMap.put(HTML.Tag.AREA, new AreaAction()); 2270 tagMap.put(HTML.Tag.B, conv); 2271 tagMap.put(HTML.Tag.BASE, new BaseAction()); 2272 tagMap.put(HTML.Tag.BASEFONT, ca); 2273 tagMap.put(HTML.Tag.BIG, ca); 2274 tagMap.put(HTML.Tag.BLOCKQUOTE, ba); 2275 tagMap.put(HTML.Tag.BODY, ba); 2276 tagMap.put(HTML.Tag.BR, sa); 2277 tagMap.put(HTML.Tag.CAPTION, ba); 2278 tagMap.put(HTML.Tag.CENTER, ba); 2279 tagMap.put(HTML.Tag.CITE, ca); 2280 tagMap.put(HTML.Tag.CODE, ca); 2281 tagMap.put(HTML.Tag.DD, ba); 2282 tagMap.put(HTML.Tag.DFN, ca); 2283 tagMap.put(HTML.Tag.DIR, ba); 2284 tagMap.put(HTML.Tag.DIV, ba); 2285 tagMap.put(HTML.Tag.DL, ba); 2286 tagMap.put(HTML.Tag.DT, pa); 2287 tagMap.put(HTML.Tag.EM, ca); 2288 tagMap.put(HTML.Tag.FONT, conv); 2289 tagMap.put(HTML.Tag.FORM, new FormTagAction()); 2290 tagMap.put(HTML.Tag.FRAME, sa); 2291 tagMap.put(HTML.Tag.FRAMESET, ba); 2292 tagMap.put(HTML.Tag.H1, pa); 2293 tagMap.put(HTML.Tag.H2, pa); 2294 tagMap.put(HTML.Tag.H3, pa); 2295 tagMap.put(HTML.Tag.H4, pa); 2296 tagMap.put(HTML.Tag.H5, pa); 2297 tagMap.put(HTML.Tag.H6, pa); 2298 tagMap.put(HTML.Tag.HEAD, new HeadAction()); 2299 tagMap.put(HTML.Tag.HR, sa); 2300 tagMap.put(HTML.Tag.HTML, ba); 2301 tagMap.put(HTML.Tag.I, conv); 2302 tagMap.put(HTML.Tag.IMG, sa); 2303 tagMap.put(HTML.Tag.INPUT, fa); 2304 tagMap.put(HTML.Tag.ISINDEX, new IsindexAction()); 2305 tagMap.put(HTML.Tag.KBD, ca); 2306 tagMap.put(HTML.Tag.LI, ba); 2307 tagMap.put(HTML.Tag.LINK, new LinkAction()); 2308 tagMap.put(HTML.Tag.MAP, new MapAction()); 2309 tagMap.put(HTML.Tag.MENU, ba); 2310 tagMap.put(HTML.Tag.META, new MetaAction()); 2311 tagMap.put(HTML.Tag.NOBR, ca); 2312 tagMap.put(HTML.Tag.NOFRAMES, ba); 2313 tagMap.put(HTML.Tag.OBJECT, sa); 2314 tagMap.put(HTML.Tag.OL, ba); 2315 tagMap.put(HTML.Tag.OPTION, fa); 2316 tagMap.put(HTML.Tag.P, pa); 2317 tagMap.put(HTML.Tag.PARAM, new ObjectAction()); 2318 tagMap.put(HTML.Tag.PRE, new PreAction()); 2319 tagMap.put(HTML.Tag.SAMP, ca); 2320 tagMap.put(HTML.Tag.SCRIPT, ha); 2321 tagMap.put(HTML.Tag.SELECT, fa); 2322 tagMap.put(HTML.Tag.SMALL, ca); 2323 tagMap.put(HTML.Tag.SPAN, ca); 2324 tagMap.put(HTML.Tag.STRIKE, conv); 2325 tagMap.put(HTML.Tag.S, ca); 2326 tagMap.put(HTML.Tag.STRONG, ca); 2327 tagMap.put(HTML.Tag.STYLE, new StyleAction()); 2328 tagMap.put(HTML.Tag.SUB, conv); 2329 tagMap.put(HTML.Tag.SUP, conv); 2330 tagMap.put(HTML.Tag.TABLE, ba); 2331 tagMap.put(HTML.Tag.TD, ba); 2332 tagMap.put(HTML.Tag.TEXTAREA, fa); 2333 tagMap.put(HTML.Tag.TH, ba); 2334 tagMap.put(HTML.Tag.TITLE, new TitleAction()); 2335 tagMap.put(HTML.Tag.TR, ba); 2336 tagMap.put(HTML.Tag.TT, ca); 2337 tagMap.put(HTML.Tag.U, conv); 2338 tagMap.put(HTML.Tag.UL, ba); 2339 tagMap.put(HTML.Tag.VAR, ca); 2340 2341 if (insertTag != null) { 2342 this.insertTag = insertTag; 2343 this.popDepth = popDepth; 2344 this.pushDepth = pushDepth; 2345 this.insertInsertTag = insertInsertTag; 2346 foundInsertTag = false; 2347 } 2348 else { 2349 foundInsertTag = true; 2350 } 2351 if (insertAfterImplied) { 2352 this.popDepth = popDepth; 2353 this.pushDepth = pushDepth; 2354 this.insertAfterImplied = true; 2355 foundInsertTag = false; 2356 midInsert = false; 2357 this.insertInsertTag = true; 2358 this.wantsTrailingNewline = wantsTrailingNewline; 2359 } 2360 else { 2361 midInsert = (!emptyDocument && insertTag == null); 2362 if (midInsert) { 2363 generateEndsSpecsForMidInsert(); 2364 } 2365 } 2366 2367 /** 2368 * This block initializes the {@code inParagraph} flag. 2369 * It is left in {@code false} value automatically 2370 * if the target document is empty or future inserts 2371 * were positioned into the 'body' tag. 2372 */ 2373 if (!emptyDocument && !midInsert) { 2374 int targetOffset = Math.max(this.offset - 1, 0); 2375 Element elem = 2376 HTMLDocument.this.getCharacterElement(targetOffset); 2377 /* Going up by the left document structure path */ 2378 for (int i = 0; i <= this.popDepth; i++) { 2379 elem = elem.getParentElement(); 2380 } 2381 /* Going down by the right document structure path */ 2382 for (int i = 0; i < this.pushDepth; i++) { 2383 int index = elem.getElementIndex(this.offset); 2384 elem = elem.getElement(index); 2385 } 2386 AttributeSet attrs = elem.getAttributes(); 2387 if (attrs != null) { 2388 HTML.Tag tagToInsertInto = 2389 (HTML.Tag) attrs.getAttribute(StyleConstants.NameAttribute); 2390 if (tagToInsertInto != null) { 2391 this.inParagraph = tagToInsertInto.isParagraph(); 2392 } 2393 } 2394 } 2395 } 2396 2397 /** 2398 * Generates an initial batch of end {@code ElementSpecs} 2399 * in parseBuffer to position future inserts into the body. 2400 */ 2401 private void generateEndsSpecsForMidInsert() { 2402 int count = heightToElementWithName(HTML.Tag.BODY, 2403 Math.max(0, offset - 1)); 2404 boolean joinNext = false; 2405 2406 if (count == -1 && offset > 0) { 2407 count = heightToElementWithName(HTML.Tag.BODY, offset); 2408 if (count != -1) { 2409 // Previous isn't in body, but current is. Have to 2410 // do some end specs, followed by join next. 2411 count = depthTo(offset - 1) - 1; 2412 joinNext = true; 2413 } 2414 } 2415 if (count == -1) { 2416 throw new RuntimeException("Must insert new content into body element-"); 2417 } 2418 if (count != -1) { 2419 // Insert a newline, if necessary. 2420 try { 2421 if (!joinNext && offset > 0 && 2422 !getText(offset - 1, 1).equals("\n")) { 2423 SimpleAttributeSet newAttrs = new SimpleAttributeSet(); 2424 newAttrs.addAttribute(StyleConstants.NameAttribute, 2425 HTML.Tag.CONTENT); 2426 ElementSpec spec = new ElementSpec(newAttrs, 2427 ElementSpec.ContentType, NEWLINE, 0, 1); 2428 parseBuffer.addElement(spec); 2429 } 2430 // Should never throw, but will catch anyway. 2431 } catch (BadLocationException ble) {} 2432 while (count-- > 0) { 2433 parseBuffer.addElement(new ElementSpec 2434 (null, ElementSpec.EndTagType)); 2435 } 2436 if (joinNext) { 2437 ElementSpec spec = new ElementSpec(null, ElementSpec. 2438 StartTagType); 2439 2440 spec.setDirection(ElementSpec.JoinNextDirection); 2441 parseBuffer.addElement(spec); 2442 } 2443 } 2444 // We should probably throw an exception if (count == -1) 2445 // Or look for the body and reset the offset. 2446 } 2447 2448 /** 2449 * @return number of parents to reach the child at offset. 2450 */ 2451 private int depthTo(int offset) { 2452 Element e = getDefaultRootElement(); 2453 int count = 0; 2454 2455 while (!e.isLeaf()) { 2456 count++; 2457 e = e.getElement(e.getElementIndex(offset)); 2458 } 2459 return count; 2460 } 2461 2462 /** 2463 * @return number of parents of the leaf at {@code offset} 2464 * until a parent with name, {@code name} has been 2465 * found. -1 indicates no matching parent with 2466 * {@code name}. 2467 */ 2468 private int heightToElementWithName(Object name, int offset) { 2469 Element e = getCharacterElement(offset).getParentElement(); 2470 int count = 0; 2471 2472 while (e != null && e.getAttributes().getAttribute 2473 (StyleConstants.NameAttribute) != name) { 2474 count++; 2475 e = e.getParentElement(); 2476 } 2477 return (e == null) ? -1 : count; 2478 } 2479 2480 /** 2481 * This will make sure there aren't two BODYs (the second is 2482 * typically created when you do a remove all, and then an insert). 2483 */ 2484 private void adjustEndElement() { 2485 int length = getLength(); 2486 if (length == 0) { 2487 return; 2488 } 2489 obtainLock(); 2490 try { 2491 Element[] pPath = getPathTo(length - 1); 2492 int pLength = pPath.length; 2493 if (pLength > 1 && pPath[1].getAttributes().getAttribute 2494 (StyleConstants.NameAttribute) == HTML.Tag.BODY && 2495 pPath[1].getEndOffset() == length) { 2496 String lastText = getText(length - 1, 1); 2497 DefaultDocumentEvent event; 2498 Element[] added; 2499 Element[] removed; 2500 int index; 2501 // Remove the fake second body. 2502 added = new Element[0]; 2503 removed = new Element[1]; 2504 index = pPath[0].getElementIndex(length); 2505 removed[0] = pPath[0].getElement(index); 2506 ((BranchElement)pPath[0]).replace(index, 1, added); 2507 ElementEdit firstEdit = new ElementEdit(pPath[0], index, 2508 removed, added); 2509 2510 // Insert a new element to represent the end that the 2511 // second body was representing. 2512 SimpleAttributeSet sas = new SimpleAttributeSet(); 2513 sas.addAttribute(StyleConstants.NameAttribute, 2514 HTML.Tag.CONTENT); 2515 sas.addAttribute(IMPLIED_CR, Boolean.TRUE); 2516 added = new Element[1]; 2517 added[0] = createLeafElement(pPath[pLength - 1], 2518 sas, length, length + 1); 2519 index = pPath[pLength - 1].getElementCount(); 2520 ((BranchElement)pPath[pLength - 1]).replace(index, 0, 2521 added); 2522 event = new DefaultDocumentEvent(length, 1, 2523 DocumentEvent.EventType.CHANGE); 2524 event.addEdit(new ElementEdit(pPath[pLength - 1], 2525 index, new Element[0], added)); 2526 event.addEdit(firstEdit); 2527 event.end(); 2528 fireChangedUpdate(event); 2529 fireUndoableEditUpdate(new UndoableEditEvent(this, event)); 2530 2531 if (lastText.equals("\n")) { 2532 // We now have two \n's, one part of the Document. 2533 // We need to remove one 2534 event = new DefaultDocumentEvent(length - 1, 1, 2535 DocumentEvent.EventType.REMOVE); 2536 removeUpdate(event); 2537 UndoableEdit u = getContent().remove(length - 1, 1); 2538 if (u != null) { 2539 event.addEdit(u); 2540 } 2541 postRemoveUpdate(event); 2542 // Mark the edit as done. 2543 event.end(); 2544 fireRemoveUpdate(event); 2545 fireUndoableEditUpdate(new UndoableEditEvent( 2546 this, event)); 2547 } 2548 } 2549 } 2550 catch (BadLocationException ble) { 2551 } 2552 finally { 2553 releaseLock(); 2554 } 2555 } 2556 2557 private Element[] getPathTo(int offset) { 2558 Stack<Element> elements = new Stack<Element>(); 2559 Element e = getDefaultRootElement(); 2560 int index; 2561 while (!e.isLeaf()) { 2562 elements.push(e); 2563 e = e.getElement(e.getElementIndex(offset)); 2564 } 2565 Element[] retValue = new Element[elements.size()]; 2566 elements.copyInto(retValue); 2567 return retValue; 2568 } 2569 2570 // -- HTMLEditorKit.ParserCallback methods -------------------- 2571 2572 /** 2573 * The last method called on the reader. It allows 2574 * any pending changes to be flushed into the document. 2575 * Since this is currently loading synchronously, the entire 2576 * set of changes are pushed in at this point. 2577 */ 2578 public void flush() throws BadLocationException { 2579 if (emptyDocument && !insertAfterImplied) { 2580 if (HTMLDocument.this.getLength() > 0 || 2581 parseBuffer.size() > 0) { 2582 flushBuffer(true); 2583 adjustEndElement(); 2584 } 2585 // We won't insert when 2586 } 2587 else { 2588 flushBuffer(true); 2589 } 2590 } 2591 2592 /** 2593 * Called by the parser to indicate a block of text was 2594 * encountered. 2595 */ 2596 public void handleText(char[] data, int pos) { 2597 if (receivedEndHTML || (midInsert && !inBody)) { 2598 return; 2599 } 2600 2601 // see if complex glyph layout support is needed 2602 if(HTMLDocument.this.getProperty(I18NProperty).equals( Boolean.FALSE ) ) { 2603 // if a default direction of right-to-left has been specified, 2604 // we want complex layout even if the text is all left to right. 2605 Object d = getProperty(TextAttribute.RUN_DIRECTION); 2606 if ((d != null) && (d.equals(TextAttribute.RUN_DIRECTION_RTL))) { 2607 HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE); 2608 } else { 2609 if (SwingUtilities2.isComplexLayout(data, 0, data.length)) { 2610 HTMLDocument.this.putProperty( I18NProperty, Boolean.TRUE); 2611 } 2612 } 2613 } 2614 2615 if (inTextArea) { 2616 textAreaContent(data); 2617 } else if (inPre) { 2618 preContent(data); 2619 } else if (inTitle) { 2620 putProperty(Document.TitleProperty, new String(data)); 2621 } else if (option != null) { 2622 option.setLabel(new String(data)); 2623 } else if (inStyle) { 2624 if (styles != null) { 2625 styles.addElement(new String(data)); 2626 } 2627 } else if (inBlock > 0) { 2628 if (!foundInsertTag && insertAfterImplied) { 2629 // Assume content should be added. 2630 foundInsertTag(false); 2631 foundInsertTag = true; 2632 // If content is added directly to the body, it should 2633 // be wrapped by p-implied. 2634 inParagraph = impliedP = !insertInBody; 2635 } 2636 if (data.length >= 1) { 2637 addContent(data, 0, data.length); 2638 } 2639 } 2640 } 2641 2642 /** 2643 * Callback from the parser. Route to the appropriate 2644 * handler for the tag. 2645 */ 2646 public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) { 2647 if (receivedEndHTML) { 2648 return; 2649 } 2650 if (midInsert && !inBody) { 2651 if (t == HTML.Tag.BODY) { 2652 inBody = true; 2653 // Increment inBlock since we know we are in the body, 2654 // this is needed incase an implied-p is needed. If 2655 // inBlock isn't incremented, and an implied-p is 2656 // encountered, addContent won't be called! 2657 inBlock++; 2658 } 2659 return; 2660 } 2661 if (!inBody && t == HTML.Tag.BODY) { 2662 inBody = true; 2663 } 2664 if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) { 2665 // Map the style attributes. 2666 String decl = (String)a.getAttribute(HTML.Attribute.STYLE); 2667 a.removeAttribute(HTML.Attribute.STYLE); 2668 styleAttributes = getStyleSheet().getDeclaration(decl); 2669 a.addAttributes(styleAttributes); 2670 } 2671 else { 2672 styleAttributes = null; 2673 } 2674 TagAction action = tagMap.get(t); 2675 2676 if (action != null) { 2677 action.start(t, a); 2678 } 2679 } 2680 2681 public void handleComment(char[] data, int pos) { 2682 if (receivedEndHTML) { 2683 addExternalComment(new String(data)); 2684 return; 2685 } 2686 if (inStyle) { 2687 if (styles != null) { 2688 styles.addElement(new String(data)); 2689 } 2690 } 2691 else if (getPreservesUnknownTags()) { 2692 if (inBlock == 0 && (foundInsertTag || 2693 insertTag != HTML.Tag.COMMENT)) { 2694 // Comment outside of body, will not be able to show it, 2695 // but can add it as a property on the Document. 2696 addExternalComment(new String(data)); 2697 return; 2698 } 2699 SimpleAttributeSet sas = new SimpleAttributeSet(); 2700 sas.addAttribute(HTML.Attribute.COMMENT, new String(data)); 2701 addSpecialElement(HTML.Tag.COMMENT, sas); 2702 } 2703 2704 TagAction action = tagMap.get(HTML.Tag.COMMENT); 2705 if (action != null) { 2706 action.start(HTML.Tag.COMMENT, new SimpleAttributeSet()); 2707 action.end(HTML.Tag.COMMENT); 2708 } 2709 } 2710 2711 /** 2712 * Adds the comment {@code comment} to the set of comments 2713 * maintained outside of the scope of elements. 2714 */ 2715 private void addExternalComment(String comment) { 2716 Object comments = getProperty(AdditionalComments); 2717 if (comments != null && !(comments instanceof Vector)) { 2718 // No place to put comment. 2719 return; 2720 } 2721 if (comments == null) { 2722 comments = new Vector<>(); 2723 putProperty(AdditionalComments, comments); 2724 } 2725 @SuppressWarnings("unchecked") 2726 Vector<Object> v = (Vector<Object>)comments; 2727 v.addElement(comment); 2728 } 2729 2730 /** 2731 * Callback from the parser. Route to the appropriate 2732 * handler for the tag. 2733 */ 2734 public void handleEndTag(HTML.Tag t, int pos) { 2735 if (receivedEndHTML || (midInsert && !inBody)) { 2736 return; 2737 } 2738 if (t == HTML.Tag.HTML) { 2739 receivedEndHTML = true; 2740 } 2741 if (t == HTML.Tag.BODY) { 2742 inBody = false; 2743 if (midInsert) { 2744 inBlock--; 2745 } 2746 } 2747 TagAction action = tagMap.get(t); 2748 if (action != null) { 2749 action.end(t); 2750 } 2751 } 2752 2753 /** 2754 * Callback from the parser. Route to the appropriate 2755 * handler for the tag. 2756 */ 2757 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) { 2758 if (receivedEndHTML || (midInsert && !inBody)) { 2759 return; 2760 } 2761 2762 if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) { 2763 // Map the style attributes. 2764 String decl = (String)a.getAttribute(HTML.Attribute.STYLE); 2765 a.removeAttribute(HTML.Attribute.STYLE); 2766 styleAttributes = getStyleSheet().getDeclaration(decl); 2767 a.addAttributes(styleAttributes); 2768 } 2769 else { 2770 styleAttributes = null; 2771 } 2772 2773 TagAction action = tagMap.get(t); 2774 if (action != null) { 2775 action.start(t, a); 2776 action.end(t); 2777 } 2778 else if (getPreservesUnknownTags()) { 2779 // unknown tag, only add if should preserve it. 2780 addSpecialElement(t, a); 2781 } 2782 } 2783 2784 /** 2785 * This is invoked after the stream has been parsed, but before 2786 * {@code flush}. {@code eol} will be one of \n, \r 2787 * or \r\n, which ever is encountered the most in parsing the 2788 * stream. 2789 * 2790 * @since 1.3 2791 */ 2792 public void handleEndOfLineString(String eol) { 2793 if (emptyDocument && eol != null) { 2794 putProperty(DefaultEditorKit.EndOfLineStringProperty, 2795 eol); 2796 } 2797 } 2798 2799 // ---- tag handling support ------------------------------ 2800 2801 /** 2802 * Registers a handler for the given tag. By default 2803 * all of the well-known tags will have been registered. 2804 * This can be used to change the handling of a particular 2805 * tag or to add support for custom tags. 2806 * 2807 * @param t an HTML tag 2808 * @param a tag action handler 2809 */ 2810 protected void registerTag(HTML.Tag t, TagAction a) { 2811 tagMap.put(t, a); 2812 } 2813 2814 /** 2815 * An action to be performed in response 2816 * to parsing a tag. This allows customization 2817 * of how each tag is handled and avoids a large 2818 * switch statement. 2819 */ 2820 public class TagAction { 2821 2822 /** 2823 * Called when a start tag is seen for the 2824 * type of tag this action was registered 2825 * to. The tag argument indicates the actual 2826 * tag for those actions that are shared across 2827 * many tags. By default this does nothing and 2828 * completely ignores the tag. 2829 * 2830 * @param t the HTML tag 2831 * @param a the attributes 2832 */ 2833 public void start(HTML.Tag t, MutableAttributeSet a) { 2834 } 2835 2836 /** 2837 * Called when an end tag is seen for the 2838 * type of tag this action was registered 2839 * to. The tag argument indicates the actual 2840 * tag for those actions that are shared across 2841 * many tags. By default this does nothing and 2842 * completely ignores the tag. 2843 * 2844 * @param t the HTML tag 2845 */ 2846 public void end(HTML.Tag t) { 2847 } 2848 2849 } 2850 2851 /** 2852 * Action assigned by default to handle the Block task of the reader. 2853 */ 2854 public class BlockAction extends TagAction { 2855 2856 public void start(HTML.Tag t, MutableAttributeSet attr) { 2857 blockOpen(t, attr); 2858 } 2859 2860 public void end(HTML.Tag t) { 2861 blockClose(t); 2862 } 2863 } 2864 2865 2866 /** 2867 * Action used for the actual element form tag. This is named such 2868 * as there was already a public class named FormAction. 2869 */ 2870 private class FormTagAction extends BlockAction { 2871 public void start(HTML.Tag t, MutableAttributeSet attr) { 2872 super.start(t, attr); 2873 // initialize a ButtonGroupsMap when 2874 // FORM tag is encountered. This will 2875 // be used for any radio buttons that 2876 // might be defined in the FORM. 2877 // for new group new ButtonGroup will be created (fix for 4529702) 2878 // group name is a key in radioButtonGroupsMap 2879 radioButtonGroupsMap = new HashMap<String, ButtonGroup>(); 2880 } 2881 2882 public void end(HTML.Tag t) { 2883 super.end(t); 2884 // reset the button group to null since 2885 // the form has ended. 2886 radioButtonGroupsMap = null; 2887 } 2888 } 2889 2890 2891 /** 2892 * Action assigned by default to handle the Paragraph task of the reader. 2893 */ 2894 public class ParagraphAction extends BlockAction { 2895 2896 public void start(HTML.Tag t, MutableAttributeSet a) { 2897 super.start(t, a); 2898 inParagraph = true; 2899 } 2900 2901 public void end(HTML.Tag t) { 2902 super.end(t); 2903 inParagraph = false; 2904 } 2905 } 2906 2907 /** 2908 * Action assigned by default to handle the Special task of the reader. 2909 */ 2910 public class SpecialAction extends TagAction { 2911 2912 public void start(HTML.Tag t, MutableAttributeSet a) { 2913 addSpecialElement(t, a); 2914 } 2915 2916 } 2917 2918 /** 2919 * Action assigned by default to handle the Isindex task of the reader. 2920 */ 2921 public class IsindexAction extends TagAction { 2922 2923 public void start(HTML.Tag t, MutableAttributeSet a) { 2924 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); 2925 addSpecialElement(t, a); 2926 blockClose(HTML.Tag.IMPLIED); 2927 } 2928 2929 } 2930 2931 2932 /** 2933 * Action assigned by default to handle the Hidden task of the reader. 2934 */ 2935 public class HiddenAction extends TagAction { 2936 2937 public void start(HTML.Tag t, MutableAttributeSet a) { 2938 addSpecialElement(t, a); 2939 } 2940 2941 public void end(HTML.Tag t) { 2942 if (!isEmpty(t)) { 2943 MutableAttributeSet a = new SimpleAttributeSet(); 2944 a.addAttribute(HTML.Attribute.ENDTAG, "true"); 2945 addSpecialElement(t, a); 2946 } 2947 } 2948 2949 boolean isEmpty(HTML.Tag t) { 2950 if (t == HTML.Tag.APPLET || 2951 t == HTML.Tag.SCRIPT) { 2952 return false; 2953 } 2954 return true; 2955 } 2956 } 2957 2958 2959 /** 2960 * Subclass of HiddenAction to set the content type for style sheets, 2961 * and to set the name of the default style sheet. 2962 */ 2963 class MetaAction extends HiddenAction { 2964 2965 public void start(HTML.Tag t, MutableAttributeSet a) { 2966 Object equiv = a.getAttribute(HTML.Attribute.HTTPEQUIV); 2967 if (equiv != null) { 2968 equiv = ((String)equiv).toLowerCase(); 2969 if (equiv.equals("content-style-type")) { 2970 String value = (String)a.getAttribute 2971 (HTML.Attribute.CONTENT); 2972 setDefaultStyleSheetType(value); 2973 isStyleCSS = "text/css".equals 2974 (getDefaultStyleSheetType()); 2975 } 2976 else if (equiv.equals("default-style")) { 2977 defaultStyle = (String)a.getAttribute 2978 (HTML.Attribute.CONTENT); 2979 } 2980 } 2981 super.start(t, a); 2982 } 2983 2984 boolean isEmpty(HTML.Tag t) { 2985 return true; 2986 } 2987 } 2988 2989 2990 /** 2991 * End if overridden to create the necessary stylesheets that 2992 * are referenced via the link tag. It is done in this manner 2993 * as the meta tag can be used to specify an alternate style sheet, 2994 * and is not guaranteed to come before the link tags. 2995 */ 2996 class HeadAction extends BlockAction { 2997 2998 public void start(HTML.Tag t, MutableAttributeSet a) { 2999 inHead = true; 3000 // This check of the insertTag is put in to avoid considering 3001 // the implied-p that is generated for the head. This allows 3002 // inserts for HR to work correctly. 3003 if ((insertTag == null && !insertAfterImplied) || 3004 (insertTag == HTML.Tag.HEAD) || 3005 (insertAfterImplied && 3006 (foundInsertTag || !a.isDefined(IMPLIED)))) { 3007 super.start(t, a); 3008 } 3009 } 3010 3011 public void end(HTML.Tag t) { 3012 inHead = inStyle = false; 3013 // See if there is a StyleSheet to link to. 3014 if (styles != null) { 3015 boolean isDefaultCSS = isStyleCSS; 3016 for (int counter = 0, maxCounter = styles.size(); 3017 counter < maxCounter;) { 3018 Object value = styles.elementAt(counter); 3019 if (value == HTML.Tag.LINK) { 3020 handleLink((AttributeSet)styles. 3021 elementAt(++counter)); 3022 counter++; 3023 } 3024 else { 3025 // Rule. 3026 // First element gives type. 3027 String type = (String)styles.elementAt(++counter); 3028 boolean isCSS = (type == null) ? isDefaultCSS : 3029 type.equals("text/css"); 3030 while (++counter < maxCounter && 3031 (styles.elementAt(counter) 3032 instanceof String)) { 3033 if (isCSS) { 3034 addCSSRules((String)styles.elementAt 3035 (counter)); 3036 } 3037 } 3038 } 3039 } 3040 } 3041 if ((insertTag == null && !insertAfterImplied) || 3042 insertTag == HTML.Tag.HEAD || 3043 (insertAfterImplied && foundInsertTag)) { 3044 super.end(t); 3045 } 3046 } 3047 3048 boolean isEmpty(HTML.Tag t) { 3049 return false; 3050 } 3051 3052 private void handleLink(AttributeSet attr) { 3053 // Link. 3054 String type = (String)attr.getAttribute(HTML.Attribute.TYPE); 3055 if (type == null) { 3056 type = getDefaultStyleSheetType(); 3057 } 3058 // Only choose if type==text/css 3059 // Select link if rel==stylesheet. 3060 // Otherwise if rel==alternate stylesheet and 3061 // title matches default style. 3062 if (type.equals("text/css")) { 3063 String rel = (String)attr.getAttribute(HTML.Attribute.REL); 3064 String title = (String)attr.getAttribute 3065 (HTML.Attribute.TITLE); 3066 String media = (String)attr.getAttribute 3067 (HTML.Attribute.MEDIA); 3068 if (media == null) { 3069 media = "all"; 3070 } 3071 else { 3072 media = media.toLowerCase(); 3073 } 3074 if (rel != null) { 3075 rel = rel.toLowerCase(); 3076 if ((media.indexOf("all") != -1 || 3077 media.indexOf("screen") != -1) && 3078 (rel.equals("stylesheet") || 3079 (rel.equals("alternate stylesheet") && 3080 title.equals(defaultStyle)))) { 3081 linkCSSStyleSheet((String)attr.getAttribute 3082 (HTML.Attribute.HREF)); 3083 } 3084 } 3085 } 3086 } 3087 } 3088 3089 3090 /** 3091 * A subclass to add the AttributeSet to styles if the 3092 * attributes contains an attribute for 'rel' with value 3093 * 'stylesheet' or 'alternate stylesheet'. 3094 */ 3095 class LinkAction extends HiddenAction { 3096 3097 public void start(HTML.Tag t, MutableAttributeSet a) { 3098 String rel = (String)a.getAttribute(HTML.Attribute.REL); 3099 if (rel != null) { 3100 rel = rel.toLowerCase(); 3101 if (rel.equals("stylesheet") || 3102 rel.equals("alternate stylesheet")) { 3103 if (styles == null) { 3104 styles = new Vector<Object>(3); 3105 } 3106 styles.addElement(t); 3107 styles.addElement(a.copyAttributes()); 3108 } 3109 } 3110 super.start(t, a); 3111 } 3112 } 3113 3114 class MapAction extends TagAction { 3115 3116 public void start(HTML.Tag t, MutableAttributeSet a) { 3117 lastMap = new Map((String)a.getAttribute(HTML.Attribute.NAME)); 3118 addMap(lastMap); 3119 } 3120 3121 public void end(HTML.Tag t) { 3122 } 3123 } 3124 3125 3126 class AreaAction extends TagAction { 3127 3128 public void start(HTML.Tag t, MutableAttributeSet a) { 3129 if (lastMap != null) { 3130 lastMap.addArea(a.copyAttributes()); 3131 } 3132 } 3133 3134 public void end(HTML.Tag t) { 3135 } 3136 } 3137 3138 3139 class StyleAction extends TagAction { 3140 3141 public void start(HTML.Tag t, MutableAttributeSet a) { 3142 if (inHead) { 3143 if (styles == null) { 3144 styles = new Vector<Object>(3); 3145 } 3146 styles.addElement(t); 3147 styles.addElement(a.getAttribute(HTML.Attribute.TYPE)); 3148 inStyle = true; 3149 } 3150 } 3151 3152 public void end(HTML.Tag t) { 3153 inStyle = false; 3154 } 3155 3156 boolean isEmpty(HTML.Tag t) { 3157 return false; 3158 } 3159 } 3160 3161 /** 3162 * Action assigned by default to handle the Pre block task of the reader. 3163 */ 3164 public class PreAction extends BlockAction { 3165 3166 public void start(HTML.Tag t, MutableAttributeSet attr) { 3167 inPre = true; 3168 blockOpen(t, attr); 3169 attr.addAttribute(CSS.Attribute.WHITE_SPACE, "pre"); 3170 blockOpen(HTML.Tag.IMPLIED, attr); 3171 } 3172 3173 public void end(HTML.Tag t) { 3174 blockClose(HTML.Tag.IMPLIED); 3175 // set inPre to false after closing, so that if a newline 3176 // is added it won't generate a blockOpen. 3177 inPre = false; 3178 blockClose(t); 3179 } 3180 } 3181 3182 /** 3183 * Action assigned by default to handle the Character task of the reader. 3184 */ 3185 public class CharacterAction extends TagAction { 3186 3187 public void start(HTML.Tag t, MutableAttributeSet attr) { 3188 pushCharacterStyle(); 3189 if (!foundInsertTag) { 3190 // Note that the third argument should really be based off 3191 // inParagraph and impliedP. If we're wrong (that is 3192 // insertTagDepthDelta shouldn't be changed), we'll end up 3193 // removing an extra EndSpec, which won't matter anyway. 3194 boolean insert = canInsertTag(t, attr, false); 3195 if (foundInsertTag) { 3196 if (!inParagraph) { 3197 inParagraph = impliedP = true; 3198 } 3199 } 3200 if (!insert) { 3201 return; 3202 } 3203 } 3204 if (attr.isDefined(IMPLIED)) { 3205 attr.removeAttribute(IMPLIED); 3206 } 3207 charAttr.addAttribute(t, attr.copyAttributes()); 3208 if (styleAttributes != null) { 3209 charAttr.addAttributes(styleAttributes); 3210 } 3211 } 3212 3213 public void end(HTML.Tag t) { 3214 popCharacterStyle(); 3215 } 3216 } 3217 3218 /** 3219 * Provides conversion of HTML tag/attribute 3220 * mappings that have a corresponding StyleConstants 3221 * and CSS mapping. The conversion is to CSS attributes. 3222 */ 3223 class ConvertAction extends TagAction { 3224 3225 public void start(HTML.Tag t, MutableAttributeSet attr) { 3226 pushCharacterStyle(); 3227 if (!foundInsertTag) { 3228 // Note that the third argument should really be based off 3229 // inParagraph and impliedP. If we're wrong (that is 3230 // insertTagDepthDelta shouldn't be changed), we'll end up 3231 // removing an extra EndSpec, which won't matter anyway. 3232 boolean insert = canInsertTag(t, attr, false); 3233 if (foundInsertTag) { 3234 if (!inParagraph) { 3235 inParagraph = impliedP = true; 3236 } 3237 } 3238 if (!insert) { 3239 return; 3240 } 3241 } 3242 if (attr.isDefined(IMPLIED)) { 3243 attr.removeAttribute(IMPLIED); 3244 } 3245 if (styleAttributes != null) { 3246 charAttr.addAttributes(styleAttributes); 3247 } 3248 // We also need to add attr, otherwise we lose custom 3249 // attributes, including class/id for style lookups, and 3250 // further confuse style lookup (doesn't have tag). 3251 charAttr.addAttribute(t, attr.copyAttributes()); 3252 StyleSheet sheet = getStyleSheet(); 3253 if (t == HTML.Tag.B) { 3254 sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold"); 3255 } else if (t == HTML.Tag.I) { 3256 sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_STYLE, "italic"); 3257 } else if (t == HTML.Tag.U) { 3258 Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION); 3259 String value = "underline"; 3260 value = (v != null) ? value + "," + v.toString() : value; 3261 sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value); 3262 } else if (t == HTML.Tag.STRIKE) { 3263 Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION); 3264 String value = "line-through"; 3265 value = (v != null) ? value + "," + v.toString() : value; 3266 sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value); 3267 } else if (t == HTML.Tag.SUP) { 3268 Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN); 3269 String value = "sup"; 3270 value = (v != null) ? value + "," + v.toString() : value; 3271 sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value); 3272 } else if (t == HTML.Tag.SUB) { 3273 Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN); 3274 String value = "sub"; 3275 value = (v != null) ? value + "," + v.toString() : value; 3276 sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value); 3277 } else if (t == HTML.Tag.FONT) { 3278 String color = (String) attr.getAttribute(HTML.Attribute.COLOR); 3279 if (color != null) { 3280 sheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color); 3281 } 3282 String face = (String) attr.getAttribute(HTML.Attribute.FACE); 3283 if (face != null) { 3284 sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, face); 3285 } 3286 String size = (String) attr.getAttribute(HTML.Attribute.SIZE); 3287 if (size != null) { 3288 sheet.addCSSAttributeFromHTML(charAttr, CSS.Attribute.FONT_SIZE, size); 3289 } 3290 } 3291 } 3292 3293 public void end(HTML.Tag t) { 3294 popCharacterStyle(); 3295 } 3296 3297 } 3298 3299 class AnchorAction extends CharacterAction { 3300 3301 public void start(HTML.Tag t, MutableAttributeSet attr) { 3302 // set flag to catch empty anchors 3303 emptyAnchor = true; 3304 super.start(t, attr); 3305 } 3306 3307 public void end(HTML.Tag t) { 3308 if (emptyAnchor) { 3309 // if the anchor was empty it was probably a 3310 // named anchor point and we don't want to throw 3311 // it away. 3312 char[] one = new char[1]; 3313 one[0] = '\n'; 3314 addContent(one, 0, 1); 3315 } 3316 super.end(t); 3317 } 3318 } 3319 3320 class TitleAction extends HiddenAction { 3321 3322 public void start(HTML.Tag t, MutableAttributeSet attr) { 3323 inTitle = true; 3324 super.start(t, attr); 3325 } 3326 3327 public void end(HTML.Tag t) { 3328 inTitle = false; 3329 super.end(t); 3330 } 3331 3332 boolean isEmpty(HTML.Tag t) { 3333 return false; 3334 } 3335 } 3336 3337 3338 class BaseAction extends TagAction { 3339 3340 public void start(HTML.Tag t, MutableAttributeSet attr) { 3341 String href = (String) attr.getAttribute(HTML.Attribute.HREF); 3342 if (href != null) { 3343 try { 3344 URL newBase = new URL(base, href); 3345 setBase(newBase); 3346 hasBaseTag = true; 3347 } catch (MalformedURLException ex) { 3348 } 3349 } 3350 baseTarget = (String) attr.getAttribute(HTML.Attribute.TARGET); 3351 } 3352 } 3353 3354 class ObjectAction extends SpecialAction { 3355 3356 public void start(HTML.Tag t, MutableAttributeSet a) { 3357 if (t == HTML.Tag.PARAM) { 3358 addParameter(a); 3359 } else { 3360 super.start(t, a); 3361 } 3362 } 3363 3364 public void end(HTML.Tag t) { 3365 if (t != HTML.Tag.PARAM) { 3366 super.end(t); 3367 } 3368 } 3369 3370 void addParameter(AttributeSet a) { 3371 String name = (String) a.getAttribute(HTML.Attribute.NAME); 3372 String value = (String) a.getAttribute(HTML.Attribute.VALUE); 3373 if ((name != null) && (value != null)) { 3374 ElementSpec objSpec = parseBuffer.lastElement(); 3375 MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes(); 3376 objAttr.addAttribute(name, value); 3377 } 3378 } 3379 } 3380 3381 /** 3382 * Action to support forms by building all of the elements 3383 * used to represent form controls. This will process 3384 * the <INPUT>, <TEXTAREA>, <SELECT>, 3385 * and <OPTION> tags. The element created by 3386 * this action is expected to have the attribute 3387 * {@code StyleConstants.ModelAttribute} set to 3388 * the model that holds the state for the form control. 3389 * This enables multiple views, and allows document to 3390 * be iterated over picking up the data of the form. 3391 * The following are the model assignments for the 3392 * various type of form elements. 3393 * <table summary="model assignments for the various types of form elements"> 3394 * <tr> 3395 * <th>Element Type 3396 * <th>Model Type 3397 * <tr> 3398 * <td>input, type button 3399 * <td>{@link DefaultButtonModel} 3400 * <tr> 3401 * <td>input, type checkbox 3402 * <td>{@link javax.swing.JToggleButton.ToggleButtonModel} 3403 * <tr> 3404 * <td>input, type image 3405 * <td>{@link DefaultButtonModel} 3406 * <tr> 3407 * <td>input, type password 3408 * <td>{@link PlainDocument} 3409 * <tr> 3410 * <td>input, type radio 3411 * <td>{@link javax.swing.JToggleButton.ToggleButtonModel} 3412 * <tr> 3413 * <td>input, type reset 3414 * <td>{@link DefaultButtonModel} 3415 * <tr> 3416 * <td>input, type submit 3417 * <td>{@link DefaultButtonModel} 3418 * <tr> 3419 * <td>input, type text or type is null. 3420 * <td>{@link PlainDocument} 3421 * <tr> 3422 * <td>select 3423 * <td>{@link DefaultComboBoxModel} or an {@link DefaultListModel}, with an item type of Option 3424 * <tr> 3425 * <td>textarea 3426 * <td>{@link PlainDocument} 3427 * </table> 3428 * 3429 */ 3430 public class FormAction extends SpecialAction { 3431 3432 public void start(HTML.Tag t, MutableAttributeSet attr) { 3433 if (t == HTML.Tag.INPUT) { 3434 String type = (String) 3435 attr.getAttribute(HTML.Attribute.TYPE); 3436 /* 3437 * if type is not defined the default is 3438 * assumed to be text. 3439 */ 3440 if (type == null) { 3441 type = "text"; 3442 attr.addAttribute(HTML.Attribute.TYPE, "text"); 3443 } 3444 setModel(type, attr); 3445 } else if (t == HTML.Tag.TEXTAREA) { 3446 inTextArea = true; 3447 textAreaDocument = new TextAreaDocument(); 3448 attr.addAttribute(StyleConstants.ModelAttribute, 3449 textAreaDocument); 3450 } else if (t == HTML.Tag.SELECT) { 3451 int size = HTML.getIntegerAttributeValue(attr, 3452 HTML.Attribute.SIZE, 3453 1); 3454 boolean multiple = attr.getAttribute(HTML.Attribute.MULTIPLE) != null; 3455 if ((size > 1) || multiple) { 3456 OptionListModel<Option> m = new OptionListModel<Option>(); 3457 if (multiple) { 3458 m.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 3459 } 3460 selectModel = m; 3461 } else { 3462 selectModel = new OptionComboBoxModel<Option>(); 3463 } 3464 attr.addAttribute(StyleConstants.ModelAttribute, 3465 selectModel); 3466 3467 } 3468 3469 // build the element, unless this is an option. 3470 if (t == HTML.Tag.OPTION) { 3471 option = new Option(attr); 3472 3473 if (selectModel instanceof OptionListModel) { 3474 @SuppressWarnings("unchecked") 3475 OptionListModel<Option> m = (OptionListModel<Option>) selectModel; 3476 m.addElement(option); 3477 if (option.isSelected()) { 3478 m.addSelectionInterval(optionCount, optionCount); 3479 m.setInitialSelection(optionCount); 3480 } 3481 } else if (selectModel instanceof OptionComboBoxModel) { 3482 @SuppressWarnings("unchecked") 3483 OptionComboBoxModel<Option> m = (OptionComboBoxModel<Option>) selectModel; 3484 m.addElement(option); 3485 if (option.isSelected()) { 3486 m.setSelectedItem(option); 3487 m.setInitialSelection(option); 3488 } 3489 } 3490 optionCount++; 3491 } else { 3492 super.start(t, attr); 3493 } 3494 } 3495 3496 public void end(HTML.Tag t) { 3497 if (t == HTML.Tag.OPTION) { 3498 option = null; 3499 } else { 3500 if (t == HTML.Tag.SELECT) { 3501 selectModel = null; 3502 optionCount = 0; 3503 } else if (t == HTML.Tag.TEXTAREA) { 3504 inTextArea = false; 3505 3506 /* Now that the textarea has ended, 3507 * store the entire initial text 3508 * of the text area. This will 3509 * enable us to restore the initial 3510 * state if a reset is requested. 3511 */ 3512 textAreaDocument.storeInitialText(); 3513 } 3514 super.end(t); 3515 } 3516 } 3517 3518 void setModel(String type, MutableAttributeSet attr) { 3519 if (type.equals("submit") || 3520 type.equals("reset") || 3521 type.equals("image")) { 3522 3523 // button model 3524 attr.addAttribute(StyleConstants.ModelAttribute, 3525 new DefaultButtonModel()); 3526 } else if (type.equals("text") || 3527 type.equals("password")) { 3528 // plain text model 3529 int maxLength = HTML.getIntegerAttributeValue( 3530 attr, HTML.Attribute.MAXLENGTH, -1); 3531 Document doc; 3532 3533 if (maxLength > 0) { 3534 doc = new FixedLengthDocument(maxLength); 3535 } 3536 else { 3537 doc = new PlainDocument(); 3538 } 3539 String value = (String) 3540 attr.getAttribute(HTML.Attribute.VALUE); 3541 try { 3542 doc.insertString(0, value, null); 3543 } catch (BadLocationException e) { 3544 } 3545 attr.addAttribute(StyleConstants.ModelAttribute, doc); 3546 } else if (type.equals("file")) { 3547 // plain text model 3548 attr.addAttribute(StyleConstants.ModelAttribute, 3549 new PlainDocument()); 3550 } else if (type.equals("checkbox") || 3551 type.equals("radio")) { 3552 JToggleButton.ToggleButtonModel model = new JToggleButton.ToggleButtonModel(); 3553 if (type.equals("radio")) { 3554 String name = (String) attr.getAttribute(HTML.Attribute.NAME); 3555 if ( radioButtonGroupsMap == null ) { //fix for 4772743 3556 radioButtonGroupsMap = new HashMap<String, ButtonGroup>(); 3557 } 3558 ButtonGroup radioButtonGroup = radioButtonGroupsMap.get(name); 3559 if (radioButtonGroup == null) { 3560 radioButtonGroup = new ButtonGroup(); 3561 radioButtonGroupsMap.put(name,radioButtonGroup); 3562 } 3563 model.setGroup(radioButtonGroup); 3564 } 3565 boolean checked = (attr.getAttribute(HTML.Attribute.CHECKED) != null); 3566 model.setSelected(checked); 3567 attr.addAttribute(StyleConstants.ModelAttribute, model); 3568 } 3569 } 3570 3571 /** 3572 * If a <SELECT> tag is being processed, this 3573 * model will be a reference to the model being filled 3574 * with the <OPTION> elements (which produce 3575 * objects of type {@code Option}. 3576 */ 3577 Object selectModel; 3578 int optionCount; 3579 } 3580 3581 3582 // --- utility methods used by the reader ------------------ 3583 3584 /** 3585 * Pushes the current character style on a stack in preparation 3586 * for forming a new nested character style. 3587 */ 3588 protected void pushCharacterStyle() { 3589 charAttrStack.push(charAttr.copyAttributes()); 3590 } 3591 3592 /** 3593 * Pops a previously pushed character style off the stack 3594 * to return to a previous style. 3595 */ 3596 protected void popCharacterStyle() { 3597 if (!charAttrStack.empty()) { 3598 charAttr = (MutableAttributeSet) charAttrStack.peek(); 3599 charAttrStack.pop(); 3600 } 3601 } 3602 3603 /** 3604 * Adds the given content to the textarea document. 3605 * This method gets called when we are in a textarea 3606 * context. Therefore all text that is seen belongs 3607 * to the text area and is hence added to the 3608 * TextAreaDocument associated with the text area. 3609 * 3610 * @param data the given content 3611 */ 3612 protected void textAreaContent(char[] data) { 3613 try { 3614 textAreaDocument.insertString(textAreaDocument.getLength(), new String(data), null); 3615 } catch (BadLocationException e) { 3616 // Should do something reasonable 3617 } 3618 } 3619 3620 /** 3621 * Adds the given content that was encountered in a 3622 * PRE element. This synthesizes lines to hold the 3623 * runs of text, and makes calls to addContent to 3624 * actually add the text. 3625 * 3626 * @param data the given content 3627 */ 3628 protected void preContent(char[] data) { 3629 int last = 0; 3630 for (int i = 0; i < data.length; i++) { 3631 if (data[i] == '\n') { 3632 addContent(data, last, i - last + 1); 3633 blockClose(HTML.Tag.IMPLIED); 3634 MutableAttributeSet a = new SimpleAttributeSet(); 3635 a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre"); 3636 blockOpen(HTML.Tag.IMPLIED, a); 3637 last = i + 1; 3638 } 3639 } 3640 if (last < data.length) { 3641 addContent(data, last, data.length - last); 3642 } 3643 } 3644 3645 /** 3646 * Adds an instruction to the parse buffer to create a 3647 * block element with the given attributes. 3648 * 3649 * @param t an HTML tag 3650 * @param attr the attribute set 3651 */ 3652 protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) { 3653 if (impliedP) { 3654 blockClose(HTML.Tag.IMPLIED); 3655 } 3656 3657 inBlock++; 3658 3659 if (!canInsertTag(t, attr, true)) { 3660 return; 3661 } 3662 if (attr.isDefined(IMPLIED)) { 3663 attr.removeAttribute(IMPLIED); 3664 } 3665 lastWasNewline = false; 3666 attr.addAttribute(StyleConstants.NameAttribute, t); 3667 ElementSpec es = new ElementSpec( 3668 attr.copyAttributes(), ElementSpec.StartTagType); 3669 parseBuffer.addElement(es); 3670 } 3671 3672 /** 3673 * Adds an instruction to the parse buffer to close out 3674 * a block element of the given type. 3675 * 3676 * @param t the HTML tag 3677 */ 3678 protected void blockClose(HTML.Tag t) { 3679 inBlock--; 3680 3681 if (!foundInsertTag) { 3682 return; 3683 } 3684 3685 // Add a new line, if the last character wasn't one. This is 3686 // needed for proper positioning of the cursor. addContent 3687 // with true will force an implied paragraph to be generated if 3688 // there isn't one. This may result in a rather bogus structure 3689 // (perhaps a table with a child pargraph), but the paragraph 3690 // is needed for proper positioning and display. 3691 if(!lastWasNewline) { 3692 pushCharacterStyle(); 3693 charAttr.addAttribute(IMPLIED_CR, Boolean.TRUE); 3694 addContent(NEWLINE, 0, 1, true); 3695 popCharacterStyle(); 3696 lastWasNewline = true; 3697 } 3698 3699 if (impliedP) { 3700 impliedP = false; 3701 inParagraph = false; 3702 if (t != HTML.Tag.IMPLIED) { 3703 blockClose(HTML.Tag.IMPLIED); 3704 } 3705 } 3706 // an open/close with no content will be removed, so we 3707 // add a space of content to keep the element being formed. 3708 ElementSpec prev = (parseBuffer.size() > 0) ? 3709 parseBuffer.lastElement() : null; 3710 if (prev != null && prev.getType() == ElementSpec.StartTagType) { 3711 char[] one = new char[1]; 3712 one[0] = ' '; 3713 addContent(one, 0, 1); 3714 } 3715 ElementSpec es = new ElementSpec( 3716 null, ElementSpec.EndTagType); 3717 parseBuffer.addElement(es); 3718 } 3719 3720 /** 3721 * Adds some text with the current character attributes. 3722 * 3723 * @param data the content to add 3724 * @param offs the initial offset 3725 * @param length the length 3726 */ 3727 protected void addContent(char[] data, int offs, int length) { 3728 addContent(data, offs, length, true); 3729 } 3730 3731 /** 3732 * Adds some text with the current character attributes. 3733 * 3734 * @param data the content to add 3735 * @param offs the initial offset 3736 * @param length the length 3737 * @param generateImpliedPIfNecessary whether to generate implied 3738 * paragraphs 3739 */ 3740 protected void addContent(char[] data, int offs, int length, 3741 boolean generateImpliedPIfNecessary) { 3742 if (!foundInsertTag) { 3743 return; 3744 } 3745 3746 if (generateImpliedPIfNecessary && (! inParagraph) && (! inPre)) { 3747 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); 3748 inParagraph = true; 3749 impliedP = true; 3750 } 3751 emptyAnchor = false; 3752 charAttr.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT); 3753 AttributeSet a = charAttr.copyAttributes(); 3754 ElementSpec es = new ElementSpec( 3755 a, ElementSpec.ContentType, data, offs, length); 3756 parseBuffer.addElement(es); 3757 3758 if (parseBuffer.size() > threshold) { 3759 if ( threshold <= MaxThreshold ) { 3760 threshold *= StepThreshold; 3761 } 3762 try { 3763 flushBuffer(false); 3764 } catch (BadLocationException ble) { 3765 } 3766 } 3767 if(length > 0) { 3768 lastWasNewline = (data[offs + length - 1] == '\n'); 3769 } 3770 } 3771 3772 /** 3773 * Adds content that is basically specified entirely 3774 * in the attribute set. 3775 * 3776 * @param t an HTML tag 3777 * @param a the attribute set 3778 */ 3779 protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a) { 3780 if ((t != HTML.Tag.FRAME) && (! inParagraph) && (! inPre)) { 3781 nextTagAfterPImplied = t; 3782 blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet()); 3783 nextTagAfterPImplied = null; 3784 inParagraph = true; 3785 impliedP = true; 3786 } 3787 if (!canInsertTag(t, a, t.isBlock())) { 3788 return; 3789 } 3790 if (a.isDefined(IMPLIED)) { 3791 a.removeAttribute(IMPLIED); 3792 } 3793 emptyAnchor = false; 3794 a.addAttributes(charAttr); 3795 a.addAttribute(StyleConstants.NameAttribute, t); 3796 char[] one = new char[1]; 3797 one[0] = ' '; 3798 ElementSpec es = new ElementSpec( 3799 a.copyAttributes(), ElementSpec.ContentType, one, 0, 1); 3800 parseBuffer.addElement(es); 3801 // Set this to avoid generating a newline for frames, frames 3802 // shouldn't have any content, and shouldn't need a newline. 3803 if (t == HTML.Tag.FRAME) { 3804 lastWasNewline = true; 3805 } 3806 } 3807 3808 /** 3809 * Flushes the current parse buffer into the document. 3810 * @param endOfStream true if there is no more content to parser 3811 */ 3812 void flushBuffer(boolean endOfStream) throws BadLocationException { 3813 int oldLength = HTMLDocument.this.getLength(); 3814 int size = parseBuffer.size(); 3815 if (endOfStream && (insertTag != null || insertAfterImplied) && 3816 size > 0) { 3817 adjustEndSpecsForPartialInsert(); 3818 size = parseBuffer.size(); 3819 } 3820 ElementSpec[] spec = new ElementSpec[size]; 3821 parseBuffer.copyInto(spec); 3822 3823 if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) { 3824 create(spec); 3825 } else { 3826 insert(offset, spec); 3827 } 3828 parseBuffer.removeAllElements(); 3829 offset += HTMLDocument.this.getLength() - oldLength; 3830 flushCount++; 3831 } 3832 3833 /** 3834 * This will be invoked for the last flush, if {@code insertTag} 3835 * is non null. 3836 */ 3837 private void adjustEndSpecsForPartialInsert() { 3838 int size = parseBuffer.size(); 3839 if (insertTagDepthDelta < 0) { 3840 // When inserting via an insertTag, the depths (of the tree 3841 // being read in, and existing hierarchy) may not match up. 3842 // This attemps to clean it up. 3843 int removeCounter = insertTagDepthDelta; 3844 while (removeCounter < 0 && size >= 0 && 3845 parseBuffer.elementAt(size - 1). 3846 getType() == ElementSpec.EndTagType) { 3847 parseBuffer.removeElementAt(--size); 3848 removeCounter++; 3849 } 3850 } 3851 if (flushCount == 0 && (!insertAfterImplied || 3852 !wantsTrailingNewline)) { 3853 // If this starts with content (or popDepth > 0 && 3854 // pushDepth > 0) and ends with EndTagTypes, make sure 3855 // the last content isn't a \n, otherwise will end up with 3856 // an extra \n in the middle of content. 3857 int index = 0; 3858 if (pushDepth > 0) { 3859 if (parseBuffer.elementAt(0).getType() == 3860 ElementSpec.ContentType) { 3861 index++; 3862 } 3863 } 3864 index += (popDepth + pushDepth); 3865 int cCount = 0; 3866 int cStart = index; 3867 while (index < size && parseBuffer.elementAt 3868 (index).getType() == ElementSpec.ContentType) { 3869 index++; 3870 cCount++; 3871 } 3872 if (cCount > 1) { 3873 while (index < size && parseBuffer.elementAt 3874 (index).getType() == ElementSpec.EndTagType) { 3875 index++; 3876 } 3877 if (index == size) { 3878 char[] lastText = parseBuffer.elementAt 3879 (cStart + cCount - 1).getArray(); 3880 if (lastText.length == 1 && lastText[0] == NEWLINE[0]){ 3881 index = cStart + cCount - 1; 3882 while (size > index) { 3883 parseBuffer.removeElementAt(--size); 3884 } 3885 } 3886 } 3887 } 3888 } 3889 if (wantsTrailingNewline) { 3890 // Make sure there is in fact a newline 3891 for (int counter = parseBuffer.size() - 1; counter >= 0; 3892 counter--) { 3893 ElementSpec spec = parseBuffer.elementAt(counter); 3894 if (spec.getType() == ElementSpec.ContentType) { 3895 if (spec.getArray()[spec.getLength() - 1] != '\n') { 3896 SimpleAttributeSet attrs =new SimpleAttributeSet(); 3897 3898 attrs.addAttribute(StyleConstants.NameAttribute, 3899 HTML.Tag.CONTENT); 3900 parseBuffer.insertElementAt(new ElementSpec( 3901 attrs, 3902 ElementSpec.ContentType, NEWLINE, 0, 1), 3903 counter + 1); 3904 } 3905 break; 3906 } 3907 } 3908 } 3909 } 3910 3911 /** 3912 * Adds the CSS rules in {@code rules}. 3913 */ 3914 void addCSSRules(String rules) { 3915 StyleSheet ss = getStyleSheet(); 3916 ss.addRule(rules); 3917 } 3918 3919 /** 3920 * Adds the CSS stylesheet at {@code href} to the known list 3921 * of stylesheets. 3922 */ 3923 void linkCSSStyleSheet(String href) { 3924 URL url; 3925 try { 3926 url = new URL(base, href); 3927 } catch (MalformedURLException mfe) { 3928 try { 3929 url = new URL(href); 3930 } catch (MalformedURLException mfe2) { 3931 url = null; 3932 } 3933 } 3934 if (url != null) { 3935 getStyleSheet().importStyleSheet(url); 3936 } 3937 } 3938 3939 /** 3940 * Returns true if can insert starting at {@code t}. This 3941 * will return false if the insert tag is set, and hasn't been found 3942 * yet. 3943 */ 3944 private boolean canInsertTag(HTML.Tag t, AttributeSet attr, 3945 boolean isBlockTag) { 3946 if (!foundInsertTag) { 3947 boolean needPImplied = ((t == HTML.Tag.IMPLIED) 3948 && (!inParagraph) 3949 && (!inPre)); 3950 if (needPImplied && (nextTagAfterPImplied != null)) { 3951 3952 /* 3953 * If insertTag == null then just proceed to 3954 * foundInsertTag() call below and return true. 3955 */ 3956 if (insertTag != null) { 3957 boolean nextTagIsInsertTag = 3958 isInsertTag(nextTagAfterPImplied); 3959 if ( (! nextTagIsInsertTag) || (! insertInsertTag) ) { 3960 return false; 3961 } 3962 } 3963 /* 3964 * Proceed to foundInsertTag() call... 3965 */ 3966 } else if ((insertTag != null && !isInsertTag(t)) 3967 || (insertAfterImplied 3968 && (attr == null 3969 || attr.isDefined(IMPLIED) 3970 || t == HTML.Tag.IMPLIED 3971 ) 3972 ) 3973 ) { 3974 return false; 3975 } 3976 3977 // Allow the insert if t matches the insert tag, or 3978 // insertAfterImplied is true and the element is implied. 3979 foundInsertTag(isBlockTag); 3980 if (!insertInsertTag) { 3981 return false; 3982 } 3983 } 3984 return true; 3985 } 3986 3987 private boolean isInsertTag(HTML.Tag tag) { 3988 return (insertTag == tag); 3989 } 3990 3991 private void foundInsertTag(boolean isBlockTag) { 3992 foundInsertTag = true; 3993 if (!insertAfterImplied && (popDepth > 0 || pushDepth > 0)) { 3994 try { 3995 if (offset == 0 || !getText(offset - 1, 1).equals("\n")) { 3996 // Need to insert a newline. 3997 AttributeSet newAttrs = null; 3998 boolean joinP = true; 3999 4000 if (offset != 0) { 4001 // Determine if we can use JoinPrevious, we can't 4002 // if the Element has some attributes that are 4003 // not meant to be duplicated. 4004 Element charElement = getCharacterElement 4005 (offset - 1); 4006 AttributeSet attrs = charElement.getAttributes(); 4007 4008 if (attrs.isDefined(StyleConstants. 4009 ComposedTextAttribute)) { 4010 joinP = false; 4011 } 4012 else { 4013 Object name = attrs.getAttribute 4014 (StyleConstants.NameAttribute); 4015 if (name instanceof HTML.Tag) { 4016 HTML.Tag tag = (HTML.Tag)name; 4017 if (tag == HTML.Tag.IMG || 4018 tag == HTML.Tag.HR || 4019 tag == HTML.Tag.COMMENT || 4020 (tag instanceof HTML.UnknownTag)) { 4021 joinP = false; 4022 } 4023 } 4024 } 4025 } 4026 if (!joinP) { 4027 // If not joining with the previous element, be 4028 // sure and set the name (otherwise it will be 4029 // inherited). 4030 newAttrs = new SimpleAttributeSet(); 4031 ((SimpleAttributeSet)newAttrs).addAttribute 4032 (StyleConstants.NameAttribute, 4033 HTML.Tag.CONTENT); 4034 } 4035 ElementSpec es = new ElementSpec(newAttrs, 4036 ElementSpec.ContentType, NEWLINE, 0, 4037 NEWLINE.length); 4038 if (joinP) { 4039 es.setDirection(ElementSpec. 4040 JoinPreviousDirection); 4041 } 4042 parseBuffer.addElement(es); 4043 } 4044 } catch (BadLocationException ble) {} 4045 } 4046 // pops 4047 for (int counter = 0; counter < popDepth; counter++) { 4048 parseBuffer.addElement(new ElementSpec(null, ElementSpec. 4049 EndTagType)); 4050 } 4051 // pushes 4052 for (int counter = 0; counter < pushDepth; counter++) { 4053 ElementSpec es = new ElementSpec(null, ElementSpec. 4054 StartTagType); 4055 es.setDirection(ElementSpec.JoinNextDirection); 4056 parseBuffer.addElement(es); 4057 } 4058 insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) - 4059 popDepth + pushDepth - inBlock; 4060 if (isBlockTag) { 4061 // A start spec will be added (for this tag), so we account 4062 // for it here. 4063 insertTagDepthDelta++; 4064 } 4065 else { 4066 // An implied paragraph close (end spec) is going to be added, 4067 // so we account for it here. 4068 insertTagDepthDelta--; 4069 inParagraph = true; 4070 lastWasNewline = false; 4071 } 4072 } 4073 4074 /** 4075 * This is set to true when and end is invoked for {@literal <html>}. 4076 */ 4077 private boolean receivedEndHTML; 4078 /** Number of times {@code flushBuffer} has been invoked. */ 4079 private int flushCount; 4080 /** If true, behavior is similar to insertTag, but instead of 4081 * waiting for insertTag will wait for first Element without 4082 * an 'implied' attribute and begin inserting then. */ 4083 private boolean insertAfterImplied; 4084 /** This is only used if insertAfterImplied is true. If false, only 4085 * inserting content, and there is a trailing newline it is removed. */ 4086 private boolean wantsTrailingNewline; 4087 int threshold; 4088 int offset; 4089 boolean inParagraph = false; 4090 boolean impliedP = false; 4091 boolean inPre = false; 4092 boolean inTextArea = false; 4093 TextAreaDocument textAreaDocument = null; 4094 boolean inTitle = false; 4095 boolean lastWasNewline = true; 4096 boolean emptyAnchor; 4097 /** True if (!emptyDocument && insertTag == null), this is used so 4098 * much it is cached. */ 4099 boolean midInsert; 4100 /** True when the body has been encountered. */ 4101 boolean inBody; 4102 /** If non null, gives parent Tag that insert is to happen at. */ 4103 HTML.Tag insertTag; 4104 /** If true, the insertTag is inserted, otherwise elements after 4105 * the insertTag is found are inserted. */ 4106 boolean insertInsertTag; 4107 /** Set to true when insertTag has been found. */ 4108 boolean foundInsertTag; 4109 /** When foundInsertTag is set to true, this will be updated to 4110 * reflect the delta between the two structures. That is, it 4111 * will be the depth the inserts are happening at minus the 4112 * depth of the tags being passed in. A value of 0 (the common 4113 * case) indicates the structures match, a value greater than 0 indicates 4114 * the insert is happening at a deeper depth than the stream is 4115 * parsing, and a value less than 0 indicates the insert is happening earlier 4116 * in the tree that the parser thinks and that we will need to remove 4117 * EndTagType specs in the flushBuffer method. 4118 */ 4119 int insertTagDepthDelta; 4120 /** How many parents to ascend before insert new elements. */ 4121 int popDepth; 4122 /** How many parents to descend (relative to popDepth) before 4123 * inserting. */ 4124 int pushDepth; 4125 /** Last Map that was encountered. */ 4126 Map lastMap; 4127 /** Set to true when a style element is encountered. */ 4128 boolean inStyle = false; 4129 /** Name of style to use. Obtained from Meta tag. */ 4130 String defaultStyle; 4131 /** Vector describing styles that should be include. Will consist 4132 * of a bunch of HTML.Tags, which will either be: 4133 * <p>LINK: in which case it is followed by an AttributeSet 4134 * <p>STYLE: in which case the following element is a String 4135 * indicating the type (may be null), and the elements following 4136 * it until the next HTML.Tag are the rules as Strings. 4137 */ 4138 Vector<Object> styles; 4139 /** True if inside the head tag. */ 4140 boolean inHead = false; 4141 /** Set to true if the style language is text/css. Since this is 4142 * used alot, it is cached. */ 4143 boolean isStyleCSS; 4144 /** True if inserting into an empty document. */ 4145 boolean emptyDocument; 4146 /** Attributes from a style Attribute. */ 4147 AttributeSet styleAttributes; 4148 4149 /** 4150 * Current option, if in an option element (needed to 4151 * load the label. 4152 */ 4153 Option option; 4154 4155 /** 4156 * Buffer to keep building elements. 4157 */ 4158 protected Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>(); 4159 /** 4160 * Current character attribute set. 4161 */ 4162 protected MutableAttributeSet charAttr = new TaggedAttributeSet(); 4163 Stack<AttributeSet> charAttrStack = new Stack<AttributeSet>(); 4164 Hashtable<HTML.Tag, TagAction> tagMap; 4165 int inBlock = 0; 4166 4167 /** 4168 * This attribute is sometimes used to refer to next tag 4169 * to be handled after p-implied when the latter is 4170 * the current tag which is being handled. 4171 */ 4172 private HTML.Tag nextTagAfterPImplied = null; 4173 } 4174 4175 4176 /** 4177 * Used by StyleSheet to determine when to avoid removing HTML.Tags 4178 * matching StyleConstants. 4179 */ 4180 static class TaggedAttributeSet extends SimpleAttributeSet { 4181 TaggedAttributeSet() { 4182 super(); 4183 } 4184 } 4185 4186 4187 /** 4188 * An element that represents a chunk of text that has 4189 * a set of HTML character level attributes assigned to 4190 * it. 4191 */ 4192 public class RunElement extends LeafElement { 4193 4194 /** 4195 * Constructs an element that represents content within the 4196 * document (has no children). 4197 * 4198 * @param parent the parent element 4199 * @param a the element attributes 4200 * @param offs0 the start offset (must be at least 0) 4201 * @param offs1 the end offset (must be at least offs0) 4202 * @since 1.4 4203 */ 4204 public RunElement(Element parent, AttributeSet a, int offs0, int offs1) { 4205 super(parent, a, offs0, offs1); 4206 } 4207 4208 /** 4209 * Gets the name of the element. 4210 * 4211 * @return the name, null if none 4212 */ 4213 public String getName() { 4214 Object o = getAttribute(StyleConstants.NameAttribute); 4215 if (o != null) { 4216 return o.toString(); 4217 } 4218 return super.getName(); 4219 } 4220 4221 /** 4222 * Gets the resolving parent. HTML attributes are not inherited 4223 * at the model level so we override this to return null. 4224 * 4225 * @return null, there are none 4226 * @see AttributeSet#getResolveParent 4227 */ 4228 public AttributeSet getResolveParent() { 4229 return null; 4230 } 4231 } 4232 4233 /** 4234 * An element that represents a structural <em>block</em> of 4235 * HTML. 4236 */ 4237 public class BlockElement extends BranchElement { 4238 4239 /** 4240 * Constructs a composite element that initially contains 4241 * no children. 4242 * 4243 * @param parent the parent element 4244 * @param a the attributes for the element 4245 * @since 1.4 4246 */ 4247 public BlockElement(Element parent, AttributeSet a) { 4248 super(parent, a); 4249 } 4250 4251 /** 4252 * Gets the name of the element. 4253 * 4254 * @return the name, null if none 4255 */ 4256 public String getName() { 4257 Object o = getAttribute(StyleConstants.NameAttribute); 4258 if (o != null) { 4259 return o.toString(); 4260 } 4261 return super.getName(); 4262 } 4263 4264 /** 4265 * Gets the resolving parent. HTML attributes are not inherited 4266 * at the model level so we override this to return null. 4267 * 4268 * @return null, there are none 4269 * @see AttributeSet#getResolveParent 4270 */ 4271 public AttributeSet getResolveParent() { 4272 return null; 4273 } 4274 4275 } 4276 4277 4278 /** 4279 * Document that allows you to set the maximum length of the text. 4280 */ 4281 private static class FixedLengthDocument extends PlainDocument { 4282 private int maxLength; 4283 4284 public FixedLengthDocument(int maxLength) { 4285 this.maxLength = maxLength; 4286 } 4287 4288 public void insertString(int offset, String str, AttributeSet a) 4289 throws BadLocationException { 4290 if (str != null && str.length() + getLength() <= maxLength) { 4291 super.insertString(offset, str, a); 4292 } 4293 } 4294 } 4295 } --- EOF ---