- /*
- * @(#)RTFReader.java 1.16 00/03/06
- *
- * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the proprietary information of Sun Microsystems, Inc.
- * Use is subject to license terms.
- *
- */
- package javax.swing.text.rtf;
-
- import java.lang.*;
- import java.util.*;
- import java.io.*;
- import java.awt.Font;
- import java.awt.Color;
-
- import javax.swing.text.*;
-
- /**
- * Takes a sequence of RTF tokens and text and appends the text
- * described by the RTF to a StyledDocument (the <em>target</em>).
- * The RTF is lexed
- * from the character stream by the RTFParser which is this class's
- * superclass.
- *
- * This class is an indirect subclass of OutputStream. It must be closed
- * in order to guarantee that all of the text has been sent to
- * the text acceptor.
- *
- * @see RTFParser
- * @see java.io.OutputStream
- */
- class RTFReader extends RTFParser
- {
- /** The object to which the parsed text is sent. */
- StyledDocument target;
-
- /** Miscellaneous information about the parser's state. This
- * dictionary is saved and restored when an RTF group begins
- * or ends. */
- Dictionary parserState; /* Current parser state */
- /** This is the "dst" item from parserState. rtfDestination
- * is the current rtf destination. It is cached in an instance
- * variable for speed. */
- Destination rtfDestination;
- /** This holds the current document attributes. */
- MutableAttributeSet documentAttributes;
-
- /** This Dictionary maps Integer font numbers to String font names. */
- Dictionary fontTable;
- /** This array maps color indices to Color objects. */
- Color[] colorTable;
- /** This array maps character style numbers to Style objects. */
- Style[] characterStyles;
- /** This array maps paragraph style numbers to Style objects. */
- Style[] paragraphStyles;
- /** This array maps section style numbers to Style objects. */
- Style[] sectionStyles;
-
- /** This is the RTF version number, extracted from the \rtf keyword.
- * The version information is currently not used. */
- int rtfversion;
-
- /** <code>true</code> to indicate that if the next keyword is unknown,
- * the containing group should be ignored. */
- boolean ignoreGroupIfUnknownKeyword;
-
- /** The parameter of the most recently parsed \\ucN keyword,
- * used for skipping alternative representations after a
- * Unicode character. */
- int skippingCharacters;
-
- static private Dictionary straightforwardAttributes;
- static {
- straightforwardAttributes = RTFAttributes.attributesByKeyword();
- }
-
- private MockAttributeSet mockery;
-
- /* this should be final, but there's a bug in javac... */
- /** textKeywords maps RTF keywords to single-character strings,
- * for those keywords which simply insert some text. */
- static Dictionary textKeywords = null;
- static {
- textKeywords = new Hashtable();
- textKeywords.put("\\", "\\");
- textKeywords.put("{", "{");
- textKeywords.put("}", "}");
- textKeywords.put(" ", "\u00A0"); /* not in the spec... */
- textKeywords.put("~", "\u00A0"); /* nonbreaking space */
- textKeywords.put("_", "\u2011"); /* nonbreaking hyphen */
- textKeywords.put("bullet", "\u2022");
- textKeywords.put("emdash", "\u2014");
- textKeywords.put("emspace", "\u2003");
- textKeywords.put("endash", "\u2013");
- textKeywords.put("enspace", "\u2002");
- textKeywords.put("ldblquote", "\u201C");
- textKeywords.put("lquote", "\u2018");
- textKeywords.put("ltrmark", "\u200E");
- textKeywords.put("rdblquote", "\u201D");
- textKeywords.put("rquote", "\u2019");
- textKeywords.put("rtlmark", "\u200F");
- textKeywords.put("tab", "\u0009");
- textKeywords.put("zwj", "\u200D");
- textKeywords.put("zwnj", "\u200C");
-
- /* There is no Unicode equivalent to an optional hyphen, as far as
- I can tell. */
- textKeywords.put("-", "\u2027"); /* TODO: optional hyphen */
- }
-
- /* some entries in parserState */
- static final String TabAlignmentKey = "tab_alignment";
- static final String TabLeaderKey = "tab_leader";
-
- static Dictionary characterSets;
- static boolean useNeXTForAnsi = false;
- static {
- characterSets = new Hashtable();
- }
-
- /* TODO: per-font font encodings ( \fcharset control word ) ? */
-
- /**
- * Creates a new RTFReader instance. Text will be sent to
- * the specified TextAcceptor.
- *
- * @param destination The TextAcceptor which is to receive the text.
- */
- public RTFReader(StyledDocument destination)
- {
- int i;
-
- target = destination;
- parserState = new Hashtable();
- fontTable = new Hashtable();
-
- rtfversion = -1;
-
- mockery = new MockAttributeSet();
- documentAttributes = new SimpleAttributeSet();
- }
-
- /** Called when the RTFParser encounters a bin keyword in the
- * RTF stream.
- *
- * @see RTFParser
- */
- public void handleBinaryBlob(byte[] data)
- {
- if (skippingCharacters > 0) {
- /* a blob only counts as one character for skipping purposes */
- skippingCharacters --;
- return;
- }
-
- /* someday, someone will want to do something with blobs */
- }
-
-
- /**
- * Handles any pure text (containing no control characters) in the input
- * stream. Called by the superclass. */
- public void handleText(String text)
- {
- if (skippingCharacters > 0) {
- if (skippingCharacters >= text.length()) {
- skippingCharacters -= text.length();
- return;
- } else {
- text = text.substring(skippingCharacters);
- skippingCharacters = 0;
- }
- }
-
- if (rtfDestination != null) {
- rtfDestination.handleText(text);
- return;
- }
-
- warning("Text with no destination. oops.");
- }
-
- /** The default color for text which has no specified color. */
- Color defaultColor()
- {
- return Color.black;
- }
-
- /** Called by the superclass when a new RTF group is begun.
- * This implementation saves the current parserState, and gives
- * the current destination a chence to save its own state.
- * @see RTFParser#begingroup
- */
- public void begingroup()
- {
- if (skippingCharacters > 0) {
- /* TODO this indicates an error in the RTF. Log it? */
- skippingCharacters = 0;
- }
-
- /* we do this little dance to avoid cloning the entire state stack and
- immediately throwing it away. */
- Object oldSaveState = parserState.get("_savedState");
- if (oldSaveState != null)
- parserState.remove("_savedState");
- Dictionary saveState = (Dictionary)((Hashtable)parserState).clone();
- if (oldSaveState != null)
- saveState.put("_savedState", oldSaveState);
- parserState.put("_savedState", saveState);
-
- if (rtfDestination != null)
- rtfDestination.begingroup();
- }
-
- /** Called by the superclass when the current RTF group is closed.
- * This restores the parserState saved by <code>begingroup()</code>
- * as well as invoking the endgroup method of the current
- * destination.
- * @see RTFParser#endgroup
- */
- public void endgroup()
- {
- if (skippingCharacters > 0) {
- /* NB this indicates an error in the RTF. Log it? */
- skippingCharacters = 0;
- }
-
- Dictionary restoredState = (Dictionary)parserState.get("_savedState");
- Destination restoredDestination = (Destination)restoredState.get("dst");
- if (restoredDestination != rtfDestination) {
- rtfDestination.close(); /* allow the destination to clean up */
- rtfDestination = restoredDestination;
- }
- Dictionary oldParserState = parserState;
- parserState = restoredState;
- if (rtfDestination != null)
- rtfDestination.endgroup(oldParserState);
- }
-
- protected void setRTFDestination(Destination newDestination)
- {
- /* Check that setting the destination won't close the
- current destination (should never happen) */
- Dictionary previousState = (Dictionary)parserState.get("_savedState");
- if (previousState != null) {
- if (rtfDestination != previousState.get("dst")) {
- warning("Warning, RTF destination overridden, invalid RTF.");
- rtfDestination.close();
- }
- }
- rtfDestination = newDestination;
- parserState.put("dst", rtfDestination);
- }
-
- /** Called by the user when there is no more input (<i>i.e.</i>,
- * at the end of the RTF file.)
- *
- * @see OutputStream#close
- */
- public void close()
- throws IOException
- {
- Enumeration docProps = documentAttributes.getAttributeNames();
- while(docProps.hasMoreElements()) {
- Object propName = docProps.nextElement();
- target.putProperty(propName,
- documentAttributes.getAttribute((String)propName));
- }
-
- /* RTFParser should have ensured that all our groups are closed */
-
- warning("RTF filter done.");
-
- super.close();
- }
-
- /**
- * Handles a parameterless RTF keyword. This is called by the superclass
- * (RTFParser) when a keyword is found in the input stream.
- *
- * @returns <code>true</code> if the keyword is recognized and handled;
- * <code>false</code> otherwise
- * @see RTFParser#handleKeyword
- */
- public boolean handleKeyword(String keyword)
- {
- Object item;
- boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
-
- if (skippingCharacters > 0) {
- skippingCharacters --;
- return true;
- }
-
- ignoreGroupIfUnknownKeyword = false;
-
- if ((item = textKeywords.get(keyword)) != null) {
- handleText((String)item);
- return true;
- }
-
- if (keyword.equals("fonttbl")) {
- setRTFDestination(new FonttblDestination());
- return true;
- }
-
- if (keyword.equals("colortbl")) {
- setRTFDestination(new ColortblDestination());
- return true;
- }
-
- if (keyword.equals("stylesheet")) {
- setRTFDestination(new StylesheetDestination());
- return true;
- }
-
- if (keyword.equals("info")) {
- setRTFDestination(new InfoDestination());
- return false;
- }
-
- if (keyword.equals("mac")) {
- setCharacterSet("mac");
- return true;
- }
-
- if (keyword.equals("ansi")) {
- if (useNeXTForAnsi)
- setCharacterSet("NeXT");
- else
- setCharacterSet("ansi");
- return true;
- }
-
- if (keyword.equals("next")) {
- setCharacterSet("NeXT");
- return true;
- }
-
- if (keyword.equals("pc")) {
- setCharacterSet("cpg437"); /* IBM Code Page 437 */
- return true;
- }
-
- if (keyword.equals("pca")) {
- setCharacterSet("cpg850"); /* IBM Code Page 850 */
- return true;
- }
-
- if (keyword.equals("*")) {
- ignoreGroupIfUnknownKeyword = true;
- return true;
- }
-
- if (rtfDestination != null) {
- if(rtfDestination.handleKeyword(keyword))
- return true;
- }
-
- /* this point is reached only if the keyword is unrecognized */
-
- /* other destinations we don't understand and therefore ignore */
- if (keyword.equals("aftncn") ||
- keyword.equals("aftnsep") ||
- keyword.equals("aftnsepc") ||
- keyword.equals("annotation") ||
- keyword.equals("atnauthor") ||
- keyword.equals("atnicn") ||
- keyword.equals("atnid") ||
- keyword.equals("atnref") ||
- keyword.equals("atntime") ||
- keyword.equals("atrfend") ||
- keyword.equals("atrfstart") ||
- keyword.equals("bkmkend") ||
- keyword.equals("bkmkstart") ||
- keyword.equals("datafield") ||
- keyword.equals("do") ||
- keyword.equals("dptxbxtext") ||
- keyword.equals("falt") ||
- keyword.equals("field") ||
- keyword.equals("file") ||
- keyword.equals("filetbl") ||
- keyword.equals("fname") ||
- keyword.equals("fontemb") ||
- keyword.equals("fontfile") ||
- keyword.equals("footer") ||
- keyword.equals("footerf") ||
- keyword.equals("footerl") ||
- keyword.equals("footerr") ||
- keyword.equals("footnote") ||
- keyword.equals("ftncn") ||
- keyword.equals("ftnsep") ||
- keyword.equals("ftnsepc") ||
- keyword.equals("header") ||
- keyword.equals("headerf") ||
- keyword.equals("headerl") ||
- keyword.equals("headerr") ||
- keyword.equals("keycode") ||
- keyword.equals("nextfile") ||
- keyword.equals("object") ||
- keyword.equals("pict") ||
- keyword.equals("pn") ||
- keyword.equals("pnseclvl") ||
- keyword.equals("pntxtb") ||
- keyword.equals("pntxta") ||
- keyword.equals("revtbl") ||
- keyword.equals("rxe") ||
- keyword.equals("tc") ||
- keyword.equals("template") ||
- keyword.equals("txe") ||
- keyword.equals("xe")) {
- ignoreGroupIfUnknownKeywordSave = true;
- }
-
- if (ignoreGroupIfUnknownKeywordSave) {
- setRTFDestination(new DiscardingDestination());
- }
-
- return false;
- }
-
- /**
- * Handles an RTF keyword and its integer parameter.
- * This is called by the superclass
- * (RTFParser) when a keyword is found in the input stream.
- *
- * @returns <code>true</code> if the keyword is recognized and handled;
- * <code>false</code> otherwise
- * @see RTFParser#handleKeyword
- */
- public boolean handleKeyword(String keyword, int parameter)
- {
- boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
-
- if (skippingCharacters > 0) {
- skippingCharacters --;
- return true;
- }
-
- ignoreGroupIfUnknownKeyword = false;
-
- if (keyword.equals("uc")) {
- /* count of characters to skip after a unicode character */
- parserState.put("UnicodeSkip", new Integer(parameter));
- return true;
- }
- if (keyword.equals("u")) {
- if (parameter < 0)
- parameter = parameter + 65536;
- handleText((char)parameter);
- Number skip = (Number)(parserState.get("UnicodeSkip"));
- if (skip != null) {
- skippingCharacters = skip.intValue();
- } else {
- skippingCharacters = 1;
- }
- return true;
- }
-
- if (keyword.equals("rtf")) {
- rtfversion = parameter;
- setRTFDestination(new DocumentDestination());
- return true;
- }
-
- if (keyword.startsWith("NeXT") ||
- keyword.equals("private"))
- ignoreGroupIfUnknownKeywordSave = true;
-
- if (rtfDestination != null) {
- if(rtfDestination.handleKeyword(keyword, parameter))
- return true;
- }
-
- /* this point is reached only if the keyword is unrecognized */
-
- if (ignoreGroupIfUnknownKeywordSave) {
- setRTFDestination(new DiscardingDestination());
- }
-
- return false;
- }
-
- private void setTargetAttribute(String name, Object value)
- {
- // target.changeAttributes(new LFDictionary(LFArray.arrayWithObject(value), LFArray.arrayWithObject(name)));
- }
-
- /**
- * setCharacterSet sets the current translation table to correspond with
- * the named character set. The character set is loaded if necessary.
- *
- * @see AbstractFilter
- */
- public void setCharacterSet(String name)
- {
- Object set;
-
- try {
- set = getCharacterSet(name);
- } catch (Exception e) {
- warning("Exception loading RTF character set \"" + name + "\": " + e);
- set = null;
- }
-
- if (set != null) {
- translationTable = (char[])set;
- } else {
- warning("Unknown RTF character set \"" + name + "\"");
- if (!name.equals("ansi")) {
- try {
- translationTable = (char[])getCharacterSet("ansi");
- } catch (IOException e) {
- throw new InternalError("RTFReader: Unable to find character set resources (" + e + ")");
- }
- }
- }
-
- setTargetAttribute(Constants.RTFCharacterSet, name);
- }
-
- /** Adds a character set to the RTFReader's list
- * of known character sets */
- public static void
- defineCharacterSet(String name, char[] table)
- {
- if (table.length < 256)
- throw new IllegalArgumentException("Translation table must have 256 entries.");
- characterSets.put(name, table);
- }
-
- /** Looks up a named character set. A character set is a 256-entry
- * array of characters, mapping unsigned byte values to their Unicode
- * equivalents. The character set is loaded if necessary.
- *
- * @returns the character set
- */
- public static Object
- getCharacterSet(final String name)
- throws IOException
- {
- char[] set;
-
- set = (char [])characterSets.get(name);
- if (set == null) {
- InputStream charsetStream;
- charsetStream = (InputStream)java.security.AccessController.
- doPrivileged(new java.security.PrivilegedAction() {
- public Object run() {
- return RTFReader.class.getResourceAsStream
- ("charsets/" + name + ".txt");
- }
- });
- set = readCharset(charsetStream);
- defineCharacterSet(name, set);
- }
- return set;
- }
-
- /** Parses a character set from an InputStream. The character set
- * must contain 256 decimal integers, separated by whitespace, with
- * no punctuation. B- and C- style comments are allowed.
- *
- * @returns the newly read character set
- */
- static char[] readCharset(InputStream strm)
- throws IOException
- {
- char[] values = new char[256];
- int i;
- StreamTokenizer in = new StreamTokenizer(new BufferedReader(
- new InputStreamReader(strm)));
-
- in.eolIsSignificant(false);
- in.commentChar('#');
- in.slashSlashComments(true);
- in.slashStarComments(true);
-
- i = 0;
- while (i < 256) {
- int ttype;
- try {
- ttype = in.nextToken();
- } catch (Exception e) {
- throw new IOException("Unable to read from character set file (" + e + ")");
- }
- if (ttype != in.TT_NUMBER) {
- // System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
- throw new IOException("Unexpected token in character set file");
- // continue;
- }
- values[i] = (char)(in.nval);
- i++;
- }
-
- return values;
- }
-
- static char[] readCharset(java.net.URL href)
- throws IOException
- {
- return readCharset(href.openStream());
- }
-
- /** An interface (could be an entirely abstract class) describing
- * a destination. The RTF reader always has a current destination
- * which is where text is sent.
- *
- * @see RTFReader
- */
- interface Destination {
- void handleBinaryBlob(byte[] data);
- void handleText(String text);
- boolean handleKeyword(String keyword);
- boolean handleKeyword(String keyword, int parameter);
-
- void begingroup();
- void endgroup(Dictionary oldState);
-
- void close();
- }
-
- /** This data-sink class is used to implement ignored destinations
- * (e.g. {\*\blegga blah blah blah} )
- * It accepts all keywords and text but does nothing with them. */
- class DiscardingDestination implements Destination
- {
- public void handleBinaryBlob(byte[] data)
- {
- /* Discard binary blobs. */
- }
-
- public void handleText(String text)
- {
- /* Discard text. */
- }
-
- public boolean handleKeyword(String text)
- {
- /* Accept and discard keywords. */
- return true;
- }
-
- public boolean handleKeyword(String text, int parameter)
- {
- /* Accept and discard parameterized keywords. */
- return true;
- }
-
- public void begingroup()
- {
- /* Ignore groups --- the RTFReader will keep track of the
- current group level as necessary */
- }
-
- public void endgroup(Dictionary oldState)
- {
- /* Ignore groups */
- }
-
- public void close()
- {
- /* No end-of-destination cleanup needed */
- }
- }
-
- /** Reads the fonttbl group, inserting fonts into the RTFReader's
- * fontTable dictionary. */
- class FonttblDestination implements Destination
- {
- int nextFontNumber;
- String nextFontFamily;
-
- public void handleBinaryBlob(byte[] data)
- { /* Discard binary blobs. */ }
-
- /* TODO do these routines work correctly if a write buffer divides a
- font name? (Probably not. Should allow for it as rare case) */
- public void handleText(String text)
- {
- int semicolon = text.indexOf(';');
- String fontName;
- Object fontNum; /* an Integer, but we don't care */
-
- if (semicolon > 0)
- fontName = text.substring(0, semicolon);
- else
- fontName = text;
-
- /* TODO: do something with the font family. */
-
- fontTable.put(new Integer(nextFontNumber), fontName);
-
- nextFontNumber = -1;
- nextFontFamily = null;
- return;
- }
-
- public boolean handleKeyword(String keyword)
- {
- if (keyword.charAt(0) == 'f') {
- nextFontFamily = keyword.substring(1);
- return true;
- }
-
- return false;
- }
-
- public boolean handleKeyword(String keyword, int parameter)
- {
- if (keyword.equals("f")) {
- nextFontNumber = parameter;
- return true;
- }
-
- return false;
- }
-
- /* Groups are irrelevant. */
- public void begingroup() {}
- public void endgroup(Dictionary oldState) {}
-
- /* currently, the only thing we do when the font table ends is
- dump its contents to the debugging log. */
- public void close()
- {
- Enumeration nums = fontTable.keys();
- warning("Done reading font table.");
- while(nums.hasMoreElements()) {
- Integer num = (Integer)nums.nextElement();
- warning("Number " + num + ": " + fontTable.get(num));
- }
- }
- }
-
- /** Reads the colortbl group. Upon end-of-group, the RTFReader's
- * color table is set to an array containing the read colors. */
- class ColortblDestination implements Destination
- {
- int red, green, blue;
- Vector proTemTable;
-
- public ColortblDestination()
- {
- red = 0;
- green = 0;
- blue = 0;
- proTemTable = new Vector();
- }
-
- public void handleText(String text)
- {
- int index = 0;
-
- for (index = 0; index < text.length(); index ++) {
- if (text.charAt(index) == ';') {
- Color newColor;
- newColor = new Color(red, green, blue);
- proTemTable.addElement(newColor);
- }
- }
- }
-
- public void close()
- {
- int count = proTemTable.size();
- warning("Done reading color table, " + count + " entries.");
- colorTable = new Color[count];
- proTemTable.copyInto(colorTable);
- }
-
- public boolean handleKeyword(String keyword, int parameter)
- {
- if (keyword.equals("red"))
- red = parameter;
- else if (keyword.equals("green"))
- green = parameter;
- else if (keyword.equals("blue"))
- blue = parameter;
- else
- return false;
-
- return true;
- }
-
- /* Colortbls don't understand any parameterless keywords */
- public boolean handleKeyword(String keyword) { return false; }
-
- /* Groups are irrelevant. */
- public void begingroup() {}
- public void endgroup(Dictionary oldState) {}
-
- /* Shouldn't see any binary blobs ... */
- public void handleBinaryBlob(byte[] data) {}
- }
-
- /** Handles the stylesheet keyword. Styles are read and sorted
- * into the three style arrays in the RTFReader. */
- class StylesheetDestination
- extends DiscardingDestination
- implements Destination
- {
- Dictionary definedStyles;
-
- public StylesheetDestination()
- {
- definedStyles = new Hashtable();
- }
-
- public void begingroup()
- {
- setRTFDestination(new StyleDefiningDestination());
- }
-
- public void close()
- {
- Vector chrStyles, pgfStyles, secStyles;
- chrStyles = new Vector();
- pgfStyles = new Vector();
- secStyles = new Vector();
- Enumeration styles = definedStyles.elements();
- while(styles.hasMoreElements()) {
- StyleDefiningDestination style;
- Style defined;
- style = (StyleDefiningDestination)styles.nextElement();
- defined = style.realize();
- warning("Style "+style.number+" ("+style.styleName+"): "+defined);
- String stype = (String)defined.getAttribute(Constants.StyleType);
- Vector toSet;
- if (stype.equals(Constants.STSection)) {
- toSet = secStyles;
- } else if (stype.equals(Constants.STCharacter)) {
- toSet = chrStyles;
- } else {
- toSet = pgfStyles;
- }
- if (toSet.size() <= style.number)
- toSet.setSize(style.number + 1);
- toSet.setElementAt(defined, style.number);
- }
- if (!(chrStyles.isEmpty())) {
- Style[] styleArray = new Style[chrStyles.size()];
- chrStyles.copyInto(styleArray);
- characterStyles = styleArray;
- }
- if (!(pgfStyles.isEmpty())) {
- Style[] styleArray = new Style[pgfStyles.size()];
- pgfStyles.copyInto(styleArray);
- paragraphStyles = styleArray;
- }
- if (!(secStyles.isEmpty())) {
- Style[] styleArray = new Style[secStyles.size()];
- secStyles.copyInto(styleArray);
- sectionStyles = styleArray;
- }
-
- /* (old debugging code)
- int i, m;
- if (characterStyles != null) {
- m = characterStyles.length;
- for(i=0;i<m;i++)
- warnings.println("chrStyle["+i+"]="+characterStyles[i]);
- } else warnings.println("No character styles.");
- if (paragraphStyles != null) {
- m = paragraphStyles.length;
- for(i=0;i<m;i++)
- warnings.println("pgfStyle["+i+"]="+paragraphStyles[i]);
- } else warnings.println("No paragraph styles.");
- if (sectionStyles != null) {
- m = characterStyles.length;
- for(i=0;i<m;i++)
- warnings.println("secStyle["+i+"]="+sectionStyles[i]);
- } else warnings.println("No section styles.");
- */
- }
-
- /** This subclass handles an individual style */
- class StyleDefiningDestination
- extends AttributeTrackingDestination
- implements Destination
- {
- final int STYLENUMBER_NONE = 222;
- boolean additive;
- boolean characterStyle;
- boolean sectionStyle;
- public String styleName;
- public int number;
- int basedOn;
- int nextStyle;
- boolean hidden;
-
- Style realizedStyle;
-
- public StyleDefiningDestination()
- {
- additive = false;
- characterStyle = false;
- sectionStyle = false;
- styleName = null;
- number = 0;
- basedOn = STYLENUMBER_NONE;
- nextStyle = STYLENUMBER_NONE;
- hidden = false;
- }
-
- public void handleText(String text)
- {
- if (styleName != null)
- styleName = styleName + text;
- else
- styleName = text;
- }
-
- public void close() {
- int semicolon = styleName.indexOf(';');
- if (semicolon > 0)
- styleName = styleName.substring(0, semicolon);
- definedStyles.put(new Integer(number), this);
- super.close();
- }
-
- public boolean handleKeyword(String keyword)
- {
- if (keyword.equals("additive")) {
- additive = true;
- return true;
- }
- if (keyword.equals("shidden")) {
- hidden = true;
- return true;
- }
- return super.handleKeyword(keyword);
- }
-
- public boolean handleKeyword(String keyword, int parameter)
- {
- if (keyword.equals("s")) {
- characterStyle = false;
- sectionStyle = false;
- number = parameter;
- } else if (keyword.equals("cs")) {
- characterStyle = true;
- sectionStyle = false;
- number = parameter;
- } else if (keyword.equals("ds")) {
- characterStyle = false;
- sectionStyle = true;
- number = parameter;
- } else if (keyword.equals("sbasedon")) {
- basedOn = parameter;
- } else if (keyword.equals("snext")) {
- nextStyle = parameter;
- } else {
- return super.handleKeyword(keyword, parameter);
- }
- return true;
- }
-
- public Style realize()
- {
- Style basis = null;
- Style next = null;
-
- if (realizedStyle != null)
- return realizedStyle;
-
- if (basedOn != STYLENUMBER_NONE) {
- StyleDefiningDestination styleDest;
- styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(basedOn));
- if (styleDest != null) {
- basis = styleDest.realize();
- }
- }
-
- /* NB: Swing StyleContext doesn't allow distinct styles with
- the same name; RTF apparently does. This may confuse the
- user. */
- realizedStyle = target.addStyle(styleName, basis);
-
- if (characterStyle) {
- realizedStyle.addAttributes(currentTextAttributes());
- realizedStyle.addAttribute(Constants.StyleType,
- Constants.STCharacter);
- } else if (sectionStyle) {
- realizedStyle.addAttributes(currentSectionAttributes());
- realizedStyle.addAttribute(Constants.StyleType,
- Constants.STSection);
- } else { /* must be a paragraph style */
- realizedStyle.addAttributes(currentParagraphAttributes());
- realizedStyle.addAttribute(Constants.StyleType,
- Constants.STParagraph);
- }
-
- if (nextStyle != STYLENUMBER_NONE) {
- StyleDefiningDestination styleDest;
- styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(nextStyle));
- if (styleDest != null) {
- next = styleDest.realize();
- }
- }
-
- if (next != null)
- realizedStyle.addAttribute(Constants.StyleNext, next);
- realizedStyle.addAttribute(Constants.StyleAdditive,
- new Boolean(additive));
- realizedStyle.addAttribute(Constants.StyleHidden,
- new Boolean(hidden));
-
- return realizedStyle;
- }
- }
- }
-
- /** Handles the info group. Currently no info keywords are recognized
- * so this is a subclass of DiscardingDestination. */
- class InfoDestination
- extends DiscardingDestination
- implements Destination
- {
- }
-
- /** RTFReader.TextHandlingDestination is an abstract RTF destination
- * which simply tracks the attributes specified by the RTF control words
- * in internal form and can produce acceptable AttributeSets for the
- * current character, paragraph, and section attributes. It is up
- * to the subclasses to determine what is done with the actual text. */
- abstract class AttributeTrackingDestination implements Destination
- {
- /** This is the "chr" element of parserState, cached for
- * more efficient use */
- MutableAttributeSet characterAttributes;
- /** This is the "pgf" element of parserState, cached for
- * more efficient use */
- MutableAttributeSet paragraphAttributes;
- /** This is the "sec" element of parserState, cached for
- * more efficient use */
- MutableAttributeSet sectionAttributes;
-
- public AttributeTrackingDestination()
- {
- characterAttributes = rootCharacterAttributes();
- parserState.put("chr", characterAttributes);
- paragraphAttributes = rootParagraphAttributes();
- parserState.put("pgf", paragraphAttributes);
- sectionAttributes = rootSectionAttributes();
- parserState.put("sec", sectionAttributes);
- }
-
- abstract public void handleText(String text);
-
- public void handleBinaryBlob(byte[] data)
- {
- /* This should really be in TextHandlingDestination, but
- * since *nobody* does anything with binary blobs, this
- * is more convenient. */
- warning("Unexpected binary data in RTF file.");
- }
-
- public void begingroup()
- {
- AttributeSet characterParent = currentTextAttributes();
- AttributeSet paragraphParent = currentParagraphAttributes();
- AttributeSet sectionParent = currentSectionAttributes();
-
- /* It would probably be more efficient to use the
- * resolver property of the attributes set for
- * implementing rtf groups,
- * but that's needed for styles. */
-
- /* update the cached attribute dictionaries */
- characterAttributes = new SimpleAttributeSet();
- characterAttributes.addAttributes(characterParent);
- parserState.put("chr", characterAttributes);
-
- paragraphAttributes = new SimpleAttributeSet();
- paragraphAttributes.addAttributes(paragraphParent);
- parserState.put("pgf", paragraphAttributes);
-
- sectionAttributes = new SimpleAttributeSet();
- sectionAttributes.addAttributes(sectionParent);
- parserState.put("sec", sectionAttributes);
- }
-
- public void endgroup(Dictionary oldState)
- {
- characterAttributes = (MutableAttributeSet)parserState.get("chr");
- paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
- sectionAttributes = (MutableAttributeSet)parserState.get("sec");
- }
-
- public void close()
- {
- }
-
- public boolean handleKeyword(String keyword)
- {
- if (keyword.equals("ulnone")) {
- return handleKeyword("ul", 0);
- }
-
- {
- Object item = straightforwardAttributes.get(keyword);
- if (item != null) {
- RTFAttribute attr = (RTFAttribute)item;
- boolean ok;
-
- switch(attr.domain()) {
- case RTFAttribute.D_CHARACTER:
- ok = attr.set(characterAttributes);
- break;
- case RTFAttribute.D_PARAGRAPH:
- ok = attr.set(paragraphAttributes);
- break;
- case RTFAttribute.D_SECTION:
- ok = attr.set(sectionAttributes);
- break;
- case RTFAttribute.D_META:
- mockery.backing = parserState;
- ok = attr.set(mockery);
- mockery.backing = null;
- break;
- case RTFAttribute.D_DOCUMENT:
- ok = attr.set(documentAttributes);
- break;
- default:
- /* should never happen */
- ok = false;
- break;
- }
- if (ok)
- return true;
- }
- }
-
-
- if (keyword.equals("plain")) {
- resetCharacterAttributes();
- return true;
- }
-
- if (keyword.equals("pard")) {
- resetParagraphAttributes();
- return true;
- }
-
- if (keyword.equals("sectd")) {
- resetSectionAttributes();
- return true;
- }
-
- return false;
- }
-
- public boolean handleKeyword(String keyword, int parameter)
- {
- boolean booleanParameter = (parameter != 0);
-
- if (keyword.equals("fc"))
- keyword = "cf"; /* whatEVER, dude. */
-
- if (keyword.equals("f")) {
- parserState.put(keyword, new Integer(parameter));
- return true;
- }
- if (keyword.equals("cf")) {
- parserState.put(keyword, new Integer(parameter));
- return true;
- }
-
- {
- Object item = straightforwardAttributes.get(keyword);
- if (item != null) {
- RTFAttribute attr = (RTFAttribute)item;
- boolean ok;
-
- switch(attr.domain()) {
- case RTFAttribute.D_CHARACTER:
- ok = attr.set(characterAttributes, parameter);
- break;
- case RTFAttribute.D_PARAGRAPH:
- ok = attr.set(paragraphAttributes, parameter);
- break;
- case RTFAttribute.D_SECTION:
- ok = attr.set(sectionAttributes, parameter);
- break;
- case RTFAttribute.D_META:
- mockery.backing = parserState;
- ok = attr.set(mockery, parameter);
- mockery.backing = null;
- break;
- case RTFAttribute.D_DOCUMENT:
- ok = attr.set(documentAttributes, parameter);
- break;
- default:
- /* should never happen */
- ok = false;
- break;
- }
- if (ok)
- return true;
- }
- }
-
- if (keyword.equals("fs")) {
- StyleConstants.setFontSize(characterAttributes, (parameter / 2));
- return true;
- }
-
- /* TODO: superscript/subscript */
-
- if (keyword.equals("sl")) {
- if (parameter == 1000) { /* magic value! */
- characterAttributes.removeAttribute(StyleConstants.LineSpacing);
- } else {
- /* TODO: The RTF sl attribute has special meaning if it's
- negative. Make sure that SwingText has the same special
- meaning, or find a way to imitate that. When SwingText
- handles this, also recognize the slmult keyword. */
- StyleConstants.setLineSpacing(characterAttributes,
- parameter / 20f);
- }
- return true;
- }
-
- /* TODO: Other kinds of underlining */
-
- if (keyword.equals("tx") || keyword.equals("tb")) {
- float tabPosition = parameter / 20f;
- int tabAlignment, tabLeader;
- Number item;
-
- tabAlignment = TabStop.ALIGN_LEFT;
- item = (Number)(parserState.get("tab_alignment"));
- if (item != null)
- tabAlignment = item.intValue();
- tabLeader = TabStop.LEAD_NONE;
- item = (Number)(parserState.get("tab_leader"));
- if (item != null)
- tabLeader = item.intValue();
- if (keyword.equals("tb"))
- tabAlignment = TabStop.ALIGN_BAR;
-
- parserState.remove("tab_alignment");
- parserState.remove("tab_leader");
-
- TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
- Dictionary tabs;
- Integer stopCount;
-
- tabs = (Dictionary)parserState.get("_tabs");
- if (tabs == null) {
- tabs = new Hashtable();
- parserState.put("_tabs", tabs);
- stopCount = new Integer(1);
- } else {
- stopCount = (Integer)tabs.get("stop count");
- stopCount = new Integer(1 + stopCount.intValue());
- }
- tabs.put(stopCount, newStop);
- tabs.put("stop count", stopCount);
- parserState.remove("_tabs_immutable");
-
- return true;
- }
-
- if (keyword.equals("s") &&
- paragraphStyles != null) {
- parserState.put("paragraphStyle", paragraphStyles[parameter]);
- return true;
- }
-
- if (keyword.equals("cs") &&
- characterStyles != null) {
- parserState.put("characterStyle", characterStyles[parameter]);
- return true;
- }
-
- if (keyword.equals("ds") &&
- sectionStyles != null) {
- parserState.put("sectionStyle", sectionStyles[parameter]);
- return true;
- }
-
- return false;
- }
-
- /** Returns a new MutableAttributeSet containing the
- * default character attributes */
- protected MutableAttributeSet rootCharacterAttributes()
- {
- MutableAttributeSet set = new SimpleAttributeSet();
-
- /* TODO: default font */
-
- StyleConstants.setItalic(set, false);
- StyleConstants.setBold(set, false);
- StyleConstants.setUnderline(set, false);
- StyleConstants.setForeground(set, defaultColor());
-
- return set;
- }
-
- /** Returns a new MutableAttributeSet containing the
- * default paragraph attributes */
- protected MutableAttributeSet rootParagraphAttributes()
- {
- MutableAttributeSet set = new SimpleAttributeSet();
-
- StyleConstants.setLeftIndent(set, 0f);
- StyleConstants.setRightIndent(set, 0f);
- StyleConstants.setFirstLineIndent(set, 0f);
-
- /* TODO: what should this be, really? */
- set.setResolveParent(target.getStyle(StyleContext.DEFAULT_STYLE));
-
- return set;
- }
-
- /** Returns a new MutableAttributeSet containing the
- * default section attributes */
- protected MutableAttributeSet rootSectionAttributes()
- {
- MutableAttributeSet set = new SimpleAttributeSet();
-
- return set;
- }
-
- /**
- * Calculates the current text (character) attributes in a form suitable
- * for SwingText from the current parser state.
- *
- * @returns a new MutableAttributeSet containing the text attributes.
- */
- MutableAttributeSet currentTextAttributes()
- {
- MutableAttributeSet attributes =
- new SimpleAttributeSet(characterAttributes);
- Integer fontnum;
- Integer stateItem;
-
- /* figure out the font name */
- /* TODO: catch exceptions for undefined attributes,
- bad font indices, etc.? (as it stands, it is the caller's
- job to clean up after corrupt RTF) */
- fontnum = (Integer)parserState.get("f");
- /* note setFontFamily() can not handle a null font */
- String fontFamily;
- if (fontnum != null)
- fontFamily = (String)fontTable.get(fontnum);
- else
- fontFamily = null;
- if (fontFamily != null)
- StyleConstants.setFontFamily(attributes, fontFamily);
- else
- attributes.removeAttribute(StyleConstants.FontFamily);
-
- if (colorTable != null) {
- stateItem = (Integer)parserState.get("cf");
- if (stateItem != null) {
- Color fg = colorTable[stateItem.intValue()];
- StyleConstants.setForeground(attributes, fg);
- } else {
- /* AttributeSet dies if you set a value to null */
- attributes.removeAttribute(StyleConstants.Foreground);
- }
- }
-
- if (colorTable != null) {
- stateItem = (Integer)parserState.get("cb");
- if (stateItem != null) {
- Color bg = colorTable[stateItem.intValue()];
- attributes.addAttribute(StyleConstants.Background,
- bg);
- } else {
- /* AttributeSet dies if you set a value to null */
- attributes.removeAttribute(StyleConstants.Background);
- }
- }
-
- Style characterStyle = (Style)parserState.get("characterStyle");
- if (characterStyle != null)
- attributes.setResolveParent(characterStyle);
-
- /* Other attributes are maintained directly in "attributes" */
-
- return attributes;
- }
-
- /**
- * Calculates the current paragraph attributes (with keys
- * as given in StyleConstants) from the current parser state.
- *
- * @returns a newly created MutableAttributeSet.
- * @see StyleConstants
- */
- MutableAttributeSet currentParagraphAttributes()
- {
- /* NB if there were a mutableCopy() method we should use it */
- MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
-
- Integer stateItem;
-
- /*** Tab stops ***/
- TabStop tabs[];
-
- tabs = (TabStop[])parserState.get("_tabs_immutable");
- if (tabs == null) {
- Dictionary workingTabs = (Dictionary)parserState.get("_tabs");
- if (workingTabs != null) {
- int count = ((Integer)workingTabs.get("stop count")).intValue();
- tabs = new TabStop[count];
- for (int ix = 1; ix <= count; ix ++)
- tabs[ix-1] = (TabStop)workingTabs.get(new Integer(ix));
- parserState.put("_tabs_immutable", tabs);
- }
- }
- if (tabs != null)
- bld.addAttribute(Constants.Tabs, tabs);
-
- Style paragraphStyle = (Style)parserState.get("paragraphStyle");
- if (paragraphStyle != null)
- bld.setResolveParent(paragraphStyle);
-
- return bld;
- }
-
- /**
- * Calculates the current section attributes
- * from the current parser state.
- *
- * @returns a newly created MutableAttributeSet.
- */
- public AttributeSet currentSectionAttributes()
- {
- MutableAttributeSet attributes = new SimpleAttributeSet(sectionAttributes);
-
- Style sectionStyle = (Style)parserState.get("sectionStyle");
- if (sectionStyle != null)
- attributes.setResolveParent(sectionStyle);
-
- return attributes;
- }
-
- /** Resets the filter's internal notion of the current character
- * attributes to their default values. Invoked to handle the
- * \plain keyword. */
- protected void resetCharacterAttributes()
- {
- handleKeyword("f", 0);
- handleKeyword("cf", 0);
-
- handleKeyword("fs", 24); /* 12 pt. */
-
- Enumeration attributes = straightforwardAttributes.elements();
- while(attributes.hasMoreElements()) {
- RTFAttribute attr = (RTFAttribute)attributes.nextElement();
- if (attr.domain() == RTFAttribute.D_CHARACTER)
- attr.setDefault(characterAttributes);
- }
-
- handleKeyword("sl", 1000);
-
- parserState.remove("characterStyle");
- }
-
- /** Resets the filter's internal notion of the current paragraph's
- * attributes to their default values. Invoked to handle the
- * \pard keyword. */
- protected void resetParagraphAttributes()
- {
- parserState.remove("_tabs");
- parserState.remove("_tabs_immutable");
- parserState.remove("paragraphStyle");
-
- StyleConstants.setAlignment(paragraphAttributes,
- StyleConstants.ALIGN_LEFT);
-
- Enumeration attributes = straightforwardAttributes.elements();
- while(attributes.hasMoreElements()) {
- RTFAttribute attr = (RTFAttribute)attributes.nextElement();
- if (attr.domain() == RTFAttribute.D_PARAGRAPH)
- attr.setDefault(characterAttributes);
- }
- }
-
- /** Resets the filter's internal notion of the current section's
- * attributes to their default values. Invoked to handle the
- * \sectd keyword. */
- protected void resetSectionAttributes()
- {
- Enumeration attributes = straightforwardAttributes.elements();
- while(attributes.hasMoreElements()) {
- RTFAttribute attr = (RTFAttribute)attributes.nextElement();
- if (attr.domain() == RTFAttribute.D_SECTION)
- attr.setDefault(characterAttributes);
- }
-
- parserState.remove("sectionStyle");
- }
- }
-
- /** RTFReader.TextHandlingDestination provides basic text handling
- * functionality. Subclasses must implement: <dl>
- * <dt>deliverText()<dd>to handle a run of text with the same
- * attributes
- * <dt>finishParagraph()<dd>to end the current paragraph and
- * set the paragraph's attributes
- * <dt>endSection()<dd>to end the current section
- * </dl>
- */
- abstract class TextHandlingDestination
- extends AttributeTrackingDestination
- implements Destination
- {
- /** <code>true</code> if the reader has not just finished
- * a paragraph; false upon startup */
- boolean inParagraph;
-
- public TextHandlingDestination()
- {
- super();
- inParagraph = false;
- }
-
- public void handleText(String text)
- {
- if (! inParagraph)
- beginParagraph();
-
- deliverText(text, currentTextAttributes());
- }
-
- abstract void deliverText(String text, AttributeSet characterAttributes);
-
- public void close()
- {
- if (inParagraph)
- endParagraph();
-
- super.close();
- }
-
- public boolean handleKeyword(String keyword)
- {
- if (keyword.equals("\r") || keyword.equals("\n")) {
- keyword = "par";
- }
-
- if (keyword.equals("par")) {
- // warnings.println("Ending paragraph.");
- endParagraph();
- return true;
- }
-
- if (keyword.equals("sect")) {
- // warnings.println("Ending section.");
- endSection();
- return true;
- }
-
- return super.handleKeyword(keyword);
- }
-
- protected void beginParagraph()
- {
- inParagraph = true;
- }
-
- protected void endParagraph()
- {
- AttributeSet pgfAttributes = currentParagraphAttributes();
- AttributeSet chrAttributes = currentTextAttributes();
- finishParagraph(pgfAttributes, chrAttributes);
- inParagraph = false;
- }
-
- abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
-
- abstract void endSection();
- }
-
- /** RTFReader.DocumentDestination is a concrete subclass of
- * TextHandlingDestination which appends the text to the
- * StyledDocument given by the <code>target</code> ivar of the
- * containing RTFReader.
- */
- class DocumentDestination
- extends TextHandlingDestination
- implements Destination
- {
- public void deliverText(String text, AttributeSet characterAttributes)
- {
- try {
- target.insertString(target.getLength(),
- text,
- currentTextAttributes());
- } catch (BadLocationException ble) {
- /* This shouldn't be able to happen, of course */
- /* TODO is InternalError the correct error to throw? */
- throw new InternalError(ble.getMessage());
- }
- }
-
- public void finishParagraph(AttributeSet pgfAttributes,
- AttributeSet chrAttributes)
- {
- int pgfEndPosition = target.getLength();
- try {
- target.insertString(pgfEndPosition, "\n", chrAttributes);
- target.setParagraphAttributes(pgfEndPosition, 1, pgfAttributes, true);
- } catch (BadLocationException ble) {
- /* This shouldn't be able to happen, of course */
- /* TODO is InternalError the correct error to throw? */
- throw new InternalError(ble.getMessage());
- }
- }
-
- public void endSection()
- {
- /* If we implemented sections, we'd end 'em here */
- }
- }
-
- }
-
-