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