Refactored the code: put everything in the RTF namespace, separated group

functions from the former RTFElement (now RTF::Element).
Put the parsing basics into a separate class RTF::Parser (used to be in
the static RTFHeader::Parse()).
RTF::Header is now always correctly set to RTF::TEXT_DESTINATION.
Some minor other related changes.


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@10563 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler 2005-01-03 02:03:34 +00:00
parent 40d9768b92
commit a7e0bca0f7
2 changed files with 493 additions and 412 deletions

View File

@ -14,191 +14,15 @@
#include <ctype.h>
static void
dump(RTFElement &element, int32 level = 0)
{
printf("%03ld:", level);
for (int32 i = 0; i < level; i++)
printf(" ");
if (RTFHeader *header = dynamic_cast<RTFHeader *>(&element)) {
printf("<RTF header, major version %ld>\n", header->Version());
} else if (RTFCommand *command = dynamic_cast<RTFCommand *>(&element)) {
printf("<Command: %s", command->Name());
if (command->HasOption())
printf(", Option %ld", command->Option());
puts(">");
} else if (RTFText *text = dynamic_cast<RTFText *>(&element)) {
printf("<Text>");
puts(text->Text());
} else
puts("<Group>");
for (uint32 i = 0; i < element.CountElements(); i++)
dump(*element.ElementAt(i), level + 1);
}
static char read_char(BDataIO &stream, bool endOfFileAllowed = false) throw (status_t);
static int32 parse_integer(char first, BDataIO &stream, char &_last) throw (status_t);
// #pragma mark -
using namespace RTF;
RTFElement::RTFElement()
:
fParent(NULL),
fDestination(RTF_OTHER)
{
}
RTFElement::~RTFElement()
{
RTFElement *element;
while ((element = (RTFElement *)fElements.RemoveItem(0L)) != NULL) {
delete element;
}
}
void
RTFElement::Parse(char first, BDataIO &stream, char &last) throw (status_t)
{
if (first == '\0')
first = ReadChar(stream);
if (first != '{')
throw (status_t)B_BAD_TYPE;
last = ReadChar(stream);
}
status_t
RTFElement::AddElement(RTFElement *element)
{
if (element == NULL)
return B_BAD_VALUE;
if (fElements.AddItem(element)) {
element->fParent = this;
return B_OK;
}
return B_NO_MEMORY;
}
uint32
RTFElement::CountElements() const
{
return (uint32)fElements.CountItems();
}
RTFElement *
RTFElement::ElementAt(uint32 index) const
{
return static_cast<RTFElement *>(fElements.ItemAt(index));
}
RTFCommand *
RTFElement::FindDefinition(const char *name, int32 index) const
{
if (index < 0)
return NULL;
RTFElement *element;
int32 number = 0;
for (uint32 i = 0; (element = ElementAt(i)) != NULL; i++) {
if (RTFText *text = dynamic_cast<RTFText *>(element)) {
// the ';' indicates the next definition
if (!strcmp(text->Text(), ";"))
number++;
} else if (RTFCommand *command = dynamic_cast<RTFCommand *>(element)) {
if (command != NULL
&& !strcmp(name, command->Name())
&& number == index)
return command;
}
}
return NULL;
}
RTFElement *
RTFElement::FindGroup(const char *name) const
{
RTFElement *group;
for (uint32 i = 0; (group = ElementAt(i)) != NULL; i++) {
RTFCommand *command = dynamic_cast<RTFCommand *>(group->ElementAt(0));
if (command != NULL && !strcmp(name, command->Name()))
return group;
}
return NULL;
}
const char *
RTFElement::GroupName() const
{
RTFCommand *command = dynamic_cast<RTFCommand *>(ElementAt(0));
if (command != NULL)
return command->Name();
return NULL;
}
RTFElement *
RTFElement::Parent() const
{
return fParent;
}
void
RTFElement::PrintToStream()
{
dump(*this, 0);
}
void
RTFElement::DetermineDestination()
{
const char *name = GroupName();
if (name == NULL)
fDestination = RTF_TEXT;
if (!strcmp(name, "*")) {
fDestination = RTF_COMMENT;
return;
}
const char *texts[] = {"rtf", "sect", "par"};
for (uint32 i = 0; i < sizeof(texts) / sizeof(texts[0]); i++) {
if (!strcmp(texts[i], name)) {
fDestination = RTF_TEXT;
return;
}
}
fDestination = RTF_OTHER;
}
rtf_destination
RTFElement::Destination() const
{
return fDestination;
}
/* static */
char
RTFElement::ReadChar(BDataIO &stream, bool endOfFileAllowed) throw (status_t)
static char
read_char(BDataIO &stream, bool endOfFileAllowed) throw (status_t)
{
char c;
ssize_t bytesRead = stream.Read(&c, 1);
@ -213,9 +37,8 @@ RTFElement::ReadChar(BDataIO &stream, bool endOfFileAllowed) throw (status_t)
}
/* static */
int32
RTFElement::ParseInteger(char first, BDataIO &stream, char &_last) throw (status_t)
static int32
parse_integer(char first, BDataIO &stream, char &_last) throw (status_t)
{
int32 integer = 0;
int32 count = 0;
@ -223,7 +46,7 @@ RTFElement::ParseInteger(char first, BDataIO &stream, char &_last) throw (status
char digit = first;
if (digit == '\0')
digit = ReadChar(stream);
digit = read_char(stream);
while (true) {
if (isdigit(digit)) {
@ -234,7 +57,7 @@ RTFElement::ParseInteger(char first, BDataIO &stream, char &_last) throw (status
goto out;
}
digit = ReadChar(stream);
digit = read_char(stream);
}
out:
@ -245,141 +68,122 @@ out:
}
static void
dump(Element &element, int32 level = 0)
{
printf("%03ld:", level);
for (int32 i = 0; i < level; i++)
printf(" ");
if (RTF::Header *header = dynamic_cast<RTF::Header *>(&element)) {
printf("<RTF header, major version %ld>\n", header->Version());
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(&element)) {
printf("<Command: %s", command->Name());
if (command->HasOption())
printf(", Option %ld", command->Option());
puts(">");
} else if (RTF::Text *text = dynamic_cast<RTF::Text *>(&element)) {
printf("<Text>");
puts(text->String());
} else if (dynamic_cast<RTF::Group *>(&element) != NULL)
puts("<Group>");
if (RTF::Group *group = dynamic_cast<RTF::Group *>(&element)) {
for (uint32 i = 0; i < group->CountElements(); i++)
dump(*group->ElementAt(i), level + 1);
}
}
// #pragma mark -
RTFHeader::RTFHeader()
Parser::Parser(BDataIO &stream)
:
fVersion(0)
fStream(stream),
fIdentified(false)
{
}
RTFHeader::~RTFHeader()
{
}
void
RTFHeader::Parse(char first, BDataIO &stream, char &last) throw (status_t)
{
int32 openBrackets = 1;
// The stream has been picked up by the static RTFHeader::Parse(), so
// the version follows in the stream -- let's pick it up
fVersion = ParseInteger(first, stream, last);
RTFElement *parent = this;
char c = last;
while (true) {
RTFElement *element = NULL;
switch (c) {
case '{':
openBrackets++;
parent->AddElement(element = new RTFElement());
parent = element;
break;
case '\\':
parent->AddElement(element = new RTFCommand());
break;
case '}':
openBrackets--;
parent->DetermineDestination();
parent = parent->Parent();
case '\n':
case '\r':
{
ssize_t bytesRead = stream.Read(&c, 1);
if (bytesRead < B_OK)
throw (status_t)bytesRead;
else if (bytesRead != 1) {
// this is the only valid exit status
if (openBrackets == 0)
return;
throw B_ERROR;
}
continue;
}
default:
parent->AddElement(element = new RTFText());
break;
}
if (element == NULL)
throw (status_t)B_ERROR;
element->Parse(c, stream, last);
c = last;
}
}
int32
RTFHeader::Version() const
{
return fVersion;
}
const char *
RTFHeader::Charset() const
{
RTFCommand *command = dynamic_cast<RTFCommand *>(ElementAt(0));
if (command == NULL)
return NULL;
return command->Name();
}
rgb_color
RTFHeader::Color(int32 index)
{
rgb_color color = {0, 0, 0, 255};
RTFElement *colorTable = FindGroup("colortbl");
if (colorTable != NULL) {
if (RTFCommand *gun = colorTable->FindDefinition("red", index))
color.red = gun->Option();
if (RTFCommand *gun = colorTable->FindDefinition("green", index))
color.green = gun->Option();
if (RTFCommand *gun = colorTable->FindDefinition("blue", index))
color.blue = gun->Option();
}
return color;
}
status_t
RTFHeader::Identify(BDataIO &stream)
Parser::Identify()
{
char header[5];
if (stream.Read(header, sizeof(header)) < (ssize_t)sizeof(header))
if (fStream.Read(header, sizeof(header)) < (ssize_t)sizeof(header))
return B_IO_ERROR;
return strncmp(header, "{\\rtf", 5) ? B_BAD_TYPE : B_OK;
if (strncmp(header, "{\\rtf", 5))
return B_BAD_TYPE;
fIdentified = true;
return B_OK;
}
status_t
RTFHeader::Parse(BDataIO &stream, RTFHeader &header, bool identified)
Parser::Parse(Header &header)
{
if (!identified && Identify(stream) != B_OK)
if (!fIdentified && Identify() != B_OK)
return B_BAD_TYPE;
try {
int32 openBrackets = 1;
// since we already preparsed parts of the RTF header, the header
// is handled here directly
char last;
header.Parse('\0', stream, last);
header.Parse('\0', fStream, last);
Group *parent = &header;
char c = last;
while (true) {
Element *element = NULL;
switch (c) {
case '{':
openBrackets++;
parent->AddElement(element = new Group());
parent = static_cast<Group *>(element);
break;
case '\\':
parent->AddElement(element = new Command());
break;
case '}':
openBrackets--;
parent->DetermineDestination();
parent = parent->Parent();
// supposed to fall through
case '\n':
case '\r':
{
ssize_t bytesRead = fStream.Read(&c, 1);
if (bytesRead < B_OK)
throw (status_t)bytesRead;
else if (bytesRead != 1) {
// this is the only valid exit status
if (openBrackets == 0)
return B_OK;
throw B_ERROR;
}
continue;
}
default:
parent->AddElement(element = new Text());
break;
}
if (element == NULL)
throw (status_t)B_ERROR;
element->Parse(c, fStream, last);
c = last;
}
} catch (status_t status) {
return status;
}
@ -391,23 +195,278 @@ RTFHeader::Parse(BDataIO &stream, RTFHeader &header, bool identified)
// #pragma mark -
RTFText::RTFText()
Element::Element()
:
fParent(NULL)
{
}
RTFText::~RTFText()
Element::~Element()
{
SetText(NULL);
}
void
RTFText::Parse(char first, BDataIO &stream, char &last) throw (status_t)
Element::SetParent(Group *parent)
{
fParent = parent;
}
Group *
Element::Parent() const
{
return fParent;
}
void
Element::PrintToStream(int32 level)
{
dump(*this, level);
}
// #pragma mark -
Group::Group()
:
fDestination(OTHER_DESTINATION)
{
}
Group::~Group()
{
Element *element;
while ((element = (Element *)fElements.RemoveItem(0L)) != NULL) {
delete element;
}
}
void
Group::Parse(char first, BDataIO &stream, char &last) throw (status_t)
{
if (first == '\0')
first = read_char(stream);
if (first != '{')
throw (status_t)B_BAD_TYPE;
last = read_char(stream);
}
status_t
Group::AddElement(Element *element)
{
if (element == NULL)
return B_BAD_VALUE;
if (fElements.AddItem(element)) {
element->SetParent(this);
return B_OK;
}
return B_NO_MEMORY;
}
uint32
Group::CountElements() const
{
return (uint32)fElements.CountItems();
}
Element *
Group::ElementAt(uint32 index) const
{
return static_cast<Element *>(fElements.ItemAt(index));
}
Command *
Group::FindDefinition(const char *name, int32 index) const
{
if (index < 0)
return NULL;
Element *element;
int32 number = 0;
for (uint32 i = 0; (element = ElementAt(i)) != NULL; i++) {
if (Text *text = dynamic_cast<Text *>(element)) {
// the ';' indicates the next definition
if (!strcmp(text->String(), ";"))
number++;
} else if (Command *command = dynamic_cast<Command *>(element)) {
if (command != NULL
&& !strcmp(name, command->Name())
&& number == index)
return command;
}
}
return NULL;
}
Group *
Group::FindGroup(const char *name) const
{
Element *element;
for (uint32 i = 0; (element = ElementAt(i)) != NULL; i++) {
Group *group = dynamic_cast<Group *>(element);
if (group == NULL)
continue;
Command *command = dynamic_cast<Command *>(group->ElementAt(0));
if (command != NULL && !strcmp(name, command->Name()))
return group;
}
return NULL;
}
const char *
Group::Name() const
{
Command *command = dynamic_cast<Command *>(ElementAt(0));
if (command != NULL)
return command->Name();
return NULL;
}
void
Group::DetermineDestination()
{
const char *name = Name();
if (name == NULL) {
fDestination = TEXT_DESTINATION;
return;
}
if (!strcmp(name, "*")) {
fDestination = COMMENT_DESTINATION;
return;
}
const char *texts[] = {"rtf", "sect", "par"};
for (uint32 i = 0; i < sizeof(texts) / sizeof(texts[0]); i++) {
if (!strcmp(name, texts[i])) {
fDestination = TEXT_DESTINATION;
return;
}
}
fDestination = OTHER_DESTINATION;
}
group_destination
Group::Destination() const
{
return fDestination;
}
// #pragma mark -
Header::Header()
:
fVersion(0)
{
}
Header::~Header()
{
}
void
Header::Parse(char first, BDataIO &stream, char &last) throw (status_t)
{
// The stream has been peeked into by the parser already, and
// only the version follows in the stream -- let's pick it up
fVersion = parse_integer(first, stream, last);
// recreate "rtf" command to name this group
Command *command = new Command();
command->SetName("rtf");
command->SetOption(fVersion);
AddElement(command);
}
int32
Header::Version() const
{
return fVersion;
}
const char *
Header::Charset() const
{
Command *command = dynamic_cast<Command *>(ElementAt(1));
if (command == NULL)
return NULL;
return command->Name();
}
rgb_color
Header::Color(int32 index)
{
rgb_color color = {0, 0, 0, 255};
Group *colorTable = FindGroup("colortbl");
if (colorTable != NULL) {
if (Command *gun = colorTable->FindDefinition("red", index))
color.red = gun->Option();
if (Command *gun = colorTable->FindDefinition("green", index))
color.green = gun->Option();
if (Command *gun = colorTable->FindDefinition("blue", index))
color.blue = gun->Option();
}
return color;
}
// #pragma mark -
Text::Text()
{
}
Text::~Text()
{
SetTo(NULL);
}
void
Text::Parse(char first, BDataIO &stream, char &last) throw (status_t)
{
char c = first;
if (c == '\0')
c = ReadChar(stream);
c = read_char(stream);
fText = "";
@ -417,7 +476,7 @@ RTFText::Parse(char first, BDataIO &stream, char &last) throw (status_t)
// ToDo: this is horribly inefficient with BStrings
fText.Append(c, 1);
c = ReadChar(stream);
c = read_char(stream);
}
// ToDo: add support for different charsets - right now, only ASCII is supported!
@ -428,21 +487,21 @@ RTFText::Parse(char first, BDataIO &stream, char &last) throw (status_t)
status_t
RTFText::SetText(const char *text)
Text::SetTo(const char *text)
{
return fText.SetTo(text) != NULL ? B_OK : B_NO_MEMORY;
}
const char *
RTFText::Text() const
Text::String() const
{
return fText.String();
}
uint32
RTFText::TextLength() const
Text::Length() const
{
return fText.Length();
}
@ -451,7 +510,7 @@ RTFText::TextLength() const
// #pragma mark -
RTFCommand::RTFCommand()
Command::Command()
:
fName(NULL),
fHasOption(false),
@ -460,76 +519,74 @@ RTFCommand::RTFCommand()
}
RTFCommand::~RTFCommand()
Command::~Command()
{
}
void
RTFCommand::Parse(char first, BDataIO &stream, char &last) throw (status_t)
Command::Parse(char first, BDataIO &stream, char &last) throw (status_t)
{
if (first == '\0')
first = ReadChar(stream);
first = read_char(stream);
if (first != '\\')
throw B_BAD_TYPE;
// get name
char name[kRTFCommandLength];
char name[kCommandLength];
size_t length = 0;
char c;
while (isalpha(c = ReadChar(stream))) {
while (isalpha(c = read_char(stream))) {
name[length++] = c;
if (length >= kRTFCommandLength - 1)
if (length >= kCommandLength - 1)
throw B_BAD_TYPE;
}
if (length == 0) {
if (c == '*') {
// we're a comment!
name[0] = 'c';
length++;
} else if (c == '\n') {
if (c == '\n' || c == '\r') {
// we're a hard return
name[0] = '\n';
length++;
}
}
fName.SetTo("par");
} else
fName.SetTo(c, 1);
fName.SetTo(name, length);
// read over character
c = read_char(stream);
} else
fName.SetTo(name, length);
// parse numeric option
if (c == '-')
c = ReadChar(stream);
c = read_char(stream);
last = c;
if (isdigit(c))
SetOption(ParseInteger(c, stream, last));
SetOption(parse_integer(c, stream, last));
// a space delimiter is eaten up by the command
if (isspace(last))
last = ReadChar(stream);
last = read_char(stream);
}
status_t
RTFCommand::SetName(const char *name)
Command::SetName(const char *name)
{
return fName.SetTo(name) != NULL ? B_OK : B_NO_MEMORY;
}
const char *
RTFCommand::Name()
Command::Name()
{
return fName.String();
}
void
RTFCommand::UnsetOption()
Command::UnsetOption()
{
fHasOption = false;
fOption = -1;
@ -537,7 +594,7 @@ RTFCommand::UnsetOption()
void
RTFCommand::SetOption(int32 option)
Command::SetOption(int32 option)
{
fOption = option;
fHasOption = true;
@ -545,14 +602,14 @@ RTFCommand::SetOption(int32 option)
bool
RTFCommand::HasOption() const
Command::HasOption() const
{
return fHasOption;
}
int32
RTFCommand::Option() const
Command::Option() const
{
return fOption;
}
@ -561,14 +618,14 @@ RTFCommand::Option() const
// #pragma mark -
RTFIterator::RTFIterator(RTFElement &start, rtf_destination destination)
Iterator::Iterator(Element &start, group_destination destination)
{
SetTo(start, destination);
}
void
RTFIterator::SetTo(RTFElement &start, rtf_destination destination)
Iterator::SetTo(Element &start, group_destination destination)
{
fStart = &start;
fDestination = destination;
@ -578,7 +635,7 @@ RTFIterator::SetTo(RTFElement &start, rtf_destination destination)
void
RTFIterator::Rewind()
Iterator::Rewind()
{
fStack.MakeEmpty();
fStack.Push(fStart);
@ -586,31 +643,31 @@ RTFIterator::Rewind()
bool
RTFIterator::HasNext() const
Iterator::HasNext() const
{
return !fStack.IsEmpty();
}
RTFElement *
RTFIterator::Next()
Element *
Iterator::Next()
{
RTFElement *element;
Element *element;
if (!fStack.Pop(&element))
return NULL;
// put this element's children on the stack in
// reverse order, so that we iterate over the
// tree in in-order
Group *group = dynamic_cast<Group *>(element);
if (group != NULL
&& (fDestination == ALL_DESTINATIONS
|| fDestination == group->Destination())) {
// put this group's children on the stack in
// reverse order, so that we iterate over
// the tree in in-order
for (int32 i = element->CountElements(); i-- > 0;) {
RTFElement *child = element->ElementAt(i);
if (fDestination == RTF_ALL_DESTINATIONS
|| child->CountElements() == 0
|| fDestination == element->Destination())
fStack.Push(child);
for (int32 i = group->CountElements(); i-- > 0;) {
fStack.Push(group->ElementAt(i));
}
}
return element;

View File

@ -13,90 +13,113 @@
#include <GraphicsDefs.h>
class BDataIO;
class RTFCommand;
namespace RTF {
static const size_t kRTFCommandLength = 32;
class Group;
class Header;
class Command;
static const size_t kCommandLength = 32;
enum rtf_destination {
RTF_TEXT,
RTF_COMMENT,
RTF_OTHER,
RTF_ALL_DESTINATIONS = 255
enum group_destination {
TEXT_DESTINATION,
COMMENT_DESTINATION,
OTHER_DESTINATION,
ALL_DESTINATIONS = 255
};
class RTFElement {
class Parser {
public:
RTFElement();
virtual ~RTFElement();
Parser(BDataIO &stream);
status_t AddElement(RTFElement *element);
uint32 CountElements() const;
RTFElement *ElementAt(uint32 index) const;
RTFCommand *FindDefinition(const char *name, int32 index = 0) const;
RTFElement *FindGroup(const char *name) const;
const char *GroupName() const;
RTFElement *Parent() const;
virtual void Parse(char first, BDataIO &stream, char &last) throw (status_t);
virtual void PrintToStream();
void DetermineDestination();
rtf_destination Destination() const;
protected:
static char ReadChar(BDataIO &stream, bool endOfFileAllowed = false) throw (status_t);
static int32 ParseInteger(char first, BDataIO &stream, char &last) throw (status_t);
status_t Identify();
status_t Parse(RTF::Header &header);
private:
RTFElement *fParent;
BList fElements;
rtf_destination fDestination;
BDataIO &fStream;
bool fIdentified;
};
class RTFHeader : public RTFElement {
class Element {
public:
RTFHeader();
virtual ~RTFHeader();
Element();
virtual ~Element();
void SetParent(Group *parent);
Group *Parent() const;
virtual void Parse(char first, BDataIO &stream, char &last) throw (status_t) = 0;
virtual void PrintToStream(int32 level = 0);
private:
Group *fParent;
};
class Group : public Element {
public:
Group();
virtual ~Group();
status_t AddElement(RTF::Element *element);
uint32 CountElements() const;
Element *ElementAt(uint32 index) const;
Command *FindDefinition(const char *name, int32 index = 0) const;
Group *FindGroup(const char *name) const;
const char *Name() const;
void DetermineDestination();
group_destination Destination() const;
virtual void Parse(char first, BDataIO &stream, char &last) throw (status_t);
protected:
BList fElements;
group_destination fDestination;
};
class Header : public Group {
public:
Header();
virtual ~Header();
int32 Version() const;
const char *Charset() const;
rgb_color Color(int32 index);
static status_t Identify(BDataIO &stream);
static status_t Parse(BDataIO &stream, RTFHeader &header, bool identified = false);
protected:
virtual void Parse(char first, BDataIO &stream, char &last) throw (status_t);
private:
int32 fVersion;
int32 fVersion;
};
class RTFText : public RTFElement {
class Text : public Element {
public:
RTFText();
virtual ~RTFText();
Text();
virtual ~Text();
status_t SetText(const char *text);
const char *Text() const;
uint32 TextLength() const;
status_t SetTo(const char *text);
const char *String() const;
uint32 Length() const;
protected:
virtual void Parse(char first, BDataIO &stream, char &last) throw (status_t);
private:
BString fText;
BString fText;
};
class RTFCommand : public RTFElement {
class Command : public Element {
public:
RTFCommand();
virtual ~RTFCommand();
Command();
virtual ~Command();
status_t SetName(const char *name);
const char *Name();
@ -106,31 +129,32 @@ class RTFCommand : public RTFElement {
bool HasOption() const;
int32 Option() const;
protected:
virtual void Parse(char first, BDataIO &stream, char &last) throw (status_t);
private:
BString fName;
bool fHasOption;
int32 fOption;
BString fName;
bool fHasOption;
int32 fOption;
};
//---------------------------------
class RTFIterator {
class Iterator {
public:
RTFIterator(RTFElement &start, rtf_destination destination = RTF_ALL_DESTINATIONS);
Iterator(Element &start, group_destination destination = ALL_DESTINATIONS);
void SetTo(RTFElement &start, rtf_destination destination = RTF_ALL_DESTINATIONS);
void SetTo(Element &start, group_destination destination = ALL_DESTINATIONS);
void Rewind();
bool HasNext() const;
RTFElement *Next();
Element *Next();
private:
RTFElement *fStart;
Stack<RTFElement *> fStack;
rtf_destination fDestination;
Element *fStart;
Stack<Element *> fStack;
group_destination fDestination;
};
} // namespace RTF
#endif /* RTF_H */