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