203 return Color.black;
204 }
205
206 /** Called by the superclass when a new RTF group is begun.
207 * This implementation saves the current <code>parserState</code>, and gives
208 * the current destination a chance to save its own state.
209 * @see RTFParser#begingroup
210 */
211 public void begingroup()
212 {
213 if (skippingCharacters > 0) {
214 /* TODO this indicates an error in the RTF. Log it? */
215 skippingCharacters = 0;
216 }
217
218 /* we do this little dance to avoid cloning the entire state stack and
219 immediately throwing it away. */
220 Object oldSaveState = parserState.get("_savedState");
221 if (oldSaveState != null)
222 parserState.remove("_savedState");
223 Dictionary<String, Object> saveState = (Dictionary<String, Object>)((Hashtable)parserState).clone();
224 if (oldSaveState != null)
225 saveState.put("_savedState", oldSaveState);
226 parserState.put("_savedState", saveState);
227
228 if (rtfDestination != null)
229 rtfDestination.begingroup();
230 }
231
232 /** Called by the superclass when the current RTF group is closed.
233 * This restores the parserState saved by <code>begingroup()</code>
234 * as well as invoking the endgroup method of the current
235 * destination.
236 * @see RTFParser#endgroup
237 */
238 public void endgroup()
239 {
240 if (skippingCharacters > 0) {
241 /* NB this indicates an error in the RTF. Log it? */
242 skippingCharacters = 0;
243 }
244
245 Dictionary<Object, Object> restoredState = (Dictionary<Object, Object>)parserState.get("_savedState");
246 Destination restoredDestination = (Destination)restoredState.get("dst");
247 if (restoredDestination != rtfDestination) {
248 rtfDestination.close(); /* allow the destination to clean up */
249 rtfDestination = restoredDestination;
250 }
251 Dictionary oldParserState = parserState;
252 parserState = restoredState;
253 if (rtfDestination != null)
254 rtfDestination.endgroup(oldParserState);
255 }
256
257 protected void setRTFDestination(Destination newDestination)
258 {
259 /* Check that setting the destination won't close the
260 current destination (should never happen) */
261 Dictionary previousState = (Dictionary)parserState.get("_savedState");
262 if (previousState != null) {
263 if (rtfDestination != previousState.get("dst")) {
264 warning("Warning, RTF destination overridden, invalid RTF.");
265 rtfDestination.close();
266 }
267 }
268 rtfDestination = newDestination;
269 parserState.put("dst", rtfDestination);
270 }
271
272 /** Called by the user when there is no more input (<i>i.e.</i>,
273 * at the end of the RTF file.)
274 *
275 * @see OutputStream#close
276 */
277 public void close()
278 throws IOException
279 {
280 Enumeration docProps = documentAttributes.getAttributeNames();
281 while(docProps.hasMoreElements()) {
282 Object propName = docProps.nextElement();
283 target.putProperty(propName,
284 documentAttributes.getAttribute(propName));
285 }
286
287 /* RTFParser should have ensured that all our groups are closed */
288
289 warning("RTF filter done.");
290
291 super.close();
292 }
293
294 /**
295 * Handles a parameterless RTF keyword. This is called by the superclass
296 * (RTFParser) when a keyword is found in the input stream.
297 *
298 * @returns <code>true</code> if the keyword is recognized and handled;
299 * <code>false</code> otherwise
300 * @see RTFParser#handleKeyword
611
612 static char[] readCharset(java.net.URL href)
613 throws IOException
614 {
615 return readCharset(href.openStream());
616 }
617
618 /** An interface (could be an entirely abstract class) describing
619 * a destination. The RTF reader always has a current destination
620 * which is where text is sent.
621 *
622 * @see RTFReader
623 */
624 interface Destination {
625 void handleBinaryBlob(byte[] data);
626 void handleText(String text);
627 boolean handleKeyword(String keyword);
628 boolean handleKeyword(String keyword, int parameter);
629
630 void begingroup();
631 void endgroup(Dictionary oldState);
632
633 void close();
634 }
635
636 /** This data-sink class is used to implement ignored destinations
637 * (e.g. {\*\blegga blah blah blah} )
638 * It accepts all keywords and text but does nothing with them. */
639 class DiscardingDestination implements Destination
640 {
641 public void handleBinaryBlob(byte[] data)
642 {
643 /* Discard binary blobs. */
644 }
645
646 public void handleText(String text)
647 {
648 /* Discard text. */
649 }
650
651 public boolean handleKeyword(String text)
652 {
653 /* Accept and discard keywords. */
654 return true;
655 }
656
657 public boolean handleKeyword(String text, int parameter)
658 {
659 /* Accept and discard parameterized keywords. */
660 return true;
661 }
662
663 public void begingroup()
664 {
665 /* Ignore groups --- the RTFReader will keep track of the
666 current group level as necessary */
667 }
668
669 public void endgroup(Dictionary oldState)
670 {
671 /* Ignore groups */
672 }
673
674 public void close()
675 {
676 /* No end-of-destination cleanup needed */
677 }
678 }
679
680 /** Reads the fonttbl group, inserting fonts into the RTFReader's
681 * fontTable dictionary. */
682 class FonttblDestination implements Destination
683 {
684 int nextFontNumber;
685 Integer fontNumberKey = null;
686 String nextFontFamily;
687
688 public void handleBinaryBlob(byte[] data)
689 { /* Discard binary blobs. */ }
719 if (keyword.charAt(0) == 'f') {
720 nextFontFamily = keyword.substring(1);
721 return true;
722 }
723
724 return false;
725 }
726
727 public boolean handleKeyword(String keyword, int parameter)
728 {
729 if (keyword.equals("f")) {
730 nextFontNumber = parameter;
731 return true;
732 }
733
734 return false;
735 }
736
737 /* Groups are irrelevant. */
738 public void begingroup() {}
739 public void endgroup(Dictionary oldState) {}
740
741 /* currently, the only thing we do when the font table ends is
742 dump its contents to the debugging log. */
743 public void close()
744 {
745 Enumeration<Integer> nums = fontTable.keys();
746 warning("Done reading font table.");
747 while(nums.hasMoreElements()) {
748 Integer num = nums.nextElement();
749 warning("Number " + num + ": " + fontTable.get(num));
750 }
751 }
752 }
753
754 /** Reads the colortbl group. Upon end-of-group, the RTFReader's
755 * color table is set to an array containing the read colors. */
756 class ColortblDestination implements Destination
757 {
758 int red, green, blue;
759 Vector<Color> proTemTable;
789
790 public boolean handleKeyword(String keyword, int parameter)
791 {
792 if (keyword.equals("red"))
793 red = parameter;
794 else if (keyword.equals("green"))
795 green = parameter;
796 else if (keyword.equals("blue"))
797 blue = parameter;
798 else
799 return false;
800
801 return true;
802 }
803
804 /* Colortbls don't understand any parameterless keywords */
805 public boolean handleKeyword(String keyword) { return false; }
806
807 /* Groups are irrelevant. */
808 public void begingroup() {}
809 public void endgroup(Dictionary oldState) {}
810
811 /* Shouldn't see any binary blobs ... */
812 public void handleBinaryBlob(byte[] data) {}
813 }
814
815 /** Handles the stylesheet keyword. Styles are read and sorted
816 * into the three style arrays in the RTFReader. */
817 class StylesheetDestination
818 extends DiscardingDestination
819 implements Destination
820 {
821 Dictionary<Integer, StyleDefiningDestination> definedStyles;
822
823 public StylesheetDestination()
824 {
825 definedStyles = new Hashtable<Integer, StyleDefiningDestination>();
826 }
827
828 public void begingroup()
829 {
1081
1082 /* It would probably be more efficient to use the
1083 * resolver property of the attributes set for
1084 * implementing rtf groups,
1085 * but that's needed for styles. */
1086
1087 /* update the cached attribute dictionaries */
1088 characterAttributes = new SimpleAttributeSet();
1089 characterAttributes.addAttributes(characterParent);
1090 parserState.put("chr", characterAttributes);
1091
1092 paragraphAttributes = new SimpleAttributeSet();
1093 paragraphAttributes.addAttributes(paragraphParent);
1094 parserState.put("pgf", paragraphAttributes);
1095
1096 sectionAttributes = new SimpleAttributeSet();
1097 sectionAttributes.addAttributes(sectionParent);
1098 parserState.put("sec", sectionAttributes);
1099 }
1100
1101 public void endgroup(Dictionary oldState)
1102 {
1103 characterAttributes = (MutableAttributeSet)parserState.get("chr");
1104 paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
1105 sectionAttributes = (MutableAttributeSet)parserState.get("sec");
1106 }
1107
1108 public void close()
1109 {
1110 }
1111
1112 public boolean handleKeyword(String keyword)
1113 {
1114 if (keyword.equals("ulnone")) {
1115 return handleKeyword("ul", 0);
1116 }
1117
1118 {
1119 RTFAttribute attr = straightforwardAttributes.get(keyword);
1120 if (attr != null) {
1121 boolean ok;
1245 Number item;
1246
1247 tabAlignment = TabStop.ALIGN_LEFT;
1248 item = (Number)(parserState.get("tab_alignment"));
1249 if (item != null)
1250 tabAlignment = item.intValue();
1251 tabLeader = TabStop.LEAD_NONE;
1252 item = (Number)(parserState.get("tab_leader"));
1253 if (item != null)
1254 tabLeader = item.intValue();
1255 if (keyword.equals("tb"))
1256 tabAlignment = TabStop.ALIGN_BAR;
1257
1258 parserState.remove("tab_alignment");
1259 parserState.remove("tab_leader");
1260
1261 TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
1262 Dictionary<Object, Object> tabs;
1263 Integer stopCount;
1264
1265 tabs = (Dictionary<Object, Object>)parserState.get("_tabs");
1266 if (tabs == null) {
1267 tabs = new Hashtable<Object, Object>();
1268 parserState.put("_tabs", tabs);
1269 stopCount = Integer.valueOf(1);
1270 } else {
1271 stopCount = (Integer)tabs.get("stop count");
1272 stopCount = Integer.valueOf(1 + stopCount.intValue());
1273 }
1274 tabs.put(stopCount, newStop);
1275 tabs.put("stop count", stopCount);
1276 parserState.remove("_tabs_immutable");
1277
1278 return true;
1279 }
1280
1281 if (keyword.equals("s") &&
1282 paragraphStyles != null) {
1283 parserState.put("paragraphStyle", paragraphStyles[parameter]);
1284 return true;
1285 }
1403
1404 /**
1405 * Calculates the current paragraph attributes (with keys
1406 * as given in StyleConstants) from the current parser state.
1407 *
1408 * @returns a newly created MutableAttributeSet.
1409 * @see StyleConstants
1410 */
1411 MutableAttributeSet currentParagraphAttributes()
1412 {
1413 /* NB if there were a mutableCopy() method we should use it */
1414 MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
1415
1416 Integer stateItem;
1417
1418 /*** Tab stops ***/
1419 TabStop tabs[];
1420
1421 tabs = (TabStop[])parserState.get("_tabs_immutable");
1422 if (tabs == null) {
1423 Dictionary workingTabs = (Dictionary)parserState.get("_tabs");
1424 if (workingTabs != null) {
1425 int count = ((Integer)workingTabs.get("stop count")).intValue();
1426 tabs = new TabStop[count];
1427 for (int ix = 1; ix <= count; ix ++)
1428 tabs[ix-1] = (TabStop)workingTabs.get(Integer.valueOf(ix));
1429 parserState.put("_tabs_immutable", tabs);
1430 }
1431 }
1432 if (tabs != null)
1433 bld.addAttribute(Constants.Tabs, tabs);
1434
1435 Style paragraphStyle = (Style)parserState.get("paragraphStyle");
1436 if (paragraphStyle != null)
1437 bld.setResolveParent(paragraphStyle);
1438
1439 return bld;
1440 }
1441
1442 /**
1443 * Calculates the current section attributes
|
203 return Color.black;
204 }
205
206 /** Called by the superclass when a new RTF group is begun.
207 * This implementation saves the current <code>parserState</code>, and gives
208 * the current destination a chance to save its own state.
209 * @see RTFParser#begingroup
210 */
211 public void begingroup()
212 {
213 if (skippingCharacters > 0) {
214 /* TODO this indicates an error in the RTF. Log it? */
215 skippingCharacters = 0;
216 }
217
218 /* we do this little dance to avoid cloning the entire state stack and
219 immediately throwing it away. */
220 Object oldSaveState = parserState.get("_savedState");
221 if (oldSaveState != null)
222 parserState.remove("_savedState");
223 @SuppressWarnings("unchecked")
224 Dictionary<String, Object> saveState = (Dictionary<String, Object>)((Hashtable)parserState).clone();
225 if (oldSaveState != null)
226 saveState.put("_savedState", oldSaveState);
227 parserState.put("_savedState", saveState);
228
229 if (rtfDestination != null)
230 rtfDestination.begingroup();
231 }
232
233 /** Called by the superclass when the current RTF group is closed.
234 * This restores the parserState saved by <code>begingroup()</code>
235 * as well as invoking the endgroup method of the current
236 * destination.
237 * @see RTFParser#endgroup
238 */
239 public void endgroup()
240 {
241 if (skippingCharacters > 0) {
242 /* NB this indicates an error in the RTF. Log it? */
243 skippingCharacters = 0;
244 }
245
246 @SuppressWarnings("unchecked")
247 Dictionary<Object, Object> restoredState = (Dictionary<Object, Object>)parserState.get("_savedState");
248 Destination restoredDestination = (Destination)restoredState.get("dst");
249 if (restoredDestination != rtfDestination) {
250 rtfDestination.close(); /* allow the destination to clean up */
251 rtfDestination = restoredDestination;
252 }
253 Dictionary<Object, Object> oldParserState = parserState;
254 parserState = restoredState;
255 if (rtfDestination != null)
256 rtfDestination.endgroup(oldParserState);
257 }
258
259 protected void setRTFDestination(Destination newDestination)
260 {
261 /* Check that setting the destination won't close the
262 current destination (should never happen) */
263 @SuppressWarnings("unchecked")
264 Dictionary<Object, Object> previousState = (Dictionary)parserState.get("_savedState");
265 if (previousState != null) {
266 if (rtfDestination != previousState.get("dst")) {
267 warning("Warning, RTF destination overridden, invalid RTF.");
268 rtfDestination.close();
269 }
270 }
271 rtfDestination = newDestination;
272 parserState.put("dst", rtfDestination);
273 }
274
275 /** Called by the user when there is no more input (<i>i.e.</i>,
276 * at the end of the RTF file.)
277 *
278 * @see OutputStream#close
279 */
280 public void close()
281 throws IOException
282 {
283 Enumeration<?> docProps = documentAttributes.getAttributeNames();
284 while(docProps.hasMoreElements()) {
285 Object propName = docProps.nextElement();
286 target.putProperty(propName,
287 documentAttributes.getAttribute(propName));
288 }
289
290 /* RTFParser should have ensured that all our groups are closed */
291
292 warning("RTF filter done.");
293
294 super.close();
295 }
296
297 /**
298 * Handles a parameterless RTF keyword. This is called by the superclass
299 * (RTFParser) when a keyword is found in the input stream.
300 *
301 * @returns <code>true</code> if the keyword is recognized and handled;
302 * <code>false</code> otherwise
303 * @see RTFParser#handleKeyword
614
615 static char[] readCharset(java.net.URL href)
616 throws IOException
617 {
618 return readCharset(href.openStream());
619 }
620
621 /** An interface (could be an entirely abstract class) describing
622 * a destination. The RTF reader always has a current destination
623 * which is where text is sent.
624 *
625 * @see RTFReader
626 */
627 interface Destination {
628 void handleBinaryBlob(byte[] data);
629 void handleText(String text);
630 boolean handleKeyword(String keyword);
631 boolean handleKeyword(String keyword, int parameter);
632
633 void begingroup();
634 void endgroup(Dictionary<Object, Object> oldState);
635
636 void close();
637 }
638
639 /** This data-sink class is used to implement ignored destinations
640 * (e.g. {\*\blegga blah blah blah} )
641 * It accepts all keywords and text but does nothing with them. */
642 class DiscardingDestination implements Destination
643 {
644 public void handleBinaryBlob(byte[] data)
645 {
646 /* Discard binary blobs. */
647 }
648
649 public void handleText(String text)
650 {
651 /* Discard text. */
652 }
653
654 public boolean handleKeyword(String text)
655 {
656 /* Accept and discard keywords. */
657 return true;
658 }
659
660 public boolean handleKeyword(String text, int parameter)
661 {
662 /* Accept and discard parameterized keywords. */
663 return true;
664 }
665
666 public void begingroup()
667 {
668 /* Ignore groups --- the RTFReader will keep track of the
669 current group level as necessary */
670 }
671
672 public void endgroup(Dictionary<Object, Object> oldState)
673 {
674 /* Ignore groups */
675 }
676
677 public void close()
678 {
679 /* No end-of-destination cleanup needed */
680 }
681 }
682
683 /** Reads the fonttbl group, inserting fonts into the RTFReader's
684 * fontTable dictionary. */
685 class FonttblDestination implements Destination
686 {
687 int nextFontNumber;
688 Integer fontNumberKey = null;
689 String nextFontFamily;
690
691 public void handleBinaryBlob(byte[] data)
692 { /* Discard binary blobs. */ }
722 if (keyword.charAt(0) == 'f') {
723 nextFontFamily = keyword.substring(1);
724 return true;
725 }
726
727 return false;
728 }
729
730 public boolean handleKeyword(String keyword, int parameter)
731 {
732 if (keyword.equals("f")) {
733 nextFontNumber = parameter;
734 return true;
735 }
736
737 return false;
738 }
739
740 /* Groups are irrelevant. */
741 public void begingroup() {}
742 public void endgroup(Dictionary<Object, Object> oldState) {}
743
744 /* currently, the only thing we do when the font table ends is
745 dump its contents to the debugging log. */
746 public void close()
747 {
748 Enumeration<Integer> nums = fontTable.keys();
749 warning("Done reading font table.");
750 while(nums.hasMoreElements()) {
751 Integer num = nums.nextElement();
752 warning("Number " + num + ": " + fontTable.get(num));
753 }
754 }
755 }
756
757 /** Reads the colortbl group. Upon end-of-group, the RTFReader's
758 * color table is set to an array containing the read colors. */
759 class ColortblDestination implements Destination
760 {
761 int red, green, blue;
762 Vector<Color> proTemTable;
792
793 public boolean handleKeyword(String keyword, int parameter)
794 {
795 if (keyword.equals("red"))
796 red = parameter;
797 else if (keyword.equals("green"))
798 green = parameter;
799 else if (keyword.equals("blue"))
800 blue = parameter;
801 else
802 return false;
803
804 return true;
805 }
806
807 /* Colortbls don't understand any parameterless keywords */
808 public boolean handleKeyword(String keyword) { return false; }
809
810 /* Groups are irrelevant. */
811 public void begingroup() {}
812 public void endgroup(Dictionary<Object, Object> oldState) {}
813
814 /* Shouldn't see any binary blobs ... */
815 public void handleBinaryBlob(byte[] data) {}
816 }
817
818 /** Handles the stylesheet keyword. Styles are read and sorted
819 * into the three style arrays in the RTFReader. */
820 class StylesheetDestination
821 extends DiscardingDestination
822 implements Destination
823 {
824 Dictionary<Integer, StyleDefiningDestination> definedStyles;
825
826 public StylesheetDestination()
827 {
828 definedStyles = new Hashtable<Integer, StyleDefiningDestination>();
829 }
830
831 public void begingroup()
832 {
1084
1085 /* It would probably be more efficient to use the
1086 * resolver property of the attributes set for
1087 * implementing rtf groups,
1088 * but that's needed for styles. */
1089
1090 /* update the cached attribute dictionaries */
1091 characterAttributes = new SimpleAttributeSet();
1092 characterAttributes.addAttributes(characterParent);
1093 parserState.put("chr", characterAttributes);
1094
1095 paragraphAttributes = new SimpleAttributeSet();
1096 paragraphAttributes.addAttributes(paragraphParent);
1097 parserState.put("pgf", paragraphAttributes);
1098
1099 sectionAttributes = new SimpleAttributeSet();
1100 sectionAttributes.addAttributes(sectionParent);
1101 parserState.put("sec", sectionAttributes);
1102 }
1103
1104 public void endgroup(Dictionary<Object, Object> oldState)
1105 {
1106 characterAttributes = (MutableAttributeSet)parserState.get("chr");
1107 paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
1108 sectionAttributes = (MutableAttributeSet)parserState.get("sec");
1109 }
1110
1111 public void close()
1112 {
1113 }
1114
1115 public boolean handleKeyword(String keyword)
1116 {
1117 if (keyword.equals("ulnone")) {
1118 return handleKeyword("ul", 0);
1119 }
1120
1121 {
1122 RTFAttribute attr = straightforwardAttributes.get(keyword);
1123 if (attr != null) {
1124 boolean ok;
1248 Number item;
1249
1250 tabAlignment = TabStop.ALIGN_LEFT;
1251 item = (Number)(parserState.get("tab_alignment"));
1252 if (item != null)
1253 tabAlignment = item.intValue();
1254 tabLeader = TabStop.LEAD_NONE;
1255 item = (Number)(parserState.get("tab_leader"));
1256 if (item != null)
1257 tabLeader = item.intValue();
1258 if (keyword.equals("tb"))
1259 tabAlignment = TabStop.ALIGN_BAR;
1260
1261 parserState.remove("tab_alignment");
1262 parserState.remove("tab_leader");
1263
1264 TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
1265 Dictionary<Object, Object> tabs;
1266 Integer stopCount;
1267
1268 @SuppressWarnings("unchecked")
1269 Dictionary<Object, Object>tmp = (Dictionary)parserState.get("_tabs");
1270 tabs = tmp;
1271 if (tabs == null) {
1272 tabs = new Hashtable<Object, Object>();
1273 parserState.put("_tabs", tabs);
1274 stopCount = Integer.valueOf(1);
1275 } else {
1276 stopCount = (Integer)tabs.get("stop count");
1277 stopCount = Integer.valueOf(1 + stopCount.intValue());
1278 }
1279 tabs.put(stopCount, newStop);
1280 tabs.put("stop count", stopCount);
1281 parserState.remove("_tabs_immutable");
1282
1283 return true;
1284 }
1285
1286 if (keyword.equals("s") &&
1287 paragraphStyles != null) {
1288 parserState.put("paragraphStyle", paragraphStyles[parameter]);
1289 return true;
1290 }
1408
1409 /**
1410 * Calculates the current paragraph attributes (with keys
1411 * as given in StyleConstants) from the current parser state.
1412 *
1413 * @returns a newly created MutableAttributeSet.
1414 * @see StyleConstants
1415 */
1416 MutableAttributeSet currentParagraphAttributes()
1417 {
1418 /* NB if there were a mutableCopy() method we should use it */
1419 MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
1420
1421 Integer stateItem;
1422
1423 /*** Tab stops ***/
1424 TabStop tabs[];
1425
1426 tabs = (TabStop[])parserState.get("_tabs_immutable");
1427 if (tabs == null) {
1428 @SuppressWarnings("unchecked")
1429 Dictionary<Object, Object> workingTabs = (Dictionary)parserState.get("_tabs");
1430 if (workingTabs != null) {
1431 int count = ((Integer)workingTabs.get("stop count")).intValue();
1432 tabs = new TabStop[count];
1433 for (int ix = 1; ix <= count; ix ++)
1434 tabs[ix-1] = (TabStop)workingTabs.get(Integer.valueOf(ix));
1435 parserState.put("_tabs_immutable", tabs);
1436 }
1437 }
1438 if (tabs != null)
1439 bld.addAttribute(Constants.Tabs, tabs);
1440
1441 Style paragraphStyle = (Style)parserState.get("paragraphStyle");
1442 if (paragraphStyle != null)
1443 bld.setResolveParent(paragraphStyle);
1444
1445 return bld;
1446 }
1447
1448 /**
1449 * Calculates the current section attributes
|