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><code>HTML.Tag.BIG</code> <td>CharacterAction
  1375. * <tr><td><code>HTML.Tag.BLOCKQUOTE</code><td>BlockAction
  1376. * <tr><td><code>HTML.Tag.BODY</code> <td>BlockAction
  1377. * <tr><td><code>HTML.Tag.BR</code> <td>SpecialAction
  1378. * <tr><td><code>HTML.Tag.CAPTION</code> <td>BlockAction
  1379. * <tr><td><code>HTML.Tag.CENTER</code> <td>BlockAction
  1380. * <tr><td><code>HTML.Tag.CITE</code> <td>CharacterAction
  1381. * <tr><td><code>HTML.Tag.CODE</code> <td>CharacterAction
  1382. * <tr><td><code>HTML.Tag.DD</code> <td>BlockAction
  1383. * <tr><td><code>HTML.Tag.DFN</code> <td>CharacterAction
  1384. * <tr><td><code>HTML.Tag.DIR</code> <td>BlockAction
  1385. * <tr><td><code>HTML.Tag.DIV</code> <td>BlockAction
  1386. * <tr><td><code>HTML.Tag.DL</code> <td>BlockAction
  1387. * <tr><td><code>HTML.Tag.DT</code> <td>ParagraphAction
  1388. * <tr><td><code>HTML.Tag.EM</code> <td>CharacterAction
  1389. * <tr><td><code>HTML.Tag.FONT</code> <td>CharacterAction
  1390. * <tr><td><code>HTML.Tag.FORM</code> <td>CharacterAction
  1391. * <tr><td><code>HTML.Tag.FRAME</code> <td>SpecialAction
  1392. * <tr><td><code>HTML.Tag.FRAMESET</code> <td>BlockAction
  1393. * <tr><td><code>HTML.Tag.H1</code> <td>ParagraphAction
  1394. * <tr><td><code>HTML.Tag.H2</code> <td>ParagraphAction
  1395. * <tr><td><code>HTML.Tag.H3</code> <td>ParagraphAction
  1396. * <tr><td><code>HTML.Tag.H4</code> <td>ParagraphAction
  1397. * <tr><td><code>HTML.Tag.H5</code> <td>ParagraphAction
  1398. * <tr><td><code>HTML.Tag.H6</code> <td>ParagraphAction
  1399. * <tr><td><code>HTML.Tag.HEAD</code> <td>HeadAction
  1400. * <tr><td><code>HTML.Tag.HR</code> <td>SpecialAction
  1401. * <tr><td><code>HTML.Tag.HTML</code> <td>BlockAction
  1402. * <tr><td><code>HTML.Tag.I</code> <td>CharacterAction
  1403. * <tr><td><code>HTML.Tag.IMG</code> <td>SpecialAction
  1404. * <tr><td><code>HTML.Tag.INPUT</code> <td>FormAction
  1405. * <tr><td><code>HTML.Tag.ISINDEX</code> <td>IsndexAction
  1406. * <tr><td><code>HTML.Tag.KBD</code> <td>CharacterAction
  1407. * <tr><td><code>HTML.Tag.LI</code> <td>BlockAction
  1408. * <tr><td><code>HTML.Tag.LINK</code> <td>LinkAction
  1409. * <tr><td><code>HTML.Tag.MAP</code> <td>MapAction
  1410. * <tr><td><code>HTML.Tag.MENU</code> <td>BlockAction
  1411. * <tr><td><code>HTML.Tag.META</code> <td>MetaAction
  1412. * <tr><td><code>HTML.Tag.NOFRAMES</code> <td>BlockAction
  1413. * <tr><td><code>HTML.Tag.OBJECT</code> <td>SpecialAction
  1414. * <tr><td><code>HTML.Tag.OL</code> <td>BlockAction
  1415. * <tr><td><code>HTML.Tag.OPTION</code> <td>FormAction
  1416. * <tr><td><code>HTML.Tag.P</code> <td>ParagraphAction
  1417. * <tr><td><code>HTML.Tag.PARAM</code> <td>HiddenAction
  1418. * <tr><td><code>HTML.Tag.PRE</code> <td>PreAction
  1419. * <tr><td><code>HTML.Tag.SAMP</code> <td>CharacterAction
  1420. * <tr><td><code>HTML.Tag.SCRIPT</code> <td>HiddenAction
  1421. * <tr><td><code>HTML.Tag.SELECT</code> <td>FormAction
  1422. * <tr><td><code>HTML.Tag.SMALL</code> <td>CharacterAction
  1423. * <tr><td><code>HTML.Tag.STRIKE</code> <td>CharacterAction
  1424. * <tr><td><code>HTML.Tag.S</code> <td>CharacterAction
  1425. * <tr><td><code>HTML.Tag.STRONG</code> <td>CharacterAction
  1426. * <tr><td><code>HTML.Tag.STYLE</code> <td>StyleAction
  1427. * <tr><td><code>HTML.Tag.SUB</code> <td>CharacterAction
  1428. * <tr><td><code>HTML.Tag.SUP</code> <td>CharacterAction
  1429. * <tr><td><code>HTML.Tag.TABLE</code> <td>BlockAction
  1430. * <tr><td><code>HTML.Tag.TD</code> <td>BlockAction
  1431. * <tr><td><code>HTML.Tag.TEXTAREA</code> <td>FormAction
  1432. * <tr><td><code>HTML.Tag.TH</code> <td>BlockAction
  1433. * <tr><td><code>HTML.Tag.TITLE</code> <td>TitleAction
  1434. * <tr><td><code>HTML.Tag.TR</code> <td>BlockAction
  1435. * <tr><td><code>HTML.Tag.TT</code> <td>CharacterAction
  1436. * <tr><td><code>HTML.Tag.U</code> <td>CharacterAction
  1437. * <tr><td><code>HTML.Tag.UL</code> <td>BlockAction
  1438. * <tr><td><code>HTML.Tag.VAR</code> <td>CharacterAction
  1439. * </table>
  1440. */
  1441. public class HTMLReader extends HTMLEditorKit.ParserCallback {
  1442. public HTMLReader(int offset) {
  1443. this(offset, 0, 0, null);
  1444. }
  1445. public HTMLReader(int offset, int popDepth, int pushDepth,
  1446. HTML.Tag insertTag) {
  1447. this(offset, popDepth, pushDepth, insertTag, true, false, true);
  1448. }
  1449. /**
  1450. * Generates a RuntimeException (will eventually generate
  1451. * a BadLocationException when API changes are alloced) if inserting
  1452. * into non
  1453. * empty document, <code>insertTag</code> is non null, and
  1454. * <code>offset</code> is not in the body.
  1455. */
  1456. // PENDING(sky): Add throws BadLocationException and remove
  1457. // RuntimeException
  1458. HTMLReader(int offset, int popDepth, int pushDepth,
  1459. HTML.Tag insertTag, boolean insertInsertTag,
  1460. boolean insertAfterImplied, boolean wantsTrailingNewline) {
  1461. emptyDocument = (getLength() == 0);
  1462. isStyleCSS = "text/css".equals(getDefaultStyleSheetType());
  1463. this.offset = offset;
  1464. threshold = HTMLDocument.this.getTokenThreshold();
  1465. tagMap = new Hashtable(57);
  1466. TagAction na = new TagAction();
  1467. TagAction ba = new BlockAction();
  1468. TagAction pa = new ParagraphAction();
  1469. TagAction ca = new CharacterAction();
  1470. TagAction sa = new SpecialAction();
  1471. TagAction fa = new FormAction();
  1472. TagAction ha = new HiddenAction();
  1473. TagAction conv = new ConvertAction();
  1474. // register handlers for the well known tags
  1475. tagMap.put(HTML.Tag.A, new AnchorAction());
  1476. tagMap.put(HTML.Tag.ADDRESS, ca);
  1477. tagMap.put(HTML.Tag.APPLET, ha);
  1478. tagMap.put(HTML.Tag.AREA, new AreaAction());
  1479. tagMap.put(HTML.Tag.B, conv);
  1480. tagMap.put(HTML.Tag.BASE, new BaseAction());
  1481. tagMap.put(HTML.Tag.BASEFONT, ca);
  1482. tagMap.put(HTML.Tag.BIG, ca);
  1483. tagMap.put(HTML.Tag.BLOCKQUOTE, ba);
  1484. tagMap.put(HTML.Tag.BODY, ba);
  1485. tagMap.put(HTML.Tag.BR, sa);
  1486. tagMap.put(HTML.Tag.CAPTION, ba);
  1487. tagMap.put(HTML.Tag.CENTER, ba);
  1488. tagMap.put(HTML.Tag.CITE, ca);
  1489. tagMap.put(HTML.Tag.CODE, ca);
  1490. tagMap.put(HTML.Tag.DD, ba);
  1491. tagMap.put(HTML.Tag.DFN, ca);
  1492. tagMap.put(HTML.Tag.DIR, ba);
  1493. tagMap.put(HTML.Tag.DIV, ba);
  1494. tagMap.put(HTML.Tag.DL, ba);
  1495. tagMap.put(HTML.Tag.DT, pa);
  1496. tagMap.put(HTML.Tag.EM, ca);
  1497. tagMap.put(HTML.Tag.FONT, conv);
  1498. tagMap.put(HTML.Tag.FORM, ca);
  1499. tagMap.put(HTML.Tag.FRAME, sa);
  1500. tagMap.put(HTML.Tag.FRAMESET, ba);
  1501. tagMap.put(HTML.Tag.H1, pa);
  1502. tagMap.put(HTML.Tag.H2, pa);
  1503. tagMap.put(HTML.Tag.H3, pa);
  1504. tagMap.put(HTML.Tag.H4, pa);
  1505. tagMap.put(HTML.Tag.H5, pa);
  1506. tagMap.put(HTML.Tag.H6, pa);
  1507. tagMap.put(HTML.Tag.HEAD, new HeadAction());
  1508. tagMap.put(HTML.Tag.HR, sa);
  1509. tagMap.put(HTML.Tag.HTML, ba);
  1510. tagMap.put(HTML.Tag.I, conv);
  1511. tagMap.put(HTML.Tag.IMG, sa);
  1512. tagMap.put(HTML.Tag.INPUT, fa);
  1513. tagMap.put(HTML.Tag.ISINDEX, new IsindexAction());
  1514. tagMap.put(HTML.Tag.KBD, ca);
  1515. tagMap.put(HTML.Tag.LI, ba);
  1516. tagMap.put(HTML.Tag.LINK, new LinkAction());
  1517. tagMap.put(HTML.Tag.MAP, new MapAction());
  1518. tagMap.put(HTML.Tag.MENU, ba);
  1519. tagMap.put(HTML.Tag.META, new MetaAction());
  1520. tagMap.put(HTML.Tag.NOBR, ca);
  1521. tagMap.put(HTML.Tag.NOFRAMES, ba);
  1522. tagMap.put(HTML.Tag.OBJECT, sa);
  1523. tagMap.put(HTML.Tag.OL, ba);
  1524. tagMap.put(HTML.Tag.OPTION, fa);
  1525. tagMap.put(HTML.Tag.P, pa);
  1526. tagMap.put(HTML.Tag.PARAM, new ObjectAction());
  1527. tagMap.put(HTML.Tag.PRE, new PreAction());
  1528. tagMap.put(HTML.Tag.SAMP, ca);
  1529. tagMap.put(HTML.Tag.SCRIPT, ha);
  1530. tagMap.put(HTML.Tag.SELECT, fa);
  1531. tagMap.put(HTML.Tag.SMALL, ca);
  1532. tagMap.put(HTML.Tag.STRIKE, conv);
  1533. tagMap.put(HTML.Tag.S, ca);
  1534. tagMap.put(HTML.Tag.STRONG, ca);
  1535. tagMap.put(HTML.Tag.STYLE, new StyleAction());
  1536. tagMap.put(HTML.Tag.SUB, conv);
  1537. tagMap.put(HTML.Tag.SUP, conv);
  1538. tagMap.put(HTML.Tag.TABLE, ba);
  1539. tagMap.put(HTML.Tag.TD, ba);
  1540. tagMap.put(HTML.Tag.TEXTAREA, fa);
  1541. tagMap.put(HTML.Tag.TH, ba);
  1542. tagMap.put(HTML.Tag.TITLE, new TitleAction());
  1543. tagMap.put(HTML.Tag.TR, ba);
  1544. tagMap.put(HTML.Tag.TT, ca);
  1545. tagMap.put(HTML.Tag.U, conv);
  1546. tagMap.put(HTML.Tag.UL, ba);
  1547. tagMap.put(HTML.Tag.VAR, ca);
  1548. // Clear out the old comments.
  1549. putProperty(AdditionalComments, null);
  1550. if (insertTag != null) {
  1551. this.insertTag = insertTag;
  1552. this.popDepth = popDepth;
  1553. this.pushDepth = pushDepth;
  1554. this.insertInsertTag = insertInsertTag;
  1555. foundInsertTag = false;
  1556. }
  1557. else {
  1558. foundInsertTag = true;
  1559. }
  1560. if (insertAfterImplied) {
  1561. this.popDepth = popDepth;
  1562. this.pushDepth = pushDepth;
  1563. this.insertAfterImplied = true;
  1564. foundInsertTag = false;
  1565. midInsert = false;
  1566. this.insertInsertTag = true;
  1567. this.wantsTrailingNewline = wantsTrailingNewline;
  1568. }
  1569. else {
  1570. midInsert = (!emptyDocument && insertTag == null);
  1571. if (midInsert) {
  1572. generateEndsSpecsForMidInsert();
  1573. }
  1574. }
  1575. }
  1576. /**
  1577. * Generates an initial batch of end ElementSpecs in parseBuffer
  1578. * to position future inserts into the body.
  1579. */
  1580. private void generateEndsSpecsForMidInsert() {
  1581. int count = heightToElementWithName(HTML.Tag.BODY,
  1582. Math.max(0, offset - 1));
  1583. boolean joinNext = false;
  1584. if (count == -1 && offset > 0) {
  1585. count = heightToElementWithName(HTML.Tag.BODY, offset);
  1586. if (count != -1) {
  1587. // Previous isn't in body, but current is. Have to
  1588. // do some end specs, followed by join next.
  1589. count = depthTo(offset - 1) - 1;
  1590. joinNext = true;
  1591. }
  1592. }
  1593. if (count == -1) {
  1594. throw new RuntimeException("Must insert new content into body element-");
  1595. }
  1596. if (count != -1) {
  1597. // Insert a newline, if necessary.
  1598. try {
  1599. if (!joinNext && offset > 0 &&
  1600. !getText(offset - 1, 1).equals("\n")) {
  1601. SimpleAttributeSet newAttrs = new SimpleAttributeSet();
  1602. newAttrs.addAttribute(StyleConstants.NameAttribute,
  1603. HTML.Tag.CONTENT);
  1604. ElementSpec spec = new ElementSpec(newAttrs,
  1605. ElementSpec.ContentType, NEWLINE, 0, 1);
  1606. parseBuffer.addElement(spec);
  1607. }
  1608. // Should never throw, but will catch anyway.
  1609. } catch (BadLocationException ble) {}
  1610. while (count-- > 0) {
  1611. parseBuffer.addElement(new ElementSpec
  1612. (null, ElementSpec.EndTagType));
  1613. }
  1614. if (joinNext) {
  1615. ElementSpec spec = new ElementSpec(null, ElementSpec.
  1616. StartTagType);
  1617. spec.setDirection(ElementSpec.JoinNextDirection);
  1618. parseBuffer.addElement(spec);
  1619. }
  1620. }
  1621. // We should probably throw an exception if (count == -1)
  1622. // Or look for the body and reset the offset.
  1623. }
  1624. /**
  1625. * @return number of parents to reach the child at offset.
  1626. */
  1627. private int depthTo(int offset) {
  1628. Element e = getDefaultRootElement();
  1629. int count = 0;
  1630. while (!e.isLeaf()) {
  1631. count++;
  1632. e = e.getElement(e.getElementIndex(offset));
  1633. }
  1634. return count;
  1635. }
  1636. /**
  1637. * @return number of parents of the leaf at <code>offset</code>
  1638. * until a parent with name, <code>name</code> has been
  1639. * found. -1 indicates no matching parent with
  1640. * <code>name</code>.
  1641. */
  1642. private int heightToElementWithName(Object name, int offset) {
  1643. Element e = getCharacterElement(offset).getParentElement();
  1644. int count = 0;
  1645. while (e != null && e.getAttributes().getAttribute
  1646. (StyleConstants.NameAttribute) != name) {
  1647. count++;
  1648. e = e.getParentElement();
  1649. }
  1650. return (e == null) ? -1 : count;
  1651. }
  1652. /**
  1653. * This will make sure the fake element (path at getLength())
  1654. * has the form HTML BODY P.
  1655. */
  1656. private void adjustEndElement() {
  1657. int length = getLength();
  1658. if (length == 0) {
  1659. return;
  1660. }
  1661. obtainLock();
  1662. try {
  1663. Element[] pPath = getPathTo(length - 1);
  1664. if (pPath.length > 1 &&
  1665. pPath[1].getAttributes().getAttribute
  1666. (StyleConstants.NameAttribute) == HTML.Tag.BODY &&
  1667. pPath[1].getEndOffset() == length) {
  1668. String lastText = getText(length - 1, 1);
  1669. DefaultDocumentEvent event;
  1670. Element[] added;
  1671. Element[] removed;
  1672. int index;
  1673. // Remove the fake second body.
  1674. added = new Element[0];
  1675. removed = new Element[1];
  1676. index = pPath[0].getElementIndex(length);
  1677. removed[0] = pPath[0].getElement(index);
  1678. ((BranchElement)pPath[0]).replace(index, 1, added);
  1679. ElementEdit firstEdit = new ElementEdit(pPath[0], index,
  1680. removed, added);
  1681. // And then add paragraph, or adjust deepest leaf.
  1682. if (pPath.length == 3 &&
  1683. (pPath[2].getAttributes().getAttribute
  1684. (StyleConstants.NameAttribute) == HTML.Tag.P ||
  1685. pPath[2].getAttributes().getAttribute
  1686. (StyleConstants.NameAttribute) == HTML.Tag.IMPLIED) &&
  1687. !lastText.equals("\n")) {
  1688. index = pPath[2].getElementIndex(length - 1);
  1689. AttributeSet attrs = pPath[2].getElement(index).
  1690. getAttributes();
  1691. if (attrs.getAttributeCount() == 1 &&
  1692. attrs.getAttribute(StyleConstants.NameAttribute)
  1693. == HTML.Tag.CONTENT) {
  1694. // Can extend existing one.
  1695. added = new Element[1];
  1696. removed = new Element[1];
  1697. removed[0] = pPath[2].getElement(index);
  1698. int start = removed[0].getStartOffset();
  1699. added[0] = createLeafElement(pPath[2], attrs,
  1700. start, length + 1);
  1701. ((BranchElement)pPath[2]).replace(index, 1, added);
  1702. event = new DefaultDocumentEvent(start,
  1703. length - start + 1, DocumentEvent.
  1704. EventType.CHANGE);
  1705. event.addEdit(new ElementEdit(pPath[2], index,
  1706. removed, added));
  1707. }
  1708. else {
  1709. // Create new leaf.
  1710. SimpleAttributeSet sas = new SimpleAttributeSet();
  1711. sas.addAttribute(StyleConstants.NameAttribute,
  1712. HTML.Tag.CONTENT);
  1713. added = new Element[1];
  1714. added[0] = createLeafElement(pPath[2], sas,
  1715. length, length + 1);
  1716. ((BranchElement)pPath[2]).replace(index + 1, 0,
  1717. added);
  1718. event = new DefaultDocumentEvent(length, 1,
  1719. DocumentEvent.EventType.CHANGE);
  1720. removed = new Element[0];
  1721. event.addEdit(new ElementEdit(pPath[2], index + 1,
  1722. removed, added));
  1723. }
  1724. }
  1725. else {
  1726. // Create paragraph
  1727. // If the previous sibling (element at
  1728. // ppPath[1].getElement(length - 1)) is a leaf we
  1729. // should really remove it and combine the two, as
  1730. // leafs of the body aren't a good idea, but this only
  1731. // happens when reading an empty document, so it isn't
  1732. // all that common.
  1733. SimpleAttributeSet sas = new SimpleAttributeSet();
  1734. sas.addAttribute(StyleConstants.NameAttribute,
  1735. HTML.Tag.P);
  1736. BranchElement newP = (BranchElement)
  1737. createBranchElement(pPath[1],sas);
  1738. added = new Element[1];
  1739. added[0] = newP;
  1740. removed = new Element[0];
  1741. index = pPath[1].getElementIndex(length - 1) + 1;
  1742. ((BranchElement)pPath[1]).replace(index, 0, added);
  1743. event = new DefaultDocumentEvent(length, 1,
  1744. DocumentEvent.EventType.CHANGE);
  1745. event.addEdit(new ElementEdit(pPath[1], index,
  1746. removed, added));
  1747. added = new Element[1];
  1748. sas = new SimpleAttributeSet();
  1749. sas.addAttribute(StyleConstants.NameAttribute,
  1750. HTML.Tag.CONTENT);
  1751. added[0] = createLeafElement(newP, sas, length,
  1752. length + 1);
  1753. newP.replace(0, 0, added);
  1754. }
  1755. event.addEdit(firstEdit);
  1756. event.end();
  1757. // And finally post the event.
  1758. fireChangedUpdate(event);
  1759. fireUndoableEditUpdate(new UndoableEditEvent(this, event));
  1760. }
  1761. }
  1762. catch (BadLocationException ble) {
  1763. }
  1764. finally {
  1765. releaseLock();
  1766. }
  1767. }
  1768. private Element[] getPathTo(int offset) {
  1769. Stack elements = new Stack();
  1770. Element e = getDefaultRootElement();
  1771. int index;
  1772. while (!e.isLeaf()) {
  1773. elements.push(e);
  1774. e = e.getElement(e.getElementIndex(offset));
  1775. }
  1776. Element[] retValue = new Element[elements.size()];
  1777. elements.copyInto(retValue);
  1778. return retValue;
  1779. }
  1780. // -- HTMLEditorKit.ParserCallback methods --------------------
  1781. /**
  1782. * The last method called on the reader. It allows
  1783. * any pending changes to be flushed into the document.
  1784. * Since this is currently loading synchronously, the entire
  1785. * set of changes are pushed in at this point.
  1786. */
  1787. public void flush() throws BadLocationException {
  1788. flushBuffer(true);
  1789. if (emptyDocument && !insertAfterImplied) {
  1790. adjustEndElement();
  1791. }
  1792. }
  1793. /**
  1794. * Called by the parser to indicate a block of text was
  1795. * encountered.
  1796. */
  1797. public void handleText(char[] data, int pos) {
  1798. if (midInsert && !inBody) {
  1799. return;
  1800. }
  1801. if (inTextArea) {
  1802. textAreaContent(data);
  1803. } else if (inPre) {
  1804. preContent(data);
  1805. } else if (inTitle) {
  1806. putProperty(Document.TitleProperty, new String(data));
  1807. } else if (option != null) {
  1808. option.setLabel(new String(data));
  1809. } else if (inStyle) {
  1810. if (styles != null) {
  1811. styles.addElement(new String(data));
  1812. }
  1813. } else if (inBlock > 0) {
  1814. if (!foundInsertTag && insertAfterImplied) {
  1815. // Assume content should be added.
  1816. foundInsertTag(false);
  1817. foundInsertTag = true;
  1818. inParagraph = impliedP = true;
  1819. }
  1820. if (data.length >= 1) {
  1821. addContent(data, 0, data.length);
  1822. }
  1823. }
  1824. }
  1825. /**
  1826. * Callback from the parser. Route to the appropriate
  1827. * handler for the tag.
  1828. */
  1829. public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
  1830. if (midInsert && !inBody) {
  1831. if (t == HTML.Tag.BODY) {
  1832. inBody = true;
  1833. // Increment inBlock since we know we are in the body,
  1834. // this is needed incase an implied-p is needed. If
  1835. // inBlock isn't incremented, and an implied-p is
  1836. // encountered, addContent won't be called!
  1837. inBlock++;
  1838. }
  1839. return;
  1840. }
  1841. if (!inBody && t == HTML.Tag.BODY) {
  1842. inBody = true;
  1843. }
  1844. if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
  1845. // Map the style attributes.
  1846. String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
  1847. a.removeAttribute(HTML.Attribute.STYLE);
  1848. styleAttributes = getStyleSheet().getDeclaration(decl);
  1849. a.addAttributes(styleAttributes);
  1850. }
  1851. else {
  1852. styleAttributes = null;
  1853. }
  1854. TagAction action = (TagAction) tagMap.get(t);
  1855. if (action != null) {
  1856. action.start(t, a);
  1857. }
  1858. }
  1859. public void handleComment(char[] data, int pos) {
  1860. if (inStyle) {
  1861. if (styles != null) {
  1862. styles.addElement(new String(data));
  1863. }
  1864. }
  1865. else if (getPreservesUnknownTags()) {
  1866. if (inBlock == 0) {
  1867. // Comment outside of body, will not be able to show it,
  1868. // but can add it as a property on the Document.
  1869. Object comments = getProperty(AdditionalComments);
  1870. if (comments != null && !(comments instanceof Vector)) {
  1871. // No place to put comment.
  1872. return;
  1873. }
  1874. if (comments == null) {
  1875. comments = new Vector();
  1876. putProperty(AdditionalComments, comments);
  1877. }
  1878. ((Vector)comments).addElement(new String(data));
  1879. return;
  1880. }
  1881. SimpleAttributeSet sas = new SimpleAttributeSet();
  1882. sas.addAttribute(HTML.Attribute.COMMENT, new String(data));
  1883. addSpecialElement(HTML.Tag.COMMENT, sas);
  1884. }
  1885. }
  1886. /**
  1887. * Callback from the parser. Route to the appropriate
  1888. * handler for the tag.
  1889. */
  1890. public void handleEndTag(HTML.Tag t, int pos) {
  1891. if (midInsert && !inBody) {
  1892. return;
  1893. }
  1894. if (t == HTML.Tag.BODY) {
  1895. inBody = false;
  1896. if (midInsert) {
  1897. inBlock--;
  1898. }
  1899. }
  1900. TagAction action = (TagAction) tagMap.get(t);
  1901. if (action != null) {
  1902. action.end(t);
  1903. }
  1904. }
  1905. /**
  1906. * Callback from the parser. Route to the appropriate
  1907. * handler for the tag.
  1908. */
  1909. public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
  1910. if (midInsert && !inBody) {
  1911. return;
  1912. }
  1913. if (isStyleCSS && a.isDefined(HTML.Attribute.STYLE)) {
  1914. // Map the style attributes.
  1915. String decl = (String)a.getAttribute(HTML.Attribute.STYLE);
  1916. a.removeAttribute(HTML.Attribute.STYLE);
  1917. styleAttributes = getStyleSheet().getDeclaration(decl);
  1918. a.addAttributes(styleAttributes);
  1919. }
  1920. else {
  1921. styleAttributes = null;
  1922. }
  1923. TagAction action = (TagAction) tagMap.get(t);
  1924. if (action != null) {
  1925. action.start(t, a);
  1926. action.end(t);
  1927. }
  1928. else if (getPreservesUnknownTags()) {
  1929. // unknown tag, only add if should preserve it.
  1930. addSpecialElement(t, a);
  1931. }
  1932. }
  1933. /**
  1934. * This is invoked after the stream has been parsed, but before
  1935. * <code>flush</code>. <code>eol</code> will be one of \n, \r
  1936. * or \r\n, which ever is encountered the most in parsing the
  1937. * stream.
  1938. *
  1939. * @since 1.3
  1940. */
  1941. public void handleEndOfLineString(String eol) {
  1942. if (emptyDocument && eol != null) {
  1943. putProperty(DefaultEditorKit.EndOfLineStringProperty,
  1944. eol);
  1945. }
  1946. }
  1947. // ---- tag handling support ------------------------------
  1948. /**
  1949. * Registers a handler for the given tag. By default
  1950. * all of the well-known tags will have been registered.
  1951. * This can be used to change the handling of a particular
  1952. * tag or to add support for custom tags.
  1953. */
  1954. protected void registerTag(HTML.Tag t, TagAction a) {
  1955. tagMap.put(t, a);
  1956. }
  1957. /**
  1958. * An action to be performed in response
  1959. * to parsing a tag. This allows customization
  1960. * of how each tag is handled and avoids a large
  1961. * switch statement.
  1962. */
  1963. public class TagAction {
  1964. /**
  1965. * Called when a start tag is seen for the
  1966. * type of tag this action was registered
  1967. * to. The tag argument indicates the actual
  1968. * tag for those actions that are shared across
  1969. * many tags. By default this does nothing and
  1970. * completely ignores the tag.
  1971. */
  1972. public void start(HTML.Tag t, MutableAttributeSet a) {
  1973. }
  1974. /**
  1975. * Called when an end tag is seen for the
  1976. * type of tag this action was registered
  1977. * to. The tag argument indicates the actual
  1978. * tag for those actions that are shared across
  1979. * many tags. By default this does nothing and
  1980. * completely ignores the tag.
  1981. */
  1982. public void end(HTML.Tag t) {
  1983. }
  1984. }
  1985. public class BlockAction extends TagAction {
  1986. public void start(HTML.Tag t, MutableAttributeSet attr) {
  1987. blockOpen(t, attr);
  1988. }
  1989. public void end(HTML.Tag t) {
  1990. blockClose(t);
  1991. }
  1992. }
  1993. public class ParagraphAction extends BlockAction {
  1994. public void start(HTML.Tag t, MutableAttributeSet a) {
  1995. super.start(t, a);
  1996. inParagraph = true;
  1997. }
  1998. public void end(HTML.Tag t) {
  1999. super.end(t);
  2000. inParagraph = false;
  2001. }
  2002. }
  2003. public class SpecialAction extends TagAction {
  2004. public void start(HTML.Tag t, MutableAttributeSet a) {
  2005. addSpecialElement(t, a);
  2006. }
  2007. }
  2008. public class IsindexAction extends TagAction {
  2009. public void start(HTML.Tag t, MutableAttributeSet a) {
  2010. blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
  2011. addSpecialElement(t, a);
  2012. blockClose(HTML.Tag.IMPLIED);
  2013. }
  2014. }
  2015. public class HiddenAction extends TagAction {
  2016. public void start(HTML.Tag t, MutableAttributeSet a) {
  2017. addSpecialElement(t, a);
  2018. }
  2019. public void end(HTML.Tag t) {
  2020. if (!isEmpty(t)) {
  2021. MutableAttributeSet a = new SimpleAttributeSet();
  2022. a.addAttribute(HTML.Attribute.ENDTAG, "true");
  2023. addSpecialElement(t, a);
  2024. }
  2025. }
  2026. boolean isEmpty(HTML.Tag t) {
  2027. if (t == HTML.Tag.APPLET ||
  2028. t == HTML.Tag.SCRIPT) {
  2029. return false;
  2030. }
  2031. return true;
  2032. }
  2033. }
  2034. /**
  2035. * Subclass of HiddenAction to set the content type for style sheets,
  2036. * and to set the name of the default style sheet.
  2037. */
  2038. class MetaAction extends HiddenAction {
  2039. public void start(HTML.Tag t, MutableAttributeSet a) {
  2040. Object equiv = a.getAttribute(HTML.Attribute.HTTPEQUIV);
  2041. if (equiv != null) {
  2042. equiv = ((String)equiv).toLowerCase();
  2043. if (equiv.equals("content-style-type")) {
  2044. String value = (String)a.getAttribute
  2045. (HTML.Attribute.CONTENT);
  2046. setDefaultStyleSheetType(value);
  2047. isStyleCSS = "text/css".equals
  2048. (getDefaultStyleSheetType());
  2049. }
  2050. else if (equiv.equals("default-style")) {
  2051. defaultStyle = (String)a.getAttribute
  2052. (HTML.Attribute.CONTENT);
  2053. }
  2054. }
  2055. super.start(t, a);
  2056. }
  2057. boolean isEmpty(HTML.Tag t) {
  2058. return true;
  2059. }
  2060. }
  2061. /**
  2062. * End if overridden to create the necessary stylesheets that
  2063. * are referenced via the link tag. It is done in this manner
  2064. * as the meta tag can be used to specify an alternate style sheet,
  2065. * and is not guaranteed to come before the link tags.
  2066. */
  2067. class HeadAction extends BlockAction {
  2068. public void start(HTML.Tag t, MutableAttributeSet a) {
  2069. inHead = true;
  2070. // This check of the insertTag is put in to avoid considering
  2071. // the implied-p that is generated for the head. This allows
  2072. // inserts for HR to work correctly.
  2073. if ((insertTag == null && !insertAfterImplied) ||
  2074. (insertTag == HTML.Tag.HEAD) ||
  2075. (insertAfterImplied &&
  2076. (foundInsertTag || !a.isDefined(IMPLIED)))) {
  2077. super.start(t, a);
  2078. }
  2079. }
  2080. public void end(HTML.Tag t) {
  2081. inHead = inStyle = false;
  2082. // See if there is a StyleSheet to link to.
  2083. if (styles != null) {
  2084. boolean isDefaultCSS = isStyleCSS;
  2085. for (int counter = 0, maxCounter = styles.size();
  2086. counter < maxCounter;) {
  2087. Object value = styles.elementAt(counter);
  2088. if (value == HTML.Tag.LINK) {
  2089. handleLink((AttributeSet)styles.
  2090. elementAt(++counter));
  2091. counter++;
  2092. }
  2093. else {
  2094. // Rule.
  2095. // First element gives type.
  2096. String type = (String)styles.elementAt(++counter);
  2097. boolean isCSS = (type == null) ? isDefaultCSS :
  2098. type.equals("text/css");
  2099. while (++counter < maxCounter &&
  2100. (styles.elementAt(counter)
  2101. instanceof String)) {
  2102. if (isCSS) {
  2103. addCSSRules((String)styles.elementAt
  2104. (counter));
  2105. }
  2106. }
  2107. }
  2108. }
  2109. }
  2110. if ((insertTag == null && !insertAfterImplied) ||
  2111. insertTag == HTML.Tag.HEAD ||
  2112. (insertAfterImplied && foundInsertTag)) {
  2113. super.end(t);
  2114. }
  2115. }
  2116. boolean isEmpty(HTML.Tag t) {
  2117. return false;
  2118. }
  2119. private void handleLink(AttributeSet attr) {
  2120. // Link.
  2121. String type = (String)attr.getAttribute(HTML.Attribute.TYPE);
  2122. if (type == null) {
  2123. type = getDefaultStyleSheetType();
  2124. }
  2125. // Only choose if type==text/css
  2126. // Select link if rel==stylesheet.
  2127. // Otherwise if rel==alternate stylesheet and
  2128. // title matches default style.
  2129. if (type.equals("text/css")) {
  2130. String rel = (String)attr.getAttribute(HTML.Attribute.REL);
  2131. String title = (String)attr.getAttribute
  2132. (HTML.Attribute.TITLE);
  2133. String media = (String)attr.getAttribute
  2134. (HTML.Attribute.MEDIA);
  2135. if (media == null) {
  2136. media = "all";
  2137. }
  2138. else {
  2139. media = media.toLowerCase();
  2140. }
  2141. if (rel != null) {
  2142. rel = rel.toLowerCase();
  2143. if ((media.indexOf("all") != -1 ||
  2144. media.indexOf("screen") != -1) &&
  2145. (rel.equals("stylesheet") ||
  2146. (rel.equals("alternate stylesheet") &&
  2147. title.equals(defaultStyle)))) {
  2148. linkCSSStyleSheet((String)attr.getAttribute
  2149. (HTML.Attribute.HREF));
  2150. }
  2151. }
  2152. }
  2153. }
  2154. }
  2155. /**
  2156. * A subclass to add the AttributeSet to styles if the
  2157. * attributes contains an attribute for 'rel' with value
  2158. * 'stylesheet' or 'alternate stylesheet'.
  2159. */
  2160. class LinkAction extends HiddenAction {
  2161. public void start(HTML.Tag t, MutableAttributeSet a) {
  2162. String rel = (String)a.getAttribute(HTML.Attribute.REL);
  2163. if (rel != null) {
  2164. rel = rel.toLowerCase();
  2165. if (rel.equals("stylesheet") ||
  2166. rel.equals("alternate stylesheet")) {
  2167. if (styles == null) {
  2168. styles = new Vector(3);
  2169. }
  2170. styles.addElement(t);
  2171. styles.addElement(a.copyAttributes());
  2172. }
  2173. }
  2174. super.start(t, a);
  2175. }
  2176. }
  2177. class MapAction extends TagAction {
  2178. public void start(HTML.Tag t, MutableAttributeSet a) {
  2179. lastMap = new Map((String)a.getAttribute(HTML.Attribute.NAME));
  2180. addMap(lastMap);
  2181. }
  2182. public void end(HTML.Tag t) {
  2183. }
  2184. }
  2185. class AreaAction extends TagAction {
  2186. public void start(HTML.Tag t, MutableAttributeSet a) {
  2187. if (lastMap != null) {
  2188. lastMap.addArea(a.copyAttributes());
  2189. }
  2190. }
  2191. public void end(HTML.Tag t) {
  2192. }
  2193. }
  2194. class StyleAction extends TagAction {
  2195. public void start(HTML.Tag t, MutableAttributeSet a) {
  2196. if (inHead) {
  2197. if (styles == null) {
  2198. styles = new Vector(3);
  2199. }
  2200. styles.addElement(t);
  2201. styles.addElement(a.getAttribute(HTML.Attribute.TYPE));
  2202. inStyle = true;
  2203. }
  2204. }
  2205. public void end(HTML.Tag t) {
  2206. inStyle = false;
  2207. }
  2208. boolean isEmpty(HTML.Tag t) {
  2209. return false;
  2210. }
  2211. }
  2212. public class PreAction extends BlockAction {
  2213. public void start(HTML.Tag t, MutableAttributeSet attr) {
  2214. inPre = true;
  2215. blockOpen(t, attr);
  2216. attr.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
  2217. blockOpen(HTML.Tag.IMPLIED, attr);
  2218. }
  2219. public void end(HTML.Tag t) {
  2220. blockClose(HTML.Tag.IMPLIED);
  2221. // set inPre to false after closing, so that if a newline
  2222. // is added it won't generate a blockOpen.
  2223. inPre = false;
  2224. blockClose(t);
  2225. }
  2226. }
  2227. public class CharacterAction extends TagAction {
  2228. public void start(HTML.Tag t, MutableAttributeSet attr) {
  2229. pushCharacterStyle();
  2230. if (!foundInsertTag) {
  2231. // Note that the third argument should really be based off
  2232. // inParagraph and impliedP. If we're wrong (that is
  2233. // insertTagDepthDelta shouldn't be changed), we'll end up
  2234. // removing an extra EndSpec, which won't matter anyway.
  2235. boolean insert = canInsertTag(t, attr, false);
  2236. if (foundInsertTag) {
  2237. if (!inParagraph) {
  2238. inParagraph = impliedP = true;
  2239. }
  2240. }
  2241. if (!insert) {
  2242. return;
  2243. }
  2244. }
  2245. if (attr.isDefined(IMPLIED)) {
  2246. attr.removeAttribute(IMPLIED);
  2247. }
  2248. charAttr.addAttribute(t, attr.copyAttributes());
  2249. if (styleAttributes != null) {
  2250. charAttr.addAttributes(styleAttributes);
  2251. }
  2252. if (t == HTML.Tag.FORM) {
  2253. /* initialize a ButtonGroup when
  2254. FORM tag is encountered. This will
  2255. be used for any radio buttons that
  2256. might be defined in the FORM.
  2257. */
  2258. radioButtonGroup = new ButtonGroup();
  2259. }
  2260. }
  2261. public void end(HTML.Tag t) {
  2262. popCharacterStyle();
  2263. if (t == HTML.Tag.FORM) {
  2264. /*
  2265. * reset the button group to null since
  2266. * the form has ended.
  2267. */
  2268. radioButtonGroup = null;
  2269. }
  2270. }
  2271. }
  2272. /**
  2273. * Provides conversion of HTML tag/attribute
  2274. * mappings that have a corresponding StyleConstants
  2275. * and CSS mapping. The conversion is to CSS attributes.
  2276. */
  2277. class ConvertAction extends TagAction {
  2278. public void start(HTML.Tag t, MutableAttributeSet attr) {
  2279. pushCharacterStyle();
  2280. if (!foundInsertTag) {
  2281. // Note that the third argument should really be based off
  2282. // inParagraph and impliedP. If we're wrong (that is
  2283. // insertTagDepthDelta shouldn't be changed), we'll end up
  2284. // removing an extra EndSpec, which won't matter anyway.
  2285. boolean insert = canInsertTag(t, attr, false);
  2286. if (foundInsertTag) {
  2287. if (!inParagraph) {
  2288. inParagraph = impliedP = true;
  2289. }
  2290. }
  2291. if (!insert) {
  2292. return;
  2293. }
  2294. }
  2295. if (attr.isDefined(IMPLIED)) {
  2296. attr.removeAttribute(IMPLIED);
  2297. }
  2298. if (styleAttributes != null) {
  2299. charAttr.addAttributes(styleAttributes);
  2300. }
  2301. // We also need to add attr, otherwise we lose custom
  2302. // attributes, including class/id for style lookups, and
  2303. // further confuse style lookup (doesn't have tag).
  2304. charAttr.addAttribute(t, attr.copyAttributes());
  2305. StyleSheet sheet = getStyleSheet();
  2306. if (t == HTML.Tag.B) {
  2307. sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_WEIGHT, "bold");
  2308. } else if (t == HTML.Tag.I) {
  2309. sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_STYLE, "italic");
  2310. } else if (t == HTML.Tag.U) {
  2311. Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
  2312. String value = "underline";
  2313. value = (v != null) ? value + "," + v.toString() : value;
  2314. sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
  2315. } else if (t == HTML.Tag.STRIKE) {
  2316. Object v = charAttr.getAttribute(CSS.Attribute.TEXT_DECORATION);
  2317. String value = "line-through";
  2318. value = (v != null) ? value + "," + v.toString() : value;
  2319. sheet.addCSSAttribute(charAttr, CSS.Attribute.TEXT_DECORATION, value);
  2320. } else if (t == HTML.Tag.SUP) {
  2321. Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
  2322. String value = "sup";
  2323. value = (v != null) ? value + "," + v.toString() : value;
  2324. sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
  2325. } else if (t == HTML.Tag.SUB) {
  2326. Object v = charAttr.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
  2327. String value = "sub";
  2328. value = (v != null) ? value + "," + v.toString() : value;
  2329. sheet.addCSSAttribute(charAttr, CSS.Attribute.VERTICAL_ALIGN, value);
  2330. } else if (t == HTML.Tag.FONT) {
  2331. String color = (String) attr.getAttribute(HTML.Attribute.COLOR);
  2332. if (color != null) {
  2333. sheet.addCSSAttribute(charAttr, CSS.Attribute.COLOR, color);
  2334. }
  2335. String face = (String) attr.getAttribute(HTML.Attribute.FACE);
  2336. if (face != null) {
  2337. sheet.addCSSAttribute(charAttr, CSS.Attribute.FONT_FAMILY, face);
  2338. }
  2339. String size = (String) attr.getAttribute(HTML.Attribute.SIZE);
  2340. if (size != null) {
  2341. sheet.addCSSAttributeFromHTML(charAttr, CSS.Attribute.FONT_SIZE, size);
  2342. }
  2343. }
  2344. }
  2345. public void end(HTML.Tag t) {
  2346. popCharacterStyle();
  2347. }
  2348. }
  2349. class AnchorAction extends CharacterAction {
  2350. public void start(HTML.Tag t, MutableAttributeSet attr) {
  2351. // set flag to catch empty anchors
  2352. emptyAnchor = true;
  2353. super.start(t, attr);
  2354. }
  2355. public void end(HTML.Tag t) {
  2356. if (emptyAnchor) {
  2357. // if the anchor was empty it was probably a
  2358. // named anchor point and we don't want to throw
  2359. // it away.
  2360. char[] one = new char[1];
  2361. one[0] = ' ';
  2362. addContent(one, 0, 1);
  2363. }
  2364. super.end(t);
  2365. }
  2366. }
  2367. class TitleAction extends HiddenAction {
  2368. public void start(HTML.Tag t, MutableAttributeSet attr) {
  2369. inTitle = true;
  2370. super.start(t, attr);
  2371. }
  2372. public void end(HTML.Tag t) {
  2373. inTitle = false;
  2374. super.end(t);
  2375. }
  2376. boolean isEmpty(HTML.Tag t) {
  2377. return false;
  2378. }
  2379. }
  2380. class BaseAction extends TagAction {
  2381. public void start(HTML.Tag t, MutableAttributeSet attr) {
  2382. String href = (String) attr.getAttribute(HTML.Attribute.HREF);
  2383. if (href != null) {
  2384. try {
  2385. URL newBase = new URL(base, href);
  2386. setBase(newBase);
  2387. } catch (MalformedURLException ex) {
  2388. }
  2389. }
  2390. }
  2391. }
  2392. class ObjectAction extends SpecialAction {
  2393. public void start(HTML.Tag t, MutableAttributeSet a) {
  2394. if (t == HTML.Tag.PARAM) {
  2395. addParameter(a);
  2396. } else {
  2397. super.start(t, a);
  2398. }
  2399. }
  2400. public void end(HTML.Tag t) {
  2401. if (t != HTML.Tag.PARAM) {
  2402. super.end(t);
  2403. }
  2404. }
  2405. void addParameter(AttributeSet a) {
  2406. String name = (String) a.getAttribute(HTML.Attribute.NAME);
  2407. String value = (String) a.getAttribute(HTML.Attribute.VALUE);
  2408. if ((name != null) && (value != null)) {
  2409. ElementSpec objSpec = (ElementSpec) parseBuffer.lastElement();
  2410. MutableAttributeSet objAttr = (MutableAttributeSet) objSpec.getAttributes();
  2411. objAttr.addAttribute(name, value);
  2412. }
  2413. }
  2414. }
  2415. /**
  2416. * Action to support forms by building all of the elements
  2417. * used to represent form controls. This will process
  2418. * the <INPUT>, <TEXTAREA>, <SELECT>,
  2419. * and <OPTION> tags. The element created by
  2420. * this action is expected to have the attribute
  2421. * <code>StyleConstants.ModelAttribute</code> set to
  2422. * the model that holds the state for the form control.
  2423. * This enables multiple views, and allows document to
  2424. * be iterated over picking up the data of the form.
  2425. * The following are the model assignments for the
  2426. * various type of form elements.
  2427. * <table>
  2428. * <tr>
  2429. * <th>Element Type
  2430. * <th>Model Type
  2431. * <tr>
  2432. * <td>input, type button
  2433. * <td>DefaultButtonModel
  2434. * <tr>
  2435. * <td>input, type checkbox
  2436. * <td>JToggleButton.ToggleButtonModel
  2437. * <tr>
  2438. * <td>input, type image
  2439. * <td>DefaultButtonModel
  2440. * <tr>
  2441. * <td>input, type password
  2442. * <td>PlainDocument
  2443. * <tr>
  2444. * <td>input, type radio
  2445. * <td>JToggleButton.ToggleButtonModel
  2446. * <tr>
  2447. * <td>input, type reset
  2448. * <td>DefaultButtonModel
  2449. * <tr>
  2450. * <td>input, type submit
  2451. * <td>DefaultButtonModel
  2452. * <tr>
  2453. * <td>input, type text or type is null.
  2454. * <td>PlainDocument
  2455. * <tr>
  2456. * <td>select
  2457. * <td>OptionComboBoxModel or an OptionListBoxModel, with an item type of Option
  2458. * <tr>
  2459. * <td>textarea
  2460. * <td>TextAreaDocument
  2461. * </table>
  2462. *
  2463. */
  2464. public class FormAction extends SpecialAction {
  2465. public void start(HTML.Tag t, MutableAttributeSet attr) {
  2466. if (t == HTML.Tag.INPUT) {
  2467. String type = (String)
  2468. attr.getAttribute(HTML.Attribute.TYPE);
  2469. /*
  2470. * if type is not defined teh default is
  2471. * assumed to be text.
  2472. */
  2473. if (type == null) {
  2474. type = "text";
  2475. attr.addAttribute(HTML.Attribute.TYPE, "text");
  2476. }
  2477. setModel(type, attr);
  2478. } else if (t == HTML.Tag.TEXTAREA) {
  2479. inTextArea = true;
  2480. textAreaDocument = new TextAreaDocument();
  2481. attr.addAttribute(StyleConstants.ModelAttribute,
  2482. textAreaDocument);
  2483. } else if (t == HTML.Tag.SELECT) {
  2484. int size = HTML.getIntegerAttributeValue(attr,
  2485. HTML.Attribute.SIZE,
  2486. 1);
  2487. boolean multiple = ((String)attr.getAttribute(HTML.Attribute.MULTIPLE) != null);
  2488. if ((size > 1) || multiple) {
  2489. OptionListModel m = new OptionListModel();
  2490. if (multiple) {
  2491. m.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
  2492. }
  2493. selectModel = m;
  2494. } else {
  2495. selectModel = new OptionComboBoxModel();
  2496. }
  2497. attr.addAttribute(StyleConstants.ModelAttribute,
  2498. selectModel);
  2499. }
  2500. // build the element, unless this is an option.
  2501. if (t == HTML.Tag.OPTION) {
  2502. option = new Option(attr);
  2503. if (selectModel instanceof OptionListModel) {
  2504. OptionListModel m = (OptionListModel)selectModel;
  2505. m.addElement(option);
  2506. if (option.isSelected()) {
  2507. m.addSelectionInterval(optionCount, optionCount);
  2508. m.setInitialSelection(optionCount);
  2509. }
  2510. } else if (selectModel instanceof OptionComboBoxModel) {
  2511. OptionComboBoxModel m = (OptionComboBoxModel)selectModel;
  2512. m.addElement(option);
  2513. if (option.isSelected()) {
  2514. m.setSelectedItem(option);
  2515. m.setInitialSelection(option);
  2516. }
  2517. }
  2518. optionCount++;
  2519. } else {
  2520. super.start(t, attr);
  2521. }
  2522. }
  2523. public void end(HTML.Tag t) {
  2524. if (t == HTML.Tag.OPTION) {
  2525. option = null;
  2526. } else {
  2527. if (t == HTML.Tag.SELECT) {
  2528. selectModel = null;
  2529. optionCount = 0;
  2530. } else if (t == HTML.Tag.TEXTAREA) {
  2531. inTextArea = false;
  2532. /* Now that the textarea has ended,
  2533. * store the entire initial text
  2534. * of the text area. This will
  2535. * enable us to restore the initial
  2536. * state if a reset is requested.
  2537. */
  2538. textAreaDocument.storeInitialText();
  2539. }
  2540. super.end(t);
  2541. }
  2542. }
  2543. void setModel(String type, MutableAttributeSet attr) {
  2544. if (type.equals("submit") ||
  2545. type.equals("reset") ||
  2546. type.equals("image")) {
  2547. // button model
  2548. attr.addAttribute(StyleConstants.ModelAttribute,
  2549. new DefaultButtonModel());
  2550. } else if (type.equals("text") ||
  2551. type.equals("password")) {
  2552. // plain text model
  2553. attr.addAttribute(StyleConstants.ModelAttribute,
  2554. new PlainDocument());
  2555. } else if (type.equals("checkbox") ||
  2556. type.equals("radio")) {
  2557. JToggleButton.ToggleButtonModel model = new JToggleButton.ToggleButtonModel();
  2558. if (type.equals("radio")) {
  2559. model.setGroup(radioButtonGroup);
  2560. }
  2561. attr.addAttribute(StyleConstants.ModelAttribute, model);
  2562. }
  2563. }
  2564. /**
  2565. * If a <SELECT> tag is being processed, this
  2566. * model will be a reference to the model being filled
  2567. * with the <OPTION> elements (which produce
  2568. * objects of type <code>Option</code>.
  2569. */
  2570. Object selectModel;
  2571. int optionCount;
  2572. }
  2573. // --- utility methods used by the reader ------------------
  2574. /**
  2575. * Pushes the current character style on a stack in preparation
  2576. * for forming a new nested character style.
  2577. */
  2578. protected void pushCharacterStyle() {
  2579. charAttrStack.push(charAttr.copyAttributes());
  2580. }
  2581. /**
  2582. * Pops a previously pushed character style off the stack
  2583. * to return to a previous style.
  2584. */
  2585. protected void popCharacterStyle() {
  2586. if (!charAttrStack.empty()) {
  2587. charAttr = (MutableAttributeSet) charAttrStack.peek();
  2588. charAttrStack.pop();
  2589. }
  2590. }
  2591. /**
  2592. * Adds the given content to the textarea document.
  2593. * This method gets called when we are in a textarea
  2594. * context. Therefore all text that is seen belongs
  2595. * to the text area and is hence added to the
  2596. * TextAreaDocument associated with the text area.
  2597. */
  2598. protected void textAreaContent(char[] data) {
  2599. try {
  2600. textAreaDocument.insertString(textAreaDocument.getLength(), new String(data), null);
  2601. } catch (BadLocationException e) {
  2602. // Should do something reasonable
  2603. }
  2604. }
  2605. /**
  2606. * Adds the given content that was encountered in a
  2607. * PRE element. This synthesizes lines to hold the
  2608. * runs of text, and makes calls to addContent to
  2609. * actually add the text.
  2610. */
  2611. protected void preContent(char[] data) {
  2612. int last = 0;
  2613. for (int i = 0; i < data.length; i++) {
  2614. if (data[i] == '\n') {
  2615. addContent(data, last, i - last + 1);
  2616. blockClose(HTML.Tag.IMPLIED);
  2617. MutableAttributeSet a = new SimpleAttributeSet();
  2618. a.addAttribute(CSS.Attribute.WHITE_SPACE, "pre");
  2619. blockOpen(HTML.Tag.IMPLIED, a);
  2620. last = i + 1;
  2621. }
  2622. }
  2623. if (last < data.length) {
  2624. addContent(data, last, data.length - last);
  2625. }
  2626. }
  2627. /**
  2628. * Adds an instruction to the parse buffer to create a
  2629. * block element with the given attributes.
  2630. */
  2631. protected void blockOpen(HTML.Tag t, MutableAttributeSet attr) {
  2632. if (impliedP) {
  2633. impliedP = false;
  2634. inParagraph = false;
  2635. blockClose(HTML.Tag.IMPLIED);
  2636. }
  2637. inBlock++;
  2638. if (!canInsertTag(t, attr, true)) {
  2639. return;
  2640. }
  2641. if (attr.isDefined(IMPLIED)) {
  2642. attr.removeAttribute(IMPLIED);
  2643. }
  2644. lastWasNewline = false;
  2645. attr.addAttribute(StyleConstants.NameAttribute, t);
  2646. ElementSpec es = new ElementSpec(
  2647. attr.copyAttributes(), ElementSpec.StartTagType);
  2648. parseBuffer.addElement(es);
  2649. }
  2650. /**
  2651. * Adds an instruction to the parse buffer to close out
  2652. * a block element of the given type.
  2653. */
  2654. protected void blockClose(HTML.Tag t) {
  2655. inBlock--;
  2656. if (!foundInsertTag) {
  2657. return;
  2658. }
  2659. // Add a new line, if the last character wasn't one. This is
  2660. // needed for proper positioning of the cursor.
  2661. if(!lastWasNewline) {
  2662. addContent(NEWLINE, 0, 1, false);
  2663. lastWasNewline = true;
  2664. }
  2665. if (impliedP) {
  2666. impliedP = false;
  2667. inParagraph = false;
  2668. blockClose(HTML.Tag.IMPLIED);
  2669. }
  2670. // an open/close with no content will be removed, so we
  2671. // add a space of content to keep the element being formed.
  2672. ElementSpec prev = (parseBuffer.size() > 0) ?
  2673. (ElementSpec) parseBuffer.lastElement() : null;
  2674. if (prev != null && prev.getType() == ElementSpec.StartTagType) {
  2675. char[] one = new char[1];
  2676. one[0] = ' ';
  2677. addContent(one, 0, 1);
  2678. }
  2679. ElementSpec es = new ElementSpec(
  2680. null, ElementSpec.EndTagType);
  2681. parseBuffer.addElement(es);
  2682. }
  2683. /**
  2684. * Adds some text with the current character attributes.
  2685. *
  2686. * @param embedded the attributes of an embedded object.
  2687. */
  2688. protected void addContent(char[] data, int offs, int length) {
  2689. addContent(data, offs, length, true);
  2690. }
  2691. /**
  2692. * Adds some text with the current character attributes.
  2693. *
  2694. * @param embedded the attributes of an embedded object.
  2695. */
  2696. protected void addContent(char[] data, int offs, int length,
  2697. boolean generateImpliedPIfNecessary) {
  2698. if (!foundInsertTag) {
  2699. return;
  2700. }
  2701. if (generateImpliedPIfNecessary && (! inParagraph) && (! inPre)) {
  2702. blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
  2703. inParagraph = true;
  2704. impliedP = true;
  2705. }
  2706. emptyAnchor = false;
  2707. charAttr.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
  2708. AttributeSet a = charAttr.copyAttributes();
  2709. ElementSpec es = new ElementSpec(
  2710. a, ElementSpec.ContentType, data, offs, length);
  2711. parseBuffer.addElement(es);
  2712. if (parseBuffer.size() > threshold) {
  2713. try {
  2714. flushBuffer(false);
  2715. } catch (BadLocationException ble) {
  2716. }
  2717. }
  2718. if(length > 0) {
  2719. lastWasNewline = (data[offs + length - 1] == '\n');
  2720. }
  2721. }
  2722. /**
  2723. * Adds content that is basically specified entirely
  2724. * in the attribute set.
  2725. */
  2726. protected void addSpecialElement(HTML.Tag t, MutableAttributeSet a) {
  2727. if ((t != HTML.Tag.FRAME) && (! inParagraph) && (! inPre)) {
  2728. blockOpen(HTML.Tag.IMPLIED, new SimpleAttributeSet());
  2729. inParagraph = true;
  2730. impliedP = true;
  2731. }
  2732. if (!canInsertTag(t, a, true)) {
  2733. return;
  2734. }
  2735. if (a.isDefined(IMPLIED)) {
  2736. a.removeAttribute(IMPLIED);
  2737. }
  2738. emptyAnchor = false;
  2739. a.addAttributes(charAttr);
  2740. a.addAttribute(StyleConstants.NameAttribute, t);
  2741. char[] one = new char[1];
  2742. one[0] = ' ';
  2743. ElementSpec es = new ElementSpec(
  2744. a.copyAttributes(), ElementSpec.ContentType, one, 0, 1);
  2745. parseBuffer.addElement(es);
  2746. // Set this to avoid generating a newline for frames, frames
  2747. // shouldn't have any content, and shouldn't need a newline.
  2748. if (t == HTML.Tag.FRAME) {
  2749. lastWasNewline = true;
  2750. }
  2751. }
  2752. /**
  2753. * Flushes the current parse buffer into the document.
  2754. * @param endOfStream true if there is no more content to parser
  2755. */
  2756. void flushBuffer(boolean endOfStream) throws BadLocationException {
  2757. int oldLength = HTMLDocument.this.getLength();
  2758. int size = parseBuffer.size();
  2759. if (endOfStream && (insertTag != null || insertAfterImplied) &&
  2760. size > 0) {
  2761. adjustEndSpecsForPartialInsert();
  2762. size = parseBuffer.size();
  2763. }
  2764. ElementSpec[] spec = new ElementSpec[size];
  2765. parseBuffer.copyInto(spec);
  2766. if (oldLength == 0 && (insertTag == null && !insertAfterImplied)) {
  2767. create(spec);
  2768. } else {
  2769. insert(offset, spec);
  2770. }
  2771. parseBuffer.removeAllElements();
  2772. offset += HTMLDocument.this.getLength() - oldLength;
  2773. flushCount++;
  2774. }
  2775. /**
  2776. * This will be invoked for the last flush, if <code>insertTag</code>
  2777. * is non null.
  2778. */
  2779. private void adjustEndSpecsForPartialInsert() {
  2780. int size = parseBuffer.size();
  2781. if (insertTagDepthDelta < 0) {
  2782. // When inserting via an insertTag, the depths (of the tree
  2783. // being read in, and existing hiearchy) may not match up.
  2784. // This attemps to clean it up.
  2785. int removeCounter = insertTagDepthDelta;
  2786. while (removeCounter < 0 && size >= 0 &&
  2787. ((ElementSpec)parseBuffer.elementAt(size - 1)).
  2788. getType() == ElementSpec.EndTagType) {
  2789. parseBuffer.removeElementAt(--size);
  2790. removeCounter++;
  2791. }
  2792. }
  2793. if (flushCount == 0 && (!insertAfterImplied ||
  2794. !wantsTrailingNewline)) {
  2795. // If this starts with content (or popDepth > 0 &&
  2796. // pushDepth > 0) and ends with EndTagTypes, make sure
  2797. // the last content isn't a \n, otherwise will end up with
  2798. // an extra \n in the middle of content.
  2799. int index = 0;
  2800. if (pushDepth > 0) {
  2801. if (((ElementSpec)parseBuffer.elementAt(0)).getType() ==
  2802. ElementSpec.ContentType) {
  2803. index++;
  2804. }
  2805. }
  2806. index += (popDepth + pushDepth);
  2807. int cCount = 0;
  2808. int cStart = index;
  2809. while (index < size && ((ElementSpec)parseBuffer.elementAt
  2810. (index)).getType() == ElementSpec.ContentType) {
  2811. index++;
  2812. cCount++;
  2813. }
  2814. if (cCount > 1) {
  2815. while (index < size && ((ElementSpec)parseBuffer.elementAt
  2816. (index)).getType() == ElementSpec.EndTagType) {
  2817. index++;
  2818. }
  2819. if (index == size) {
  2820. char[] lastText = ((ElementSpec)parseBuffer.elementAt
  2821. (cStart + cCount - 1)).getArray();
  2822. if (lastText.length == 1 && lastText[0] == NEWLINE[0]){
  2823. index = cStart + cCount - 1;
  2824. while (size > index) {
  2825. parseBuffer.removeElementAt(--size);
  2826. }
  2827. }
  2828. }
  2829. }
  2830. }
  2831. }
  2832. /**
  2833. * Adds the CSS rules in <code>rules</code>.
  2834. */
  2835. void addCSSRules(String rules) {
  2836. StyleSheet ss = getStyleSheet();
  2837. ss.addRule(rules);
  2838. }
  2839. /**
  2840. * Adds the CSS stylesheet at <code>href</code> to the known list
  2841. * of stylesheets.
  2842. */
  2843. void linkCSSStyleSheet(String href) {
  2844. URL url = null;
  2845. try {
  2846. url = new URL(base, href);
  2847. } catch (MalformedURLException mfe) {
  2848. try {
  2849. url = new URL(href);
  2850. } catch (MalformedURLException mfe2) {
  2851. url = null;
  2852. }
  2853. }
  2854. if (url != null) {
  2855. getStyleSheet().importStyleSheet(url);
  2856. }
  2857. }
  2858. /**
  2859. * Returns true if can insert starting at <code>t</code>. This
  2860. * will return false if the insert tag is set, and hasn't been found
  2861. * yet.
  2862. */
  2863. private boolean canInsertTag(HTML.Tag t, AttributeSet attr,
  2864. boolean isBlockTag) {
  2865. if (!foundInsertTag) {
  2866. if ((insertTag != null && !isInsertTag(t)) ||
  2867. (insertAfterImplied &&
  2868. (attr == null || attr.isDefined(IMPLIED)))) {
  2869. return false;
  2870. }
  2871. foundInsertTag(isBlockTag);
  2872. if (!insertInsertTag) {
  2873. return false;
  2874. }
  2875. }
  2876. return true;
  2877. }
  2878. private boolean isInsertTag(HTML.Tag tag) {
  2879. return (insertTag == tag);
  2880. }
  2881. private void foundInsertTag(boolean isBlockTag) {
  2882. foundInsertTag = true;
  2883. if (!insertAfterImplied && (popDepth > 0 || pushDepth > 0)) {
  2884. try {
  2885. if (offset == 0 || !getText(offset - 1, 1).equals("\n")) {
  2886. // Need to insert a newline.
  2887. AttributeSet newAttrs = null;
  2888. boolean joinP = true;
  2889. if (offset != 0) {
  2890. // Determine if we can use JoinPrevious, we can't
  2891. // if the Element has some attributes that are
  2892. // not meant to be duplicated.
  2893. Element charElement = getCharacterElement
  2894. (offset - 1);
  2895. AttributeSet attrs = charElement.getAttributes();
  2896. if (attrs.isDefined(StyleConstants.
  2897. ComposedTextAttribute)) {
  2898. joinP = false;
  2899. }
  2900. else {
  2901. Object name = attrs.getAttribute
  2902. (StyleConstants.NameAttribute);
  2903. if (name instanceof HTML.Tag) {
  2904. HTML.Tag tag = (HTML.Tag)name;
  2905. if (tag == HTML.Tag.IMG ||
  2906. tag == HTML.Tag.HR ||
  2907. tag == HTML.Tag.COMMENT ||
  2908. (tag instanceof HTML.UnknownTag)) {
  2909. joinP = false;
  2910. }
  2911. }
  2912. }
  2913. }
  2914. if (!joinP) {
  2915. // If not joining with the previous element, be
  2916. // sure and set the name (otherwise it will be
  2917. // inherited).
  2918. newAttrs = new SimpleAttributeSet();
  2919. ((SimpleAttributeSet)newAttrs).addAttribute
  2920. (StyleConstants.NameAttribute,
  2921. HTML.Tag.CONTENT);
  2922. }
  2923. ElementSpec es = new ElementSpec(newAttrs,
  2924. ElementSpec.ContentType, NEWLINE, 0,
  2925. NEWLINE.length);
  2926. if (joinP) {
  2927. es.setDirection(ElementSpec.
  2928. JoinPreviousDirection);
  2929. }
  2930. parseBuffer.addElement(es);
  2931. }
  2932. } catch (BadLocationException ble) {}
  2933. }
  2934. // pops
  2935. for (int counter = 0; counter < popDepth; counter++) {
  2936. parseBuffer.addElement(new ElementSpec(null, ElementSpec.
  2937. EndTagType));
  2938. }
  2939. // pushes
  2940. for (int counter = 0; counter < pushDepth; counter++) {
  2941. ElementSpec es = new ElementSpec(null, ElementSpec.
  2942. StartTagType);
  2943. es.setDirection(ElementSpec.JoinNextDirection);
  2944. parseBuffer.addElement(es);
  2945. }
  2946. insertTagDepthDelta = depthTo(Math.max(0, offset - 1)) -
  2947. popDepth + pushDepth - inBlock;
  2948. if (isBlockTag) {
  2949. // A start spec will be added (for this tag), so we account
  2950. // for it here.
  2951. insertTagDepthDelta++;
  2952. }
  2953. else {
  2954. // An implied paragraph close (end spec) is going to be added,
  2955. // so we account for it here.
  2956. insertTagDepthDelta--;
  2957. }
  2958. }
  2959. /** Number of times <code>flushBuffer</code> has been invoked. */
  2960. private int flushCount;
  2961. /** If true, behavior is similiar to insertTag, but instead of
  2962. * waiting for insertTag will wait for first Element without
  2963. * an 'implied' attribute and begin inserting then. */
  2964. private boolean insertAfterImplied;
  2965. /** This is only used if insertAfterImplied is true. If false, only
  2966. * inserting content, and there is a trailing newline it is removed. */
  2967. private boolean wantsTrailingNewline;
  2968. int threshold;
  2969. int offset;
  2970. boolean inParagraph = false;
  2971. boolean impliedP = false;
  2972. boolean inPre = false;
  2973. boolean inTextArea = false;
  2974. TextAreaDocument textAreaDocument = null;
  2975. boolean inTitle = false;
  2976. boolean lastWasNewline = true;
  2977. boolean emptyAnchor;
  2978. /** True if (!emptyDocument && insertTag == null), this is used so
  2979. * much it is cached. */
  2980. boolean midInsert;
  2981. /** True when the body has been encountered. */
  2982. boolean inBody;
  2983. /** If non null, gives parent Tag that insert is to happen at. */
  2984. HTML.Tag insertTag;
  2985. /** If true, the insertTag is inserted, otherwise elements after
  2986. * the insertTag is found are inserted. */
  2987. boolean insertInsertTag;
  2988. /** Set to true when insertTag has been found. */
  2989. boolean foundInsertTag;
  2990. /** When foundInsertTag is set to true, this will be updated to
  2991. * reflect the delta between the two structures. That is, it
  2992. * will be the depth the inserts are happening at minus the
  2993. * depth of the tags being passed in. A value of 0 (the common
  2994. * case) indicates the structures match, a value greater than 0 indicates
  2995. * the insert is happening at a deeper depth than the stream is
  2996. * parsing, and a value less than 0 indicates the insert is happening earlier
  2997. * in the tree that the parser thinks and that we will need to remove
  2998. * EndTagType specs in the flushBuffer method.
  2999. */
  3000. int insertTagDepthDelta;
  3001. /** How many parents to ascend before insert new elements. */
  3002. int popDepth;
  3003. /** How many parents to descend (relative to popDepth) before
  3004. * inserting. */
  3005. int pushDepth;
  3006. /** Last Map that was encountered. */
  3007. Map lastMap;
  3008. /** Set to true when a style element is encountered. */
  3009. boolean inStyle = false;
  3010. /** Name of style to use. Obtained from Meta tag. */
  3011. String defaultStyle;
  3012. /** Vector describing styles that should be include. Will consist
  3013. * of a bunch of HTML.Tags, which will either be:
  3014. * <p>LINK: in which case it is followed by an AttributeSet
  3015. * <p>STYLE: in which case the following element is a String
  3016. * indicating the type (may be null), and the elements following
  3017. * it until the next HTML.Tag are the rules as Strings.
  3018. */
  3019. Vector styles;
  3020. /** True if inside the head tag. */
  3021. boolean inHead = false;
  3022. /** Set to true if the style language is text/css. Since this is
  3023. * used alot, it is cached. */
  3024. boolean isStyleCSS;
  3025. /** True if inserting into an empty document. */
  3026. boolean emptyDocument;
  3027. /** Attributes from a style Attribute. */
  3028. AttributeSet styleAttributes;
  3029. /**
  3030. * Current option, if in an option element (needed to
  3031. * load the label.
  3032. */
  3033. Option option;
  3034. protected Vector parseBuffer = new Vector(); // Vector<ElementSpec>
  3035. protected MutableAttributeSet charAttr = new SimpleAttributeSet();
  3036. Stack charAttrStack = new Stack();
  3037. Hashtable tagMap;
  3038. int inBlock = 0;
  3039. }
  3040. /**
  3041. * An element that represents a chunk of text that has
  3042. * a set of HTML character level attributes assigned to
  3043. * it.
  3044. */
  3045. public class RunElement extends LeafElement {
  3046. /**
  3047. * Constructs an element that represents content within the
  3048. * document (has no children).
  3049. *
  3050. * @param parent the parent element
  3051. * @param a the element attributes
  3052. * @param offs0 the start offset (must be at least 0)
  3053. * @param offs1 the end offset (must be at least offs0)
  3054. */
  3055. public RunElement(Element parent, AttributeSet a, int offs0, int offs1) {
  3056. super(parent, a, offs0, offs1);
  3057. }
  3058. /**
  3059. * Gets the name of the element.
  3060. *
  3061. * @return the name, null if none
  3062. */
  3063. public String getName() {
  3064. Object o = getAttribute(StyleConstants.NameAttribute);
  3065. if (o != null) {
  3066. return o.toString();
  3067. }
  3068. return super.getName();
  3069. }
  3070. /**
  3071. * Gets the resolving parent. HTML attributes are not inherited
  3072. * at the model level so we override this to return null.
  3073. *
  3074. * @return null, there are none
  3075. * @see AttributeSet#getResolveParent
  3076. */
  3077. public AttributeSet getResolveParent() {
  3078. return null;
  3079. }
  3080. }
  3081. /**
  3082. * An element that represents a structural <em>block</em> of
  3083. * HTML.
  3084. */
  3085. public class BlockElement extends BranchElement {
  3086. /**
  3087. * Constructs a composite element that initially contains
  3088. * no children.
  3089. *
  3090. * @param parent the parent element
  3091. * @param a the attributes for the element
  3092. */
  3093. public BlockElement(Element parent, AttributeSet a) {
  3094. super(parent, a);
  3095. }
  3096. /**
  3097. * Gets the name of the element.
  3098. *
  3099. * @return the name, null if none
  3100. */
  3101. public String getName() {
  3102. Object o = getAttribute(StyleConstants.NameAttribute);
  3103. if (o != null) {
  3104. return o.toString();
  3105. }
  3106. return super.getName();
  3107. }
  3108. /**
  3109. * Gets the resolving parent. HTML attributes are not inherited
  3110. * at the model level so we override this to return null.
  3111. *
  3112. * @return null, there are none
  3113. * @see AttributeSet#getResolveParent
  3114. */
  3115. public AttributeSet getResolveParent() {
  3116. return null;
  3117. }
  3118. }
  3119. /**
  3120. * The following methods provide functionality required to
  3121. * iterate over a the elements of the form and in the case
  3122. * of a form submission, extract the data from each model
  3123. * that is associated with each form element, and in the
  3124. * case of reset, reinitialize the each model to its
  3125. * initial state.
  3126. */
  3127. /**
  3128. * Searches the names of the attribute
  3129. * in the set, for HTML.Tag.FORM. If found,
  3130. * returns the attribute set that is associated
  3131. * with HTML.Tag.FORM.
  3132. *
  3133. * @param attr the attribute set to search
  3134. * @return FORM attributes or null if not found.
  3135. */
  3136. AttributeSet getFormAttributes(AttributeSet attr) {
  3137. Enumeration names = attr.getAttributeNames();
  3138. while (names.hasMoreElements()) {
  3139. Object name = names.nextElement();
  3140. if (name instanceof HTML.Tag) {
  3141. HTML.Tag tag = (HTML.Tag)name;
  3142. if (tag == HTML.Tag.FORM) {
  3143. Object o = attr.getAttribute(tag);
  3144. if (o != null && o instanceof AttributeSet) {
  3145. return (AttributeSet)o;
  3146. }
  3147. }
  3148. }
  3149. }
  3150. return null;
  3151. }
  3152. /**
  3153. * Determines which form elements are part
  3154. * of the same form as the element that triggered the submit or
  3155. * reset action. This is determined by matching the form attributes
  3156. * associated with the trigger element, with the form attributes associated
  3157. * with every other form element in the document.
  3158. *
  3159. * @param elemAttributes attributes associated with a form element
  3160. * @param formAttributes attributes associated with the trigger element
  3161. * @return true if matched false otherwise.
  3162. */
  3163. private boolean formMatchesSubmissionRequest(AttributeSet elemAttributes,
  3164. AttributeSet formAttributes) {
  3165. AttributeSet attr = getFormAttributes(elemAttributes);
  3166. if (attr != null) {
  3167. return formAttributes.isEqual(attr);
  3168. }
  3169. return false;
  3170. }
  3171. /**
  3172. * Iterates over the
  3173. * element hierarchy, extracting data from the
  3174. * models associated with the relevant form elements.
  3175. * "Relevant" means the form elements that are part
  3176. * of the same form whose element triggered the submit
  3177. * action.
  3178. *
  3179. * @param buffer the buffer that contains that data to submit
  3180. * @param targetElement the element that triggered the
  3181. * form submission
  3182. */
  3183. void getFormData(StringBuffer buffer, Element targetElement) {
  3184. AttributeSet attr = targetElement.getAttributes();
  3185. AttributeSet formAttributes = getFormAttributes(attr);
  3186. ElementIterator it = new ElementIterator(getDefaultRootElement());
  3187. Element next;
  3188. if (formAttributes == null) {
  3189. return;
  3190. }
  3191. boolean startedLoading = false;
  3192. while((next = it.next()) != null) {
  3193. AttributeSet elemAttr = next.getAttributes();
  3194. if (formMatchesSubmissionRequest(elemAttr, formAttributes)) {
  3195. startedLoading = true;
  3196. String type = (String) elemAttr.getAttribute(HTML.Attribute.TYPE);
  3197. if (type != null &&
  3198. type.equals("submit") &&
  3199. next != targetElement) {
  3200. // do nothing - this submit isnt the trigger
  3201. } else if (type == null ||
  3202. !type.equals("image")) {
  3203. // images only result in data if they triggered
  3204. // the submit and they require that the mouse click
  3205. // coords be appended to the data. Hence its
  3206. // processing is handled by the view.
  3207. loadElementDataIntoBuffer(next, buffer);
  3208. }
  3209. } else if (startedLoading && next.isLeaf()) {
  3210. /*
  3211. if startedLoading is true, this means that we
  3212. did find the form elements that pertain to
  3213. the form being submitted and so loading has started.
  3214. In this context, if we encounter a leaf element
  3215. that does not have the HTML.Tag.FORM attribute
  3216. set with the same attribute values, then we have
  3217. reached the end of the form and we can safely
  3218. quit.
  3219. */
  3220. break;
  3221. }
  3222. }
  3223. return;
  3224. }
  3225. /**
  3226. * Loads the data
  3227. * associated with the element into the buffer.
  3228. * The format in which data is appended depends
  3229. * on the type of the form element. Essentially
  3230. * data is loaded in name/value pairs.
  3231. *
  3232. */
  3233. private void loadElementDataIntoBuffer(Element elem, StringBuffer buffer) {
  3234. AttributeSet attr = elem.getAttributes();
  3235. String name = (String)attr.getAttribute(HTML.Attribute.NAME);
  3236. if (name == null) {
  3237. return;
  3238. }
  3239. String value = null;
  3240. HTML.Tag tag = (HTML.Tag) elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
  3241. if (tag == HTML.Tag.INPUT) {
  3242. value = getInputElementData(attr);
  3243. } else if (tag == HTML.Tag.TEXTAREA) {
  3244. value = getTextAreaData(attr);
  3245. } else if (tag == HTML.Tag.SELECT) {
  3246. loadSelectData(attr, buffer);
  3247. }
  3248. if (name != null && value != null) {
  3249. appendBuffer(buffer, name, value);
  3250. }
  3251. }
  3252. /**
  3253. * Returns the data associated with an <INPUT> form
  3254. * element. The value of "type" attributes is
  3255. * used to determine the type of the model associated
  3256. * with the element and then the relevant data is
  3257. * extracted.
  3258. */
  3259. private String getInputElementData(AttributeSet attr) {
  3260. Object model = attr.getAttribute(StyleConstants.ModelAttribute);
  3261. String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
  3262. String value = null;
  3263. if (type.equals("text") || type.equals("password")) {
  3264. Document doc = (Document)model;
  3265. try {
  3266. value = doc.getText(0, doc.getLength());
  3267. } catch (BadLocationException e) {
  3268. value = null;
  3269. }
  3270. } else if (type.equals("submit") || type.equals("hidden")) {
  3271. value = (String) attr.getAttribute(HTML.Attribute.VALUE);
  3272. if (value == null) {
  3273. value = "";
  3274. }
  3275. } else if (type.equals("radio") || type.equals("checkbox")) {
  3276. ButtonModel m = (ButtonModel)model;
  3277. if (m.isSelected()) {
  3278. value = (String) attr.getAttribute(HTML.Attribute.VALUE);
  3279. if (value == null) {
  3280. value = "on";
  3281. }
  3282. }
  3283. }
  3284. return value;
  3285. }
  3286. /**
  3287. * Returns the data associated with the <TEXTAREA> form
  3288. * element. This is done by getting the text stored in the
  3289. * Document model.
  3290. */
  3291. private String getTextAreaData(AttributeSet attr) {
  3292. Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
  3293. try {
  3294. return doc.getText(0, doc.getLength());
  3295. } catch (BadLocationException e) {
  3296. return null;
  3297. }
  3298. }
  3299. /**
  3300. * Loads the buffer with the data associated with the Select
  3301. * form element. Basically, only items that are selected
  3302. * and have their name attribute set are added to the buffer.
  3303. */
  3304. private void loadSelectData(AttributeSet attr, StringBuffer buffer) {
  3305. String name = (String)attr.getAttribute(HTML.Attribute.NAME);
  3306. if (name == null) {
  3307. return;
  3308. }
  3309. Object m = attr.getAttribute(StyleConstants.ModelAttribute);
  3310. if (m instanceof OptionListModel) {
  3311. OptionListModel model = (OptionListModel)m;
  3312. for (int i = 0; i < model.getSize(); i++) {
  3313. if (model.isSelectedIndex(i)) {
  3314. Option option = (Option) model.getElementAt(i);
  3315. appendBuffer(buffer, name, option.getValue());
  3316. }
  3317. }
  3318. } else if (m instanceof ComboBoxModel) {
  3319. ComboBoxModel model = (ComboBoxModel)m;
  3320. Option option = (Option)model.getSelectedItem();
  3321. if (option != null) {
  3322. appendBuffer(buffer, name, option.getValue());
  3323. }
  3324. }
  3325. }
  3326. /**
  3327. * Appends name / value pairs into the
  3328. * buffer. Both names and values are encoded using the
  3329. * URLEncoder.encode() method before being added to the
  3330. * buffer.
  3331. */
  3332. private void appendBuffer(StringBuffer buffer, String name, String value) {
  3333. ampersand(buffer);
  3334. String encodedName = URLEncoder.encode(name);
  3335. buffer.append(encodedName);
  3336. buffer.append('=');
  3337. String encodedValue = URLEncoder.encode(value);
  3338. buffer.append(encodedValue);
  3339. }
  3340. /**
  3341. * Appends an '&' as a separator if the buffer has
  3342. * a length greater than zero.
  3343. */
  3344. private void ampersand(StringBuffer buf) {
  3345. if (buf.length() > 0) {
  3346. buf.append('&');
  3347. }
  3348. }
  3349. /**
  3350. * Iterates over the element hierarchy to determine if
  3351. * the element parameter, which is assumed to be an
  3352. * <INPUT> element of type password or text, is the last
  3353. * one of either kind, in the form to which it belongs.
  3354. */
  3355. boolean isLastTextOrPasswordField(Element elem) {
  3356. ElementIterator it = new ElementIterator(getDefaultRootElement());
  3357. Element next;
  3358. boolean found = false;
  3359. AttributeSet formAttributes = getFormAttributes(elem.getAttributes());
  3360. while((next = it.next()) != null) {
  3361. AttributeSet elemAttr = next.getAttributes();
  3362. if (formMatchesSubmissionRequest(elemAttr, formAttributes)) {
  3363. if (found) {
  3364. if (matchNameAttribute(elemAttr, HTML.Tag.INPUT)) {
  3365. String type = (String) elemAttr.getAttribute(HTML.Attribute.TYPE);
  3366. if (type.equals("text") || type.equals("password")) {
  3367. return false;
  3368. }
  3369. }
  3370. }
  3371. if (next == elem) {
  3372. found = true;
  3373. }
  3374. } else if (found && next.isLeaf()) {
  3375. // You can safely break, coz you have exited the
  3376. // form that you care about.
  3377. break;
  3378. }
  3379. }
  3380. return true;
  3381. }
  3382. /**
  3383. * Resets the form
  3384. * to its initial state by reinitializing the models
  3385. * associated with each form element to their initial
  3386. * values.
  3387. *
  3388. * param elem the element that triggered the reset
  3389. */
  3390. void resetForm(Element elem) {
  3391. ElementIterator it = new ElementIterator(getDefaultRootElement());
  3392. Element next;
  3393. boolean startedReset = false;
  3394. AttributeSet formAttributes = getFormAttributes(elem.getAttributes());
  3395. while((next = it.next()) != null) {
  3396. AttributeSet elemAttr = next.getAttributes();
  3397. if (formMatchesSubmissionRequest(elemAttr, formAttributes)) {
  3398. Object m = elemAttr.getAttribute(StyleConstants.ModelAttribute);
  3399. if (m instanceof TextAreaDocument) {
  3400. TextAreaDocument doc = (TextAreaDocument)m;
  3401. doc.reset();
  3402. } else if (m instanceof PlainDocument) {
  3403. try {
  3404. PlainDocument doc = (PlainDocument)m;
  3405. doc.remove(0, doc.getLength());
  3406. if (matchNameAttribute(elemAttr, HTML.Tag.INPUT)) {
  3407. String value = (String)elemAttr.getAttribute(HTML.Attribute.VALUE);
  3408. if (value != null) {
  3409. doc.insertString(0, value, null);
  3410. }
  3411. }
  3412. } catch (BadLocationException e) {
  3413. }
  3414. } else if (m instanceof OptionListModel) {
  3415. OptionListModel model = (OptionListModel) m;
  3416. int size = model.getSize();
  3417. for (int i = 0; i < size; i++) {
  3418. model.removeIndexInterval(i, i);
  3419. }
  3420. BitSet selectionRange = model.getInitialSelection();
  3421. for (int i = 0; i < selectionRange.size(); i++) {
  3422. if (selectionRange.get(i)) {
  3423. model.addSelectionInterval(i, i);
  3424. }
  3425. }
  3426. } else if (m instanceof OptionComboBoxModel) {
  3427. OptionComboBoxModel model = (OptionComboBoxModel) m;
  3428. Option option = model.getInitialSelection();
  3429. if (option != null) {
  3430. model.setSelectedItem(option);
  3431. }
  3432. } else if (m instanceof JToggleButton.ToggleButtonModel) {
  3433. boolean checked = ((String)elemAttr.getAttribute(HTML.Attribute.CHECKED) != null);
  3434. JToggleButton.ToggleButtonModel model = (JToggleButton.ToggleButtonModel)m;
  3435. model.setSelected(checked);
  3436. }
  3437. startedReset = true;
  3438. } else if (startedReset && next.isLeaf()) {
  3439. break;
  3440. }
  3441. }
  3442. }
  3443. }