1. /*
  2. * @(#)RTFReader.java 1.14 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text.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 StyledDocument (the <em>target</em>).
  17. * The RTF is lexed
  18. * from the character stream by the RTFParser 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 parserState, and gives
  163. * the current destination a chence 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(String name)
  467. throws IOException
  468. {
  469. char[] set;
  470. set = (char [])characterSets.get(name);
  471. if (set == null) {
  472. InputStream charsetStream = null;
  473. /* TODO: when class names are allowed as literal Class objects,
  474. change this to use the nicer syntax */
  475. Class This;
  476. try {
  477. This = Class.forName("javax.swing.text.rtf.RTFReader");
  478. } catch (ClassNotFoundException e) {
  479. throw new InternalError("Help, I do not exist (" + e + ")");
  480. }
  481. //PENDING(prinz): Use new security modelf
  482. charsetStream = This.getResourceAsStream("charsets/" + name + ".txt");
  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 InputStreamReader(strm));
  500. in.eolIsSignificant(false);
  501. in.commentChar('#');
  502. in.slashSlashComments(true);
  503. in.slashStarComments(true);
  504. i = 0;
  505. while (i < 256) {
  506. int ttype;
  507. try {
  508. ttype = in.nextToken();
  509. } catch (Exception e) {
  510. throw new IOException("Unable to read from character set file (" + e + ")");
  511. }
  512. if (ttype != in.TT_NUMBER) {
  513. // System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
  514. throw new IOException("Unexpected token in character set file");
  515. // continue;
  516. }
  517. values[i] = (char)(in.nval);
  518. i++;
  519. }
  520. return values;
  521. }
  522. static char[] readCharset(java.net.URL href)
  523. throws IOException
  524. {
  525. return readCharset(href.openStream());
  526. }
  527. /** An interface (could be an entirely abstract class) describing
  528. * a destination. The RTF reader always has a current destination
  529. * which is where text is sent.
  530. *
  531. * @see RTFReader
  532. */
  533. interface Destination {
  534. void handleBinaryBlob(byte[] data);
  535. void handleText(String text);
  536. boolean handleKeyword(String keyword);
  537. boolean handleKeyword(String keyword, int parameter);
  538. void begingroup();
  539. void endgroup(Dictionary oldState);
  540. void close();
  541. }
  542. /** This data-sink class is used to implement ignored destinations
  543. * (e.g. {\*\blegga blah blah blah} )
  544. * It accepts all keywords and text but does nothing with them. */
  545. class DiscardingDestination implements Destination
  546. {
  547. public void handleBinaryBlob(byte[] data)
  548. {
  549. /* Discard binary blobs. */
  550. }
  551. public void handleText(String text)
  552. {
  553. /* Discard text. */
  554. }
  555. public boolean handleKeyword(String text)
  556. {
  557. /* Accept and discard keywords. */
  558. return true;
  559. }
  560. public boolean handleKeyword(String text, int parameter)
  561. {
  562. /* Accept and discard parameterized keywords. */
  563. return true;
  564. }
  565. public void begingroup()
  566. {
  567. /* Ignore groups --- the RTFReader will keep track of the
  568. current group level as necessary */
  569. }
  570. public void endgroup(Dictionary oldState)
  571. {
  572. /* Ignore groups */
  573. }
  574. public void close()
  575. {
  576. /* No end-of-destination cleanup needed */
  577. }
  578. }
  579. /** Reads the fonttbl group, inserting fonts into the RTFReader's
  580. * fontTable dictionary. */
  581. class FonttblDestination implements Destination
  582. {
  583. int nextFontNumber;
  584. String nextFontFamily;
  585. public void handleBinaryBlob(byte[] data)
  586. { /* Discard binary blobs. */ }
  587. /* TODO do these routines work correctly if a write buffer divides a
  588. font name? (Probably not. Should allow for it as rare case) */
  589. public void handleText(String text)
  590. {
  591. int semicolon = text.indexOf(';');
  592. String fontName;
  593. Object fontNum; /* an Integer, but we don't care */
  594. if (semicolon > 0)
  595. fontName = text.substring(0, semicolon);
  596. else
  597. fontName = text;
  598. /* TODO: do something with the font family. */
  599. fontTable.put(new Integer(nextFontNumber), fontName);
  600. nextFontNumber = -1;
  601. nextFontFamily = null;
  602. return;
  603. }
  604. public boolean handleKeyword(String keyword)
  605. {
  606. if (keyword.charAt(0) == 'f') {
  607. nextFontFamily = keyword.substring(1);
  608. return true;
  609. }
  610. return false;
  611. }
  612. public boolean handleKeyword(String keyword, int parameter)
  613. {
  614. if (keyword.equals("f")) {
  615. nextFontNumber = parameter;
  616. return true;
  617. }
  618. return false;
  619. }
  620. /* Groups are irrelevant. */
  621. public void begingroup() {}
  622. public void endgroup(Dictionary oldState) {}
  623. /* currently, the only thing we do when the font table ends is
  624. dump its contents to the debugging log. */
  625. public void close()
  626. {
  627. Enumeration nums = fontTable.keys();
  628. warning("Done reading font table.");
  629. while(nums.hasMoreElements()) {
  630. Integer num = (Integer)nums.nextElement();
  631. warning("Number " + num + ": " + fontTable.get(num));
  632. }
  633. }
  634. }
  635. /** Reads the colortbl group. Upon end-of-group, the RTFReader's
  636. * color table is set to an array containing the read colors. */
  637. class ColortblDestination implements Destination
  638. {
  639. int red, green, blue;
  640. Vector proTemTable;
  641. public ColortblDestination()
  642. {
  643. red = 0;
  644. green = 0;
  645. blue = 0;
  646. proTemTable = new Vector();
  647. }
  648. public void handleText(String text)
  649. {
  650. int index = 0;
  651. for (index = 0; index < text.length(); index ++) {
  652. if (text.charAt(index) == ';') {
  653. Color newColor;
  654. newColor = new Color(red, green, blue);
  655. proTemTable.addElement(newColor);
  656. }
  657. }
  658. }
  659. public void close()
  660. {
  661. int count = proTemTable.size();
  662. warning("Done reading color table, " + count + " entries.");
  663. colorTable = new Color[count];
  664. proTemTable.copyInto(colorTable);
  665. }
  666. public boolean handleKeyword(String keyword, int parameter)
  667. {
  668. if (keyword.equals("red"))
  669. red = parameter;
  670. else if (keyword.equals("green"))
  671. green = parameter;
  672. else if (keyword.equals("blue"))
  673. blue = parameter;
  674. else
  675. return false;
  676. return true;
  677. }
  678. /* Colortbls don't understand any parameterless keywords */
  679. public boolean handleKeyword(String keyword) { return false; }
  680. /* Groups are irrelevant. */
  681. public void begingroup() {}
  682. public void endgroup(Dictionary oldState) {}
  683. /* Shouldn't see any binary blobs ... */
  684. public void handleBinaryBlob(byte[] data) {}
  685. }
  686. /** Handles the stylesheet keyword. Styles are read and sorted
  687. * into the three style arrays in the RTFReader. */
  688. class StylesheetDestination
  689. extends DiscardingDestination
  690. implements Destination
  691. {
  692. Dictionary definedStyles;
  693. public StylesheetDestination()
  694. {
  695. definedStyles = new Hashtable();
  696. }
  697. public void begingroup()
  698. {
  699. setRTFDestination(new StyleDefiningDestination());
  700. }
  701. public void close()
  702. {
  703. Vector chrStyles, pgfStyles, secStyles;
  704. chrStyles = new Vector();
  705. pgfStyles = new Vector();
  706. secStyles = new Vector();
  707. Enumeration styles = definedStyles.elements();
  708. while(styles.hasMoreElements()) {
  709. StyleDefiningDestination style;
  710. Style defined;
  711. style = (StyleDefiningDestination)styles.nextElement();
  712. defined = style.realize();
  713. warning("Style "+style.number+" ("+style.styleName+"): "+defined);
  714. String stype = (String)defined.getAttribute(Constants.StyleType);
  715. Vector toSet;
  716. if (stype.equals(Constants.STSection)) {
  717. toSet = secStyles;
  718. } else if (stype.equals(Constants.STCharacter)) {
  719. toSet = chrStyles;
  720. } else {
  721. toSet = pgfStyles;
  722. }
  723. if (toSet.size() <= style.number)
  724. toSet.setSize(style.number + 1);
  725. toSet.setElementAt(defined, style.number);
  726. }
  727. if (!(chrStyles.isEmpty())) {
  728. Style[] styleArray = new Style[chrStyles.size()];
  729. chrStyles.copyInto(styleArray);
  730. characterStyles = styleArray;
  731. }
  732. if (!(pgfStyles.isEmpty())) {
  733. Style[] styleArray = new Style[pgfStyles.size()];
  734. pgfStyles.copyInto(styleArray);
  735. paragraphStyles = styleArray;
  736. }
  737. if (!(secStyles.isEmpty())) {
  738. Style[] styleArray = new Style[secStyles.size()];
  739. secStyles.copyInto(styleArray);
  740. sectionStyles = styleArray;
  741. }
  742. /* (old debugging code)
  743. int i, m;
  744. if (characterStyles != null) {
  745. m = characterStyles.length;
  746. for(i=0;i<m;i++)
  747. warnings.println("chrStyle["+i+"]="+characterStyles[i]);
  748. } else warnings.println("No character styles.");
  749. if (paragraphStyles != null) {
  750. m = paragraphStyles.length;
  751. for(i=0;i<m;i++)
  752. warnings.println("pgfStyle["+i+"]="+paragraphStyles[i]);
  753. } else warnings.println("No paragraph styles.");
  754. if (sectionStyles != null) {
  755. m = characterStyles.length;
  756. for(i=0;i<m;i++)
  757. warnings.println("secStyle["+i+"]="+sectionStyles[i]);
  758. } else warnings.println("No section styles.");
  759. */
  760. }
  761. /** This subclass handles an individual style */
  762. class StyleDefiningDestination
  763. extends AttributeTrackingDestination
  764. implements Destination
  765. {
  766. final int STYLENUMBER_NONE = 222;
  767. boolean additive;
  768. boolean characterStyle;
  769. boolean sectionStyle;
  770. public String styleName;
  771. public int number;
  772. int basedOn;
  773. int nextStyle;
  774. boolean hidden;
  775. Style realizedStyle;
  776. public StyleDefiningDestination()
  777. {
  778. additive = false;
  779. characterStyle = false;
  780. sectionStyle = false;
  781. styleName = null;
  782. number = 0;
  783. basedOn = STYLENUMBER_NONE;
  784. nextStyle = STYLENUMBER_NONE;
  785. hidden = false;
  786. }
  787. public void handleText(String text)
  788. {
  789. if (styleName != null)
  790. styleName = styleName + text;
  791. else
  792. styleName = text;
  793. }
  794. public void close() {
  795. int semicolon = styleName.indexOf(';');
  796. if (semicolon > 0)
  797. styleName = styleName.substring(0, semicolon);
  798. definedStyles.put(new Integer(number), this);
  799. super.close();
  800. }
  801. public boolean handleKeyword(String keyword)
  802. {
  803. if (keyword.equals("additive")) {
  804. additive = true;
  805. return true;
  806. }
  807. if (keyword.equals("shidden")) {
  808. hidden = true;
  809. return true;
  810. }
  811. return super.handleKeyword(keyword);
  812. }
  813. public boolean handleKeyword(String keyword, int parameter)
  814. {
  815. if (keyword.equals("s")) {
  816. characterStyle = false;
  817. sectionStyle = false;
  818. number = parameter;
  819. } else if (keyword.equals("cs")) {
  820. characterStyle = true;
  821. sectionStyle = false;
  822. number = parameter;
  823. } else if (keyword.equals("ds")) {
  824. characterStyle = false;
  825. sectionStyle = true;
  826. number = parameter;
  827. } else if (keyword.equals("sbasedon")) {
  828. basedOn = parameter;
  829. } else if (keyword.equals("snext")) {
  830. nextStyle = parameter;
  831. } else {
  832. return super.handleKeyword(keyword, parameter);
  833. }
  834. return true;
  835. }
  836. public Style realize()
  837. {
  838. Style basis = null;
  839. Style next = null;
  840. if (realizedStyle != null)
  841. return realizedStyle;
  842. if (basedOn != STYLENUMBER_NONE) {
  843. StyleDefiningDestination styleDest;
  844. styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(basedOn));
  845. if (styleDest != null) {
  846. basis = styleDest.realize();
  847. }
  848. }
  849. /* NB: Swing StyleContext doesn't allow distinct styles with
  850. the same name; RTF apparently does. This may confuse the
  851. user. */
  852. realizedStyle = target.addStyle(styleName, basis);
  853. if (characterStyle) {
  854. realizedStyle.addAttributes(currentTextAttributes());
  855. realizedStyle.addAttribute(Constants.StyleType,
  856. Constants.STCharacter);
  857. } else if (sectionStyle) {
  858. realizedStyle.addAttributes(currentSectionAttributes());
  859. realizedStyle.addAttribute(Constants.StyleType,
  860. Constants.STSection);
  861. } else { /* must be a paragraph style */
  862. realizedStyle.addAttributes(currentParagraphAttributes());
  863. realizedStyle.addAttribute(Constants.StyleType,
  864. Constants.STParagraph);
  865. }
  866. if (nextStyle != STYLENUMBER_NONE) {
  867. StyleDefiningDestination styleDest;
  868. styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(nextStyle));
  869. if (styleDest != null) {
  870. next = styleDest.realize();
  871. }
  872. }
  873. if (next != null)
  874. realizedStyle.addAttribute(Constants.StyleNext, next);
  875. realizedStyle.addAttribute(Constants.StyleAdditive,
  876. new Boolean(additive));
  877. realizedStyle.addAttribute(Constants.StyleHidden,
  878. new Boolean(hidden));
  879. return realizedStyle;
  880. }
  881. }
  882. }
  883. /** Handles the info group. Currently no info keywords are recognized
  884. * so this is a subclass of DiscardingDestination. */
  885. class InfoDestination
  886. extends DiscardingDestination
  887. implements Destination
  888. {
  889. }
  890. /** RTFReader.TextHandlingDestination is an abstract RTF destination
  891. * which simply tracks the attributes specified by the RTF control words
  892. * in internal form and can produce acceptable AttributeSets for the
  893. * current character, paragraph, and section attributes. It is up
  894. * to the subclasses to determine what is done with the actual text. */
  895. abstract class AttributeTrackingDestination implements Destination
  896. {
  897. /** This is the "chr" element of parserState, cached for
  898. * more efficient use */
  899. MutableAttributeSet characterAttributes;
  900. /** This is the "pgf" element of parserState, cached for
  901. * more efficient use */
  902. MutableAttributeSet paragraphAttributes;
  903. /** This is the "sec" element of parserState, cached for
  904. * more efficient use */
  905. MutableAttributeSet sectionAttributes;
  906. public AttributeTrackingDestination()
  907. {
  908. characterAttributes = rootCharacterAttributes();
  909. parserState.put("chr", characterAttributes);
  910. paragraphAttributes = rootParagraphAttributes();
  911. parserState.put("pgf", paragraphAttributes);
  912. sectionAttributes = rootSectionAttributes();
  913. parserState.put("sec", sectionAttributes);
  914. }
  915. abstract public void handleText(String text);
  916. public void handleBinaryBlob(byte[] data)
  917. {
  918. /* This should really be in TextHandlingDestination, but
  919. * since *nobody* does anything with binary blobs, this
  920. * is more convenient. */
  921. warning("Unexpected binary data in RTF file.");
  922. }
  923. public void begingroup()
  924. {
  925. AttributeSet characterParent = currentTextAttributes();
  926. AttributeSet paragraphParent = currentParagraphAttributes();
  927. AttributeSet sectionParent = currentSectionAttributes();
  928. /* It would probably be more efficient to use the
  929. * resolver property of the attributes set for
  930. * implementing rtf groups,
  931. * but that's needed for styles. */
  932. /* update the cached attribute dictionaries */
  933. characterAttributes = new SimpleAttributeSet();
  934. characterAttributes.addAttributes(characterParent);
  935. parserState.put("chr", characterAttributes);
  936. paragraphAttributes = new SimpleAttributeSet();
  937. paragraphAttributes.addAttributes(paragraphParent);
  938. parserState.put("pgf", paragraphAttributes);
  939. sectionAttributes = new SimpleAttributeSet();
  940. sectionAttributes.addAttributes(sectionParent);
  941. parserState.put("sec", sectionAttributes);
  942. }
  943. public void endgroup(Dictionary oldState)
  944. {
  945. characterAttributes = (MutableAttributeSet)parserState.get("chr");
  946. paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
  947. sectionAttributes = (MutableAttributeSet)parserState.get("sec");
  948. }
  949. public void close()
  950. {
  951. }
  952. public boolean handleKeyword(String keyword)
  953. {
  954. if (keyword.equals("ulnone")) {
  955. return handleKeyword("ul", 0);
  956. }
  957. {
  958. Object item = straightforwardAttributes.get(keyword);
  959. if (item != null) {
  960. RTFAttribute attr = (RTFAttribute)item;
  961. boolean ok;
  962. switch(attr.domain()) {
  963. case RTFAttribute.D_CHARACTER:
  964. ok = attr.set(characterAttributes);
  965. break;
  966. case RTFAttribute.D_PARAGRAPH:
  967. ok = attr.set(paragraphAttributes);
  968. break;
  969. case RTFAttribute.D_SECTION:
  970. ok = attr.set(sectionAttributes);
  971. break;
  972. case RTFAttribute.D_META:
  973. mockery.backing = parserState;
  974. ok = attr.set(mockery);
  975. mockery.backing = null;
  976. break;
  977. case RTFAttribute.D_DOCUMENT:
  978. ok = attr.set(documentAttributes);
  979. break;
  980. default:
  981. /* should never happen */
  982. ok = false;
  983. break;
  984. }
  985. if (ok)
  986. return true;
  987. }
  988. }
  989. if (keyword.equals("plain")) {
  990. resetCharacterAttributes();
  991. return true;
  992. }
  993. if (keyword.equals("pard")) {
  994. resetParagraphAttributes();
  995. return true;
  996. }
  997. if (keyword.equals("sectd")) {
  998. resetSectionAttributes();
  999. return true;
  1000. }
  1001. return false;
  1002. }
  1003. public boolean handleKeyword(String keyword, int parameter)
  1004. {
  1005. boolean booleanParameter = (parameter != 0);
  1006. if (keyword.equals("fc"))
  1007. keyword = "cf"; /* whatEVER, dude. */
  1008. if (keyword.equals("f")) {
  1009. parserState.put(keyword, new Integer(parameter));
  1010. return true;
  1011. }
  1012. if (keyword.equals("cf")) {
  1013. parserState.put(keyword, new Integer(parameter));
  1014. return true;
  1015. }
  1016. {
  1017. Object item = straightforwardAttributes.get(keyword);
  1018. if (item != null) {
  1019. RTFAttribute attr = (RTFAttribute)item;
  1020. boolean ok;
  1021. switch(attr.domain()) {
  1022. case RTFAttribute.D_CHARACTER:
  1023. ok = attr.set(characterAttributes, parameter);
  1024. break;
  1025. case RTFAttribute.D_PARAGRAPH:
  1026. ok = attr.set(paragraphAttributes, parameter);
  1027. break;
  1028. case RTFAttribute.D_SECTION:
  1029. ok = attr.set(sectionAttributes, parameter);
  1030. break;
  1031. case RTFAttribute.D_META:
  1032. mockery.backing = parserState;
  1033. ok = attr.set(mockery, parameter);
  1034. mockery.backing = null;
  1035. break;
  1036. case RTFAttribute.D_DOCUMENT:
  1037. ok = attr.set(documentAttributes, parameter);
  1038. break;
  1039. default:
  1040. /* should never happen */
  1041. ok = false;
  1042. break;
  1043. }
  1044. if (ok)
  1045. return true;
  1046. }
  1047. }
  1048. if (keyword.equals("fs")) {
  1049. StyleConstants.setFontSize(characterAttributes, (parameter / 2));
  1050. return true;
  1051. }
  1052. /* TODO: superscript/subscript */
  1053. if (keyword.equals("sl")) {
  1054. if (parameter == 1000) { /* magic value! */
  1055. characterAttributes.removeAttribute(StyleConstants.LineSpacing);
  1056. } else {
  1057. /* TODO: The RTF sl attribute has special meaning if it's
  1058. negative. Make sure that SwingText has the same special
  1059. meaning, or find a way to imitate that. When SwingText
  1060. handles this, also recognize the slmult keyword. */
  1061. StyleConstants.setLineSpacing(characterAttributes,
  1062. parameter / 20f);
  1063. }
  1064. return true;
  1065. }
  1066. /* TODO: Other kinds of underlining */
  1067. if (keyword.equals("tx") || keyword.equals("tb")) {
  1068. float tabPosition = parameter / 20f;
  1069. int tabAlignment, tabLeader;
  1070. Number item;
  1071. tabAlignment = TabStop.ALIGN_LEFT;
  1072. item = (Number)(parserState.get("tab_alignment"));
  1073. if (item != null)
  1074. tabAlignment = item.intValue();
  1075. tabLeader = TabStop.LEAD_NONE;
  1076. item = (Number)(parserState.get("tab_leader"));
  1077. if (item != null)
  1078. tabLeader = item.intValue();
  1079. if (keyword.equals("tb"))
  1080. tabAlignment = TabStop.ALIGN_BAR;
  1081. parserState.remove("tab_alignment");
  1082. parserState.remove("tab_leader");
  1083. TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
  1084. Dictionary tabs;
  1085. Integer stopCount;
  1086. tabs = (Dictionary)parserState.get("_tabs");
  1087. if (tabs == null) {
  1088. tabs = new Hashtable();
  1089. parserState.put("_tabs", tabs);
  1090. stopCount = new Integer(1);
  1091. } else {
  1092. stopCount = (Integer)tabs.get("stop count");
  1093. stopCount = new Integer(1 + stopCount.intValue());
  1094. }
  1095. tabs.put(stopCount, newStop);
  1096. tabs.put("stop count", stopCount);
  1097. parserState.remove("_tabs_immutable");
  1098. return true;
  1099. }
  1100. if (keyword.equals("s") &&
  1101. paragraphStyles != null) {
  1102. parserState.put("paragraphStyle", paragraphStyles[parameter]);
  1103. return true;
  1104. }
  1105. if (keyword.equals("cs") &&
  1106. characterStyles != null) {
  1107. parserState.put("characterStyle", characterStyles[parameter]);
  1108. return true;
  1109. }
  1110. if (keyword.equals("ds") &&
  1111. sectionStyles != null) {
  1112. parserState.put("sectionStyle", sectionStyles[parameter]);
  1113. return true;
  1114. }
  1115. return false;
  1116. }
  1117. /** Returns a new MutableAttributeSet containing the
  1118. * default character attributes */
  1119. protected MutableAttributeSet rootCharacterAttributes()
  1120. {
  1121. MutableAttributeSet set = new SimpleAttributeSet();
  1122. /* TODO: default font */
  1123. StyleConstants.setItalic(set, false);
  1124. StyleConstants.setBold(set, false);
  1125. StyleConstants.setUnderline(set, false);
  1126. StyleConstants.setForeground(set, defaultColor());
  1127. return set;
  1128. }
  1129. /** Returns a new MutableAttributeSet containing the
  1130. * default paragraph attributes */
  1131. protected MutableAttributeSet rootParagraphAttributes()
  1132. {
  1133. MutableAttributeSet set = new SimpleAttributeSet();
  1134. StyleConstants.setLeftIndent(set, 0f);
  1135. StyleConstants.setRightIndent(set, 0f);
  1136. StyleConstants.setFirstLineIndent(set, 0f);
  1137. /* TODO: what should this be, really? */
  1138. set.setResolveParent(target.getStyle(StyleContext.DEFAULT_STYLE));
  1139. return set;
  1140. }
  1141. /** Returns a new MutableAttributeSet containing the
  1142. * default section attributes */
  1143. protected MutableAttributeSet rootSectionAttributes()
  1144. {
  1145. MutableAttributeSet set = new SimpleAttributeSet();
  1146. return set;
  1147. }
  1148. /**
  1149. * Calculates the current text (character) attributes in a form suitable
  1150. * for SwingText from the current parser state.
  1151. *
  1152. * @returns a new MutableAttributeSet containing the text attributes.
  1153. */
  1154. MutableAttributeSet currentTextAttributes()
  1155. {
  1156. MutableAttributeSet attributes =
  1157. new SimpleAttributeSet(characterAttributes);
  1158. Integer fontnum;
  1159. Integer stateItem;
  1160. /* figure out the font name */
  1161. /* TODO: catch exceptions for undefined attributes,
  1162. bad font indices, etc.? (as it stands, it is the caller's
  1163. job to clean up after corrupt RTF) */
  1164. fontnum = (Integer)parserState.get("f");
  1165. /* note setFontFamily() can not handle a null font */
  1166. String fontFamily;
  1167. if (fontnum != null)
  1168. fontFamily = (String)fontTable.get(fontnum);
  1169. else
  1170. fontFamily = null;
  1171. if (fontFamily != null)
  1172. StyleConstants.setFontFamily(attributes, fontFamily);
  1173. else
  1174. attributes.removeAttribute(StyleConstants.FontFamily);
  1175. if (colorTable != null) {
  1176. stateItem = (Integer)parserState.get("cf");
  1177. if (stateItem != null) {
  1178. Color fg = colorTable[stateItem.intValue()];
  1179. StyleConstants.setForeground(attributes, fg);
  1180. } else {
  1181. /* AttributeSet dies if you set a value to null */
  1182. attributes.removeAttribute(StyleConstants.Foreground);
  1183. }
  1184. }
  1185. if (colorTable != null) {
  1186. stateItem = (Integer)parserState.get("cb");
  1187. if (stateItem != null) {
  1188. Color bg = colorTable[stateItem.intValue()];
  1189. attributes.addAttribute(StyleConstants.Background,
  1190. bg);
  1191. } else {
  1192. /* AttributeSet dies if you set a value to null */
  1193. attributes.removeAttribute(StyleConstants.Background);
  1194. }
  1195. }
  1196. Style characterStyle = (Style)parserState.get("characterStyle");
  1197. if (characterStyle != null)
  1198. attributes.setResolveParent(characterStyle);
  1199. /* Other attributes are maintained directly in "attributes" */
  1200. return attributes;
  1201. }
  1202. /**
  1203. * Calculates the current paragraph attributes (with keys
  1204. * as given in StyleConstants) from the current parser state.
  1205. *
  1206. * @returns a newly created MutableAttributeSet.
  1207. * @see StyleConstants
  1208. */
  1209. MutableAttributeSet currentParagraphAttributes()
  1210. {
  1211. /* NB if there were a mutableCopy() method we should use it */
  1212. MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
  1213. Integer stateItem;
  1214. /*** Tab stops ***/
  1215. TabStop tabs[];
  1216. tabs = (TabStop[])parserState.get("_tabs_immutable");
  1217. if (tabs == null) {
  1218. Dictionary workingTabs = (Dictionary)parserState.get("_tabs");
  1219. if (workingTabs != null) {
  1220. int count = ((Integer)workingTabs.get("stop count")).intValue();
  1221. tabs = new TabStop[count];
  1222. for (int ix = 1; ix <= count; ix ++)
  1223. tabs[ix-1] = (TabStop)workingTabs.get(new Integer(ix));
  1224. parserState.put("_tabs_immutable", tabs);
  1225. }
  1226. }
  1227. if (tabs != null)
  1228. bld.addAttribute(Constants.Tabs, tabs);
  1229. Style paragraphStyle = (Style)parserState.get("paragraphStyle");
  1230. if (paragraphStyle != null)
  1231. bld.setResolveParent(paragraphStyle);
  1232. return bld;
  1233. }
  1234. /**
  1235. * Calculates the current section attributes
  1236. * from the current parser state.
  1237. *
  1238. * @returns a newly created MutableAttributeSet.
  1239. */
  1240. public AttributeSet currentSectionAttributes()
  1241. {
  1242. MutableAttributeSet attributes = new SimpleAttributeSet(sectionAttributes);
  1243. Style sectionStyle = (Style)parserState.get("sectionStyle");
  1244. if (sectionStyle != null)
  1245. attributes.setResolveParent(sectionStyle);
  1246. return attributes;
  1247. }
  1248. /** Resets the filter's internal notion of the current character
  1249. * attributes to their default values. Invoked to handle the
  1250. * \plain keyword. */
  1251. protected void resetCharacterAttributes()
  1252. {
  1253. handleKeyword("f", 0);
  1254. handleKeyword("cf", 0);
  1255. handleKeyword("fs", 24); /* 12 pt. */
  1256. Enumeration attributes = straightforwardAttributes.elements();
  1257. while(attributes.hasMoreElements()) {
  1258. RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1259. if (attr.domain() == RTFAttribute.D_CHARACTER)
  1260. attr.setDefault(characterAttributes);
  1261. }
  1262. handleKeyword("sl", 1000);
  1263. parserState.remove("characterStyle");
  1264. }
  1265. /** Resets the filter's internal notion of the current paragraph's
  1266. * attributes to their default values. Invoked to handle the
  1267. * \pard keyword. */
  1268. protected void resetParagraphAttributes()
  1269. {
  1270. parserState.remove("_tabs");
  1271. parserState.remove("_tabs_immutable");
  1272. parserState.remove("paragraphStyle");
  1273. StyleConstants.setAlignment(paragraphAttributes,
  1274. StyleConstants.ALIGN_LEFT);
  1275. Enumeration attributes = straightforwardAttributes.elements();
  1276. while(attributes.hasMoreElements()) {
  1277. RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1278. if (attr.domain() == RTFAttribute.D_PARAGRAPH)
  1279. attr.setDefault(characterAttributes);
  1280. }
  1281. }
  1282. /** Resets the filter's internal notion of the current section's
  1283. * attributes to their default values. Invoked to handle the
  1284. * \sectd keyword. */
  1285. protected void resetSectionAttributes()
  1286. {
  1287. Enumeration attributes = straightforwardAttributes.elements();
  1288. while(attributes.hasMoreElements()) {
  1289. RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1290. if (attr.domain() == RTFAttribute.D_SECTION)
  1291. attr.setDefault(characterAttributes);
  1292. }
  1293. parserState.remove("sectionStyle");
  1294. }
  1295. }
  1296. /** RTFReader.TextHandlingDestination provides basic text handling
  1297. * functionality. Subclasses must implement: <dl>
  1298. * <dt>deliverText()<dd>to handle a run of text with the same
  1299. * attributes
  1300. * <dt>finishParagraph()<dd>to end the current paragraph and
  1301. * set the paragraph's attributes
  1302. * <dt>endSection()<dd>to end the current section
  1303. * </dl>
  1304. */
  1305. abstract class TextHandlingDestination
  1306. extends AttributeTrackingDestination
  1307. implements Destination
  1308. {
  1309. /** <code>true</code> if the reader has not just finished
  1310. * a paragraph; false upon startup */
  1311. boolean inParagraph;
  1312. public TextHandlingDestination()
  1313. {
  1314. super();
  1315. inParagraph = false;
  1316. }
  1317. public void handleText(String text)
  1318. {
  1319. if (! inParagraph)
  1320. beginParagraph();
  1321. deliverText(text, currentTextAttributes());
  1322. }
  1323. abstract void deliverText(String text, AttributeSet characterAttributes);
  1324. public void close()
  1325. {
  1326. if (inParagraph)
  1327. endParagraph();
  1328. super.close();
  1329. }
  1330. public boolean handleKeyword(String keyword)
  1331. {
  1332. if (keyword.equals("\r") || keyword.equals("\n")) {
  1333. keyword = "par";
  1334. }
  1335. if (keyword.equals("par")) {
  1336. // warnings.println("Ending paragraph.");
  1337. endParagraph();
  1338. return true;
  1339. }
  1340. if (keyword.equals("sect")) {
  1341. // warnings.println("Ending section.");
  1342. endSection();
  1343. return true;
  1344. }
  1345. return super.handleKeyword(keyword);
  1346. }
  1347. protected void beginParagraph()
  1348. {
  1349. inParagraph = true;
  1350. }
  1351. protected void endParagraph()
  1352. {
  1353. AttributeSet pgfAttributes = currentParagraphAttributes();
  1354. AttributeSet chrAttributes = currentTextAttributes();
  1355. finishParagraph(pgfAttributes, chrAttributes);
  1356. inParagraph = false;
  1357. }
  1358. abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
  1359. abstract void endSection();
  1360. }
  1361. /** RTFReader.DocumentDestination is a concrete subclass of
  1362. * TextHandlingDestination which appends the text to the
  1363. * StyledDocument given by the <code>target</code> ivar of the
  1364. * containing RTFReader.
  1365. */
  1366. class DocumentDestination
  1367. extends TextHandlingDestination
  1368. implements Destination
  1369. {
  1370. public void deliverText(String text, AttributeSet characterAttributes)
  1371. {
  1372. try {
  1373. target.insertString(target.getLength(),
  1374. text,
  1375. currentTextAttributes());
  1376. } catch (BadLocationException ble) {
  1377. /* This shouldn't be able to happen, of course */
  1378. /* TODO is InternalError the correct error to throw? */
  1379. throw new InternalError(ble.getMessage());
  1380. }
  1381. }
  1382. public void finishParagraph(AttributeSet pgfAttributes,
  1383. AttributeSet chrAttributes)
  1384. {
  1385. int pgfEndPosition = target.getLength();
  1386. try {
  1387. target.insertString(pgfEndPosition, "\n", chrAttributes);
  1388. target.setParagraphAttributes(pgfEndPosition, 1, pgfAttributes, true);
  1389. } catch (BadLocationException ble) {
  1390. /* This shouldn't be able to happen, of course */
  1391. /* TODO is InternalError the correct error to throw? */
  1392. throw new InternalError(ble.getMessage());
  1393. }
  1394. }
  1395. public void endSection()
  1396. {
  1397. /* If we implemented sections, we'd end 'em here */
  1398. }
  1399. }
  1400. }