This example Java source code file (RTFReader.java) is included in the alvinalexander.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
/*
* Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.
*/
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 <code>StyledDocument (the target).
* The RTF is lexed
* from the character stream by the <code>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<Object, Object> 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<Integer, String> 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 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<String, RTFAttribute> 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<String, String> textKeywords = null;
static {
textKeywords = new Hashtable<String, String>();
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<String, char[]> characterSets;
static boolean useNeXTForAnsi = false;
static {
characterSets = new Hashtable<String, char[]>();
}
/* 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<Object, Object>();
fontTable = new Hashtable<Integer, String>();
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 <code>parserState, 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. */
Object oldSaveState = parserState.get("_savedState");
if (oldSaveState != null)
parserState.remove("_savedState");
Dictionary<String, Object> 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()
* 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