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