1. /*
  2. * @(#)HTMLDocument.java 1.133 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.text.html;
  11. import java.awt.Color;
  12. import java.awt.Component;
  13. import java.util.*;
  14. import java.net.URL;
  15. import java.net.URLEncoder;
  16. import java.net.MalformedURLException;
  17. import java.io.*;
  18. import javax.swing.*;
  19. import javax.swing.event.*;
  20. import javax.swing.text.*;
  21. import javax.swing.undo.*;
  22. /**
  23. * A document that models HTML. The purpose of this model
  24. * is to support both browsing and editing. As a result,
  25. * the structure described by an HTML document is not
  26. * exactly replicated by default. The element structure that
  27. * is modeled by default, is built by the class
  28. * <code>HTMLDocument.HTMLReader</code>, which implements
  29. * the <code>HTMLEditorKit.ParserCallback</code> protocol
  30. * that the parser expects. To change the structure one
  31. * can subclass HTMLReader, and reimplement the method
  32. * <code>getReader</code> to return the new
  33. * reader implementation. The documentation for
  34. * HTMLReader should be consulted for the details of
  35. * the default structure created. The intent is that
  36. * the document be non-lossy (although reproducing the
  37. * HTML format may result in a different format).
  38. * <p>
  39. * The document models only HTML, and makes no attempt to
  40. * store view attributes in it. The elements are identified
  41. * by the <code>StyleContext.NameAttribute</code> attribute,
  42. * which should always have a value of type <code>HTML.Tag</code>
  43. * that identifies the kind of element. Some of the elements
  44. * (such as comments) are synthesized. The HTMLFactory
  45. * uses this attribute to determine what kind of view to build.
  46. * <p>
  47. * This document supports incremental loading. The
  48. * <code>TokenThreshold</code> property controls how
  49. * much of the parse is buffered before trying to update
  50. * the element structure of the document. This property
  51. * is set by the EditorKit so that subclasses can disable
  52. * it.
  53. * <p>
  54. * The <code>Base</code> property determines the URL
  55. * against which relative URLs are resolved.
  56. * By default, this will be the
  57. * <code>Document.StreamDescriptionProperty</code> if
  58. * the value of the property is a URL. If a <BASE>
  59. * tag is encountered, the base will become the URL specified
  60. * by that tag. Because the base URL is a property, it
  61. * can of course be set directly.
  62. * <p>
  63. * The default content storage mechanism for this document
  64. * is a gap buffer (GapContent). Alternatives can be supplied
  65. * by using the constructor that takes a Content implementation.
  66. *
  67. * @author Timothy Prinzing
  68. * @author Scott Violet
  69. * @author Sunita Mani
  70. * @version 1.133 02/02/00
  71. */
  72. public class HTMLDocument extends DefaultStyledDocument {
  73. /**
  74. * Constructs an HTML document.
  75. */
  76. public HTMLDocument() {
  77. this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleSheet());
  78. }
  79. /**
  80. * Constructs an HTML document with the default content
  81. * storage implementation and the given style/attribute
  82. * storage mechanism.
  83. *
  84. * @param styles the styles
  85. */
  86. public HTMLDocument(StyleSheet styles) {
  87. this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
  88. }
  89. /**
  90. * Constructs an HTML document with the given content
  91. * storage implementation and the given style/attribute
  92. * storage mechanism.
  93. *
  94. * @param c the container for the content
  95. * @param styles the styles
  96. */
  97. public HTMLDocument(Content c, StyleSheet styles) {
  98. super(c, styles);
  99. }
  100. /**
  101. * Fetches the reader for the parser to use to load the document
  102. * with HTML. This is implemented to return an instance of
  103. * HTMLDocument.HTMLReader. Subclasses can reimplement this
  104. * method to change how the document get structured if desired
  105. * (e.g. to handle custom tags, structurally represent character
  106. * style elements, etc.).
  107. */
  108. public HTMLEditorKit.ParserCallback getReader(int pos) {
  109. Object desc = getProperty(Document.StreamDescriptionProperty);
  110. if (desc instanceof URL) {
  111. setBase((URL)desc);
  112. }
  113. HTMLReader reader = new HTMLReader(pos);
  114. return reader;
  115. }
  116. /**
  117. * Fetches the reader for the parser to use to load the document
  118. * with HTML. This is implemented to return an instance of
  119. * HTMLDocument.HTMLReader. Subclasses can reimplement this
  120. * method to change how the document get structured if desired
  121. * (e.g. to handle custom tags, structurally represent character
  122. * style elements, etc.).
  123. *
  124. * @param popDepth the number of ElementSpec.EndTagTypes to generate before
  125. * inserting
  126. * @param pushDepth the number of ElementSpec.StartTagTypes with a direction
  127. * of ElementSpec.JoinNextDirection that should be generated
  128. * before inserting, but after the end tags have been generated
  129. * @param insertTag the first tag to start inserting into document
  130. */
  131. public HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
  132. int pushDepth,
  133. HTML.Tag insertTag) {
  134. return getReader(pos, popDepth, pushDepth, insertTag, true);
  135. }
  136. /**
  137. * Fetches the reader for the parser to use to load the document
  138. * with HTML. This is implemented to return an instance of
  139. * HTMLDocument.HTMLReader. Subclasses can reimplement this
  140. * method to change how the document get structured if desired
  141. * (e.g. to handle custom tags, structurally represent character
  142. * style elements, etc.).
  143. *
  144. * @param popDepth the number of ElementSpec.EndTagTypes to generate before
  145. * inserting
  146. * @param pushDepth the number of ElementSpec.StartTagTypes with a direction
  147. * of ElementSpec.JoinNextDirection that should be generated
  148. * before inserting, but after the end tags have been generated
  149. * @param insertTag the first tag to start inserting into document
  150. * @param insertInsertTag false if all the Elements after insertTag should
  151. * be inserted; otherwise insertTag will be inserted
  152. */
  153. HTMLEditorKit.ParserCallback getReader(int pos, int popDepth,
  154. int pushDepth,
  155. HTML.Tag insertTag,
  156. boolean insertInsertTag) {
  157. Object desc = getProperty(Document.StreamDescriptionProperty);
  158. if (desc instanceof URL) {
  159. setBase((URL)desc);
  160. }
  161. HTMLReader reader = new HTMLReader(pos, popDepth, pushDepth,
  162. insertTag, insertInsertTag, false,
  163. true);
  164. return reader;
  165. }
  166. /**
  167. * Gets the location to resolve relative URLs against. By
  168. * default this will be the document's URL if the document
  169. * was loaded from a URL. If a base tag is found and
  170. * can be parsed, it will be used as the base location.
  171. */
  172. public URL getBase() {
  173. return base;
  174. }
  175. /**
  176. * Sets the location to resolve relative URLs against. By
  177. * default this will be the document's URL if the document
  178. * was loaded from a URL. If a base tag is found and
  179. * can be parsed, it will be used as the base location.
  180. * <p>This also sets the base of the StyleSheet to be <code>u</code>
  181. * as well as the receiver.
  182. */
  183. public void setBase(URL u) {
  184. base = u;
  185. getStyleSheet().setBase(u);
  186. }
  187. /**
  188. * Inserts new elements in bulk. This is how elements get created
  189. * in the document. The parsing determines what structure is needed
  190. * and creates the specification as a set of tokens that describe the
  191. * edit while leaving the document free of a write-lock. This method
  192. * can then be called in bursts by the reader to acquire a write-lock
  193. * for a shorter duration (i.e. while the document is actually being
  194. * altered).
  195. *
  196. * @param offset the starting offset
  197. * @param data the element data
  198. * @exception BadLocationException if the given position does not
  199. * represent a valid location in the associated document.
  200. */
  201. protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
  202. super.insert(offset, data);
  203. }
  204. /**
  205. * Updates document structure as a result of text insertion. This
  206. * will happen within a write lock. This implementation simply
  207. * parses the inserted content for line breaks and builds up a set
  208. * of instructions for the element buffer.
  209. *
  210. * @param chng a description of the document change
  211. * @param attr the attributes
  212. */
  213. protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  214. if(attr == null) {
  215. attr = contentAttributeSet;
  216. }
  217. // If this is the composed text element, merge the content attribute to it
  218. else if (attr.isDefined(StyleConstants.ComposedTextAttribute)) {
  219. ((MutableAttributeSet)attr).addAttributes(contentAttributeSet);
  220. }
  221. super.insertUpdate(chng, attr);
  222. }
  223. /**
  224. * Replaces the contents of the document with the given
  225. * element specifications. This is called before insert if
  226. * the loading is done in bursts. This is the only method called
  227. * if loading the document entirely in one burst.
  228. */
  229. protected void create(ElementSpec[] data) {
  230. super.create(data);
  231. }
  232. /**
  233. * Sets attributes for a paragraph.
  234. * <p>
  235. * This method is thread safe, although most Swing methods
  236. * are not. Please see
  237. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  238. * and Swing</A> for more information.
  239. *
  240. * @param offset the offset into the paragraph (must be at least 0)
  241. * @param length the number of characters affected (must be at least 0)
  242. * @param s the attributes
  243. * @param replace whether to replace existing attributes, or merge them
  244. */
  245. public void setParagraphAttributes(int offset, int length, AttributeSet s,
  246. boolean replace) {
  247. try {
  248. writeLock();
  249. // Make sure we send out a change for the length of the paragraph.
  250. int end = Math.min(offset + length, getLength());
  251. Element e = getParagraphElement(offset);
  252. offset = e.getStartOffset();
  253. e = getParagraphElement(end);
  254. length = Math.max(0, e.getEndOffset() - offset);
  255. DefaultDocumentEvent changes =
  256. new DefaultDocumentEvent(offset, length,
  257. DocumentEvent.EventType.CHANGE);
  258. AttributeSet sCopy = s.copyAttributes();
  259. int lastEnd = Integer.MAX_VALUE;
  260. for (int pos = offset; pos <= end; pos = lastEnd) {
  261. Element paragraph = getParagraphElement(pos);
  262. if (lastEnd == paragraph.getEndOffset()) {
  263. lastEnd++;
  264. }
  265. else {
  266. lastEnd = paragraph.getEndOffset();
  267. }
  268. MutableAttributeSet attr =
  269. (MutableAttributeSet) paragraph.getAttributes();
  270. changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
  271. if (replace) {
  272. attr.removeAttributes(attr);
  273. }
  274. attr.addAttributes(s);
  275. }
  276. changes.end();
  277. fireChangedUpdate(changes);
  278. fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  279. } finally {
  280. writeUnlock();
  281. }
  282. }
  283. /**
  284. * Fetches the StyleSheet with the document-specific display
  285. * rules (CSS) that were specified in the HTML document itself.
  286. */
  287. public StyleSheet getStyleSheet() {
  288. return (StyleSheet) getAttributeContext();
  289. }
  290. /**
  291. * Fetches an iterator for the following kind of HTML tag.
  292. * This can be used for things like iterating over the
  293. * set of anchors contained, iterating over the input
  294. * elements, etc.
  295. */
  296. public Iterator getIterator(HTML.Tag t) {
  297. if (t.isBlock()) {
  298. // TBD
  299. return null;
  300. }
  301. return new LeafIterator(t, this);
  302. }
  303. /**
  304. * Creates a document leaf element that directly represents
  305. * text (doesn't have any children). This is implemented
  306. * to return an element of type
  307. * <code>HTMLDocument.RunElement</code>.
  308. *
  309. * @param parent the parent element
  310. * @param a the attributes for the element
  311. * @param p0 the beginning of the range (must be at least 0)
  312. * @param p1 the end of the range (must be at least p0)
  313. * @return the new element
  314. */
  315. protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
  316. return new RunElement(parent, a, p0, p1);
  317. }
  318. /**
  319. * Creates a document branch element, that can contain other elements.
  320. * This is implemented to return an element of type
  321. * <code>HTMLDocument.BlockElement</code>.
  322. *
  323. * @param parent the parent element
  324. * @param a the attributes
  325. * @return the element
  326. */
  327. protected Element createBranchElement(Element parent, AttributeSet a) {
  328. return new BlockElement(parent, a);
  329. }
  330. /**
  331. * Creates the root element to be used to represent the
  332. * default document structure.
  333. *
  334. * @return the element base
  335. */
  336. protected AbstractElement createDefaultRoot() {
  337. // grabs a write-lock for this initialization and
  338. // abandon it during initialization so in normal
  339. // operation we can detect an illegitimate attempt
  340. // to mutate attributes.
  341. writeLock();
  342. MutableAttributeSet a = new SimpleAttributeSet();
  343. a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.HTML);
  344. BlockElement html = new BlockElement(null, a.copyAttributes());
  345. a.removeAttributes(a);
  346. a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.BODY);
  347. BlockElement body = new BlockElement(html, a.copyAttributes());
  348. a.removeAttributes(a);
  349. a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.P);
  350. BlockElement paragraph = new BlockElement(body, a.copyAttributes());
  351. a.removeAttributes(a);
  352. a.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
  353. RunElement brk = new RunElement(paragraph, a, 0, 1);
  354. Element[] buff = new Element[1];
  355. buff[0] = brk;
  356. paragraph.replace(0, 0, buff);
  357. buff[0] = paragraph;
  358. body.replace(0, 0, buff);
  359. buff[0] = body;
  360. html.replace(0, 0, buff);
  361. writeUnlock();
  362. return html;
  363. }
  364. /**
  365. * Sets the number of tokens to buffer before trying to update
  366. * the documents element structure.
  367. */
  368. public void setTokenThreshold(int n) {
  369. putProperty(TokenThreshold, new Integer(n));
  370. }
  371. /**
  372. * Gets the number of tokens to buffer before trying to update
  373. * the documents element structure. By default, this will
  374. * be <code>Integer.MAX_VALUE</code>.
  375. */
  376. public int getTokenThreshold() {
  377. Integer i = (Integer) getProperty(TokenThreshold);
  378. if (i != null) {
  379. return i.intValue();
  380. }
  381. return Integer.MAX_VALUE;
  382. }
  383. /**
  384. * Sets how unknown tags are handled. If set to true, unknown
  385. * tags are put in the model, otherwise they are dropped.
  386. */
  387. public void setPreservesUnknownTags(boolean preservesTags) {
  388. preservesUnknownTags = preservesTags;
  389. }
  390. /**
  391. * @return true if unknown tags are to be preserved when parsing.
  392. */
  393. public boolean getPreservesUnknownTags() {
  394. return preservesUnknownTags;
  395. }
  396. /**
  397. * Processes HyperlinkEvents that
  398. * are generated by documents in an HTML frame. The HyperlinkEvent
  399. * type, as the parameter suggests, is HTMLFrameHyperlinkEvent.
  400. * In addition to the typical information contained in a HyperlinkEvent,
  401. * this event contains the element that corresponds to the frame in
  402. * which the click happened (the source element) and the
  403. * target name. The target name has 4 possible values:
  404. * <ul>
  405. * <li> _self
  406. * <li> _parent
  407. * <li> _top
  408. * <li> a named frame
  409. * </ul>
  410. *
  411. * If target is _self, the action is to change the value of the
  412. * HTML.Attribute.SRC attribute and fires a ChangedUpdate event.
  413. *
  414. * If the target is _parent, then it deletes the parent element,
  415. * which is a <FRAMESET> element, and inserts a new <FRAME> element
  416. * and sets its HTML.Attribute.SRC attribute to have a value equal
  417. * to the destination URL and fire a RemovedUpdate and InsertUpdate.
  418. *
  419. * If the target is _top, this method does nothing. In the implementation
  420. * of the view for a frame, namely the FrameView, the processing of _top
  421. * is handled. Given that _top implies replacing the entire document,
  422. * it made sense to handle this outside of the document that it will
  423. * replace.
  424. *
  425. * If the target is a named frame, then the element hierarchy is searched
  426. * for an element with a name equal to the target, its HTML.Attribute.SRC
  427. * attribute is updated and a ChangedUpdate event is fired.
  428. *
  429. * @param e the event
  430. */
  431. public void processHTMLFrameHyperlinkEvent(HTMLFrameHyperlinkEvent e) {
  432. String frameName = e.getTarget();
  433. Element element = e.getSourceElement();
  434. String urlStr = e.getURL().toString();
  435. if (frameName.equals("_self")) {
  436. /*
  437. The source and destination elements
  438. are the same.
  439. */
  440. updateFrame(element, urlStr);
  441. } else if (frameName.equals("_parent")) {
  442. /*
  443. The destination is the parent of the frame.
  444. */
  445. updateFrameSet(element.getParentElement(), urlStr);
  446. } else {
  447. /*
  448. locate a named frame
  449. */
  450. Element targetElement = findFrame(frameName);
  451. if (targetElement != null) {
  452. updateFrame(targetElement, urlStr);
  453. }
  454. }
  455. }
  456. /**
  457. * Searches the element hierarchy for an FRAME element
  458. * that has its name attribute equal to the frameName
  459. *
  460. * @param frameName
  461. * @return element the element whose NAME attribute has
  462. * a value of frameName. returns null if not
  463. * found.
  464. */
  465. private Element findFrame(String frameName) {
  466. ElementIterator it = new ElementIterator(this);
  467. Element next = null;
  468. while ((next = it.next()) != null) {
  469. AttributeSet attr = next.getAttributes();
  470. if (matchNameAttribute(attr, HTML.Tag.FRAME)) {
  471. String frameTarget = (String)attr.getAttribute(HTML.Attribute.NAME);
  472. if (frameTarget != null && frameTarget.equals(frameName)) {
  473. break;
  474. }
  475. }
  476. }
  477. return next;
  478. }
  479. /**
  480. * Returns true if the StyleConstants.NameAttribute is
  481. * equal to the tag that is passed in as a parameter.
  482. *
  483. */
  484. boolean matchNameAttribute(AttributeSet attr, HTML.Tag tag) {
  485. Object o = attr.getAttribute(StyleConstants.NameAttribute);
  486. if (o instanceof HTML.Tag) {
  487. HTML.Tag name = (HTML.Tag) o;
  488. if (name == tag) {
  489. return true;
  490. }
  491. }
  492. return false;
  493. }
  494. /**
  495. * Replaces a frameset branch Element with a frame leaf element.
  496. *
  497. * @param element the frameset element to remove.
  498. * @param url the value for the SRC attribute for the
  499. * new frame that will replace the frameset.
  500. */
  501. private void updateFrameSet(Element element, String url) {
  502. try {
  503. int startOffset = element.getStartOffset();
  504. int endOffset = element.getEndOffset();
  505. remove(startOffset, endOffset - startOffset);
  506. SimpleAttributeSet attr = new SimpleAttributeSet();
  507. attr.addAttribute(HTML.Attribute.SRC, url);
  508. attr.addAttribute(StyleConstants.NameAttribute, HTML.Tag.FRAME);
  509. insertString(startOffset, " ", attr);
  510. } catch (BadLocationException e1) {
  511. // Should handle this better
  512. }
  513. }
  514. /**
  515. * Updates the Frame elements HTML.Attribute.SRC attribute and
  516. * fires a ChangedUpdate event.
  517. *
  518. * @param element a FRAME element whose SRC attribute will be updated
  519. * @param url a string specifying the new value for the SRC attribute
  520. */
  521. private void updateFrame(Element element, String url) {
  522. try {
  523. writeLock();
  524. DefaultDocumentEvent changes = new DefaultDocumentEvent(element.getStartOffset(),
  525. 1,
  526. DocumentEvent.EventType.CHANGE);
  527. AttributeSet sCopy = element.getAttributes().copyAttributes();
  528. MutableAttributeSet attr = (MutableAttributeSet) element.getAttributes();
  529. changes.addEdit(new AttributeUndoableEdit(element, sCopy, false));
  530. attr.removeAttribute(HTML.Attribute.SRC);
  531. attr.addAttribute(HTML.Attribute.SRC, url);
  532. changes.end();
  533. fireChangedUpdate(changes);
  534. fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
  535. } finally {
  536. writeUnlock();
  537. }
  538. }
  539. /**
  540. * Returns true if the document will be viewed in a frame.
  541. */
  542. boolean isFrameDocument() {
  543. return frameDocument;
  544. }
  545. /**
  546. * Sets a boolean state about whether the document will be
  547. * viewed in a frame.
  548. */
  549. void setFrameDocumentState(boolean frameDoc) {
  550. this.frameDocument = frameDoc;
  551. }
  552. /**
  553. * Adds the specified map, this will remove a Map that has been
  554. * previously registered with the same name.
  555. */
  556. void addMap(Map map) {
  557. String name = map.getName();
  558. if (name != null) {
  559. Object maps = getProperty(MAP_PROPERTY);
  560. if (maps == null) {
  561. maps = new Hashtable(11);
  562. putProperty(MAP_PROPERTY, maps);
  563. }
  564. if (maps instanceof Hashtable) {
  565. ((Hashtable)maps).put("#" + name, map);
  566. }
  567. }
  568. }
  569. /**
  570. * Removes a previously registered map.
  571. */
  572. void removeMap(Map map) {
  573. String name = map.getName();
  574. if (name != null) {
  575. Object maps = getProperty(MAP_PROPERTY);
  576. if (maps instanceof Hashtable) {
  577. ((Hashtable)maps).remove("#" + name);
  578. }
  579. }
  580. }
  581. /**
  582. * Returns the Map associated with the given name.
  583. */
  584. Map getMap(String name) {
  585. if (name != null) {
  586. Object maps = getProperty(MAP_PROPERTY);
  587. if (maps != null && (maps instanceof Hashtable)) {
  588. return (Map)((Hashtable)maps).get(name);
  589. }
  590. }
  591. return null;
  592. }
  593. /**
  594. * Returns an Enumeration of the possible Maps.
  595. */
  596. Enumeration getMaps() {
  597. Object maps = getProperty(MAP_PROPERTY);
  598. if (maps instanceof Hashtable) {
  599. return ((Hashtable)maps).elements();
  600. }
  601. return null;
  602. }
  603. /**
  604. * Sets the content type language used for style sheets that do not
  605. * explicitly specify the type. The default is text/css.
  606. */
  607. /* public */
  608. void setDefaultStyleSheetType(String contentType) {
  609. putProperty(StyleType, contentType);
  610. }
  611. /**
  612. * Returns the content type language used for style sheets. The default
  613. * is text/css.
  614. */
  615. /* public */
  616. String getDefaultStyleSheetType() {
  617. String retValue = (String)getProperty(StyleType);
  618. if (retValue == null) {
  619. return "text/css";
  620. }
  621. return retValue;
  622. }
  623. /**
  624. * Sets the parser that is used by the methods that insert html
  625. * into the existing document, eg <code>setInnerHTML</code>,
  626. * <code>setOuterHTML</code>...
  627. * <p>
  628. * <code>HTMLEditorKit.createDefaultDocument</code> will set the parser
  629. * for you. If you create an HTMLDocument by hand, be sure and set the
  630. * parser accordingly.
  631. *
  632. * @since 1.3
  633. */
  634. public void setParser(HTMLEditorKit.Parser parser) {
  635. this.parser = parser;
  636. putProperty("__PARSER__", null);
  637. }
  638. /**
  639. * Returns the parser that is used when inserting HTML into the existing
  640. * document.
  641. *
  642. * @since 1.3
  643. */
  644. public HTMLEditorKit.Parser getParser() {
  645. Object p = getProperty("__PARSER__");
  646. if (p instanceof HTMLEditorKit.Parser) {
  647. return (HTMLEditorKit.Parser)p;
  648. }
  649. return parser;
  650. }
  651. /**
  652. * Replaces the children of the given element with the contents
  653. * specified as an HTML string.
  654. * <p>This will be seen as at least two events, n inserts followed by
  655. * a remove.
  656. * <p>For this to work correcty, the receiver must have an
  657. * HTMLEditorKit.Parser set. This will be the case if the receiver
  658. * was created from an HTMLEditorKit via the
  659. * <code>createDefaultDocument</code> method.
  660. *
  661. * @throws IllegalArgumentException is <code>elem</code> is a leaf
  662. * @throws IllegalStateException if an HTMLEditorKit.Parser has not
  663. * been set on the receiver.
  664. * @since 1.3
  665. */
  666. public void setInnerHTML(Element elem, String htmlText) throws
  667. BadLocationException, IOException {
  668. verifyParser();
  669. if (elem != null && elem.isLeaf()) {
  670. throw new IllegalArgumentException
  671. ("Can not set inner HTML of a leaf");
  672. }
  673. if (elem != null && htmlText != null) {
  674. int oldCount = elem.getElementCount();
  675. int insertPosition = elem.getStartOffset();
  676. insertHTML(elem, elem.getStartOffset(), htmlText, true);
  677. if (elem.getElementCount() > oldCount) {
  678. // Elements were inserted, do the cleanup.
  679. removeElements(elem, elem.getElementCount() - oldCount,
  680. oldCount);
  681. }
  682. }
  683. }
  684. /**
  685. * Replaces the given element in the parent with the contents
  686. * specified as an HTML string.
  687. * <p>This will be seen as at least two events, n inserts followed by
  688. * a remove.
  689. * <p>When replacing a leaf this will attempt to make sure there is
  690. * a newline present if one is needed. This may result in an additional
  691. * element being inserted. Consider, if you were to replace a character
  692. * element that contained a newline with <img> this would create
  693. * two elements, one for the image, ane one for the newline.
  694. * <p>If you try to replace the element at length you will most likely
  695. * end up with two elements, eg setOuterHTML(getCharacterElement
  696. * (getLength()), "blah") will result in two leaf elements at the end,
  697. * one representing 'blah', and the other representing the end element.
  698. * <p>For this to work correcty, the receiver must have an
  699. * HTMLEditorKit.Parser set. This will be the case if the receiver
  700. * was created from an HTMLEditorKit via the
  701. * <code>createDefaultDocument</code> method.
  702. *
  703. * @throws IllegalStateException if an HTMLEditorKit.Parser has not
  704. * been set on the receiver.
  705. * @since 1.3
  706. */
  707. public void setOuterHTML(Element elem, String htmlText) throws
  708. BadLocationException, IOException {
  709. verifyParser();
  710. if (elem != null && elem.getParentElement() != null &&
  711. htmlText != null) {
  712. int start = elem.getStartOffset();
  713. int end = elem.getEndOffset();
  714. int startLength = getLength();
  715. // We don't want a newline if elem is a leaf, and doesn't contain
  716. // a newline.
  717. boolean wantsNewline = !elem.isLeaf();
  718. if (!wantsNewline && (end > startLength ||
  719. getText(end - 1, 1).charAt(0) == NEWLINE[0])){
  720. wantsNewline = true;
  721. }
  722. Element parent = elem.getParentElement();
  723. int oldCount = parent.getElementCount();
  724. insertHTML(parent, start, htmlText, wantsNewline);
  725. // Remove old.
  726. int newLength = getLength();
  727. if (oldCount != parent.getElementCount()) {
  728. int removeIndex = parent.getElementIndex(start + newLength -
  729. startLength);
  730. removeElements(parent, removeIndex, 1);
  731. }
  732. }
  733. }
  734. /**
  735. * Inserts the HTML specified as a string at the start
  736. * of the element.
  737. * <p>For this to work correcty, the receiver must have an
  738. * HTMLEditorKit.Parser set. This will be the case if the receiver
  739. * was created from an HTMLEditorKit via the
  740. * <code>createDefaultDocument</code> method.
  741. *
  742. * @throws IllegalStateException if an HTMLEditorKit.Parser has not
  743. * been set on the receiver.
  744. * @since 1.3
  745. */
  746. public void insertAfterStart(Element elem, String htmlText) throws
  747. BadLocationException, IOException {
  748. verifyParser();
  749. if (elem != null && elem.isLeaf()) {
  750. throw new IllegalArgumentException
  751. ("Can not insert HTML after start of a leaf");
  752. }
  753. insertHTML(elem, elem.getStartOffset(), htmlText, false);
  754. }
  755. /**
  756. * Inserts the HTML specified as a string at the end of
  757. * the element.
  758. * <p> If <code>elem</code>'s children are leafs, at the
  759. * character at a <code>elem.getEndOffset() - 1</code> is a newline,
  760. * this will insert before the newline so that there isn't text after
  761. * the newline.
  762. * <p>For this to work correcty, the receiver must have an
  763. * HTMLEditorKit.Parser set. This will be the case if the receiver
  764. * was created from an HTMLEditorKit via the
  765. * <code>createDefaultDocument</code> method.
  766. *
  767. * @throws IllegalStateException if an HTMLEditorKit.Parser has not
  768. * been set on the receiver.
  769. * @since 1.3
  770. */
  771. public void insertBeforeEnd(Element elem, String htmlText) throws
  772. BadLocationException, IOException {
  773. verifyParser();
  774. if (elem != null && elem.isLeaf()) {
  775. throw new IllegalArgumentException
  776. ("Can not set inner HTML before end of leaf");
  777. }
  778. int offset = elem.getEndOffset();
  779. if (elem.getElement(elem.getElementIndex(offset - 1)).isLeaf() &&
  780. getText(offset - 1, 1).charAt(0) == NEWLINE[0]) {
  781. offset--;
  782. }
  783. insertHTML(elem, offset, htmlText, false);
  784. }
  785. /**
  786. * Inserts the HTML specified as string before the start of
  787. * the given element.
  788. * <p>For this to work correcty, the receiver must have an
  789. * HTMLEditorKit.Parser set. This will be the case if the receiver
  790. * was created from an HTMLEditorKit via the
  791. * <code>createDefaultDocument</code> method.
  792. *
  793. * @throws IllegalStateException if an HTMLEditorKit.Parser has not
  794. * been set on the receiver.
  795. * @since 1.3
  796. */
  797. public void insertBeforeStart(Element elem, String htmlText) throws
  798. BadLocationException, IOException {
  799. verifyParser();
  800. if (elem != null) {
  801. Element parent = elem.getParentElement();
  802. if (parent != null) {
  803. insertHTML(parent, elem.getStartOffset(), htmlText, false);
  804. }
  805. }
  806. }
  807. /**
  808. * Inserts the HTML specified as a string after the
  809. * the end of the given element.
  810. * <p>For this to work correcty, the receiver must have an
  811. * HTMLEditorKit.Parser set. This will be the case if the receiver
  812. * was created from an HTMLEditorKit via the
  813. * <code>createDefaultDocument</code> method.
  814. *
  815. * @throws IllegalStateException if an HTMLEditorKit.Parser has not
  816. * been set on the receiver.
  817. * @since 1.3
  818. */
  819. public void insertAfterEnd(Element elem, String htmlText) throws
  820. BadLocationException, IOException {
  821. verifyParser();
  822. if (elem != null) {
  823. Element parent = elem.getParentElement();
  824. if (parent != null) {
  825. int offset = elem.getEndOffset();
  826. if (elem.isLeaf() && getText(offset - 1, 1).
  827. charAt(0) == NEWLINE[0]) {
  828. offset--;
  829. }
  830. insertHTML(parent, offset, htmlText, false);
  831. }
  832. }
  833. }
  834. /**
  835. * Fetches the element that has the given id attribute.
  836. * If the element can't be found, null is returned. This is not
  837. * thread-safe.
  838. *
  839. * @since 1.3
  840. */
  841. public Element getElement(String id) {
  842. if (id == null) {
  843. return null;
  844. }
  845. return getElement(getDefaultRootElement(), HTML.Attribute.ID, id,
  846. true);
  847. }
  848. /**
  849. * Returns the child element of <code>e</code> that contains the
  850. * attribute, <code>attribute</code> with value <code>value</code>, or
  851. * null if one isn't found. This is not thread-safe.
  852. *
  853. * @since 1.3
  854. */
  855. public Element getElement(Element e, Object attribute, Object value) {
  856. return getElement(e, attribute, value, false);
  857. }
  858. /**
  859. * Returns the child element of <code>e</code> that contains the
  860. * attribute, <code>attribute</code> with value <code>value</code>, or
  861. * null if one isn't found. This is not thread-safe.
  862. * If <code>searchLeafAttributes</code> is true, and <code>e</code> is
  863. * a leaf, any attributes that are instances of HTML.Tag with a
  864. * value that is an AttributeSet will also be checked.
  865. */
  866. private Element getElement(Element e, Object attribute, Object value,
  867. boolean searchLeafAttributes) {
  868. AttributeSet attr = e.getAttributes();
  869. if (attr != null && attr.isDefined(attribute)) {
  870. if (value.equals(attr.getAttribute(attribute))) {
  871. return e;
  872. }
  873. }
  874. if (!e.isLeaf()) {
  875. for (int counter = 0, maxCounter = e.getElementCount();
  876. counter < maxCounter; counter++) {
  877. Element retValue = getElement(e.getElement(counter), attribute,
  878. value, searchLeafAttributes);
  879. if (retValue != null) {
  880. return retValue;
  881. }
  882. }
  883. }
  884. else if (searchLeafAttributes && attr != null) {
  885. // For some leaf elements we store the actual attributes inside
  886. // the AttributeSet of the Element (such as anchors).
  887. Enumeration names = attr.getAttributeNames();
  888. if (names != null) {
  889. while (names.hasMoreElements()) {
  890. Object name = names.nextElement();
  891. if ((name instanceof HTML.Tag) &&
  892. (attr.getAttribute(name) instanceof AttributeSet)) {
  893. AttributeSet check = (AttributeSet)attr.
  894. getAttribute(name);
  895. if (check.isDefined(attribute) &&
  896. value.equals(check.getAttribute(attribute))) {
  897. return e;
  898. }
  899. }
  900. }
  901. }
  902. }
  903. return null;
  904. }
  905. /**
  906. * Verifies the receiver has an HTMLEditorKit.Parser set. If
  907. * <code>getParser</code> returns null, this will throw an
  908. * IllegalStateException.
  909. *
  910. * @throws IllegalStateException if the receiver does not have a Parser.
  911. */
  912. private void verifyParser() {
  913. if (getParser() == null) {
  914. throw new IllegalStateException("No HTMLEditorKit.Parser");
  915. }
  916. }
  917. /**
  918. * Inserts a string of HTML into the document at the given position.
  919. * <code>parent</code> is used to identify the location to insert the
  920. * <code>html</code>. If <code>parent</code> is a leaf this can have
  921. * unexpected results.
  922. */
  923. private void insertHTML(Element parent, int offset, String html,
  924. boolean wantsTrailingNewline)
  925. throws BadLocationException, IOException {
  926. if (parent != null && html != null) {
  927. HTMLEditorKit.Parser parser = getParser();
  928. if (parser != null) {
  929. int lastOffset = Math.max(0, offset - 1);
  930. Element charElement = getCharacterElement(lastOffset);
  931. Element commonParent = parent;
  932. int pop = 0;
  933. int push = 0;
  934. if (parent.getStartOffset() > lastOffset) {
  935. while (commonParent != null &&
  936. commonParent.getStartOffset() > lastOffset) {
  937. commonParent = commonParent.getParentElement();
  938. push++;
  939. }
  940. if (commonParent == null) {
  941. throw new BadLocationException("No common parent",
  942. offset);
  943. }
  944. }
  945. while (charElement != null && charElement != commonParent) {
  946. pop++;
  947. charElement = charElement.getParentElement();
  948. }
  949. if (charElement != null) {
  950. // Found it, do the insert.
  951. HTMLReader reader = new HTMLReader(offset, pop - 1, push,
  952. null, false, true,
  953. wantsTrailingNewline);
  954. parser.parse(new StringReader(html), reader, true);
  955. reader.flush();
  956. }
  957. }
  958. }
  959. }
  960. /**
  961. * Removes child Elements of the passed in Element <code>e</code>. This
  962. * will do the necessary cleanup to ensure the element representing the
  963. * end character is correctly created.
  964. * <p>This is not a general purpose method, it assumes that <code>e</code>
  965. * will still have at least one child after the remove, and it assumes
  966. * the character at <code>e.getStartOffset() - 1</code> is a newline and
  967. * is of length 1.
  968. */
  969. private void removeElements(Element e, int index, int count) throws BadLocationException {
  970. writeLock();
  971. try {
  972. int start = e.getElement(index).getStartOffset();
  973. int end = e.getElement(index + count - 1).getEndOffset();
  974. if (end > getLength()) {
  975. removeElementsAtEnd(e, index, count, start, end);
  976. }
  977. else {
  978. removeElements(e, index, count, start, end);
  979. }
  980. } finally {
  981. writeUnlock();
  982. }
  983. }
  984. /**
  985. * Called to remove child elements of <code>e</code> when one of the
  986. * elements to remove is representing the end character.
  987. * <p>Since the Content will not allow a removal to the end character
  988. * this will do a remove from <code>start - 1</code> to <code>end</code>.
  989. * The end Element(s) will be removed, and the element representing
  990. * <code>start - 1</code> to <code>start</code> will be recreated. This
  991. * Element has to be recreated as after the content removal its offsets
  992. * become <code>start - 1</code> to <code>start - 1</code>.
  993. */
  994. private void removeElementsAtEnd(Element e, int index, int count,
  995. int start, int end) throws BadLocationException {
  996. Element[] added = new Element[0];
  997. // index must be > 0 otherwise no insert would have happened.
  998. boolean isLeaf = (e.getElement(index - 1).isLeaf());
  999. if (isLeaf) {
  1000. index--;
  1001. }
  1002. Element[] removed = new Element[count];
  1003. for (int counter = 0; counter < count; counter++) {
  1004. removed[counter] = e.getElement(counter + index);
  1005. }
  1006. DefaultDocumentEvent dde = new DefaultDocumentEvent
  1007. (start - 1, end - start + 1, DocumentEvent.EventType.REMOVE);
  1008. dde.addEdit(new ElementEdit(e, index, removed, added));
  1009. ((AbstractDocument.BranchElement)e).replace(index, removed.length,
  1010. added);
  1011. UndoableEdit u;
  1012. if (isLeaf) {
  1013. // start - 1 was a leaf, simply remove it.
  1014. u = getContent().remove(start - 1, end - start);
  1015. }
  1016. else {
  1017. // Not a leaf, descend until we find the leaf representing
  1018. // start - 1 and remove it.
  1019. Element newLineE = e.getElement(index - 1);
  1020. while (!newLineE.isLeaf()) {
  1021. newLineE = newLineE.getElement(newLineE.getElementCount() - 1);
  1022. }
  1023. Element[] oRemoved = new Element[1];
  1024. Element[] oAdded = new Element[1];
  1025. oRemoved[0] = newLineE;
  1026. AttributeSet attrs = newLineE.getAttributes();
  1027. newLineE = newLineE.getParentElement();
  1028. u = getContent().remove(start - 1, end - start);
  1029. // Now recreate the Element for start - 1.
  1030. oAdded[0] = createLeafElement(newLineE, attrs, start - 1, start);
  1031. ((AbstractDocument.BranchElement)newLineE).
  1032. replace(newLineE.getElementCount() - 1, 1, oAdded);
  1033. dde.addEdit(new ElementEdit(newLineE, newLineE.getElementCount() -
  1034. 1, oRemoved, oAdded));
  1035. }
  1036. if (u != null) {
  1037. dde.addEdit(u);
  1038. }
  1039. postRemoveUpdate(dde);
  1040. dde.end();
  1041. fireRemoveUpdate(dde);
  1042. if (u != null) {
  1043. fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
  1044. }
  1045. }
  1046. /**
  1047. * Called to remove child Elements when the end is not touched.
  1048. */
  1049. private void removeElements(Element e, int index, int count,
  1050. int start, int end) throws BadLocationException {
  1051. Element[] removed = new Element[count];
  1052. Element[] added = new Element[0];
  1053. for (int counter = 0; counter < count; counter++) {
  1054. removed[counter] = e.getElement(counter + index);
  1055. }
  1056. DefaultDocumentEvent dde = new DefaultDocumentEvent
  1057. (start, end - start, DocumentEvent.EventType.REMOVE);
  1058. ((AbstractDocument.BranchElement)e).replace(index, removed.length,
  1059. added);
  1060. dde.addEdit(new ElementEdit(e, index, removed, added));
  1061. UndoableEdit u = getContent().remove(start, end - start);
  1062. if (u != null) {
  1063. dde.addEdit(u);
  1064. }
  1065. postRemoveUpdate(dde);
  1066. dde.end();
  1067. fireRemoveUpdate(dde);
  1068. if (u != null) {
  1069. fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
  1070. }
  1071. }
  1072. // These two are provided for inner class access. The are named different
  1073. // than the super class as the super class implementations are final.
  1074. void obtainLock() {
  1075. writeLock();
  1076. }
  1077. void releaseLock() {
  1078. writeUnlock();
  1079. }
  1080. //
  1081. // Provided for inner class access.
  1082. //
  1083. /**
  1084. * Notifies all listeners that have registered interest for
  1085. * notification on this event type. The event instance
  1086. * is lazily created using the parameters passed into
  1087. * the fire method.
  1088. *
  1089. * @param e the event
  1090. * @see EventListenerList
  1091. */
  1092. protected void fireChangedUpdate(DocumentEvent e) {
  1093. super.fireChangedUpdate(e);
  1094. }
  1095. /**
  1096. * Notifies all listeners that have registered interest for
  1097. * notification on this event type. The event instance
  1098. * is lazily created using the parameters passed into
  1099. * the fire method.
  1100. *
  1101. * @param e the event
  1102. * @see EventListenerList
  1103. */
  1104. protected void fireUndoableEditUpdate(UndoableEditEvent e) {
  1105. super.fireUndoableEditUpdate(e);
  1106. }
  1107. /*
  1108. * state defines whether the document is a frame document
  1109. * or not.
  1110. */
  1111. private boolean frameDocument = false;
  1112. private boolean preservesUnknownTags = true;
  1113. /*
  1114. * Used to store a button group for radio buttons in
  1115. * a form.
  1116. */
  1117. private ButtonGroup radioButtonGroup;
  1118. /**
  1119. * Document property for the number of tokens to buffer
  1120. * before building an element subtree to represent them.
  1121. */
  1122. static final String TokenThreshold = "token threshold";
  1123. /**
  1124. * Document property key value. The value for the key will be a Vector
  1125. * of Strings that are comments not found in the body.
  1126. */
  1127. public static final String AdditionalComments = "AdditionalComments";
  1128. /**
  1129. * Document property key value. The value for the key will be a
  1130. * String indicating the default type of stylesheet links.
  1131. */
  1132. /* public */ static final String StyleType = "StyleType";
  1133. /**
  1134. * The location to resolve relative URLs against. By
  1135. * default this will be the document's URL if the document
  1136. * was loaded from a URL. If a base tag is found and
  1137. * can be parsed, it will be used as the base location.
  1138. */
  1139. URL base;
  1140. /**
  1141. * The parser that is used when inserting html into the existing
  1142. * document.
  1143. */
  1144. private HTMLEditorKit.Parser parser;
  1145. /**
  1146. * Used for inserts when a null AttributeSet is supplied.
  1147. */
  1148. private static AttributeSet contentAttributeSet;
  1149. /**
  1150. * Property Maps are registered under, will be a Hashtable.
  1151. */
  1152. static String MAP_PROPERTY = "__MAP__";
  1153. private static char[] NEWLINE;
  1154. static {
  1155. contentAttributeSet = new SimpleAttributeSet();
  1156. ((MutableAttributeSet)contentAttributeSet).
  1157. addAttribute(StyleConstants.NameAttribute,
  1158. HTML.Tag.CONTENT);
  1159. NEWLINE = new char[1];
  1160. NEWLINE[0] = '\n';
  1161. }
  1162. /**
  1163. * An iterator to iterate over a particular type of
  1164. * tag. The iterator is not thread safe. If reliable
  1165. * access to the document is not already ensured by
  1166. * the context under which the iterator is being used,
  1167. * its use should be performed under the protection of
  1168. * Document.render.
  1169. */
  1170. public static abstract class Iterator {
  1171. /**
  1172. * Fetch the attributes for this tag.
  1173. */
  1174. public abstract AttributeSet getAttributes();
  1175. /**
  1176. * Start of the range for which the current occurence of
  1177. * the tag is defined and has the same attributes.
  1178. */
  1179. public abstract int getStartOffset();
  1180. /**
  1181. * End of the range for which the current occurence of
  1182. * the tag is defined and has the same attributes.
  1183. */
  1184. public abstract int getEndOffset();
  1185. /**
  1186. * Move the iterator forward to the next occurence
  1187. * of the tag it represents.
  1188. */
  1189. public abstract void next();
  1190. /**
  1191. * Indicates if the iterator is currently
  1192. * representing an occurence of a tag. If
  1193. * false there are no more tags for this iterator.
  1194. */
  1195. public abstract boolean isValid();
  1196. /**
  1197. * Type of tag this iterator represents.
  1198. */
  1199. public abstract HTML.Tag getTag();
  1200. }
  1201. /**
  1202. * An iterator to iterate over a particular type of
  1203. * tag.
  1204. */
  1205. static class LeafIterator extends Iterator {
  1206. LeafIterator(HTML.Tag t, Document doc) {
  1207. tag = t;
  1208. pos = new ElementIterator(doc);
  1209. endOffset = 0;
  1210. next();
  1211. }
  1212. /**
  1213. * Fetches the attributes for this tag.
  1214. */
  1215. public AttributeSet getAttributes() {
  1216. Element elem = pos.current();
  1217. if (elem != null) {
  1218. AttributeSet a = (AttributeSet)
  1219. elem.getAttributes().getAttribute(tag);
  1220. return a;
  1221. }
  1222. return null;
  1223. }
  1224. /**
  1225. * Returns the start of the range for which the current occurence of
  1226. * the tag is defined and has the same attributes.
  1227. */
  1228. public int getStartOffset() {
  1229. Element elem = pos.current();
  1230. if (elem != null) {
  1231. return elem.getStartOffset();
  1232. }
  1233. return -1;
  1234. }
  1235. /**
  1236. * Returns the end of the range for which the current occurence of
  1237. * the tag is defined and has the same attributes.
  1238. */
  1239. public int getEndOffset() {
  1240. return endOffset;
  1241. }
  1242. /**
  1243. * Moves the iterator forward to the next occurence
  1244. * of the tag it represents.
  1245. */
  1246. public void next() {
  1247. for (nextLeaf(pos); isValid(); nextLeaf(pos)) {
  1248. Element elem = pos.current();
  1249. if (elem.getStartOffset() >= endOffset) {
  1250. AttributeSet a = pos.current().getAttributes();
  1251. if (a.isDefined(tag)) {
  1252. // we found the next one
  1253. setEndOffset();
  1254. break;
  1255. }
  1256. }
  1257. }
  1258. }
  1259. /**
  1260. * Returns the type of tag this iterator represents.
  1261. */
  1262. public HTML.Tag getTag() {
  1263. return tag;
  1264. }
  1265. public boolean isValid() {
  1266. return (pos.current() != null);
  1267. }
  1268. /**
  1269. * Moves the given iterator to the next leaf element.
  1270. */
  1271. void nextLeaf(ElementIterator iter) {
  1272. for (iter.next(); iter.current() != null; iter.next()) {
  1273. Element e = iter.current();
  1274. if (e.isLeaf()) {
  1275. break;
  1276. }
  1277. }
  1278. }
  1279. /**
  1280. * Marches a cloned iterator forward to locate the end
  1281. * of the run. This sets the value of endOffset.
  1282. */
  1283. void setEndOffset() {
  1284. AttributeSet a0 = getAttributes();
  1285. endOffset = pos.current().getEndOffset();
  1286. ElementIterator fwd = (ElementIterator) pos.clone();
  1287. for (nextLeaf(fwd); fwd.current() != null; nextLeaf(fwd)) {
  1288. Element e = fwd.current();
  1289. AttributeSet a1 = (AttributeSet) e.getAttributes().getAttribute(tag);
  1290. if ((a1 == null) || (! a1.equals(a0))) {
  1291. break;
  1292. }
  1293. endOffset = e.getEndOffset();
  1294. }
  1295. }
  1296. private int endOffset;
  1297. private HTML.Tag tag;
  1298. private ElementIterator pos;
  1299. }
  1300. /**
  1301. * An HTML reader to load an HTML document with an HTML
  1302. * element structure. This is a set of callbacks from
  1303. * the parser, implemented to create a set of elements
  1304. * tagged with attributes. The parse builds up tokens
  1305. * (ElementSpec) that describe the element subtree desired,
  1306. * and burst it into the document under the protection of
  1307. * a write lock using the insert method on the document
  1308. * outer class.
  1309. * <p>
  1310. * The reader can be configured by registering actions
  1311. * (of type <code>HTMLDocument.HTMLReader.TagAction</code>)
  1312. * that describe how to handle the action. The idea behind
  1313. * the actions provided is that the most natural text editing
  1314. * operations can be provided if the element structure boils
  1315. * down to paragraphs with runs of some kind of style
  1316. * in them. Some things are more naturally specified
  1317. * structurally, so arbitrary structure should be allowed
  1318. * above the paragraphs, but will need to be edited with structural
  1319. * actions. The implication of this is that some of the
  1320. * HTML elements specified in the stream being parsed will
  1321. * be collapsed into attributes, and in some cases paragraphs
  1322. * will be synthesized. When HTML elements have been
  1323. * converted to attributes, the attribute key will be of
  1324. * type HTML.Tag, and the value will be of type AttributeSet
  1325. * so that no information is lost. This enables many of the
  1326. * existing actions to work so that the user can type input,
  1327. * hit the return key, backspace, delete, etc and have a
  1328. * reasonable result. Selections can be created, and attributes
  1329. * applied or removed, etc. With this in mind, the work done
  1330. * by the reader can be categorized into the following kinds
  1331. * of tasks:
  1332. * <dl>
  1333. * <dt>Block
  1334. * <dd>Build the structure like it's specified in the stream.
  1335. * This produces elements that contain other elements.
  1336. * <dt>Paragraph
  1337. * <dd>Like block except that it's expected that the element
  1338. * will be used with a paragraph view so a paragraph element
  1339. * won't need to be synthesized.
  1340. * <dt>Character
  1341. * <dd>Contribute the element as an attribute that will start
  1342. * and stop at arbitrary text locations. This will ultimately
  1343. * be mixed into a run of text, with all of the currently
  1344. * flattened HTML character elements.
  1345. * <dt>Special
  1346. * <dd>Produce an embedded graphical element.
  1347. * <dt>Form
  1348. * <dd>Produce an element that is like the embedded graphical
  1349. * element, except that it also has a component model associated
  1350. * with it.
  1351. * <dt>Hidden
  1352. * <dd>Create an element that is hidden from view when the
  1353. * document is being viewed read-only, and visible when the
  1354. * document is being edited. This is useful to keep the
  1355. * model from losing information, and used to store things
  1356. * like comments and unrecognized tags.
  1357. *
  1358. * </dl>
  1359. * <p>
  1360. * Currently, <APPLET>, <PARAM>, <MAP>, <AREA>, <LINK>,
  1361. * <SCRIPT> and <STYLE> are unsupported.
  1362. *
  1363. * <p>
  1364. * The assignment of the actions described is shown in the
  1365. * following table for the tags defined in <code>HTML.Tag</code>.
  1366. * <table>
  1367. * <tr><td><code>HTML.Tag.A</code> <td>CharacterAction
  1368. * <tr><td><code>HTML.Tag.ADDRESS</code> <td>CharacterAction
  1369. * <tr><td><code>HTML.Tag.APPLET</code> <td>HiddenAction
  1370. * <tr><td><code>HTML.Tag.AREA</code> <td>AreaAction
  1371. * <tr><td><code>HTML.Tag.B</code> <td>CharacterAction
  1372. * <tr><td><code>HTML.Tag.BASE</code> <td>BaseAction
  1373. * <tr><td><code>HTML.Tag.BASEFONT</code> <td>CharacterAction
  1374. * <tr><td&g