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