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