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