Files
haiku-beta6/src/add-ons/translators/rtftranslator/RTF.cpp
T

676 lines
10 KiB
C++
Raw Normal View History

2005-01-02 23:28:30 +00:00
/*
* Copyright 2004-2005, Axel Dörfler, [email protected]. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "RTF.h"
#include <DataIO.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
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);
using namespace RTF;
static char
read_char(BDataIO &stream, bool endOfFileAllowed) throw (status_t)
{
char c;
ssize_t bytesRead = stream.Read(&c, 1);
if (bytesRead < B_OK)
throw (status_t)bytesRead;
if (bytesRead == 0 && !endOfFileAllowed)
throw (status_t)B_ERROR;
return c;
}
static int32
parse_integer(char first, BDataIO &stream, char &_last) throw (status_t)
{
int32 integer = 0;
int32 count = 0;
char digit = first;
if (digit == '\0')
digit = read_char(stream);
while (true) {
if (isdigit(digit)) {
integer = integer * 10 + digit - '0';
count++;
} else {
_last = digit;
goto out;
}
digit = read_char(stream);
}
out:
if (count == 0)
throw (status_t)B_BAD_TYPE;
return integer;
}
2005-01-02 23:28:30 +00:00
static void
dump(Element &element, int32 level = 0)
2005-01-02 23:28:30 +00:00
{
printf("%03ld:", level);
for (int32 i = 0; i < level; i++)
printf(" ");
if (RTF::Header *header = dynamic_cast<RTF::Header *>(&element)) {
2005-01-02 23:28:30 +00:00
printf("<RTF header, major version %ld>\n", header->Version());
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(&element)) {
2005-01-02 23:28:30 +00:00
printf("<Command: %s", command->Name());
if (command->HasOption())
printf(", Option %ld", command->Option());
puts(">");
} else if (RTF::Text *text = dynamic_cast<RTF::Text *>(&element)) {
2005-01-02 23:28:30 +00:00
printf("<Text>");
puts(text->String());
} else if (dynamic_cast<RTF::Group *>(&element) != NULL)
2005-01-02 23:28:30 +00:00
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 -
Parser::Parser(BDataIO &stream)
:
fStream(stream),
fIdentified(false)
{
}
status_t
Parser::Identify()
{
char header[5];
if (fStream.Read(header, sizeof(header)) < (ssize_t)sizeof(header))
return B_IO_ERROR;
if (strncmp(header, "{\\rtf", 5))
return B_BAD_TYPE;
fIdentified = true;
return B_OK;
}
status_t
Parser::Parse(Header &header)
{
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', 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;
}
return B_OK;
}
// #pragma mark -
Element::Element()
:
fParent(NULL)
{
}
Element::~Element()
{
}
void
Element::SetParent(Group *parent)
{
fParent = parent;
}
Group *
Element::Parent() const
{
return fParent;
}
void
Element::PrintToStream(int32 level)
{
dump(*this, level);
2005-01-02 23:28:30 +00:00
}
// #pragma mark -
Group::Group()
2005-01-02 23:28:30 +00:00
:
fDestination(OTHER_DESTINATION)
2005-01-02 23:28:30 +00:00
{
}
Group::~Group()
2005-01-02 23:28:30 +00:00
{
Element *element;
while ((element = (Element *)fElements.RemoveItem(0L)) != NULL) {
2005-01-02 23:28:30 +00:00
delete element;
}
}
void
Group::Parse(char first, BDataIO &stream, char &last) throw (status_t)
2005-01-02 23:28:30 +00:00
{
if (first == '\0')
first = read_char(stream);
2005-01-02 23:28:30 +00:00
if (first != '{')
throw (status_t)B_BAD_TYPE;
last = read_char(stream);
2005-01-02 23:28:30 +00:00
}
status_t
Group::AddElement(Element *element)
2005-01-02 23:28:30 +00:00
{
if (element == NULL)
return B_BAD_VALUE;
if (fElements.AddItem(element)) {
element->SetParent(this);
2005-01-02 23:28:30 +00:00
return B_OK;
}
return B_NO_MEMORY;
}
uint32
Group::CountElements() const
2005-01-02 23:28:30 +00:00
{
return (uint32)fElements.CountItems();
}
Element *
Group::ElementAt(uint32 index) const
2005-01-02 23:28:30 +00:00
{
return static_cast<Element *>(fElements.ItemAt(index));
2005-01-02 23:28:30 +00:00
}
Command *
Group::FindDefinition(const char *name, int32 index) const
2005-01-02 23:28:30 +00:00
{
if (index < 0)
return NULL;
Element *element;
2005-01-02 23:28:30 +00:00
int32 number = 0;
for (uint32 i = 0; (element = ElementAt(i)) != NULL; i++) {
if (Text *text = dynamic_cast<Text *>(element)) {
2005-01-02 23:28:30 +00:00
// the ';' indicates the next definition
if (!strcmp(text->String(), ";"))
2005-01-02 23:28:30 +00:00
number++;
} else if (Command *command = dynamic_cast<Command *>(element)) {
2005-01-02 23:28:30 +00:00
if (command != NULL
&& !strcmp(name, command->Name())
&& number == index)
return command;
}
}
return NULL;
}
Group *
Group::FindGroup(const char *name) const
2005-01-02 23:28:30 +00:00
{
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));
2005-01-02 23:28:30 +00:00
if (command != NULL && !strcmp(name, command->Name()))
return group;
}
return NULL;
}
const char *
Group::Name() const
2005-01-02 23:28:30 +00:00
{
Command *command = dynamic_cast<Command *>(ElementAt(0));
2005-01-02 23:28:30 +00:00
if (command != NULL)
return command->Name();
return NULL;
}
void
Group::DetermineDestination()
2005-01-02 23:28:30 +00:00
{
const char *name = Name();
if (name == NULL) {
fDestination = TEXT_DESTINATION;
return;
}
2005-01-02 23:28:30 +00:00
if (!strcmp(name, "*")) {
fDestination = COMMENT_DESTINATION;
2005-01-02 23:28:30 +00:00
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;
2005-01-02 23:28:30 +00:00
return;
}
}
fDestination = OTHER_DESTINATION;
2005-01-02 23:28:30 +00:00
}
group_destination
Group::Destination() const
2005-01-02 23:28:30 +00:00
{
return fDestination;
}
// #pragma mark -
Header::Header()
2005-01-02 23:28:30 +00:00
:
fVersion(0)
{
}
Header::~Header()
2005-01-02 23:28:30 +00:00
{
}
void
Header::Parse(char first, BDataIO &stream, char &last) throw (status_t)
2005-01-02 23:28:30 +00:00
{
// The stream has been peeked into by the parser already, and
// only the version follows in the stream -- let's pick it up
2005-01-02 23:28:30 +00:00
fVersion = parse_integer(first, stream, last);
2005-01-02 23:28:30 +00:00
// recreate "rtf" command to name this group
2005-01-02 23:28:30 +00:00
Command *command = new Command();
command->SetName("rtf");
command->SetOption(fVersion);
2005-01-02 23:28:30 +00:00
AddElement(command);
2005-01-02 23:28:30 +00:00
}
int32
Header::Version() const
2005-01-02 23:28:30 +00:00
{
return fVersion;
}
const char *
Header::Charset() const
2005-01-02 23:28:30 +00:00
{
Command *command = dynamic_cast<Command *>(ElementAt(1));
2005-01-02 23:28:30 +00:00
if (command == NULL)
return NULL;
return command->Name();
}
rgb_color
Header::Color(int32 index)
2005-01-02 23:28:30 +00:00
{
rgb_color color = {0, 0, 0, 255};
Group *colorTable = FindGroup("colortbl");
2005-01-02 23:28:30 +00:00
if (colorTable != NULL) {
if (Command *gun = colorTable->FindDefinition("red", index))
2005-01-02 23:28:30 +00:00
color.red = gun->Option();
if (Command *gun = colorTable->FindDefinition("green", index))
2005-01-02 23:28:30 +00:00
color.green = gun->Option();
if (Command *gun = colorTable->FindDefinition("blue", index))
2005-01-02 23:28:30 +00:00
color.blue = gun->Option();
}
return color;
}
// #pragma mark -
Text::Text()
2005-01-02 23:28:30 +00:00
{
}
Text::~Text()
2005-01-02 23:28:30 +00:00
{
SetTo(NULL);
2005-01-02 23:28:30 +00:00
}
void
Text::Parse(char first, BDataIO &stream, char &last) throw (status_t)
2005-01-02 23:28:30 +00:00
{
char c = first;
if (c == '\0')
c = read_char(stream);
2005-01-02 23:28:30 +00:00
fText = "";
while (true) {
if (c == '\\' || c == '}')
break;
// ToDo: this is horribly inefficient with BStrings
fText.Append(c, 1);
c = read_char(stream);
2005-01-02 23:28:30 +00:00
}
// ToDo: add support for different charsets - right now, only ASCII is supported!
// To achieve this, we should just translate everything into UTF-8 here
last = c;
}
status_t
Text::SetTo(const char *text)
2005-01-02 23:28:30 +00:00
{
return fText.SetTo(text) != NULL ? B_OK : B_NO_MEMORY;
}
const char *
Text::String() const
2005-01-02 23:28:30 +00:00
{
return fText.String();
}
uint32
Text::Length() const
2005-01-02 23:28:30 +00:00
{
return fText.Length();
}
// #pragma mark -
Command::Command()
2005-01-02 23:28:30 +00:00
:
fName(NULL),
fHasOption(false),
fOption(-1)
{
}
Command::~Command()
2005-01-02 23:28:30 +00:00
{
}
void
Command::Parse(char first, BDataIO &stream, char &last) throw (status_t)
2005-01-02 23:28:30 +00:00
{
if (first == '\0')
first = read_char(stream);
2005-01-02 23:28:30 +00:00
if (first != '\\')
throw B_BAD_TYPE;
// get name
char name[kCommandLength];
2005-01-02 23:28:30 +00:00
size_t length = 0;
char c;
while (isalpha(c = read_char(stream))) {
2005-01-02 23:28:30 +00:00
name[length++] = c;
if (length >= kCommandLength - 1)
2005-01-02 23:28:30 +00:00
throw B_BAD_TYPE;
}
if (length == 0) {
if (c == '\n' || c == '\r') {
2005-01-02 23:28:30 +00:00
// we're a hard return
fName.SetTo("par");
} else
fName.SetTo(c, 1);
2005-01-02 23:28:30 +00:00
// read over character
c = read_char(stream);
} else
fName.SetTo(name, length);
2005-01-02 23:28:30 +00:00
// parse numeric option
if (c == '-')
c = read_char(stream);
2005-01-02 23:28:30 +00:00
last = c;
if (isdigit(c))
SetOption(parse_integer(c, stream, last));
2005-01-02 23:28:30 +00:00
// a space delimiter is eaten up by the command
if (isspace(last))
last = read_char(stream);
2005-01-02 23:28:30 +00:00
}
status_t
Command::SetName(const char *name)
2005-01-02 23:28:30 +00:00
{
return fName.SetTo(name) != NULL ? B_OK : B_NO_MEMORY;
}
const char *
Command::Name()
2005-01-02 23:28:30 +00:00
{
return fName.String();
}
void
Command::UnsetOption()
2005-01-02 23:28:30 +00:00
{
fHasOption = false;
fOption = -1;
}
void
Command::SetOption(int32 option)
2005-01-02 23:28:30 +00:00
{
fOption = option;
fHasOption = true;
}
bool
Command::HasOption() const
2005-01-02 23:28:30 +00:00
{
return fHasOption;
}
int32
Command::Option() const
2005-01-02 23:28:30 +00:00
{
return fOption;
}
// #pragma mark -
Iterator::Iterator(Element &start, group_destination destination)
2005-01-02 23:28:30 +00:00
{
SetTo(start, destination);
}
void
Iterator::SetTo(Element &start, group_destination destination)
2005-01-02 23:28:30 +00:00
{
fStart = &start;
fDestination = destination;
Rewind();
}
void
Iterator::Rewind()
2005-01-02 23:28:30 +00:00
{
fStack.MakeEmpty();
fStack.Push(fStart);
}
bool
Iterator::HasNext() const
2005-01-02 23:28:30 +00:00
{
return !fStack.IsEmpty();
}
Element *
Iterator::Next()
2005-01-02 23:28:30 +00:00
{
Element *element;
2005-01-02 23:28:30 +00:00
if (!fStack.Pop(&element))
return NULL;
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
2005-01-02 23:28:30 +00:00
for (int32 i = group->CountElements(); i-- > 0;) {
fStack.Push(group->ElementAt(i));
}
2005-01-02 23:28:30 +00:00
}
return element;
}