KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * @(#)RTFParser.java 1.11 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text.rtf;
8
9 import java.io.*;
10 import java.lang.*;
11
12 /**
13  * <b>RTFParser</b> is a subclass of <b>AbstractFilter</b> which understands basic RTF syntax
14  * and passes a stream of control words, text, and begin/end group
15  * indications to its subclass.
16  *
17  * Normally programmers will only use <b>RTFFilter</b>, a subclass of this class that knows what to
18  * do with the tokens this class parses.
19  *
20  * @see AbstractFilter
21  * @see RTFFilter
22  */

23 abstract class RTFParser extends AbstractFilter JavaDoc
24 {
25   /** The current RTF group nesting level. */
26   public int level;
27
28   private int state;
29   private StringBuffer JavaDoc currentCharacters;
30   private String JavaDoc pendingKeyword; // where keywords go while we
31
// read their parameters
32
private int pendingCharacter; // for the \'xx construct
33

34   private long binaryBytesLeft; // in a \bin blob?
35
ByteArrayOutputStream binaryBuf;
36   private boolean[] savedSpecials;
37
38   /** A stream to which to write warnings and debugging information
39    * while parsing. This is set to <code>System.out</code> to log
40    * any anomalous information to stdout. */

41   protected PrintStream warnings;
42
43   // value for the 'state' variable
44
private final int S_text = 0; // reading random text
45
private final int S_backslashed = 1; // read a backslash, waiting for next
46
private final int S_token = 2; // reading a multicharacter token
47
private final int S_parameter = 3; // reading a token's parameter
48

49   private final int S_aftertick = 4; // after reading \'
50
private final int S_aftertickc = 5; // after reading \'x
51

52   private final int S_inblob = 6; // in a \bin blob
53

54   /** Implemented by subclasses to interpret a parameter-less RTF keyword.
55    * The keyword is passed without the leading '/' or any delimiting
56    * whitespace. */

57   public abstract boolean handleKeyword(String JavaDoc keyword);
58   /** Implemented by subclasses to interpret a keyword with a parameter.
59    * @param keyword The keyword, as with <code>handleKeyword(String)</code>.
60    * @param parameter The parameter following the keyword. */

61   public abstract boolean handleKeyword(String JavaDoc keyword, int parameter);
62   /** Implemented by subclasses to interpret text from the RTF stream. */
63   public abstract void handleText(String JavaDoc text);
64   public void handleText(char ch)
65   { handleText(String.valueOf(ch)); }
66   /** Implemented by subclasses to handle the contents of the \bin keyword. */
67   public abstract void handleBinaryBlob(byte[] data);
68   /** Implemented by subclasses to react to an increase
69    * in the nesting level. */

70   public abstract void begingroup();
71   /** Implemented by subclasses to react to the end of a group. */
72   public abstract void endgroup();
73
74   // table of non-text characters in rtf
75
static final boolean rtfSpecialsTable[];
76   static {
77     rtfSpecialsTable = (boolean[])noSpecialsTable.clone();
78     rtfSpecialsTable['\n'] = true;
79     rtfSpecialsTable['\r'] = true;
80     rtfSpecialsTable['{'] = true;
81     rtfSpecialsTable['}'] = true;
82     rtfSpecialsTable['\\'] = true;
83   }
84
85   public RTFParser()
86   {
87     currentCharacters = new StringBuffer JavaDoc();
88     state = S_text;
89     pendingKeyword = null;
90     level = 0;
91     //warnings = System.out;
92

93     specialsTable = rtfSpecialsTable;
94   }
95
96   // TODO: Handle wrapup at end of file correctly.
97

98   public void writeSpecial(int b)
99     throws IOException
100   {
101     write((char)b);
102   }
103
104     protected void warning(String JavaDoc s) {
105     if (warnings != null) {
106         warnings.println(s);
107     }
108     }
109
110   public void write(String JavaDoc s)
111     throws IOException
112   {
113     if (state != S_text) {
114       int index = 0;
115       int length = s.length();
116       while(index < length && state != S_text) {
117     write(s.charAt(index));
118     index ++;
119       }
120       
121       if(index >= length)
122     return;
123
124       s = s.substring(index);
125     }
126
127     if (currentCharacters.length() > 0)
128       currentCharacters.append(s);
129     else
130       handleText(s);
131   }
132
133   public void write(char ch)
134     throws IOException
135   {
136     boolean ok;
137
138     switch (state)
139     {
140       case S_text:
141         if (ch == '\n' || ch == '\r') {
142       break; // unadorned newlines are ignored
143
} else if (ch == '{') {
144       if (currentCharacters.length() > 0) {
145         handleText(currentCharacters.toString());
146         currentCharacters = new StringBuffer JavaDoc();
147       }
148       level ++;
149       begingroup();
150     } else if(ch == '}') {
151       if (currentCharacters.length() > 0) {
152         handleText(currentCharacters.toString());
153         currentCharacters = new StringBuffer JavaDoc();
154       }
155       if (level == 0)
156         throw new IOException("Too many close-groups in RTF text");
157           endgroup();
158       level --;
159     } else if(ch == '\\') {
160       if (currentCharacters.length() > 0) {
161         handleText(currentCharacters.toString());
162         currentCharacters = new StringBuffer JavaDoc();
163       }
164       state = S_backslashed;
165     } else {
166       currentCharacters.append(ch);
167     }
168     break;
169       case S_backslashed:
170     if (ch == '\'') {
171       state = S_aftertick;
172       break;
173     }
174     if (!Character.isLetter(ch)) {
175       char newstring[] = new char[1];
176       newstring[0] = ch;
177       if (!handleKeyword(new String JavaDoc(newstring))) {
178         warning("Unknown keyword: " + newstring + " (" + (byte)ch + ")");
179       }
180       state = S_text;
181       pendingKeyword = null;
182       /* currentCharacters is already an empty stringBuffer */
183       break;
184     }
185     
186     state = S_token;
187     /* FALL THROUGH */
188       case S_token:
189     if (Character.isLetter(ch)) {
190       currentCharacters.append(ch);
191     } else {
192       pendingKeyword = currentCharacters.toString();
193       currentCharacters = new StringBuffer JavaDoc();
194       
195       // Parameter following?
196
if (Character.isDigit(ch) || (ch == '-')) {
197         state = S_parameter;
198         currentCharacters.append(ch);
199       } else {
200         ok = handleKeyword(pendingKeyword);
201         if (!ok)
202           warning("Unknown keyword: " + pendingKeyword);
203         pendingKeyword = null;
204         state = S_text;
205
206         // Non-space delimiters get included in the text
207
if (!Character.isWhitespace(ch))
208           write(ch);
209       }
210     }
211     break;
212       case S_parameter:
213     if (Character.isDigit(ch)) {
214       currentCharacters.append(ch);
215     } else {
216       /* TODO: Test correct behavior of \bin keyword */
217       if (pendingKeyword.equals("bin")) { /* magic layer-breaking kwd */
218         long parameter = Long.parseLong(currentCharacters.toString());
219         pendingKeyword = null;
220         state = S_inblob;
221         binaryBytesLeft = parameter;
222         if (binaryBytesLeft > Integer.MAX_VALUE)
223         binaryBuf = new ByteArrayOutputStream(Integer.MAX_VALUE);
224         else
225         binaryBuf = new ByteArrayOutputStream((int)binaryBytesLeft);
226         savedSpecials = specialsTable;
227         specialsTable = allSpecialsTable;
228         break;
229       }
230           
231       int parameter = Integer.parseInt(currentCharacters.toString());
232       ok = handleKeyword(pendingKeyword, parameter);
233       if (!ok)
234         warning("Unknown keyword: " + pendingKeyword +
235             " (param " + currentCharacters + ")");
236       pendingKeyword = null;
237       currentCharacters = new StringBuffer JavaDoc();
238       state = S_text;
239
240       // Delimiters here are interpreted as text too
241
if (!Character.isWhitespace(ch))
242         write(ch);
243     }
244     break;
245       case S_aftertick:
246     if (Character.digit(ch, 16) == -1)
247       state = S_text;
248     else {
249       pendingCharacter = Character.digit(ch, 16);
250       state = S_aftertickc;
251     }
252     break;
253       case S_aftertickc:
254     state = S_text;
255     if (Character.digit(ch, 16) != -1)
256     {
257       pendingCharacter = pendingCharacter * 16 + Character.digit(ch, 16);
258       ch = translationTable[pendingCharacter];
259       if (ch != 0)
260           handleText(ch);
261     }
262     break;
263       case S_inblob:
264     binaryBuf.write(ch);
265     binaryBytesLeft --;
266     if (binaryBytesLeft == 0) {
267         state = S_text;
268         specialsTable = savedSpecials;
269         savedSpecials = null;
270         handleBinaryBlob(binaryBuf.toByteArray());
271         binaryBuf = null;
272     }
273       }
274   }
275
276   /** Flushes any buffered but not yet written characters.
277    * Subclasses which override this method should call this
278    * method <em>before</em> flushing
279    * any of their own buffers. */

280   public void flush()
281     throws IOException
282   {
283     super.flush();
284
285     if (state == S_text && currentCharacters.length() > 0) {
286       handleText(currentCharacters.toString());
287       currentCharacters = new StringBuffer JavaDoc();
288     }
289   }
290
291   /** Closes the parser. Currently, this simply does a <code>flush()</code>,
292    * followed by some minimal consistency checks. */

293   public void close()
294     throws IOException
295   {
296     flush();
297
298     if (state != S_text || level > 0) {
299       warning("Truncated RTF file.");
300       
301       /* TODO: any sane way to handle termination in a non-S_text state? */
302       /* probably not */
303
304       /* this will cause subclasses to behave more reasonably
305      some of the time */

306       while (level > 0) {
307       endgroup();
308       level --;
309       }
310     }
311
312     super.close();
313   }
314
315 }
316
317
Popular Tags