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