1. /*
  2. * @(#)RTFReader.java 1.24 04/08/07
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text.rtf;
  8. import java.lang.*;
  9. import java.util.*;
  10. import java.io.*;
  11. import java.awt.Font;
  12. import java.awt.Color;
  13. import javax.swing.text.*;
  14. /**
  15. * Takes a sequence of RTF tokens and text and appends the text
  16. * described by the RTF to a <code>StyledDocument</code> (the <em>target</em>).
  17. * The RTF is lexed
  18. * from the character stream by the <code>RTFParser</code> which is this class's
  19. * superclass.
  20. *
  21. * This class is an indirect subclass of OutputStream. It must be closed
  22. * in order to guarantee that all of the text has been sent to
  23. * the text acceptor.
  24. *
  25. * @see RTFParser
  26. * @see java.io.OutputStream
  27. */
  28. class RTFReader extends RTFParser
  29. {
  30. /** The object to which the parsed text is sent. */
  31. StyledDocument target;
  32. /** Miscellaneous information about the parser's state. This
  33. * dictionary is saved and restored when an RTF group begins
  34. * or ends. */
  35. Dictionary parserState; /* Current parser state */
  36. /** This is the "dst" item from parserState. rtfDestination
  37. * is the current rtf destination. It is cached in an instance
  38. * variable for speed. */
  39. Destination rtfDestination;
  40. /** This holds the current document attributes. */
  41. MutableAttributeSet documentAttributes;
  42. /** This Dictionary maps Integer font numbers to String font names. */
  43. Dictionary fontTable;
  44. /** This array maps color indices to Color objects. */
  45. Color[] colorTable;
  46. /** This array maps character style numbers to Style objects. */
  47. Style[] characterStyles;
  48. /** This array maps paragraph style numbers to Style objects. */
  49. Style[] paragraphStyles;
  50. /** This array maps section style numbers to Style objects. */
  51. Style[] sectionStyles;
  52. /** This is the RTF version number, extracted from the \rtf keyword.
  53. * The version information is currently not used. */
  54. int rtfversion;
  55. /** <code>true</code> to indicate that if the next keyword is unknown,
  56. * the containing group should be ignored. */
  57. boolean ignoreGroupIfUnknownKeyword;
  58. /** The parameter of the most recently parsed \\ucN keyword,
  59. * used for skipping alternative representations after a
  60. * Unicode character. */
  61. int skippingCharacters;
  62. static private Dictionary straightforwardAttributes;
  63. static {
  64. straightforwardAttributes = RTFAttributes.attributesByKeyword();
  65. }
  66. private MockAttributeSet mockery;
  67. /* this should be final, but there's a bug in javac... */
  68. /** textKeywords maps RTF keywords to single-character strings,
  69. * for those keywords which simply insert some text. */
  70. static Dictionary textKeywords = null;
  71. static {
  72. textKeywords = new Hashtable();
  73. textKeywords.put("\\", "\\");
  74. textKeywords.put("{", "{");
  75. textKeywords.put("}", "}");
  76. textKeywords.put(" ", "\u00A0"); /* not in the spec... */
  77. textKeywords.put("~", "\u00A0"); /* nonbreaking space */
  78. textKeywords.put("_", "\u2011"); /* nonbreaking hyphen */
  79. textKeywords.put("bullet", "\u2022");
  80. textKeywords.put("emdash", "\u2014");
  81. textKeywords.put("emspace", "\u2003");
  82. textKeywords.put("endash", "\u2013");
  83. textKeywords.put("enspace", "\u2002");
  84. textKeywords.put("ldblquote", "\u201C");
  85. textKeywords.put("lquote", "\u2018");
  86. textKeywords.put("ltrmark", "\u200E");
  87. textKeywords.put("rdblquote", "\u201D");
  88. textKeywords.put("rquote", "\u2019");
  89. textKeywords.put("rtlmark", "\u200F");
  90. textKeywords.put("tab", "\u0009");
  91. textKeywords.put("zwj", "\u200D");
  92. textKeywords.put("zwnj", "\u200C");
  93. /* There is no Unicode equivalent to an optional hyphen, as far as
  94. I can tell. */
  95. textKeywords.put("-", "\u2027"); /* TODO: optional hyphen */
  96. }
  97. /* some entries in parserState */
  98. static final String TabAlignmentKey = "tab_alignment";
  99. static final String TabLeaderKey = "tab_leader";
  100. static Dictionary characterSets;
  101. static boolean useNeXTForAnsi = false;
  102. static {
  103. characterSets = new Hashtable();
  104. }
  105. /* TODO: per-font font encodings ( \fcharset control word ) ? */
  106. /**
  107. * Creates a new RTFReader instance. Text will be sent to
  108. * the specified TextAcceptor.
  109. *
  110. * @param destination The TextAcceptor which is to receive the text.
  111. */
  112. public RTFReader(StyledDocument destination)
  113. {
  114. int i;
  115. target = destination;
  116. parserState = new Hashtable();
  117. fontTable = new Hashtable();
  118. rtfversion = -1;
  119. mockery = new MockAttributeSet();
  120. documentAttributes = new SimpleAttributeSet();
  121. }
  122. /** Called when the RTFParser encounters a bin keyword in the
  123. * RTF stream.
  124. *
  125. * @see RTFParser
  126. */
  127. public void handleBinaryBlob(byte[] data)
  128. {
  129. if (skippingCharacters > 0) {
  130. /* a blob only counts as one character for skipping purposes */
  131. skippingCharacters --;
  132. return;
  133. }
  134. /* someday, someone will want to do something with blobs */
  135. }
  136. /**
  137. * Handles any pure text (containing no control characters) in the input
  138. * stream. Called by the superclass. */
  139. public void handleText(String text)
  140. {
  141. if (skippingCharacters > 0) {
  142. if (skippingCharacters >= text.length()) {
  143. skippingCharacters -= text.length();
  144. return;
  145. } else {
  146. text = text.substring(skippingCharacters);
  147. skippingCharacters = 0;
  148. }
  149. }
  150. if (rtfDestination != null) {
  151. rtfDestination.handleText(text);
  152. return;
  153. }
  154. warning("Text with no destination. oops.");
  155. }
  156. /** The default color for text which has no specified color. */
  157. Color defaultColor()
  158. {
  159. return Color.black;
  160. }
  161. /** Called by the superclass when a new RTF group is begun.
  162. * This implementation saves the current <code>parserState</code>, and gives
  163. * the current destination a chance to save its own state.
  164. * @see RTFParser#begingroup
  165. */
  166. public void begingroup()
  167. {
  168. if (skippingCharacters > 0) {
  169. /* TODO this indicates an error in the RTF. Log it? */
  170. skippingCharacters = 0;
  171. }
  172. /* we do this little dance to avoid cloning the entire state stack and
  173. immediately throwing it away. */
  174. Object oldSaveState = parserState.get("_savedState");
  175. if (oldSaveState != null)
  176. parserState.remove("_savedState");
  177. Dictionary saveState = (Dictionary)((Hashtable)parserState).clone();
  178. if (oldSaveState != null)
  179. saveState.put("_savedState", oldSaveState);
  180. parserState.put("_savedState", saveState);
  181. if (rtfDestination != null)
  182. rtfDestination.begingroup();
  183. }
  184. /** Called by the superclass when the current RTF group is closed.
  185. * This restores the parserState saved by <code>begingroup()</code>
  186. * as well as invoking the endgroup method of the current
  187. * destination.
  188. * @see RTFParser#endgroup
  189. */
  190. public void endgroup()
  191. {
  192. if (skippingCharacters > 0) {
  193. /* NB this indicates an error in the RTF. Log it? */
  194. skippingCharacters = 0;
  195. }
  196. Dictionary restoredState = (Dictionary)parserState.get("_savedState");
  197. Destination restoredDestination = (Destination)restoredState.get("dst");
  198. if (restoredDestination != rtfDestination) {
  199. rtfDestination.close(); /* allow the destination to clean up */
  200. rtfDestination = restoredDestination;
  201. }
  202. Dictionary oldParserState = parserState;
  203. parserState = restoredState;
  204. if (rtfDestination != null)
  205. rtfDestination.endgroup(oldParserState);
  206. }
  207. protected void setRTFDestination(Destination newDestination)
  208. {
  209. /* Check that setting the destination won't close the
  210. current destination (should never happen) */
  211. Dictionary previousState = (Dictionary)parserState.get("_savedState");
  212. if (previousState != null) {
  213. if (rtfDestination != previousState.get("dst")) {
  214. warning("Warning, RTF destination overridden, invalid RTF.");
  215. rtfDestination.close();
  216. }
  217. }
  218. rtfDestination = newDestination;
  219. parserState.put("dst", rtfDestination);
  220. }
  221. /** Called by the user when there is no more input (<i>i.e.</i>,
  222. * at the end of the RTF file.)
  223. *
  224. * @see OutputStream#close
  225. */
  226. public void close()
  227. throws IOException
  228. {
  229. Enumeration docProps = documentAttributes.getAttributeNames();
  230. while(docProps.hasMoreElements()) {
  231. Object propName = docProps.nextElement();
  232. target.putProperty(propName,
  233. documentAttributes.getAttribute((String)propName));
  234. }
  235. /* RTFParser should have ensured that all our groups are closed */
  236. warning("RTF filter done.");
  237. super.close();
  238. }
  239. /**
  240. * Handles a parameterless RTF keyword. This is called by the superclass
  241. * (RTFParser) when a keyword is found in the input stream.
  242. *
  243. * @returns <code>true</code> if the keyword is recognized and handled;
  244. * <code>false</code> otherwise
  245. * @see RTFParser#handleKeyword
  246. */
  247. public boolean handleKeyword(String keyword)
  248. {
  249. Object item;
  250. boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
  251. if (skippingCharacters > 0) {
  252. skippingCharacters --;
  253. return true;
  254. }
  255. ignoreGroupIfUnknownKeyword = false;
  256. if ((item = textKeywords.get(keyword)) != null) {
  257. handleText((String)item);
  258. return true;
  259. }
  260. if (keyword.equals("fonttbl")) {
  261. setRTFDestination(new FonttblDestination());
  262. return true;
  263. }
  264. if (keyword.equals("colortbl")) {
  265. setRTFDestination(new ColortblDestination());
  266. return true;
  267. }
  268. if (keyword.equals("stylesheet")) {
  269. setRTFDestination(new StylesheetDestination());
  270. return true;
  271. }
  272. if (keyword.equals("info")) {
  273. setRTFDestination(new InfoDestination());
  274. return false;
  275. }
  276. if (keyword.equals("mac")) {
  277. setCharacterSet("mac");
  278. return true;
  279. }
  280. if (keyword.equals("ansi")) {
  281. if (useNeXTForAnsi)
  282. setCharacterSet("NeXT");
  283. else
  284. setCharacterSet("ansi");
  285. return true;
  286. }
  287. if (keyword.equals("next")) {
  288. setCharacterSet("NeXT");
  289. return true;
  290. }
  291. if (keyword.equals("pc")) {
  292. setCharacterSet("cpg437"); /* IBM Code Page 437 */
  293. return true;
  294. }
  295. if (keyword.equals("pca")) {
  296. setCharacterSet("cpg850"); /* IBM Code Page 850 */
  297. return true;
  298. }
  299. if (keyword.equals("*")) {
  300. ignoreGroupIfUnknownKeyword = true;
  301. return true;
  302. }
  303. if (rtfDestination != null) {
  304. if(rtfDestination.handleKeyword(keyword))
  305. return true;
  306. }
  307. /* this point is reached only if the keyword is unrecognized */
  308. /* other destinations we don't understand and therefore ignore */
  309. if (keyword.equals("aftncn") ||
  310. keyword.equals("aftnsep") ||
  311. keyword.equals("aftnsepc") ||
  312. keyword.equals("annotation") ||
  313. keyword.equals("atnauthor") ||
  314. keyword.equals("atnicn") ||
  315. keyword.equals("atnid") ||
  316. keyword.equals("atnref") ||
  317. keyword.equals("atntime") ||
  318. keyword.equals("atrfend") ||
  319. keyword.equals("atrfstart") ||
  320. keyword.equals("bkmkend") ||
  321. keyword.equals("bkmkstart") ||
  322. keyword.equals("datafield") ||
  323. keyword.equals("do") ||
  324. keyword.equals("dptxbxtext") ||
  325. keyword.equals("falt") ||
  326. keyword.equals("field") ||
  327. keyword.equals("file") ||
  328. keyword.equals("filetbl") ||
  329. keyword.equals("fname") ||
  330. keyword.equals("fontemb") ||
  331. keyword.equals("fontfile") ||
  332. keyword.equals("footer") ||
  333. keyword.equals("footerf") ||
  334. keyword.equals("footerl") ||
  335. keyword.equals("footerr") ||
  336. keyword.equals("footnote") ||
  337. keyword.equals("ftncn") ||
  338. keyword.equals("ftnsep") ||
  339. keyword.equals("ftnsepc") ||
  340. keyword.equals("header") ||
  341. keyword.equals("headerf") ||
  342. keyword.equals("headerl") ||
  343. keyword.equals("headerr") ||
  344. keyword.equals("keycode") ||
  345. keyword.equals("nextfile") ||
  346. keyword.equals("object") ||
  347. keyword.equals("pict") ||
  348. keyword.equals("pn") ||
  349. keyword.equals("pnseclvl") ||
  350. keyword.equals("pntxtb") ||
  351. keyword.equals("pntxta") ||
  352. keyword.equals("revtbl") ||
  353. keyword.equals("rxe") ||
  354. keyword.equals("tc") ||
  355. keyword.equals("template") ||
  356. keyword.equals("txe") ||
  357. keyword.equals("xe")) {
  358. ignoreGroupIfUnknownKeywordSave = true;
  359. }
  360. if (ignoreGroupIfUnknownKeywordSave) {
  361. setRTFDestination(new DiscardingDestination());
  362. }
  363. return false;
  364. }
  365. /**
  366. * Handles an RTF keyword and its integer parameter.
  367. * This is called by the superclass
  368. * (RTFParser) when a keyword is found in the input stream.
  369. *
  370. * @returns <code>true</code> if the keyword is recognized and handled;
  371. * <code>false</code> otherwise
  372. * @see RTFParser#handleKeyword
  373. */
  374. public boolean handleKeyword(String keyword, int parameter)
  375. {
  376. boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
  377. if (skippingCharacters > 0) {
  378. skippingCharacters --;
  379. return true;
  380. }
  381. ignoreGroupIfUnknownKeyword = false;
  382. if (keyword.equals("uc")) {
  383. /* count of characters to skip after a unicode character */
  384. parserState.put("UnicodeSkip", new Integer(parameter));
  385. return true;
  386. }
  387. if (keyword.equals("u")) {
  388. if (parameter < 0)
  389. parameter = parameter + 65536;
  390. handleText((char)parameter);
  391. Number skip = (Number)(parserState.get("UnicodeSkip"));
  392. if (skip != null) {
  393. skippingCharacters = skip.intValue();
  394. } else {
  395. skippingCharacters = 1;
  396. }
  397. return true;
  398. }
  399. if (keyword.equals("rtf")) {
  400. rtfversion = parameter;
  401. setRTFDestination(new DocumentDestination());
  402. return true;
  403. }
  404. if (keyword.startsWith("NeXT") ||
  405. keyword.equals("private"))
  406. ignoreGroupIfUnknownKeywordSave = true;
  407. if (rtfDestination != null) {
  408. if(rtfDestination.handleKeyword(keyword, parameter))
  409. return true;
  410. }
  411. /* this point is reached only if the keyword is unrecognized */
  412. if (ignoreGroupIfUnknownKeywordSave) {
  413. setRTFDestination(new DiscardingDestination());
  414. }
  415. return false;
  416. }
  417. private void setTargetAttribute(String name, Object value)
  418. {
  419. // target.changeAttributes(new LFDictionary(LFArray.arrayWithObject(value), LFArray.arrayWithObject(name)));
  420. }
  421. /**
  422. * setCharacterSet sets the current translation table to correspond with
  423. * the named character set. The character set is loaded if necessary.
  424. *
  425. * @see AbstractFilter
  426. */
  427. public void setCharacterSet(String name)
  428. {
  429. Object set;
  430. try {
  431. set = getCharacterSet(name);
  432. } catch (Exception e) {
  433. warning("Exception loading RTF character set \"" + name + "\": " + e);
  434. set = null;
  435. }
  436. if (set != null) {
  437. translationTable = (char[])set;
  438. } else {
  439. warning("Unknown RTF character set \"" + name + "\"");
  440. if (!name.equals("ansi")) {
  441. try {
  442. translationTable = (char[])getCharacterSet("ansi");
  443. } catch (IOException e) {
  444. throw new InternalError("RTFReader: Unable to find character set resources (" + e + ")");
  445. }
  446. }
  447. }
  448. setTargetAttribute(Constants.RTFCharacterSet, name);
  449. }
  450. /** Adds a character set to the RTFReader's list
  451. * of known character sets */
  452. public static void
  453. defineCharacterSet(String name, char[] table)
  454. {
  455. if (table.length < 256)
  456. throw new IllegalArgumentException("Translation table must have 256 entries.");
  457. characterSets.put(name, table);
  458. }
  459. /** Looks up a named character set. A character set is a 256-entry
  460. * array of characters, mapping unsigned byte values to their Unicode
  461. * equivalents. The character set is loaded if necessary.
  462. *
  463. * @returns the character set
  464. */
  465. public static Object
  466. getCharacterSet(final String name)
  467. throws IOException
  468. {
  469. char[] set;
  470. set = (char [])characterSets.get(name);
  471. if (set == null) {
  472. InputStream charsetStream;
  473. charsetStream = (InputStream)java.security.AccessController.
  474. doPrivileged(new java.security.PrivilegedAction() {
  475. public Object run() {
  476. return RTFReader.class.getResourceAsStream
  477. ("charsets/" + name + ".txt");
  478. }
  479. });
  480. set = readCharset(charsetStream);
  481. defineCharacterSet(name, set);
  482. }
  483. return set;
  484. }
  485. /** Parses a character set from an InputStream. The character set
  486. * must contain 256 decimal integers, separated by whitespace, with
  487. * no punctuation. B- and C- style comments are allowed.
  488. *
  489. * @returns the newly read character set
  490. */
  491. static char[] readCharset(InputStream strm)
  492. throws IOException
  493. {
  494. char[] values = new char[256];
  495. int i;
  496. StreamTokenizer in = new StreamTokenizer(new BufferedReader(
  497. new InputStreamReader(strm, "ISO-8859-1")));
  498. in.eolIsSignificant(false);
  499. in.commentChar('#');
  500. in.slashSlashComments(true);
  501. in.slashStarComments(true);
  502. i = 0;
  503. while (i < 256) {
  504. int ttype;
  505. try {
  506. ttype = in.nextToken();
  507. } catch (Exception e) {
  508. throw new IOException("Unable to read from character set file (" + e + ")");
  509. }
  510. if (ttype != in.TT_NUMBER) {
  511. // System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
  512. throw new IOException("Unexpected token in character set file");
  513. // continue;
  514. }
  515. values[i] = (char)(in.nval);
  516. i++;
  517. }
  518. return values;
  519. }
  520. static char[] readCharset(java.net.URL href)
  521. throws IOException
  522. {
  523. return readCharset(href.openStream());
  524. }
  525. /** An interface (could be an entirely abstract class) describing
  526. * a destination. The RTF reader always has a current destination
  527. * which is where text is sent.
  528. *
  529. * @see RTFReader
  530. */
  531. interface Destination {
  532. void handleBinaryBlob(byte[] data);
  533. void handleText(String text);
  534. boolean handleKeyword(String keyword);
  535. boolean handleKeyword(String keyword, int parameter);
  536. void begingroup();
  537. void endgroup(Dictionary oldState);
  538. void close();
  539. }
  540. /** This data-sink class is used to implement ignored destinations
  541. * (e.g. {\*\blegga blah blah blah} )
  542. * It accepts all keywords and text but does nothing with them. */
  543. class DiscardingDestination implements Destination
  544. {
  545. public void handleBinaryBlob(byte[] data)
  546. {
  547. /* Discard binary blobs. */
  548. }
  549. public void handleText(String text)
  550. {
  551. /* Discard text. */
  552. }
  553. public boolean handleKeyword(String text)
  554. {
  555. /* Accept and discard keywords. */
  556. return true;
  557. }
  558. public boolean handleKeyword(String text, int parameter)
  559. {
  560. /* Accept and discard parameterized keywords. */
  561. return true;
  562. }
  563. public void begingroup()
  564. {
  565. /* Ignore groups --- the RTFReader will keep track of the
  566. current group level as necessary */
  567. }
  568. public void endgroup(Dictionary oldState)
  569. {
  570. /* Ignore groups */
  571. }
  572. public void close()
  573. {
  574. /* No end-of-destination cleanup needed */
  575. }
  576. }
  577. /** Reads the fonttbl group, inserting fonts into the RTFReader's
  578. * fontTable dictionary. */
  579. class FonttblDestination implements Destination
  580. {
  581. int nextFontNumber;
  582. Object fontNumberKey = null;
  583. String nextFontFamily;
  584. public void handleBinaryBlob(byte[] data)
  585. { /* Discard binary blobs. */ }
  586. public void handleText(String text)
  587. {
  588. int semicolon = text.indexOf(';');
  589. String fontName;
  590. if (semicolon > -1)
  591. fontName = text.substring(0, semicolon);
  592. else
  593. fontName = text;
  594. /* TODO: do something with the font family. */
  595. if (nextFontNumber == -1
  596. && fontNumberKey != null) {
  597. //font name might be broken across multiple calls
  598. fontName = fontTable.get(fontNumberKey) + fontName;
  599. } else {
  600. fontNumberKey = new Integer(nextFontNumber);
  601. }
  602. fontTable.put(fontNumberKey, fontName);
  603. nextFontNumber = -1;
  604. nextFontFamily = null;
  605. return;
  606. }
  607. public boolean handleKeyword(String keyword)
  608. {
  609. if (keyword.charAt(0) == 'f') {
  610. nextFontFamily = keyword.substring(1);
  611. return true;
  612. }
  613. return false;
  614. }
  615. public boolean handleKeyword(String keyword, int parameter)
  616. {
  617. if (keyword.equals("f")) {
  618. nextFontNumber = parameter;
  619. return true;
  620. }
  621. return false;
  622. }
  623. /* Groups are irrelevant. */
  624. public void begingroup() {}
  625. public void endgroup(Dictionary oldState) {}
  626. /* currently, the only thing we do when the font table ends is
  627. dump its contents to the debugging log. */
  628. public void close()
  629. {
  630. Enumeration nums = fontTable.keys();
  631. warning("Done reading font table.");
  632. while(nums.hasMoreElements()) {
  633. Integer num = (Integer)nums.nextElement();
  634. warning("Number " + num + ": " + fontTable.get(num));
  635. }
  636. }
  637. }
  638. /** Reads the colortbl group. Upon end-of-group, the RTFReader's
  639. * color table is set to an array containing the read colors. */
  640. class ColortblDestination implements Destination
  641. {
  642. int red, green, blue;
  643. Vector proTemTable;
  644. public ColortblDestination()
  645. {
  646. red = 0;
  647. green = 0;
  648. blue = 0;
  649. proTemTable = new Vector();
  650. }
  651. public void handleText(String text)
  652. {
  653. int index = 0;
  654. for (index = 0; index < text.length(); index ++) {
  655. if (text.charAt(index) == ';') {
  656. Color newColor;
  657. newColor = new Color(red, green, blue);
  658. proTemTable.addElement(newColor);
  659. }
  660. }
  661. }
  662. public void close()
  663. {
  664. int count = proTemTable.size();
  665. warning("Done reading color table, " + count + " entries.");
  666. colorTable = new Color[count];
  667. proTemTable.copyInto(colorTable);
  668. }
  669. public boolean handleKeyword(String keyword, int parameter)
  670. {
  671. if (keyword.equals("red"))
  672. red = parameter;
  673. else if (keyword.equals("green"))
  674. green = parameter;
  675. else if (keyword.equals("blue"))
  676. blue = parameter;
  677. else
  678. return false;
  679. return true;
  680. }
  681. /* Colortbls don't understand any parameterless keywords */
  682. public boolean handleKeyword(String keyword) { return false; }
  683. /* Groups are irrelevant. */
  684. public void begingroup() {}
  685. public void endgroup(Dictionary oldState) {}
  686. /* Shouldn't see any binary blobs ... */
  687. public void handleBinaryBlob(byte[] data) {}
  688. }
  689. /** Handles the stylesheet keyword. Styles are read and sorted
  690. * into the three style arrays in the RTFReader. */
  691. class StylesheetDestination
  692. extends DiscardingDestination
  693. implements Destination
  694. {
  695. Dictionary definedStyles;
  696. public StylesheetDestination()
  697. {
  698. definedStyles = new Hashtable();
  699. }
  700. public void begingroup()
  701. {
  702. setRTFDestination(new StyleDefiningDestination());
  703. }
  704. public void close()
  705. {
  706. Vector chrStyles, pgfStyles, secStyles;
  707. chrStyles = new Vector();
  708. pgfStyles = new Vector();
  709. secStyles = new Vector();
  710. Enumeration styles = definedStyles.elements();
  711. while(styles.hasMoreElements()) {
  712. StyleDefiningDestination style;
  713. Style defined;
  714. style = (StyleDefiningDestination)styles.nextElement();
  715. defined = style.realize();
  716. warning("Style "+style.number+" ("+style.styleName+"): "+defined);
  717. String stype = (String)defined.getAttribute(Constants.StyleType);
  718. Vector toSet;
  719. if (stype.equals(Constants.STSection)) {
  720. toSet = secStyles;
  721. } else if (stype.equals(Constants.STCharacter)) {
  722. toSet = chrStyles;
  723. } else {
  724. toSet = pgfStyles;
  725. }
  726. if (toSet.size() <= style.number)
  727. toSet.setSize(style.number + 1);
  728. toSet.setElementAt(defined, style.number);
  729. }
  730. if (!(chrStyles.isEmpty())) {
  731. Style[] styleArray = new Style[chrStyles.size()];
  732. chrStyles.copyInto(styleArray);
  733. characterStyles = styleArray;
  734. }
  735. if (!(pgfStyles.isEmpty())) {
  736. Style[] styleArray = new Style[pgfStyles.size()];
  737. pgfStyles.copyInto(styleArray);
  738. paragraphStyles = styleArray;
  739. }
  740. if (!(secStyles.isEmpty())) {
  741. Style[] styleArray = new Style[secStyles.size()];
  742. secStyles.copyInto(styleArray);
  743. sectionStyles = styleArray;
  744. }
  745. /* (old debugging code)
  746. int i, m;
  747. if (characterStyles != null) {
  748. m = characterStyles.length;
  749. for(i=0;i<m;i++)
  750. warnings.println("chrStyle["+i+"]="+characterStyles[i]);
  751. } else warnings.println("No character styles.");
  752. if (paragraphStyles != null) {
  753. m = paragraphStyles.length;
  754. for(i=0;i<m;i++)
  755. warnings.println("pgfStyle["+i+"]="+paragraphStyles[i]);
  756. } else warnings.println("No paragraph styles.");
  757. if (sectionStyles != null) {
  758. m = characterStyles.length;
  759. for(i=0;i<m;i++)
  760. warnings.println("secStyle["+i+"]="+sectionStyles[i]);
  761. } else warnings.println("No section styles.");
  762. */
  763. }
  764. /** This subclass handles an individual style */
  765. class StyleDefiningDestination
  766. extends AttributeTrackingDestination
  767. implements Destination
  768. {
  769. final int STYLENUMBER_NONE = 222;
  770. boolean additive;
  771. boolean characterStyle;
  772. boolean sectionStyle;
  773. public String styleName;
  774. public int number;
  775. int basedOn;
  776. int nextStyle;
  777. boolean hidden;
  778. Style realizedStyle;
  779. public StyleDefiningDestination()
  780. {
  781. additive = false;
  782. characterStyle = false;
  783. sectionStyle = false;
  784. styleName = null;
  785. number = 0;
  786. basedOn = STYLENUMBER_NONE;
  787. nextStyle = STYLENUMBER_NONE;
  788. hidden = false;
  789. }
  790. public void handleText(String text)
  791. {
  792. if (styleName != null)
  793. styleName = styleName + text;
  794. else
  795. styleName = text;
  796. }
  797. public void close() {
  798. int semicolon = (styleName == null) ? 0 : styleName.indexOf(';');
  799. if (semicolon > 0)
  800. styleName = styleName.substring(0, semicolon);
  801. definedStyles.put(new Integer(number), this);
  802. super.close();
  803. }
  804. public boolean handleKeyword(String keyword)
  805. {
  806. if (keyword.equals("additive")) {
  807. additive = true;
  808. return true;
  809. }
  810. if (keyword.equals("shidden")) {
  811. hidden = true;
  812. return true;
  813. }
  814. return super.handleKeyword(keyword);
  815. }
  816. public boolean handleKeyword(String keyword, int parameter)
  817. {
  818. if (keyword.equals("s")) {
  819. characterStyle = false;
  820. sectionStyle = false;
  821. number = parameter;
  822. } else if (keyword.equals("cs")) {
  823. characterStyle = true;
  824. sectionStyle = false;
  825. number = parameter;
  826. } else if (keyword.equals("ds")) {
  827. characterStyle = false;
  828. sectionStyle = true;
  829. number = parameter;
  830. } else if (keyword.equals("sbasedon")) {
  831. basedOn = parameter;
  832. } else if (keyword.equals("snext")) {
  833. nextStyle = parameter;
  834. } else {
  835. return super.handleKeyword(keyword, parameter);
  836. }
  837. return true;
  838. }
  839. public Style realize()
  840. {
  841. Style basis = null;
  842. Style next = null;
  843. if (realizedStyle != null)
  844. return realizedStyle;
  845. if (basedOn != STYLENUMBER_NONE) {
  846. StyleDefiningDestination styleDest;
  847. styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(basedOn));
  848. if (styleDest != null) {
  849. basis = styleDest.realize();
  850. }
  851. }
  852. /* NB: Swing StyleContext doesn't allow distinct styles with
  853. the same name; RTF apparently does. This may confuse the
  854. user. */
  855. realizedStyle = target.addStyle(styleName, basis);
  856. if (characterStyle) {
  857. realizedStyle.addAttributes(currentTextAttributes());
  858. realizedStyle.addAttribute(Constants.StyleType,
  859. Constants.STCharacter);
  860. } else if (sectionStyle) {
  861. realizedStyle.addAttributes(currentSectionAttributes());
  862. realizedStyle.addAttribute(Constants.StyleType,
  863. Constants.STSection);
  864. } else { /* must be a paragraph style */
  865. realizedStyle.addAttributes(currentParagraphAttributes());
  866. realizedStyle.addAttribute(Constants.StyleType,
  867. Constants.STParagraph);
  868. }
  869. if (nextStyle != STYLENUMBER_NONE) {
  870. StyleDefiningDestination styleDest;
  871. styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(nextStyle));
  872. if (styleDest != null) {
  873. next = styleDest.realize();
  874. }
  875. }
  876. if (next != null)
  877. realizedStyle.addAttribute(Constants.StyleNext, next);
  878. realizedStyle.addAttribute(Constants.StyleAdditive,
  879. Boolean.valueOf(additive));
  880. realizedStyle.addAttribute(Constants.StyleHidden,
  881. Boolean.valueOf(hidden));
  882. return realizedStyle;
  883. }
  884. }
  885. }
  886. /** Handles the info group. Currently no info keywords are recognized
  887. * so this is a subclass of DiscardingDestination. */
  888. class InfoDestination
  889. extends DiscardingDestination
  890. implements Destination
  891. {
  892. }
  893. /** RTFReader.TextHandlingDestination is an abstract RTF destination
  894. * which simply tracks the attributes specified by the RTF control words
  895. * in internal form and can produce acceptable AttributeSets for the
  896. * current character, paragraph, and section attributes. It is up
  897. * to the subclasses to determine what is done with the actual text. */
  898. abstract class AttributeTrackingDestination implements Destination
  899. {
  900. /** This is the "chr" element of parserState, cached for
  901. * more efficient use */
  902. MutableAttributeSet characterAttributes;
  903. /** This is the "pgf" element of parserState, cached for
  904. * more efficient use */
  905. MutableAttributeSet paragraphAttributes;
  906. /** This is the "sec" element of parserState, cached for
  907. * more efficient use */
  908. MutableAttributeSet sectionAttributes;
  909. public AttributeTrackingDestination()
  910. {
  911. characterAttributes = rootCharacterAttributes();
  912. parserState.put("chr", characterAttributes);
  913. paragraphAttributes = rootParagraphAttributes();
  914. parserState.put("pgf", paragraphAttributes);
  915. sectionAttributes = rootSectionAttributes();
  916. parserState.put("sec", sectionAttributes);
  917. }
  918. abstract public void handleText(String text);
  919. public void handleBinaryBlob(byte[] data)
  920. {
  921. /* This should really be in TextHandlingDestination, but
  922. * since *nobody* does anything with binary blobs, this
  923. * is more convenient. */
  924. warning("Unexpected binary data in RTF file.");
  925. }
  926. public void begingroup()
  927. {
  928. AttributeSet characterParent = currentTextAttributes();
  929. AttributeSet paragraphParent = currentParagraphAttributes();
  930. AttributeSet sectionParent = currentSectionAttributes();
  931. /* It would probably be more efficient to use the
  932. * resolver property of the attributes set for
  933. * implementing rtf groups,
  934. * but that's needed for styles. */
  935. /* update the cached attribute dictionaries */
  936. characterAttributes = new SimpleAttributeSet();
  937. characterAttributes.addAttributes(characterParent);
  938. parserState.put("chr", characterAttributes);
  939. paragraphAttributes = new SimpleAttributeSet();
  940. paragraphAttributes.addAttributes(paragraphParent);
  941. parserState.put("pgf", paragraphAttributes);
  942. sectionAttributes = new SimpleAttributeSet();
  943. sectionAttributes.addAttributes(sectionParent);
  944. parserState.put("sec", sectionAttributes);
  945. }
  946. public void endgroup(Dictionary oldState)
  947. {
  948. characterAttributes = (MutableAttributeSet)parserState.get("chr");
  949. paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
  950. sectionAttributes = (MutableAttributeSet)parserState.get("sec");
  951. }
  952. public void close()
  953. {
  954. }
  955. public boolean handleKeyword(String keyword)
  956. {
  957. if (keyword.equals("ulnone")) {
  958. return handleKeyword("ul", 0);
  959. }
  960. {
  961. Object item = straightforwardAttributes.get(keyword);
  962. if (item != null) {
  963. RTFAttribute attr = (RTFAttribute)item;
  964. boolean ok;
  965. switch(attr.domain()) {
  966. case RTFAttribute.D_CHARACTER:
  967. ok = attr.set(characterAttributes);
  968. break;
  969. case RTFAttribute.D_PARAGRAPH:
  970. ok = attr.set(paragraphAttributes);
  971. break;
  972. case RTFAttribute.D_SECTION:
  973. ok = attr.set(sectionAttributes);
  974. break;
  975. case RTFAttribute.D_META:
  976. mockery.backing = parserState;
  977. ok = attr.set(mockery);
  978. mockery.backing = null;
  979. break;
  980. case RTFAttribute.D_DOCUMENT:
  981. ok = attr.set(documentAttributes);
  982. break;
  983. default:
  984. /* should never happen */
  985. ok = false;
  986. break;
  987. }
  988. if (ok)
  989. return true;
  990. }
  991. }
  992. if (keyword.equals("plain")) {
  993. resetCharacterAttributes();
  994. return true;
  995. }
  996. if (keyword.equals("pard")) {
  997. resetParagraphAttributes();
  998. return true;
  999. }
  1000. if (keyword.equals("sectd")) {
  1001. resetSectionAttributes();
  1002. return true;
  1003. }
  1004. return false;
  1005. }
  1006. public boolean handleKeyword(String keyword, int parameter)
  1007. {
  1008. boolean booleanParameter = (parameter != 0);
  1009. if (keyword.equals("fc"))
  1010. keyword = "cf"; /* whatEVER, dude. */
  1011. if (keyword.equals("f")) {
  1012. parserState.put(keyword, new Integer(parameter));
  1013. return true;
  1014. }
  1015. if (keyword.equals("cf")) {
  1016. parserState.put(keyword, new Integer(parameter));
  1017. return true;
  1018. }
  1019. {
  1020. Object item = straightforwardAttributes.get(keyword);
  1021. if (item != null) {
  1022. RTFAttribute attr = (RTFAttribute)item;
  1023. boolean ok;
  1024. switch(attr.domain()) {
  1025. case RTFAttribute.D_CHARACTER:
  1026. ok = attr.set(characterAttributes, parameter);
  1027. break;
  1028. case RTFAttribute.D_PARAGRAPH:
  1029. ok = attr.set(paragraphAttributes, parameter);
  1030. break;
  1031. case RTFAttribute.D_SECTION:
  1032. ok = attr.set(sectionAttributes, parameter);
  1033. break;
  1034. case RTFAttribute.D_META:
  1035. mockery.backing = parserState;
  1036. ok = attr.set(mockery, parameter);
  1037. mockery.backing = null;
  1038. break;
  1039. case RTFAttribute.D_DOCUMENT:
  1040. ok = attr.set(documentAttributes, parameter);
  1041. break;
  1042. default:
  1043. /* should never happen */
  1044. ok = false;
  1045. break;
  1046. }
  1047. if (ok)
  1048. return true;
  1049. }
  1050. }
  1051. if (keyword.equals("fs")) {
  1052. StyleConstants.setFontSize(characterAttributes, (parameter / 2));
  1053. return true;
  1054. }
  1055. /* TODO: superscript/subscript */
  1056. if (keyword.equals("sl")) {
  1057. if (parameter == 1000) { /* magic value! */
  1058. characterAttributes.removeAttribute(StyleConstants.LineSpacing);
  1059. } else {
  1060. /* TODO: The RTF sl attribute has special meaning if it's
  1061. negative. Make sure that SwingText has the same special
  1062. meaning, or find a way to imitate that. When SwingText
  1063. handles this, also recognize the slmult keyword. */
  1064. StyleConstants.setLineSpacing(characterAttributes,
  1065. parameter / 20f);
  1066. }
  1067. return true;
  1068. }
  1069. /* TODO: Other kinds of underlining */
  1070. if (keyword.equals("tx") || keyword.equals("tb")) {
  1071. float tabPosition = parameter / 20f;
  1072. int tabAlignment, tabLeader;
  1073. Number item;
  1074. tabAlignment = TabStop.ALIGN_LEFT;
  1075. item = (Number)(parserState.get("tab_alignment"));
  1076. if (item != null)
  1077. tabAlignment = item.intValue();
  1078. tabLeader = TabStop.LEAD_NONE;
  1079. item = (Number)(parserState.get("tab_leader"));
  1080. if (item != null)
  1081. tabLeader = item.intValue();
  1082. if (keyword.equals("tb"))
  1083. tabAlignment = TabStop.ALIGN_BAR;
  1084. parserState.remove("tab_alignment");
  1085. parserState.remove("tab_leader");
  1086. TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
  1087. Dictionary tabs;
  1088. Integer stopCount;
  1089. tabs = (Dictionary)parserState.get("_tabs");
  1090. if (tabs == null) {
  1091. tabs = new Hashtable();
  1092. parserState.put("_tabs", tabs);
  1093. stopCount = new Integer(1);
  1094. } else {
  1095. stopCount = (Integer)tabs.get("stop count");
  1096. stopCount = new Integer(1 + stopCount.intValue());
  1097. }
  1098. tabs.put(stopCount, newStop);
  1099. tabs.put("stop count", stopCount);
  1100. parserState.remove("_tabs_immutable");
  1101. return true;
  1102. }
  1103. if (keyword.equals("s") &&
  1104. paragraphStyles != null) {
  1105. parserState.put("paragraphStyle", paragraphStyles[parameter]);
  1106. return true;
  1107. }
  1108. if (keyword.equals("cs") &&
  1109. characterStyles != null) {
  1110. parserState.put("characterStyle", characterStyles[parameter]);
  1111. return true;
  1112. }
  1113. if (keyword.equals("ds") &&
  1114. sectionStyles != null) {
  1115. parserState.put("sectionStyle", sectionStyles[parameter]);
  1116. return true;
  1117. }
  1118. return false;
  1119. }
  1120. /** Returns a new MutableAttributeSet containing the
  1121. * default character attributes */
  1122. protected MutableAttributeSet rootCharacterAttributes()
  1123. {
  1124. MutableAttributeSet set = new SimpleAttributeSet();
  1125. /* TODO: default font */
  1126. StyleConstants.setItalic(set, false);
  1127. StyleConstants.setBold(set, false);
  1128. StyleConstants.setUnderline(set, false);
  1129. StyleConstants.setForeground(set, defaultColor());
  1130. return set;
  1131. }
  1132. /** Returns a new MutableAttributeSet containing the
  1133. * default paragraph attributes */
  1134. protected MutableAttributeSet rootParagraphAttributes()
  1135. {
  1136. MutableAttributeSet set = new SimpleAttributeSet();
  1137. StyleConstants.setLeftIndent(set, 0f);
  1138. StyleConstants.setRightIndent(set, 0f);
  1139. StyleConstants.setFirstLineIndent(set, 0f);
  1140. /* TODO: what should this be, really? */
  1141. set.setResolveParent(target.getStyle(StyleContext.DEFAULT_STYLE));
  1142. return set;
  1143. }
  1144. /** Returns a new MutableAttributeSet containing the
  1145. * default section attributes */
  1146. protected MutableAttributeSet rootSectionAttributes()
  1147. {
  1148. MutableAttributeSet set = new SimpleAttributeSet();
  1149. return set;
  1150. }
  1151. /**
  1152. * Calculates the current text (character) attributes in a form suitable
  1153. * for SwingText from the current parser state.
  1154. *
  1155. * @returns a new MutableAttributeSet containing the text attributes.
  1156. */
  1157. MutableAttributeSet currentTextAttributes()
  1158. {
  1159. MutableAttributeSet attributes =
  1160. new SimpleAttributeSet(characterAttributes);
  1161. Integer fontnum;
  1162. Integer stateItem;
  1163. /* figure out the font name */
  1164. /* TODO: catch exceptions for undefined attributes,
  1165. bad font indices, etc.? (as it stands, it is the caller's
  1166. job to clean up after corrupt RTF) */
  1167. fontnum = (Integer)parserState.get("f");
  1168. /* note setFontFamily() can not handle a null font */
  1169. String fontFamily;
  1170. if (fontnum != null)
  1171. fontFamily = (String)fontTable.get(fontnum);
  1172. else
  1173. fontFamily = null;
  1174. if (fontFamily != null)
  1175. StyleConstants.setFontFamily(attributes, fontFamily);
  1176. else
  1177. attributes.removeAttribute(StyleConstants.FontFamily);
  1178. if (colorTable != null) {
  1179. stateItem = (Integer)parserState.get("cf");
  1180. if (stateItem != null) {
  1181. Color fg = colorTable[stateItem.intValue()];
  1182. StyleConstants.setForeground(attributes, fg);
  1183. } else {
  1184. /* AttributeSet dies if you set a value to null */
  1185. attributes.removeAttribute(StyleConstants.Foreground);
  1186. }
  1187. }
  1188. if (colorTable != null) {
  1189. stateItem = (Integer)parserState.get("cb");
  1190. if (stateItem != null) {
  1191. Color bg = colorTable[stateItem.intValue()];
  1192. attributes.addAttribute(StyleConstants.Background,
  1193. bg);
  1194. } else {
  1195. /* AttributeSet dies if you set a value to null */
  1196. attributes.removeAttribute(StyleConstants.Background);
  1197. }
  1198. }
  1199. Style characterStyle = (Style)parserState.get("characterStyle");
  1200. if (characterStyle != null)
  1201. attributes.setResolveParent(characterStyle);
  1202. /* Other attributes are maintained directly in "attributes" */
  1203. return attributes;
  1204. }
  1205. /**
  1206. * Calculates the current paragraph attributes (with keys
  1207. * as given in StyleConstants) from the current parser state.
  1208. *
  1209. * @returns a newly created MutableAttributeSet.
  1210. * @see StyleConstants
  1211. */
  1212. MutableAttributeSet currentParagraphAttributes()
  1213. {
  1214. /* NB if there were a mutableCopy() method we should use it */
  1215. MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
  1216. Integer stateItem;
  1217. /*** Tab stops ***/
  1218. TabStop tabs[];
  1219. tabs = (TabStop[])parserState.get("_tabs_immutable");
  1220. if (tabs == null) {
  1221. Dictionary workingTabs = (Dictionary)parserState.get("_tabs");
  1222. if (workingTabs != null) {
  1223. int count = ((Integer)workingTabs.get("stop count")).intValue();
  1224. tabs = new TabStop[count];
  1225. for (int ix = 1; ix <= count; ix ++)
  1226. tabs[ix-1] = (TabStop)workingTabs.get(new Integer(ix));
  1227. parserState.put("_tabs_immutable", tabs);
  1228. }
  1229. }
  1230. if (tabs != null)
  1231. bld.addAttribute(Constants.Tabs, tabs);
  1232. Style paragraphStyle = (Style)parserState.get("paragraphStyle");
  1233. if (paragraphStyle != null)
  1234. bld.setResolveParent(paragraphStyle);
  1235. return bld;
  1236. }
  1237. /**
  1238. * Calculates the current section attributes
  1239. * from the current parser state.
  1240. *
  1241. * @returns a newly created MutableAttributeSet.
  1242. */
  1243. public AttributeSet currentSectionAttributes()
  1244. {
  1245. MutableAttributeSet attributes = new SimpleAttributeSet(sectionAttributes);
  1246. Style sectionStyle = (Style)parserState.get("sectionStyle");
  1247. if (sectionStyle != null)
  1248. attributes.setResolveParent(sectionStyle);
  1249. return attributes;
  1250. }
  1251. /** Resets the filter's internal notion of the current character
  1252. * attributes to their default values. Invoked to handle the
  1253. * \plain keyword. */
  1254. protected void resetCharacterAttributes()
  1255. {
  1256. handleKeyword("f", 0);
  1257. handleKeyword("cf", 0);
  1258. handleKeyword("fs", 24); /* 12 pt. */
  1259. Enumeration attributes = straightforwardAttributes.elements();
  1260. while(attributes.hasMoreElements()) {
  1261. RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1262. if (attr.domain() == RTFAttribute.D_CHARACTER)
  1263. attr.setDefault(characterAttributes);
  1264. }
  1265. handleKeyword("sl", 1000);
  1266. parserState.remove("characterStyle");
  1267. }
  1268. /** Resets the filter's internal notion of the current paragraph's
  1269. * attributes to their default values. Invoked to handle the
  1270. * \pard keyword. */
  1271. protected void resetParagraphAttributes()
  1272. {
  1273. parserState.remove("_tabs");
  1274. parserState.remove("_tabs_immutable");
  1275. parserState.remove("paragraphStyle");
  1276. StyleConstants.setAlignment(paragraphAttributes,
  1277. StyleConstants.ALIGN_LEFT);
  1278. Enumeration attributes = straightforwardAttributes.elements();
  1279. while(attributes.hasMoreElements()) {
  1280. RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1281. if (attr.domain() == RTFAttribute.D_PARAGRAPH)
  1282. attr.setDefault(characterAttributes);
  1283. }
  1284. }
  1285. /** Resets the filter's internal notion of the current section's
  1286. * attributes to their default values. Invoked to handle the
  1287. * \sectd keyword. */
  1288. protected void resetSectionAttributes()
  1289. {
  1290. Enumeration attributes = straightforwardAttributes.elements();
  1291. while(attributes.hasMoreElements()) {
  1292. RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1293. if (attr.domain() == RTFAttribute.D_SECTION)
  1294. attr.setDefault(characterAttributes);
  1295. }
  1296. parserState.remove("sectionStyle");
  1297. }
  1298. }
  1299. /** RTFReader.TextHandlingDestination provides basic text handling
  1300. * functionality. Subclasses must implement: <dl>
  1301. * <dt>deliverText()<dd>to handle a run of text with the same
  1302. * attributes
  1303. * <dt>finishParagraph()<dd>to end the current paragraph and
  1304. * set the paragraph's attributes
  1305. * <dt>endSection()<dd>to end the current section
  1306. * </dl>
  1307. */
  1308. abstract class TextHandlingDestination
  1309. extends AttributeTrackingDestination
  1310. implements Destination
  1311. {
  1312. /** <code>true</code> if the reader has not just finished
  1313. * a paragraph; false upon startup */
  1314. boolean inParagraph;
  1315. public TextHandlingDestination()
  1316. {
  1317. super();
  1318. inParagraph = false;
  1319. }
  1320. public void handleText(String text)
  1321. {
  1322. if (! inParagraph)
  1323. beginParagraph();
  1324. deliverText(text, currentTextAttributes());
  1325. }
  1326. abstract void deliverText(String text, AttributeSet characterAttributes);
  1327. public void close()
  1328. {
  1329. if (inParagraph)
  1330. endParagraph();
  1331. super.close();
  1332. }
  1333. public boolean handleKeyword(String keyword)
  1334. {
  1335. if (keyword.equals("\r") || keyword.equals("\n")) {
  1336. keyword = "par";
  1337. }
  1338. if (keyword.equals("par")) {
  1339. // warnings.println("Ending paragraph.");
  1340. endParagraph();
  1341. return true;
  1342. }
  1343. if (keyword.equals("sect")) {
  1344. // warnings.println("Ending section.");
  1345. endSection();
  1346. return true;
  1347. }
  1348. return super.handleKeyword(keyword);
  1349. }
  1350. protected void beginParagraph()
  1351. {
  1352. inParagraph = true;
  1353. }
  1354. protected void endParagraph()
  1355. {
  1356. AttributeSet pgfAttributes = currentParagraphAttributes();
  1357. AttributeSet chrAttributes = currentTextAttributes();
  1358. finishParagraph(pgfAttributes, chrAttributes);
  1359. inParagraph = false;
  1360. }
  1361. abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
  1362. abstract void endSection();
  1363. }
  1364. /** RTFReader.DocumentDestination is a concrete subclass of
  1365. * TextHandlingDestination which appends the text to the
  1366. * StyledDocument given by the <code>target</code> ivar of the
  1367. * containing RTFReader.
  1368. */
  1369. class DocumentDestination
  1370. extends TextHandlingDestination
  1371. implements Destination
  1372. {
  1373. public void deliverText(String text, AttributeSet characterAttributes)
  1374. {
  1375. try {
  1376. target.insertString(target.getLength(),
  1377. text,
  1378. currentTextAttributes());
  1379. } catch (BadLocationException ble) {
  1380. /* This shouldn't be able to happen, of course */
  1381. /* TODO is InternalError the correct error to throw? */
  1382. throw new InternalError(ble.getMessage());
  1383. }
  1384. }
  1385. public void finishParagraph(AttributeSet pgfAttributes,
  1386. AttributeSet chrAttributes)
  1387. {
  1388. int pgfEndPosition = target.getLength();
  1389. try {
  1390. target.insertString(pgfEndPosition, "\n", chrAttributes);
  1391. target.setParagraphAttributes(pgfEndPosition, 1, pgfAttributes, true);
  1392. } catch (BadLocationException ble) {
  1393. /* This shouldn't be able to happen, of course */
  1394. /* TODO is InternalError the correct error to throw? */
  1395. throw new InternalError(ble.getMessage());
  1396. }
  1397. }
  1398. public void endSection()
  1399. {
  1400. /* If we implemented sections, we'd end 'em here */
  1401. }
  1402. }
  1403. }