/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Takes a sequence of RTF tokens and text and appends the text
* described by the RTF to a <code>StyledDocument</code> (the <em>target</em>).
* The RTF is lexed
* from the character stream by the <code>RTFParser</code> 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
*/
{
/** The object to which the parsed text is sent. */
/** Miscellaneous information about the parser's state. This
* dictionary is saved and restored when an RTF group begins
* or ends. */
/** This is the "dst" item from parserState. rtfDestination
* is the current rtf destination. It is cached in an instance
* variable for speed. */
/** This holds the current document attributes. */
/** This Dictionary maps Integer font numbers to String font names. */
/** This array maps color indices to Color objects. */
/** This array maps character style numbers to Style objects. */
/** This array maps paragraph style numbers to Style objects. */
/** This array maps section style numbers to Style objects. */
/** 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 {
}
/* 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 {
/* There is no Unicode equivalent to an optional hyphen, as far as
I can tell. */
}
/* some entries in parserState */
static boolean useNeXTForAnsi = false;
static {
}
/* 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.
*/
{
int i;
rtfversion = -1;
mockery = new MockAttributeSet();
documentAttributes = new SimpleAttributeSet();
}
/** Called when the RTFParser encounters a bin keyword in the
* RTF stream.
*
* @see RTFParser
*/
{
if (skippingCharacters > 0) {
/* a blob only counts as one character for skipping purposes */
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. */
{
if (skippingCharacters > 0) {
return;
} else {
skippingCharacters = 0;
}
}
if (rtfDestination != null) {
return;
}
warning("Text with no destination. oops.");
}
/** The default color for text which has no specified color. */
{
}
/** Called by the superclass when a new RTF group is begun.
* This implementation saves the current <code>parserState</code>, and gives
* the current destination a chance 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. */
if (oldSaveState != null)
Dictionary<String, Object> saveState = (Dictionary<String, Object>)((Hashtable)parserState).clone();
if (oldSaveState != null)
if (rtfDestination != null)
}
/** 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<Object, Object> restoredState = (Dictionary<Object, Object>)parserState.get("_savedState");
if (restoredDestination != rtfDestination) {
}
if (rtfDestination != null)
}
{
/* Check that setting the destination won't close the
current destination (should never happen) */
if (previousState != null) {
warning("Warning, RTF destination overridden, invalid RTF.");
}
}
}
/** 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
{
while(docProps.hasMoreElements()) {
}
/* 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
*/
{
if (skippingCharacters > 0) {
return true;
}
ignoreGroupIfUnknownKeyword = false;
return true;
}
setRTFDestination(new FonttblDestination());
return true;
}
setRTFDestination(new ColortblDestination());
return true;
}
return true;
}
setRTFDestination(new InfoDestination());
return false;
}
setCharacterSet("mac");
return true;
}
if (useNeXTForAnsi)
setCharacterSet("NeXT");
else
setCharacterSet("ansi");
return true;
}
setCharacterSet("NeXT");
return true;
}
return true;
}
return true;
}
ignoreGroupIfUnknownKeyword = true;
return true;
}
if (rtfDestination != null) {
return true;
}
/* this point is reached only if the keyword is unrecognized */
/* other destinations we don't understand and therefore ignore */
ignoreGroupIfUnknownKeywordSave = true;
}
}
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
*/
{
if (skippingCharacters > 0) {
return true;
}
ignoreGroupIfUnknownKeyword = false;
/* count of characters to skip after a unicode character */
return true;
}
if (parameter < 0)
handleText((char)parameter);
} else {
skippingCharacters = 1;
}
return true;
}
setRTFDestination(new DocumentDestination());
return true;
}
ignoreGroupIfUnknownKeywordSave = true;
if (rtfDestination != null) {
return true;
}
/* this point is reached only if the keyword is unrecognized */
}
return false;
}
{
// 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
*/
{
try {
} catch (Exception e) {
}
translationTable = (char[])set;
} else {
try {
} catch (IOException e) {
}
}
}
}
/** Adds a character set to the RTFReader's list
* of known character sets */
public static void
{
throw new IllegalArgumentException("Translation table must have 256 entries.");
}
/** 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
throws IOException
{
public InputStream run() {
return RTFReader.class.getResourceAsStream
}
});
}
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
*/
throws IOException
{
char[] values = new char[256];
int i;
in.eolIsSignificant(false);
in.slashSlashComments(true);
in.slashStarComments(true);
i = 0;
while (i < 256) {
int ttype;
try {
} catch (Exception e) {
}
// System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
throw new IOException("Unexpected token in character set file");
// continue;
}
i++;
}
return values;
}
throws IOException
{
}
/** 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 begingroup();
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. */
{
{
/* Discard binary blobs. */
}
{
/* Discard text. */
}
{
/* Accept and discard keywords. */
return true;
}
{
/* Accept and discard parameterized keywords. */
return true;
}
public void begingroup()
{
/* Ignore groups --- the RTFReader will keep track of the
current group level as necessary */
}
{
/* Ignore groups */
}
public void close()
{
/* No end-of-destination cleanup needed */
}
}
/** Reads the fonttbl group, inserting fonts into the RTFReader's
* fontTable dictionary. */
{
int nextFontNumber;
{ /* Discard binary blobs. */ }
{
if (semicolon > -1)
else
/* TODO: do something with the font family. */
if (nextFontNumber == -1
&& fontNumberKey != null) {
//font name might be broken across multiple calls
} else {
}
nextFontNumber = -1;
}
{
return true;
}
return false;
}
{
return true;
}
return false;
}
/* Groups are irrelevant. */
public void begingroup() {}
/* currently, the only thing we do when the font table ends is
dump its contents to the debugging log. */
public void close()
{
warning("Done reading font table.");
while(nums.hasMoreElements()) {
}
}
}
/** Reads the colortbl group. Upon end-of-group, the RTFReader's
* color table is set to an array containing the read colors. */
{
public ColortblDestination()
{
red = 0;
green = 0;
blue = 0;
}
{
int index;
}
}
}
public void close()
{
}
{
else
return false;
return true;
}
/* Colortbls don't understand any parameterless keywords */
/* Groups are irrelevant. */
public void begingroup() {}
/* Shouldn't see any binary blobs ... */
}
/** Handles the stylesheet keyword. Styles are read and sorted
* into the three style arrays in the RTFReader. */
class StylesheetDestination
extends DiscardingDestination
implements Destination
{
public StylesheetDestination()
{
}
public void begingroup()
{
}
public void close()
{
while(styles.hasMoreElements()) {
} else {
}
}
}
}
}
/* (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
{
boolean additive;
boolean characterStyle;
boolean sectionStyle;
public int number;
int basedOn;
int nextStyle;
boolean hidden;
public StyleDefiningDestination()
{
additive = false;
characterStyle = false;
sectionStyle = false;
number = 0;
hidden = false;
}
{
else
}
public void close() {
if (semicolon > 0)
super.close();
}
{
additive = true;
return true;
}
hidden = true;
return true;
}
return super.handleKeyword(keyword);
}
{
characterStyle = false;
sectionStyle = false;
characterStyle = true;
sectionStyle = false;
characterStyle = false;
sectionStyle = true;
} else {
}
return true;
}
{
if (realizedStyle != null)
return realizedStyle;
if (basedOn != STYLENUMBER_NONE) {
}
}
/* NB: Swing StyleContext doesn't allow distinct styles with
the same name; RTF apparently does. This may confuse the
user. */
if (characterStyle) {
} else if (sectionStyle) {
} else { /* must be a paragraph style */
}
if (nextStyle != STYLENUMBER_NONE) {
}
}
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. */
{
/** This is the "chr" element of parserState, cached for
* more efficient use */
/** This is the "pgf" element of parserState, cached for
* more efficient use */
/** This is the "sec" element of parserState, cached for
* more efficient use */
public AttributeTrackingDestination()
{
}
{
/* 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()
{
/* 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();
paragraphAttributes = new SimpleAttributeSet();
sectionAttributes = new SimpleAttributeSet();
}
{
}
public void close()
{
}
{
}
{
boolean ok;
case RTFAttribute.D_CHARACTER:
break;
case RTFAttribute.D_PARAGRAPH:
break;
case RTFAttribute.D_SECTION:
break;
case RTFAttribute.D_META:
break;
case RTFAttribute.D_DOCUMENT:
break;
default:
/* should never happen */
ok = false;
break;
}
if (ok)
return true;
}
}
return true;
}
return true;
}
return true;
}
return false;
}
{
return true;
}
return true;
}
{
boolean ok;
case RTFAttribute.D_CHARACTER:
break;
case RTFAttribute.D_PARAGRAPH:
break;
case RTFAttribute.D_SECTION:
break;
case RTFAttribute.D_META:
break;
case RTFAttribute.D_DOCUMENT:
break;
default:
/* should never happen */
ok = false;
break;
}
if (ok)
return true;
}
}
return true;
}
/* TODO: superscript/subscript */
} 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. */
parameter / 20f);
}
return true;
}
/* TODO: Other kinds of underlining */
int tabAlignment, tabLeader;
} else {
}
return true;
}
paragraphStyles != null) {
return true;
}
characterStyles != null) {
return true;
}
sectionStyles != null) {
return true;
}
return false;
}
/** Returns a new MutableAttributeSet containing the
* default character attributes */
{
/* TODO: default font */
return set;
}
/** Returns a new MutableAttributeSet containing the
* default paragraph attributes */
{
/* TODO: what should this be, really? */
return set;
}
/** Returns a new MutableAttributeSet containing the
* default section attributes */
{
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.
*/
{
/* 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) */
/* note setFontFamily() can not handle a null font */
else
fontFamily = null;
if (fontFamily != null)
else
if (colorTable != null) {
} else {
/* AttributeSet dies if you set a value to null */
}
}
if (colorTable != null) {
bg);
} else {
/* AttributeSet dies if you set a value to null */
}
}
if (characterStyle != null)
/* 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
*/
{
/* NB if there were a mutableCopy() method we should use it */
/*** Tab stops ***/
if (workingTabs != null) {
}
}
if (paragraphStyle != null)
return bld;
}
/**
* Calculates the current section attributes
* from the current parser state.
*
* @returns a newly created MutableAttributeSet.
*/
{
if (sectionStyle != null)
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()
{
while(attributes.hasMoreElements()) {
}
}
/** 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()
{
while(attributes.hasMoreElements()) {
}
}
/** 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()
{
while(attributes.hasMoreElements()) {
}
}
}
/** 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;
}
{
if (! inParagraph)
}
public void close()
{
if (inParagraph)
endParagraph();
super.close();
}
{
keyword = "par";
}
// warnings.println("Ending paragraph.");
endParagraph();
return true;
}
// warnings.println("Ending section.");
endSection();
return true;
}
return super.handleKeyword(keyword);
}
protected void beginParagraph()
{
inParagraph = true;
}
protected void endParagraph()
{
inParagraph = false;
}
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
{
{
try {
text,
} catch (BadLocationException ble) {
/* This shouldn't be able to happen, of course */
/* TODO is InternalError the correct error to throw? */
}
}
{
try {
} catch (BadLocationException ble) {
/* This shouldn't be able to happen, of course */
/* TODO is InternalError the correct error to throw? */
}
}
public void endSection()
{
/* If we implemented sections, we'd end 'em here */
}
}
}