KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > rtf > RTFReader


1 /*
2  * @(#)RTFReader.java 1.26 05/01/04
3  *
4  * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text.rtf;
8
9 import java.lang.*;
10 import java.util.*;
11 import java.io.*;
12 import java.awt.Font JavaDoc;
13 import java.awt.Color JavaDoc;
14
15 import javax.swing.text.*;
16
17 /**
18  * Takes a sequence of RTF tokens and text and appends the text
19  * described by the RTF to a <code>StyledDocument</code> (the <em>target</em>).
20  * The RTF is lexed
21  * from the character stream by the <code>RTFParser</code> 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 JavaDoc
32 {
33   /** The object to which the parsed text is sent. */
34   StyledDocument target;
35
36   /** Miscellaneous information about the parser's state. This
37    * dictionary is saved and restored when an RTF group begins
38    * or ends. */

39   Dictionary parserState; /* Current parser state */
40   /** This is the "dst" item from parserState. rtfDestination
41    * is the current rtf destination. It is cached in an instance
42    * variable for speed. */

43   Destination rtfDestination;
44   /** This holds the current document attributes. */
45   MutableAttributeSet documentAttributes;
46
47   /** This Dictionary maps Integer font numbers to String font names. */
48   Dictionary fontTable;
49   /** This array maps color indices to Color objects. */
50   Color JavaDoc[] colorTable;
51   /** This array maps character style numbers to Style objects. */
52   Style[] characterStyles;
53   /** This array maps paragraph style numbers to Style objects. */
54   Style[] paragraphStyles;
55   /** This array maps section style numbers to Style objects. */
56   Style[] sectionStyles;
57
58   /** This is the RTF version number, extracted from the \rtf keyword.
59    * The version information is currently not used. */

60   int rtfversion;
61
62   /** <code>true</code> to indicate that if the next keyword is unknown,
63    * the containing group should be ignored. */

64   boolean ignoreGroupIfUnknownKeyword;
65
66   /** The parameter of the most recently parsed \\ucN keyword,
67    * used for skipping alternative representations after a
68    * Unicode character. */

69   int skippingCharacters;
70
71   static private Dictionary straightforwardAttributes;
72   static {
73       straightforwardAttributes = RTFAttributes.attributesByKeyword();
74   }
75
76   private MockAttributeSet JavaDoc mockery;
77
78   /* this should be final, but there's a bug in javac... */
79   /** textKeywords maps RTF keywords to single-character strings,
80    * for those keywords which simply insert some text. */

81   static Dictionary textKeywords = null;
82   static {
83       textKeywords = new Hashtable();
84       textKeywords.put("\\", "\\");
85       textKeywords.put("{", "{");
86       textKeywords.put("}", "}");
87       textKeywords.put(" ", "\u00A0"); /* not in the spec... */
88       textKeywords.put("~", "\u00A0"); /* nonbreaking space */
89       textKeywords.put("_", "\u2011"); /* nonbreaking hyphen */
90       textKeywords.put("bullet", "\u2022");
91       textKeywords.put("emdash", "\u2014");
92       textKeywords.put("emspace", "\u2003");
93       textKeywords.put("endash", "\u2013");
94       textKeywords.put("enspace", "\u2002");
95       textKeywords.put("ldblquote", "\u201C");
96       textKeywords.put("lquote", "\u2018");
97       textKeywords.put("ltrmark", "\u200E");
98       textKeywords.put("rdblquote", "\u201D");
99       textKeywords.put("rquote", "\u2019");
100       textKeywords.put("rtlmark", "\u200F");
101       textKeywords.put("tab", "\u0009");
102       textKeywords.put("zwj", "\u200D");
103       textKeywords.put("zwnj", "\u200C");
104       
105       /* There is no Unicode equivalent to an optional hyphen, as far as
106      I can tell. */

107       textKeywords.put("-", "\u2027"); /* TODO: optional hyphen */
108   }
109
110   /* some entries in parserState */
111   static final String JavaDoc TabAlignmentKey = "tab_alignment";
112   static final String JavaDoc TabLeaderKey = "tab_leader";
113     
114   static Dictionary characterSets;
115   static boolean useNeXTForAnsi = false;
116   static {
117       characterSets = new Hashtable();
118   }
119
120 /* TODO: per-font font encodings ( \fcharset control word ) ? */
121
122 /**
123  * Creates a new RTFReader instance. Text will be sent to
124  * the specified TextAcceptor.
125  *
126  * @param destination The TextAcceptor which is to receive the text.
127  */

128 public RTFReader(StyledDocument destination)
129 {
130     int i;
131
132     target = destination;
133     parserState = new Hashtable();
134     fontTable = new Hashtable();
135
136     rtfversion = -1;
137
138     mockery = new MockAttributeSet JavaDoc();
139     documentAttributes = new SimpleAttributeSet();
140 }
141
142 /** Called when the RTFParser encounters a bin keyword in the
143  * RTF stream.
144  *
145  * @see RTFParser
146  */

147 public void handleBinaryBlob(byte[] data)
148 {
149     if (skippingCharacters > 0) {
150     /* a blob only counts as one character for skipping purposes */
151     skippingCharacters --;
152     return;
153     }
154
155     /* someday, someone will want to do something with blobs */
156 }
157
158
159 /**
160  * Handles any pure text (containing no control characters) in the input
161  * stream. Called by the superclass. */

162 public void handleText(String JavaDoc text)
163 {
164     if (skippingCharacters > 0) {
165     if (skippingCharacters >= text.length()) {
166         skippingCharacters -= text.length();
167         return;
168     } else {
169         text = text.substring(skippingCharacters);
170         skippingCharacters = 0;
171     }
172     }
173
174     if (rtfDestination != null) {
175     rtfDestination.handleText(text);
176     return;
177     }
178
179     warning("Text with no destination. oops.");
180 }
181
182 /** The default color for text which has no specified color. */
183 Color JavaDoc defaultColor()
184 {
185     return Color.black;
186 }
187
188 /** Called by the superclass when a new RTF group is begun.
189  * This implementation saves the current <code>parserState</code>, and gives
190  * the current destination a chance to save its own state.
191  * @see RTFParser#begingroup
192  */

193 public void begingroup()
194 {
195     if (skippingCharacters > 0) {
196     /* TODO this indicates an error in the RTF. Log it? */
197     skippingCharacters = 0;
198     }
199
200     /* we do this little dance to avoid cloning the entire state stack and
201        immediately throwing it away. */

202     Object JavaDoc oldSaveState = parserState.get("_savedState");
203     if (oldSaveState != null)
204     parserState.remove("_savedState");
205     Dictionary saveState = (Dictionary)((Hashtable)parserState).clone();
206     if (oldSaveState != null)
207     saveState.put("_savedState", oldSaveState);
208     parserState.put("_savedState", saveState);
209
210     if (rtfDestination != null)
211     rtfDestination.begingroup();
212 }
213
214 /** Called by the superclass when the current RTF group is closed.
215  * This restores the parserState saved by <code>begingroup()</code>
216  * as well as invoking the endgroup method of the current
217  * destination.
218  * @see RTFParser#endgroup
219  */

220 public void endgroup()
221 {
222     if (skippingCharacters > 0) {
223     /* NB this indicates an error in the RTF. Log it? */
224     skippingCharacters = 0;
225     }
226
227     Dictionary restoredState = (Dictionary)parserState.get("_savedState");
228     Destination restoredDestination = (Destination)restoredState.get("dst");
229     if (restoredDestination != rtfDestination) {
230     rtfDestination.close(); /* allow the destination to clean up */
231     rtfDestination = restoredDestination;
232     }
233     Dictionary oldParserState = parserState;
234     parserState = restoredState;
235     if (rtfDestination != null)
236     rtfDestination.endgroup(oldParserState);
237 }
238
239 protected void setRTFDestination(Destination newDestination)
240 {
241     /* Check that setting the destination won't close the
242        current destination (should never happen) */

243     Dictionary previousState = (Dictionary)parserState.get("_savedState");
244     if (previousState != null) {
245     if (rtfDestination != previousState.get("dst")) {
246         warning("Warning, RTF destination overridden, invalid RTF.");
247         rtfDestination.close();
248     }
249     }
250     rtfDestination = newDestination;
251     parserState.put("dst", rtfDestination);
252 }
253
254 /** Called by the user when there is no more input (<i>i.e.</i>,
255  * at the end of the RTF file.)
256  *
257  * @see OutputStream#close
258  */

259 public void close()
260     throws IOException
261 {
262     Enumeration docProps = documentAttributes.getAttributeNames();
263     while(docProps.hasMoreElements()) {
264         Object JavaDoc propName = docProps.nextElement();
265     target.putProperty(propName,
266                documentAttributes.getAttribute((String JavaDoc)propName));
267     }
268
269     /* RTFParser should have ensured that all our groups are closed */
270
271     warning("RTF filter done.");
272
273     super.close();
274 }
275
276 /**
277  * Handles a parameterless RTF keyword. This is called by the superclass
278  * (RTFParser) when a keyword is found in the input stream.
279  *
280  * @returns <code>true</code> if the keyword is recognized and handled;
281  * <code>false</code> otherwise
282  * @see RTFParser#handleKeyword
283  */

284 public boolean handleKeyword(String JavaDoc keyword)
285 {
286     Object JavaDoc item;
287     boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
288
289     if (skippingCharacters > 0) {
290     skippingCharacters --;
291     return true;
292     }
293
294     ignoreGroupIfUnknownKeyword = false;
295     
296     if ((item = textKeywords.get(keyword)) != null) {
297     handleText((String JavaDoc)item);
298     return true;
299     }
300     
301     if (keyword.equals("fonttbl")) {
302     setRTFDestination(new FonttblDestination());
303         return true;
304     }
305
306     if (keyword.equals("colortbl")) {
307     setRTFDestination(new ColortblDestination());
308         return true;
309     }
310
311     if (keyword.equals("stylesheet")) {
312     setRTFDestination(new StylesheetDestination());
313         return true;
314     }
315
316     if (keyword.equals("info")) {
317     setRTFDestination(new InfoDestination());
318     return false;
319     }
320
321     if (keyword.equals("mac")) {
322     setCharacterSet("mac");
323         return true;
324     }
325
326     if (keyword.equals("ansi")) {
327     if (useNeXTForAnsi)
328         setCharacterSet("NeXT");
329     else
330         setCharacterSet("ansi");
331         return true;
332     }
333
334     if (keyword.equals("next")) {
335     setCharacterSet("NeXT");
336     return true;
337     }
338
339     if (keyword.equals("pc")) {
340     setCharacterSet("cpg437"); /* IBM Code Page 437 */
341         return true;
342     }
343
344     if (keyword.equals("pca")) {
345     setCharacterSet("cpg850"); /* IBM Code Page 850 */
346         return true;
347     }
348
349     if (keyword.equals("*")) {
350         ignoreGroupIfUnknownKeyword = true;
351         return true;
352     }
353
354     if (rtfDestination != null) {
355     if(rtfDestination.handleKeyword(keyword))
356         return true;
357     }
358
359     /* this point is reached only if the keyword is unrecognized */
360
361     /* other destinations we don't understand and therefore ignore */
362     if (keyword.equals("aftncn") ||
363     keyword.equals("aftnsep") ||
364     keyword.equals("aftnsepc") ||
365     keyword.equals("annotation") ||
366     keyword.equals("atnauthor") ||
367     keyword.equals("atnicn") ||
368     keyword.equals("atnid") ||
369     keyword.equals("atnref") ||
370     keyword.equals("atntime") ||
371     keyword.equals("atrfend") ||
372     keyword.equals("atrfstart") ||
373     keyword.equals("bkmkend") ||
374     keyword.equals("bkmkstart") ||
375     keyword.equals("datafield") ||
376     keyword.equals("do") ||
377     keyword.equals("dptxbxtext") ||
378     keyword.equals("falt") ||
379     keyword.equals("field") ||
380     keyword.equals("file") ||
381     keyword.equals("filetbl") ||
382     keyword.equals("fname") ||
383     keyword.equals("fontemb") ||
384     keyword.equals("fontfile") ||
385     keyword.equals("footer") ||
386     keyword.equals("footerf") ||
387     keyword.equals("footerl") ||
388     keyword.equals("footerr") ||
389     keyword.equals("footnote") ||
390     keyword.equals("ftncn") ||
391     keyword.equals("ftnsep") ||
392     keyword.equals("ftnsepc") ||
393     keyword.equals("header") ||
394     keyword.equals("headerf") ||
395     keyword.equals("headerl") ||
396     keyword.equals("headerr") ||
397     keyword.equals("keycode") ||
398     keyword.equals("nextfile") ||
399     keyword.equals("object") ||
400     keyword.equals("pict") ||
401     keyword.equals("pn") ||
402     keyword.equals("pnseclvl") ||
403     keyword.equals("pntxtb") ||
404     keyword.equals("pntxta") ||
405     keyword.equals("revtbl") ||
406     keyword.equals("rxe") ||
407     keyword.equals("tc") ||
408     keyword.equals("template") ||
409     keyword.equals("txe") ||
410     keyword.equals("xe")) {
411     ignoreGroupIfUnknownKeywordSave = true;
412     }
413
414     if (ignoreGroupIfUnknownKeywordSave) {
415     setRTFDestination(new DiscardingDestination());
416     }
417
418     return false;
419 }
420
421 /**
422  * Handles an RTF keyword and its integer parameter.
423  * This is called by the superclass
424  * (RTFParser) when a keyword is found in the input stream.
425  *
426  * @returns <code>true</code> if the keyword is recognized and handled;
427  * <code>false</code> otherwise
428  * @see RTFParser#handleKeyword
429  */

430 public boolean handleKeyword(String JavaDoc keyword, int parameter)
431 {
432     boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
433
434     if (skippingCharacters > 0) {
435     skippingCharacters --;
436     return true;
437     }
438
439     ignoreGroupIfUnknownKeyword = false;
440
441     if (keyword.equals("uc")) {
442     /* count of characters to skip after a unicode character */
443     parserState.put("UnicodeSkip", Integer.valueOf(parameter));
444     return true;
445     }
446     if (keyword.equals("u")) {
447     if (parameter < 0)
448         parameter = parameter + 65536;
449     handleText((char)parameter);
450     Number JavaDoc skip = (Number JavaDoc)(parserState.get("UnicodeSkip"));
451     if (skip != null) {
452         skippingCharacters = skip.intValue();
453     } else {
454         skippingCharacters = 1;
455     }
456     return true;
457     }
458
459     if (keyword.equals("rtf")) {
460         rtfversion = parameter;
461     setRTFDestination(new DocumentDestination());
462         return true;
463     }
464
465     if (keyword.startsWith("NeXT") ||
466     keyword.equals("private"))
467     ignoreGroupIfUnknownKeywordSave = true;
468
469     if (rtfDestination != null) {
470     if(rtfDestination.handleKeyword(keyword, parameter))
471         return true;
472     }
473
474     /* this point is reached only if the keyword is unrecognized */
475
476     if (ignoreGroupIfUnknownKeywordSave) {
477     setRTFDestination(new DiscardingDestination());
478     }
479
480     return false;
481 }
482
483 private void setTargetAttribute(String JavaDoc name, Object JavaDoc value)
484 {
485 // target.changeAttributes(new LFDictionary(LFArray.arrayWithObject(value), LFArray.arrayWithObject(name)));
486
}
487
488 /**
489  * setCharacterSet sets the current translation table to correspond with
490  * the named character set. The character set is loaded if necessary.
491  *
492  * @see AbstractFilter
493  */

494 public void setCharacterSet(String JavaDoc name)
495 {
496     Object JavaDoc set;
497
498     try {
499         set = getCharacterSet(name);
500     } catch (Exception JavaDoc e) {
501     warning("Exception loading RTF character set \"" + name + "\": " + e);
502     set = null;
503     }
504
505     if (set != null) {
506     translationTable = (char[])set;
507     } else {
508     warning("Unknown RTF character set \"" + name + "\"");
509     if (!name.equals("ansi")) {
510         try {
511         translationTable = (char[])getCharacterSet("ansi");
512         } catch (IOException e) {
513         throw new InternalError JavaDoc("RTFReader: Unable to find character set resources (" + e + ")");
514         }
515     }
516     }
517
518     setTargetAttribute(Constants.RTFCharacterSet, name);
519 }
520
521 /** Adds a character set to the RTFReader's list
522  * of known character sets */

523 public static void
524 defineCharacterSet(String JavaDoc name, char[] table)
525 {
526     if (table.length < 256)
527     throw new IllegalArgumentException JavaDoc("Translation table must have 256 entries.");
528     characterSets.put(name, table);
529 }
530
531 /** Looks up a named character set. A character set is a 256-entry
532  * array of characters, mapping unsigned byte values to their Unicode
533  * equivalents. The character set is loaded if necessary.
534  *
535  * @returns the character set
536  */

537 public static Object JavaDoc
538 getCharacterSet(final String JavaDoc name)
539     throws IOException
540 {
541     char[] set;
542
543     set = (char [])characterSets.get(name);
544     if (set == null) {
545       InputStream charsetStream;
546       charsetStream = (InputStream)java.security.AccessController.
547                   doPrivileged(new java.security.PrivilegedAction JavaDoc() {
548       public Object JavaDoc run() {
549           return RTFReader JavaDoc.class.getResourceAsStream
550                              ("charsets/" + name + ".txt");
551       }
552       });
553       set = readCharset(charsetStream);
554       defineCharacterSet(name, set);
555     }
556     return set;
557 }
558
559 /** Parses a character set from an InputStream. The character set
560  * must contain 256 decimal integers, separated by whitespace, with
561  * no punctuation. B- and C- style comments are allowed.
562  *
563  * @returns the newly read character set
564  */

565 static char[] readCharset(InputStream strm)
566      throws IOException
567 {
568     char[] values = new char[256];
569     int i;
570     StreamTokenizer in = new StreamTokenizer(new BufferedReader(
571             new InputStreamReader(strm, "ISO-8859-1")));
572
573     in.eolIsSignificant(false);
574     in.commentChar('#');
575     in.slashSlashComments(true);
576     in.slashStarComments(true);
577
578     i = 0;
579     while (i < 256) {
580     int ttype;
581     try {
582         ttype = in.nextToken();
583     } catch (Exception JavaDoc e) {
584         throw new IOException("Unable to read from character set file (" + e + ")");
585     }
586     if (ttype != in.TT_NUMBER) {
587 // System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
588
throw new IOException("Unexpected token in character set file");
589 // continue;
590
}
591     values[i] = (char)(in.nval);
592     i++;
593     }
594
595     return values;
596 }
597
598 static char[] readCharset(java.net.URL JavaDoc href)
599      throws IOException
600 {
601     return readCharset(href.openStream());
602 }
603
604 /** An interface (could be an entirely abstract class) describing
605  * a destination. The RTF reader always has a current destination
606  * which is where text is sent.
607  *
608  * @see RTFReader
609  */

610 interface Destination {
611     void handleBinaryBlob(byte[] data);
612     void handleText(String JavaDoc text);
613     boolean handleKeyword(String JavaDoc keyword);
614     boolean handleKeyword(String JavaDoc keyword, int parameter);
615
616     void begingroup();
617     void endgroup(Dictionary oldState);
618
619     void close();
620 }
621
622 /** This data-sink class is used to implement ignored destinations
623  * (e.g. {\*\blegga blah blah blah} )
624  * It accepts all keywords and text but does nothing with them. */

625 class DiscardingDestination implements Destination
626 {
627     public void handleBinaryBlob(byte[] data)
628     {
629     /* Discard binary blobs. */
630     }
631
632     public void handleText(String JavaDoc text)
633     {
634     /* Discard text. */
635     }
636
637     public boolean handleKeyword(String JavaDoc text)
638     {
639     /* Accept and discard keywords. */
640     return true;
641     }
642
643     public boolean handleKeyword(String JavaDoc text, int parameter)
644     {
645     /* Accept and discard parameterized keywords. */
646     return true;
647     }
648
649     public void begingroup()
650     {
651     /* Ignore groups --- the RTFReader will keep track of the
652        current group level as necessary */

653     }
654
655     public void endgroup(Dictionary oldState)
656     {
657     /* Ignore groups */
658     }
659
660     public void close()
661     {
662     /* No end-of-destination cleanup needed */
663     }
664 }
665
666 /** Reads the fonttbl group, inserting fonts into the RTFReader's
667  * fontTable dictionary. */

668 class FonttblDestination implements Destination
669 {
670     int nextFontNumber;
671     Object JavaDoc fontNumberKey = null;
672     String JavaDoc nextFontFamily;
673     
674     public void handleBinaryBlob(byte[] data)
675     { /* Discard binary blobs. */ }
676
677     public void handleText(String JavaDoc text)
678     {
679         int semicolon = text.indexOf(';');
680         String JavaDoc fontName;
681
682         if (semicolon > -1)
683             fontName = text.substring(0, semicolon);
684         else
685             fontName = text;
686         
687         
688         /* TODO: do something with the font family. */
689
690         if (nextFontNumber == -1
691             && fontNumberKey != null) {
692             //font name might be broken across multiple calls
693
fontName = fontTable.get(fontNumberKey) + fontName;
694         } else {
695             fontNumberKey = Integer.valueOf(nextFontNumber);
696         }
697         fontTable.put(fontNumberKey, fontName);
698
699     nextFontNumber = -1;
700     nextFontFamily = null;
701         return;
702     }
703
704     public boolean handleKeyword(String JavaDoc keyword)
705     {
706     if (keyword.charAt(0) == 'f') {
707         nextFontFamily = keyword.substring(1);
708         return true;
709     }
710     
711     return false;
712     }
713
714     public boolean handleKeyword(String JavaDoc keyword, int parameter)
715     {
716     if (keyword.equals("f")) {
717         nextFontNumber = parameter;
718         return true;
719     }
720
721     return false;
722     }
723
724     /* Groups are irrelevant. */
725     public void begingroup() {}
726     public void endgroup(Dictionary oldState) {}
727
728     /* currently, the only thing we do when the font table ends is
729        dump its contents to the debugging log. */

730     public void close()
731     {
732         Enumeration nums = fontTable.keys();
733         warning("Done reading font table.");
734         while(nums.hasMoreElements()) {
735             Integer JavaDoc num = (Integer JavaDoc)nums.nextElement();
736             warning("Number " + num + ": " + fontTable.get(num));
737         }
738     }
739 }
740
741 /** Reads the colortbl group. Upon end-of-group, the RTFReader's
742  * color table is set to an array containing the read colors. */

743 class ColortblDestination implements Destination
744 {
745     int red, green, blue;
746     Vector proTemTable;
747
748     public ColortblDestination()
749     {
750     red = 0;
751     green = 0;
752     blue = 0;
753     proTemTable = new Vector();
754     }
755
756     public void handleText(String JavaDoc text)
757     {
758         int index = 0;
759
760         for (index = 0; index < text.length(); index ++) {
761             if (text.charAt(index) == ';') {
762                 Color JavaDoc newColor;
763         newColor = new Color JavaDoc(red, green, blue);
764         proTemTable.addElement(newColor);
765             }
766         }
767     }
768
769     public void close()
770     {
771     int count = proTemTable.size();
772         warning("Done reading color table, " + count + " entries.");
773     colorTable = new Color JavaDoc[count];
774     proTemTable.copyInto(colorTable);
775     }
776
777     public boolean handleKeyword(String JavaDoc keyword, int parameter)
778     {
779         if (keyword.equals("red"))
780         red = parameter;
781     else if (keyword.equals("green"))
782         green = parameter;
783     else if (keyword.equals("blue"))
784         blue = parameter;
785     else
786         return false;
787     
788     return true;
789     }
790
791     /* Colortbls don't understand any parameterless keywords */
792     public boolean handleKeyword(String JavaDoc keyword) { return false; }
793
794     /* Groups are irrelevant. */
795     public void begingroup() {}
796     public void endgroup(Dictionary oldState) {}
797
798     /* Shouldn't see any binary blobs ... */
799     public void handleBinaryBlob(byte[] data) {}
800 }
801
802 /** Handles the stylesheet keyword. Styles are read and sorted
803  * into the three style arrays in the RTFReader. */

804 class StylesheetDestination
805     extends DiscardingDestination
806     implements Destination
807 {
808     Dictionary definedStyles;
809
810     public StylesheetDestination()
811     {
812     definedStyles = new Hashtable();
813     }
814
815     public void begingroup()
816     {
817     setRTFDestination(new StyleDefiningDestination());
818     }
819
820     public void close()
821     {
822         Vector chrStyles, pgfStyles, secStyles;
823     chrStyles = new Vector();
824     pgfStyles = new Vector();
825     secStyles = new Vector();
826     Enumeration styles = definedStyles.elements();
827     while(styles.hasMoreElements()) {
828         StyleDefiningDestination style;
829         Style defined;
830         style = (StyleDefiningDestination)styles.nextElement();
831         defined = style.realize();
832         warning("Style "+style.number+" ("+style.styleName+"): "+defined);
833         String JavaDoc stype = (String JavaDoc)defined.getAttribute(Constants.StyleType);
834         Vector toSet;
835         if (stype.equals(Constants.STSection)) {
836             toSet = secStyles;
837         } else if (stype.equals(Constants.STCharacter)) {
838             toSet = chrStyles;
839         } else {
840             toSet = pgfStyles;
841         }
842         if (toSet.size() <= style.number)
843             toSet.setSize(style.number + 1);
844         toSet.setElementAt(defined, style.number);
845     }
846     if (!(chrStyles.isEmpty())) {
847         Style[] styleArray = new Style[chrStyles.size()];
848         chrStyles.copyInto(styleArray);
849         characterStyles = styleArray;
850     }
851     if (!(pgfStyles.isEmpty())) {
852         Style[] styleArray = new Style[pgfStyles.size()];
853         pgfStyles.copyInto(styleArray);
854         paragraphStyles = styleArray;
855     }
856     if (!(secStyles.isEmpty())) {
857         Style[] styleArray = new Style[secStyles.size()];
858         secStyles.copyInto(styleArray);
859         sectionStyles = styleArray;
860     }
861
862 /* (old debugging code)
863     int i, m;
864     if (characterStyles != null) {
865       m = characterStyles.length;
866       for(i=0;i<m;i++)
867         warnings.println("chrStyle["+i+"]="+characterStyles[i]);
868     } else warnings.println("No character styles.");
869     if (paragraphStyles != null) {
870       m = paragraphStyles.length;
871       for(i=0;i<m;i++)
872         warnings.println("pgfStyle["+i+"]="+paragraphStyles[i]);
873     } else warnings.println("No paragraph styles.");
874     if (sectionStyles != null) {
875       m = characterStyles.length;
876       for(i=0;i<m;i++)
877         warnings.println("secStyle["+i+"]="+sectionStyles[i]);
878     } else warnings.println("No section styles.");
879 */

880     }
881
882     /** This subclass handles an individual style */
883     class StyleDefiningDestination
884     extends AttributeTrackingDestination
885     implements Destination
886     {
887     final int STYLENUMBER_NONE = 222;
888     boolean additive;
889     boolean characterStyle;
890     boolean sectionStyle;
891     public String JavaDoc styleName;
892     public int number;
893     int basedOn;
894     int nextStyle;
895     boolean hidden;
896
897     Style realizedStyle;
898
899     public StyleDefiningDestination()
900     {
901         additive = false;
902         characterStyle = false;
903         sectionStyle = false;
904         styleName = null;
905         number = 0;
906         basedOn = STYLENUMBER_NONE;
907         nextStyle = STYLENUMBER_NONE;
908         hidden = false;
909     }
910     
911     public void handleText(String JavaDoc text)
912     {
913         if (styleName != null)
914         styleName = styleName + text;
915         else
916         styleName = text;
917     }
918
919     public void close() {
920             int semicolon = (styleName == null) ? 0 : styleName.indexOf(';');
921         if (semicolon > 0)
922         styleName = styleName.substring(0, semicolon);
923         definedStyles.put(Integer.valueOf(number), this);
924         super.close();
925     }
926
927     public boolean handleKeyword(String JavaDoc keyword)
928     {
929         if (keyword.equals("additive")) {
930         additive = true;
931         return true;
932         }
933         if (keyword.equals("shidden")) {
934         hidden = true;
935         return true;
936         }
937         return super.handleKeyword(keyword);
938     }
939
940     public boolean handleKeyword(String JavaDoc keyword, int parameter)
941     {
942         if (keyword.equals("s")) {
943         characterStyle = false;
944         sectionStyle = false;
945         number = parameter;
946         } else if (keyword.equals("cs")) {
947         characterStyle = true;
948         sectionStyle = false;
949         number = parameter;
950         } else if (keyword.equals("ds")) {
951         characterStyle = false;
952         sectionStyle = true;
953         number = parameter;
954         } else if (keyword.equals("sbasedon")) {
955         basedOn = parameter;
956         } else if (keyword.equals("snext")) {
957         nextStyle = parameter;
958         } else {
959         return super.handleKeyword(keyword, parameter);
960         }
961         return true;
962     }
963
964     public Style realize()
965     {
966         Style basis = null;
967         Style next = null;
968         
969         if (realizedStyle != null)
970         return realizedStyle;
971
972         if (basedOn != STYLENUMBER_NONE) {
973         StyleDefiningDestination styleDest;
974         styleDest = (StyleDefiningDestination)definedStyles.get(Integer.valueOf(basedOn));
975         if (styleDest != null && styleDest != this) {
976             basis = styleDest.realize();
977         }
978         }
979
980         /* NB: Swing StyleContext doesn't allow distinct styles with
981            the same name; RTF apparently does. This may confuse the
982            user. */

983         realizedStyle = target.addStyle(styleName, basis);
984
985         if (characterStyle) {
986         realizedStyle.addAttributes(currentTextAttributes());
987         realizedStyle.addAttribute(Constants.StyleType,
988                        Constants.STCharacter);
989         } else if (sectionStyle) {
990         realizedStyle.addAttributes(currentSectionAttributes());
991             realizedStyle.addAttribute(Constants.StyleType,
992                        Constants.STSection);
993         } else { /* must be a paragraph style */
994         realizedStyle.addAttributes(currentParagraphAttributes());
995             realizedStyle.addAttribute(Constants.StyleType,
996                        Constants.STParagraph);
997         }
998
999         if (nextStyle != STYLENUMBER_NONE) {
1000        StyleDefiningDestination styleDest;
1001        styleDest = (StyleDefiningDestination)definedStyles.get(Integer.valueOf(nextStyle));
1002        if (styleDest != null) {
1003            next = styleDest.realize();
1004        }
1005        }
1006
1007        if (next != null)
1008        realizedStyle.addAttribute(Constants.StyleNext, next);
1009        realizedStyle.addAttribute(Constants.StyleAdditive,
1010                       Boolean.valueOf(additive));
1011        realizedStyle.addAttribute(Constants.StyleHidden,
1012                       Boolean.valueOf(hidden));
1013
1014        return realizedStyle;
1015    }
1016    }
1017}
1018
1019/** Handles the info group. Currently no info keywords are recognized
1020 * so this is a subclass of DiscardingDestination. */

1021class InfoDestination
1022    extends DiscardingDestination
1023    implements Destination
1024{
1025}
1026
1027/** RTFReader.TextHandlingDestination is an abstract RTF destination
1028 * which simply tracks the attributes specified by the RTF control words
1029 * in internal form and can produce acceptable AttributeSets for the
1030 * current character, paragraph, and section attributes. It is up
1031 * to the subclasses to determine what is done with the actual text. */

1032abstract class AttributeTrackingDestination implements Destination
1033{
1034    /** This is the "chr" element of parserState, cached for
1035     * more efficient use */

1036    MutableAttributeSet characterAttributes;
1037    /** This is the "pgf" element of parserState, cached for
1038     * more efficient use */

1039    MutableAttributeSet paragraphAttributes;
1040    /** This is the "sec" element of parserState, cached for
1041     * more efficient use */

1042    MutableAttributeSet sectionAttributes;
1043    
1044    public AttributeTrackingDestination()
1045    {
1046    characterAttributes = rootCharacterAttributes();
1047    parserState.put("chr", characterAttributes);
1048    paragraphAttributes = rootParagraphAttributes();
1049    parserState.put("pgf", paragraphAttributes);
1050    sectionAttributes = rootSectionAttributes();
1051    parserState.put("sec", sectionAttributes);
1052    }
1053
1054    abstract public void handleText(String JavaDoc text);
1055
1056    public void handleBinaryBlob(byte[] data)
1057    {
1058        /* This should really be in TextHandlingDestination, but
1059     * since *nobody* does anything with binary blobs, this
1060     * is more convenient. */

1061    warning("Unexpected binary data in RTF file.");
1062    }
1063
1064    public void begingroup()
1065    {
1066    AttributeSet characterParent = currentTextAttributes();
1067    AttributeSet paragraphParent = currentParagraphAttributes();
1068    AttributeSet sectionParent = currentSectionAttributes();
1069
1070    /* It would probably be more efficient to use the
1071     * resolver property of the attributes set for
1072     * implementing rtf groups,
1073     * but that's needed for styles. */

1074
1075    /* update the cached attribute dictionaries */
1076    characterAttributes = new SimpleAttributeSet();
1077    characterAttributes.addAttributes(characterParent);
1078    parserState.put("chr", characterAttributes);
1079
1080    paragraphAttributes = new SimpleAttributeSet();
1081    paragraphAttributes.addAttributes(paragraphParent);
1082    parserState.put("pgf", paragraphAttributes);
1083
1084    sectionAttributes = new SimpleAttributeSet();
1085    sectionAttributes.addAttributes(sectionParent);
1086    parserState.put("sec", sectionAttributes);
1087    }
1088
1089    public void endgroup(Dictionary oldState)
1090    {
1091    characterAttributes = (MutableAttributeSet)parserState.get("chr");
1092    paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
1093    sectionAttributes = (MutableAttributeSet)parserState.get("sec");
1094    }
1095
1096    public void close()
1097    {
1098    }
1099
1100    public boolean handleKeyword(String JavaDoc keyword)
1101    {
1102    if (keyword.equals("ulnone")) {
1103        return handleKeyword("ul", 0);
1104    }
1105    
1106    {
1107        Object JavaDoc item = straightforwardAttributes.get(keyword);
1108        if (item != null) {
1109            RTFAttribute JavaDoc attr = (RTFAttribute JavaDoc)item;
1110        boolean ok;
1111        
1112        switch(attr.domain()) {
1113          case RTFAttribute.D_CHARACTER:
1114            ok = attr.set(characterAttributes);
1115            break;
1116          case RTFAttribute.D_PARAGRAPH:
1117            ok = attr.set(paragraphAttributes);
1118            break;
1119          case RTFAttribute.D_SECTION:
1120            ok = attr.set(sectionAttributes);
1121            break;
1122          case RTFAttribute.D_META:
1123            mockery.backing = parserState;
1124            ok = attr.set(mockery);
1125            mockery.backing = null;
1126            break;
1127          case RTFAttribute.D_DOCUMENT:
1128            ok = attr.set(documentAttributes);
1129            break;
1130          default:
1131            /* should never happen */
1132            ok = false;
1133            break;
1134        }
1135        if (ok)
1136            return true;
1137        }
1138    }
1139
1140    
1141    if (keyword.equals("plain")) {
1142        resetCharacterAttributes();
1143        return true;
1144    }
1145    
1146    if (keyword.equals("pard")) {
1147        resetParagraphAttributes();
1148        return true;
1149    }
1150
1151    if (keyword.equals("sectd")) {
1152        resetSectionAttributes();
1153        return true;
1154    }
1155
1156    return false;
1157    }
1158
1159    public boolean handleKeyword(String JavaDoc keyword, int parameter)
1160    {
1161    boolean booleanParameter = (parameter != 0);
1162    
1163    if (keyword.equals("fc"))
1164        keyword = "cf"; /* whatEVER, dude. */
1165    
1166    if (keyword.equals("f")) {
1167        parserState.put(keyword, Integer.valueOf(parameter));
1168        return true;
1169    }
1170    if (keyword.equals("cf")) {
1171        parserState.put(keyword, Integer.valueOf(parameter));
1172        return true;
1173    }
1174
1175    {
1176        Object JavaDoc item = straightforwardAttributes.get(keyword);
1177        if (item != null) {
1178            RTFAttribute JavaDoc attr = (RTFAttribute JavaDoc)item;
1179        boolean ok;
1180        
1181        switch(attr.domain()) {
1182          case RTFAttribute.D_CHARACTER:
1183            ok = attr.set(characterAttributes, parameter);
1184            break;
1185          case RTFAttribute.D_PARAGRAPH:
1186            ok = attr.set(paragraphAttributes, parameter);
1187            break;
1188          case RTFAttribute.D_SECTION:
1189            ok = attr.set(sectionAttributes, parameter);
1190            break;
1191          case RTFAttribute.D_META:
1192            mockery.backing = parserState;
1193            ok = attr.set(mockery, parameter);
1194            mockery.backing = null;
1195            break;
1196          case RTFAttribute.D_DOCUMENT:
1197            ok = attr.set(documentAttributes, parameter);
1198            break;
1199          default:
1200            /* should never happen */
1201            ok = false;
1202            break;
1203        }
1204        if (ok)
1205            return true;
1206        }
1207    }
1208
1209    if (keyword.equals("fs")) {
1210        StyleConstants.setFontSize(characterAttributes, (parameter / 2));
1211        return true;
1212    }
1213
1214    /* TODO: superscript/subscript */
1215    
1216    if (keyword.equals("sl")) {
1217        if (parameter == 1000) { /* magic value! */
1218        characterAttributes.removeAttribute(StyleConstants.LineSpacing);
1219        } else {
1220        /* TODO: The RTF sl attribute has special meaning if it's
1221           negative. Make sure that SwingText has the same special
1222           meaning, or find a way to imitate that. When SwingText
1223           handles this, also recognize the slmult keyword. */

1224        StyleConstants.setLineSpacing(characterAttributes,
1225                          parameter / 20f);
1226        }
1227        return true;
1228    }
1229    
1230    /* TODO: Other kinds of underlining */
1231    
1232    if (keyword.equals("tx") || keyword.equals("tb")) {
1233        float tabPosition = parameter / 20f;
1234        int tabAlignment, tabLeader;
1235        Number JavaDoc item;
1236        
1237        tabAlignment = TabStop.ALIGN_LEFT;
1238        item = (Number JavaDoc)(parserState.get("tab_alignment"));
1239        if (item != null)
1240        tabAlignment = item.intValue();
1241        tabLeader = TabStop.LEAD_NONE;
1242        item = (Number JavaDoc)(parserState.get("tab_leader"));
1243        if (item != null)
1244        tabLeader = item.intValue();
1245        if (keyword.equals("tb"))
1246        tabAlignment = TabStop.ALIGN_BAR;
1247        
1248        parserState.remove("tab_alignment");
1249        parserState.remove("tab_leader");
1250        
1251        TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
1252        Dictionary tabs;
1253        Integer JavaDoc stopCount;
1254        
1255        tabs = (Dictionary)parserState.get("_tabs");
1256        if (tabs == null) {
1257        tabs = new Hashtable();
1258        parserState.put("_tabs", tabs);
1259        stopCount = Integer.valueOf(1);
1260        } else {
1261        stopCount = (Integer JavaDoc)tabs.get("stop count");
1262        stopCount = Integer.valueOf(1 + stopCount.intValue());
1263        }
1264        tabs.put(stopCount, newStop);
1265        tabs.put("stop count", stopCount);
1266        parserState.remove("_tabs_immutable");
1267        
1268        return true;
1269    }
1270
1271    if (keyword.equals("s") &&
1272        paragraphStyles != null) {
1273        parserState.put("paragraphStyle", paragraphStyles[parameter]);
1274        return true;
1275    }
1276
1277    if (keyword.equals("cs") &&
1278        characterStyles != null) {
1279        parserState.put("characterStyle", characterStyles[parameter]);
1280        return true;
1281    }
1282
1283    if (keyword.equals("ds") &&
1284        sectionStyles != null) {
1285        parserState.put("sectionStyle", sectionStyles[parameter]);
1286        return true;
1287    }
1288
1289    return false;
1290    }
1291
1292    /** Returns a new MutableAttributeSet containing the
1293     * default character attributes */

1294    protected MutableAttributeSet rootCharacterAttributes()
1295    {
1296    MutableAttributeSet set = new SimpleAttributeSet();
1297    
1298    /* TODO: default font */
1299    
1300    StyleConstants.setItalic(set, false);
1301    StyleConstants.setBold(set, false);
1302    StyleConstants.setUnderline(set, false);
1303    StyleConstants.setForeground(set, defaultColor());
1304
1305    return set;
1306    }
1307
1308    /** Returns a new MutableAttributeSet containing the
1309     * default paragraph attributes */

1310    protected MutableAttributeSet rootParagraphAttributes()
1311    {
1312    MutableAttributeSet set = new SimpleAttributeSet();
1313    
1314    StyleConstants.setLeftIndent(set, 0f);
1315    StyleConstants.setRightIndent(set, 0f);
1316    StyleConstants.setFirstLineIndent(set, 0f);
1317    
1318    /* TODO: what should this be, really? */
1319    set.setResolveParent(target.getStyle(StyleContext.DEFAULT_STYLE));
1320    
1321    return set;
1322    }
1323
1324    /** Returns a new MutableAttributeSet containing the
1325     * default section attributes */

1326    protected MutableAttributeSet rootSectionAttributes()
1327    {
1328        MutableAttributeSet set = new SimpleAttributeSet();
1329
1330    return set;
1331    }
1332
1333    /**
1334     * Calculates the current text (character) attributes in a form suitable
1335     * for SwingText from the current parser state.
1336     *
1337     * @returns a new MutableAttributeSet containing the text attributes.
1338     */

1339    MutableAttributeSet currentTextAttributes()
1340    {
1341    MutableAttributeSet attributes =
1342        new SimpleAttributeSet(characterAttributes);
1343    Integer JavaDoc fontnum;
1344    Integer JavaDoc stateItem;
1345
1346    /* figure out the font name */
1347    /* TODO: catch exceptions for undefined attributes,
1348       bad font indices, etc.? (as it stands, it is the caller's
1349       job to clean up after corrupt RTF) */

1350    fontnum = (Integer JavaDoc)parserState.get("f");
1351    /* note setFontFamily() can not handle a null font */
1352    String JavaDoc fontFamily;
1353    if (fontnum != null)
1354        fontFamily = (String JavaDoc)fontTable.get(fontnum);
1355    else
1356        fontFamily = null;
1357    if (fontFamily != null)
1358        StyleConstants.setFontFamily(attributes, fontFamily);
1359    else
1360        attributes.removeAttribute(StyleConstants.FontFamily);
1361    
1362    if (colorTable != null) {
1363        stateItem = (Integer JavaDoc)parserState.get("cf");
1364        if (stateItem != null) {
1365        Color JavaDoc fg = colorTable[stateItem.intValue()];
1366        StyleConstants.setForeground(attributes, fg);
1367        } else {
1368        /* AttributeSet dies if you set a value to null */
1369        attributes.removeAttribute(StyleConstants.Foreground);
1370        }
1371    }
1372    
1373    if (colorTable != null) {
1374        stateItem = (Integer JavaDoc)parserState.get("cb");
1375        if (stateItem != null) {
1376        Color JavaDoc bg = colorTable[stateItem.intValue()];
1377        attributes.addAttribute(StyleConstants.Background,
1378                    bg);
1379        } else {
1380        /* AttributeSet dies if you set a value to null */
1381        attributes.removeAttribute(StyleConstants.Background);
1382        }
1383    }
1384    
1385    Style characterStyle = (Style)parserState.get("characterStyle");
1386    if (characterStyle != null)
1387        attributes.setResolveParent(characterStyle);
1388
1389    /* Other attributes are maintained directly in "attributes" */
1390    
1391    return attributes;
1392    }
1393
1394    /**
1395     * Calculates the current paragraph attributes (with keys
1396     * as given in StyleConstants) from the current parser state.
1397     *
1398     * @returns a newly created MutableAttributeSet.
1399     * @see StyleConstants
1400     */

1401    MutableAttributeSet currentParagraphAttributes()
1402    {
1403    /* NB if there were a mutableCopy() method we should use it */
1404    MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
1405    
1406    Integer JavaDoc stateItem;
1407    
1408    /*** Tab stops ***/
1409    TabStop tabs[];
1410    
1411    tabs = (TabStop[])parserState.get("_tabs_immutable");
1412    if (tabs == null) {
1413        Dictionary workingTabs = (Dictionary)parserState.get("_tabs");
1414        if (workingTabs != null) {
1415        int count = ((Integer JavaDoc)workingTabs.get("stop count")).intValue();
1416        tabs = new TabStop[count];
1417        for (int ix = 1; ix <= count; ix ++)
1418            tabs[ix-1] = (TabStop)workingTabs.get(Integer.valueOf(ix));
1419        parserState.put("_tabs_immutable", tabs);
1420        }
1421    }
1422    if (tabs != null)
1423        bld.addAttribute(Constants.Tabs, tabs);
1424
1425    Style paragraphStyle = (Style)parserState.get("paragraphStyle");
1426    if (paragraphStyle != null)
1427        bld.setResolveParent(paragraphStyle);
1428    
1429    return bld;
1430    }
1431    
1432    /**
1433     * Calculates the current section attributes
1434     * from the current parser state.
1435     *
1436     * @returns a newly created MutableAttributeSet.
1437     */

1438    public AttributeSet currentSectionAttributes()
1439    {
1440    MutableAttributeSet attributes = new SimpleAttributeSet(sectionAttributes);
1441
1442    Style sectionStyle = (Style)parserState.get("sectionStyle");
1443    if (sectionStyle != null)
1444        attributes.setResolveParent(sectionStyle);
1445
1446    return attributes;
1447    }
1448
1449    /** Resets the filter's internal notion of the current character
1450     * attributes to their default values. Invoked to handle the
1451     * \plain keyword. */

1452    protected void resetCharacterAttributes()
1453    {
1454    handleKeyword("f", 0);
1455    handleKeyword("cf", 0);
1456    
1457    handleKeyword("fs", 24); /* 12 pt. */
1458    
1459    Enumeration attributes = straightforwardAttributes.elements();
1460    while(attributes.hasMoreElements()) {
1461        RTFAttribute JavaDoc attr = (RTFAttribute JavaDoc)attributes.nextElement();
1462        if (attr.domain() == RTFAttribute.D_CHARACTER)
1463            attr.setDefault(characterAttributes);
1464    }
1465    
1466    handleKeyword("sl", 1000);
1467
1468    parserState.remove("characterStyle");
1469    }
1470
1471    /** Resets the filter's internal notion of the current paragraph's
1472     * attributes to their default values. Invoked to handle the
1473     * \pard keyword. */

1474    protected void resetParagraphAttributes()
1475    {
1476    parserState.remove("_tabs");
1477    parserState.remove("_tabs_immutable");
1478    parserState.remove("paragraphStyle");
1479    
1480    StyleConstants.setAlignment(paragraphAttributes,
1481                    StyleConstants.ALIGN_LEFT);
1482
1483    Enumeration attributes = straightforwardAttributes.elements();
1484    while(attributes.hasMoreElements()) {
1485        RTFAttribute JavaDoc attr = (RTFAttribute JavaDoc)attributes.nextElement();
1486        if (attr.domain() == RTFAttribute.D_PARAGRAPH)
1487            attr.setDefault(characterAttributes);
1488    }
1489    }
1490    
1491    /** Resets the filter's internal notion of the current section's
1492     * attributes to their default values. Invoked to handle the
1493     * \sectd keyword. */

1494    protected void resetSectionAttributes()
1495    {
1496    Enumeration attributes = straightforwardAttributes.elements();
1497    while(attributes.hasMoreElements()) {
1498        RTFAttribute JavaDoc attr = (RTFAttribute JavaDoc)attributes.nextElement();
1499        if (attr.domain() == RTFAttribute.D_SECTION)
1500            attr.setDefault(characterAttributes);
1501    }
1502
1503    parserState.remove("sectionStyle");
1504    }
1505}
1506
1507/** RTFReader.TextHandlingDestination provides basic text handling
1508 * functionality. Subclasses must implement: <dl>
1509 * <dt>deliverText()<dd>to handle a run of text with the same
1510 * attributes
1511 * <dt>finishParagraph()<dd>to end the current paragraph and
1512 * set the paragraph's attributes
1513 * <dt>endSection()<dd>to end the current section
1514 * </dl>
1515 */

1516abstract class TextHandlingDestination
1517    extends AttributeTrackingDestination
1518    implements Destination
1519{
1520    /** <code>true</code> if the reader has not just finished
1521     * a paragraph; false upon startup */

1522    boolean inParagraph;
1523    
1524    public TextHandlingDestination()
1525    {
1526        super();
1527    inParagraph = false;
1528    }
1529
1530    public void handleText(String JavaDoc text)
1531    {
1532    if (! inParagraph)
1533        beginParagraph();
1534    
1535    deliverText(text, currentTextAttributes());
1536    }
1537
1538    abstract void deliverText(String JavaDoc text, AttributeSet characterAttributes);
1539
1540    public void close()
1541    {
1542    if (inParagraph)
1543        endParagraph();
1544
1545    super.close();
1546    }
1547
1548    public boolean handleKeyword(String JavaDoc keyword)
1549    {
1550    if (keyword.equals("\r") || keyword.equals("\n")) {
1551        keyword = "par";
1552    }
1553    
1554    if (keyword.equals("par")) {
1555// warnings.println("Ending paragraph.");
1556
endParagraph();
1557        return true;
1558    }
1559
1560    if (keyword.equals("sect")) {
1561// warnings.println("Ending section.");
1562
endSection();
1563        return true;
1564    }
1565    
1566    return super.handleKeyword(keyword);
1567    }
1568
1569    protected void beginParagraph()
1570    {
1571    inParagraph = true;
1572    }
1573    
1574    protected void endParagraph()
1575    {
1576    AttributeSet pgfAttributes = currentParagraphAttributes();
1577    AttributeSet chrAttributes = currentTextAttributes();
1578    finishParagraph(pgfAttributes, chrAttributes);
1579    inParagraph = false;
1580    }
1581
1582    abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
1583
1584    abstract void endSection();
1585}
1586
1587/** RTFReader.DocumentDestination is a concrete subclass of
1588 * TextHandlingDestination which appends the text to the
1589 * StyledDocument given by the <code>target</code> ivar of the
1590 * containing RTFReader.
1591 */

1592class DocumentDestination
1593    extends TextHandlingDestination
1594    implements Destination
1595{
1596    public void deliverText(String JavaDoc text, AttributeSet characterAttributes)
1597    {
1598    try {
1599        target.insertString(target.getLength(),
1600                text,
1601                currentTextAttributes());
1602    } catch (BadLocationException ble) {
1603        /* This shouldn't be able to happen, of course */
1604        /* TODO is InternalError the correct error to throw? */
1605        throw new InternalError JavaDoc(ble.getMessage());
1606    }
1607    }
1608
1609    public void finishParagraph(AttributeSet pgfAttributes,
1610                AttributeSet chrAttributes)
1611    {
1612    int pgfEndPosition = target.getLength();
1613    try {
1614        target.insertString(pgfEndPosition, "\n", chrAttributes);
1615        target.setParagraphAttributes(pgfEndPosition, 1, pgfAttributes, true);
1616    } catch (BadLocationException ble) {
1617        /* This shouldn't be able to happen, of course */
1618        /* TODO is InternalError the correct error to throw? */
1619        throw new InternalError JavaDoc(ble.getMessage());
1620    }
1621    }
1622
1623    public void endSection()
1624    {
1625        /* If we implemented sections, we'd end 'em here */
1626    }
1627}
1628
1629}
1630
1631
1632
Popular Tags