2 * Copyright 2004-2010, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
19 # define TRACE(x...) printf(x)
21 # define TRACE(x...) ;
25 static const char *kDestinationControlWords
[] = {
26 "aftncn", "aftnsep", "aftnsepc", "annotation", "atnauthor", "atndate",
27 "atnicn", "atnid", "atnparent", "atnref", "atntime", "atrfend",
28 "atrfstart", "author", "background", "bkmkend", "buptim", "colortbl",
29 "comment", "creatim", "do", "doccomm", "docvar", "fonttbl", "footer",
30 "footerf", "footerl", "footerr", "footnote", "ftncn", "ftnsep",
31 "ftnsepc", "header", "headerf", "headerl", "headerr", "info",
32 "keywords", "operator", "pict", "printim", "private1", "revtim",
33 "rxe", "stylesheet", "subject", "tc", "title", "txe", "xe",
36 static char read_char(BDataIO
&stream
, bool endOfFileAllowed
= false) throw (status_t
);
37 static int32
parse_integer(char first
, BDataIO
&stream
, char &_last
, int32 base
= 10) throw (status_t
);
44 read_char(BDataIO
&stream
, bool endOfFileAllowed
) throw (status_t
)
47 ssize_t bytesRead
= stream
.Read(&c
, 1);
50 throw (status_t
)bytesRead
;
52 if (bytesRead
== 0 && !endOfFileAllowed
)
53 throw (status_t
)B_ERROR
;
60 parse_integer(char first
, BDataIO
&stream
, char &_last
, int32 base
)
63 const char *kDigits
= "0123456789abcdef";
70 digit
= read_char(stream
);
74 for (; pos
< base
; pos
++) {
75 if (kDigits
[pos
] == tolower(digit
)) {
76 integer
= integer
* base
+ pos
;
86 digit
= read_char(stream
);
91 throw (status_t
)B_BAD_TYPE
;
98 string_array_compare(const char *key
, const char **array
)
100 return strcmp(key
, array
[0]);
105 dump(Element
&element
, int32 level
= 0)
107 printf("%03" B_PRId32
" (%p):", level
, &element
);
108 for (int32 i
= 0; i
< level
; i
++)
111 if (RTF::Header
*header
= dynamic_cast<RTF::Header
*>(&element
)) {
112 printf("<RTF header, major version %" B_PRId32
">\n", header
->Version());
113 } else if (RTF::Command
*command
= dynamic_cast<RTF::Command
*>(&element
)) {
114 printf("<Command: %s", command
->Name());
115 if (command
->HasOption())
116 printf(", Option %" B_PRId32
, command
->Option());
118 } else if (RTF::Text
*text
= dynamic_cast<RTF::Text
*>(&element
)) {
120 puts(text
->String());
121 } else if (RTF::Group
*group
= dynamic_cast<RTF::Group
*>(&element
))
122 printf("<Group \"%s\">\n", group
->Name());
124 if (RTF::Group
*group
= dynamic_cast<RTF::Group
*>(&element
)) {
125 for (uint32 i
= 0; i
< group
->CountElements(); i
++)
126 dump(*group
->ElementAt(i
), level
+ 1);
134 Parser::Parser(BPositionIO
&stream
)
136 fStream(&stream
, 65536, false),
146 if (fStream
.Read(header
, sizeof(header
)) < (ssize_t
)sizeof(header
))
149 if (strncmp(header
, "{\\rtf", 5))
158 Parser::Parse(Header
&header
)
160 if (!fIdentified
&& Identify() != B_OK
)
164 int32 openBrackets
= 1;
166 // since we already preparsed parts of the RTF header, the header
167 // is handled here directly
169 header
.Parse('\0', fStream
, last
);
171 Group
*parent
= &header
;
175 Element
*element
= NULL
;
177 // we'll just ignore the end of the stream
184 parent
->AddElement(element
= new Group());
185 parent
= static_cast<Group
*>(element
);
189 parent
->AddElement(element
= new Command());
194 parent
->DetermineDestination();
195 parent
= parent
->Parent();
196 // supposed to fall through
200 ssize_t bytesRead
= fStream
.Read(&c
, 1);
201 if (bytesRead
< B_OK
)
202 throw (status_t
)bytesRead
;
203 else if (bytesRead
!= 1) {
204 // this is the only valid exit status
205 if (openBrackets
== 0)
208 throw (status_t
)B_ERROR
;
214 parent
->AddElement(element
= new Text());
219 throw (status_t
)B_ERROR
;
221 element
->Parse(c
, fStream
, last
);
224 } catch (status_t status
) {
248 Element::SetParent(Group
*parent
)
255 Element::Parent() const
262 Element::IsDefinitionDelimiter()
269 Element::PrintToStream(int32 level
)
280 fDestination(TEXT_DESTINATION
)
288 while ((element
= (Element
*)fElements
.RemoveItem((int32
)0)) != NULL
) {
295 Group::Parse(char first
, BDataIO
&stream
, char &last
) throw (status_t
)
298 first
= read_char(stream
);
301 throw (status_t
)B_BAD_TYPE
;
303 last
= read_char(stream
);
308 Group::AddElement(Element
*element
)
313 if (fElements
.AddItem(element
)) {
314 element
->SetParent(this);
323 Group::CountElements() const
325 return (uint32
)fElements
.CountItems();
330 Group::ElementAt(uint32 index
) const
332 return static_cast<Element
*>(fElements
.ItemAt(index
));
337 Group::FindDefinitionStart(int32 index
, int32
*_startIndex
) const
344 for (uint32 i
= 0; (element
= ElementAt(i
)) != NULL
; i
++) {
345 if (number
== index
) {
351 if (element
->IsDefinitionDelimiter())
360 Group::FindDefinition(const char *name
, int32 index
) const
363 Element
*element
= FindDefinitionStart(index
, &startIndex
);
367 for (uint32 i
= startIndex
; (element
= ElementAt(i
)) != NULL
; i
++) {
368 if (element
->IsDefinitionDelimiter())
371 if (Command
*command
= dynamic_cast<Command
*>(element
)) {
372 if (command
!= NULL
&& !strcmp(name
, command
->Name()))
382 Group::FindGroup(const char *name
) const
385 for (uint32 i
= 0; (element
= ElementAt(i
)) != NULL
; i
++) {
386 Group
*group
= dynamic_cast<Group
*>(element
);
390 Command
*command
= dynamic_cast<Command
*>(group
->ElementAt(0));
391 if (command
!= NULL
&& !strcmp(name
, command
->Name()))
402 Command
*command
= dynamic_cast<Command
*>(ElementAt(0));
404 return command
->Name();
411 Group::DetermineDestination()
413 const char *name
= Name();
417 if (!strcmp(name
, "*")) {
418 fDestination
= COMMENT_DESTINATION
;
422 // binary search for destination control words
424 if (bsearch(name
, kDestinationControlWords
,
425 sizeof(kDestinationControlWords
) / sizeof(kDestinationControlWords
[0]),
426 sizeof(kDestinationControlWords
[0]),
427 (int (*)(const void *, const void *))string_array_compare
) != NULL
)
428 fDestination
= OTHER_DESTINATION
;
433 Group::Destination() const
455 Header::Parse(char first
, BDataIO
&stream
, char &last
) throw (status_t
)
457 // The stream has been peeked into by the parser already, and
458 // only the version follows in the stream -- let's pick it up
460 fVersion
= parse_integer(first
, stream
, last
);
462 // recreate "rtf" command to name this group
464 Command
*command
= new Command();
465 command
->SetName("rtf");
466 command
->SetOption(fVersion
);
473 Header::Version() const
480 Header::Charset() const
482 Command
*command
= dynamic_cast<Command
*>(ElementAt(1));
486 return command
->Name();
491 Header::Color(int32 index
)
493 rgb_color color
= {0, 0, 0, 255};
495 Group
*colorTable
= FindGroup("colortbl");
497 if (colorTable
!= NULL
) {
498 if (Command
*gun
= colorTable
->FindDefinition("red", index
))
499 color
.red
= gun
->Option();
500 if (Command
*gun
= colorTable
->FindDefinition("green", index
))
501 color
.green
= gun
->Option();
502 if (Command
*gun
= colorTable
->FindDefinition("blue", index
))
503 color
.blue
= gun
->Option();
525 Text::IsDefinitionDelimiter()
532 Text::Parse(char first
, BDataIO
&stream
, char &last
) throw (status_t
)
536 c
= read_char(stream
);
539 // definition delimiter
541 last
= read_char(stream
);
545 const size_t kBufferSteps
= 1;
546 size_t maxSize
= kBufferSteps
;
547 char *text
= fText
.LockBuffer(maxSize
);
549 throw (status_t
)B_NO_MEMORY
;
554 if (c
== '\\' || c
== '}' || c
== '{' || c
== ';' || c
== '\n' || c
== '\r')
557 if (position
>= maxSize
) {
558 fText
.UnlockBuffer(position
);
559 text
= fText
.LockBuffer(maxSize
+= kBufferSteps
);
561 throw (status_t
)B_NO_MEMORY
;
564 text
[position
++] = c
;
566 c
= read_char(stream
);
568 fText
.UnlockBuffer(position
);
570 // ToDo: add support for different charsets - right now, only ASCII is supported!
571 // To achieve this, we should just translate everything into UTF-8 here
578 Text::SetTo(const char *text
)
580 return fText
.SetTo(text
) != NULL
? B_OK
: B_NO_MEMORY
;
587 return fText
.String();
594 return fText
.Length();
616 Command::Parse(char first
, BDataIO
&stream
, char &last
) throw (status_t
)
619 first
= read_char(stream
);
622 throw (status_t
)B_BAD_TYPE
;
625 char name
[kCommandLength
];
628 while (isalpha(c
= read_char(stream
))) {
630 if (length
>= kCommandLength
- 1)
631 throw (status_t
)B_BAD_TYPE
;
635 if (c
== '\n' || c
== '\r') {
636 // we're a hard return
641 // read over character
642 c
= read_char(stream
);
644 fName
.SetTo(name
, length
);
646 TRACE("command: %s\n", fName
.String());
648 // parse numeric option
651 c
= read_char(stream
);
658 bytes
[0] = read_char(stream
);
660 BMemoryIO
memory(bytes
, 2);
662 SetOption(parse_integer(c
, memory
, last
, 16));
663 last
= read_char(stream
);
667 SetOption(parse_integer(c
, stream
, last
));
669 // a space delimiter is eaten up by the command
671 last
= read_char(stream
);
675 TRACE(" option: %ld\n", fOption
);
680 Command::SetName(const char *name
)
682 return fName
.SetTo(name
) != NULL
? B_OK
: B_NO_MEMORY
;
689 return fName
.String();
694 Command::UnsetOption()
702 Command::SetOption(int32 option
)
710 Command::HasOption() const
717 Command::Option() const
726 Iterator::Iterator(Element
&start
, group_destination destination
)
728 SetTo(start
, destination
);
733 Iterator::SetTo(Element
&start
, group_destination destination
)
736 fDestination
= destination
;
751 Iterator::HasNext() const
753 return !fStack
.IsEmpty();
762 if (!fStack
.Pop(&element
))
765 Group
*group
= dynamic_cast<Group
*>(element
);
767 && (fDestination
== ALL_DESTINATIONS
768 || fDestination
== group
->Destination())) {
769 // put this group's children on the stack in
770 // reverse order, so that we iterate over
771 // the tree in in-order
773 for (int32 i
= group
->CountElements(); i
-- > 0;) {
774 fStack
.Push(group
->ElementAt(i
));
785 Worker::Worker(RTF::Header
&start
)
798 Worker::Dispatch(Element
*element
)
800 if (RTF::Group
*group
= dynamic_cast<RTF::Group
*>(element
)) {
807 for (int32 i
= 0; (element
= group
->ElementAt(i
)) != NULL
; i
++)
811 } else if (RTF::Command
*command
= dynamic_cast<RTF::Command
*>(element
)) {
813 } else if (RTF::Text
*text
= dynamic_cast<RTF::Text
*>(element
)) {
820 Worker::Work() throw (status_t
)
827 Worker::Group(RTF::Group
*group
)
833 Worker::GroupEnd(RTF::Group
*group
)
839 Worker::Command(RTF::Command
*command
)
845 Worker::Text(RTF::Text
*text
)
865 Worker::Abort(status_t status
)