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