%{ /* rtf.ll - A simple RTF Parser (Flex code) Copyright (c) 2002 by Vladimir Shutoff (original code) Copyright (c) 2004 by Thiago S. Barcelos (Kopete port) Kopete (c) 2002-2003 by the Kopete developers ************************************************************************* * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ************************************************************************* update rtf.cc: flex -olex.yy.c `test -f rtf.ll || echo './'`rtf.ll sed '/^#/ s|lex.yy\.c|rtf.cc|' lex.yy.c >rtf.cc rm -f lex.yy.c */ #define UP 1 #define DOWN 2 #define CMD 3 #define TXT 4 #define HEX 5 #define IMG 6 #define UNICODE_CHAR 7 #define SKIP 8 #define SLASH 9 #define S_TXT 10 #define YY_NEVER_INTERACTIVE 1 #define YY_ALWAYS_INTERACTIVE 0 #define YY_MAIN 0 %} %option nounput %option nostack %option prefix="rtf" %% "{" { return UP; } "}" { return DOWN; } "\\"[\\\{\}] { return SLASH; } "\\u"[0-9]{3,7}[ ]?"?" { return UNICODE_CHAR; } "\\"[A-Za-z]+[0-9]*[ ]? { return CMD; } "\\'"[0-9A-Fa-f][0-9A-Fa-f] { return HEX; } "<##"[^>]+">" { return IMG; } [^\\{}<]+ { return TXT; } . { return TXT; } %% #include "rtf2html.h" void ParStyle::clearFormatting() { // For now, do nothing. // dir is not a formatting item. } QString RTF2HTML::quoteString(const QString &_str, quoteMode mode) { QString str = _str; str.replace(QRegExp("&"), "&"); str.replace(QRegExp("<"), "<"); str.replace(QRegExp(">"), ">"); str.replace(QRegExp("\""), """); str.replace(QRegExp("\r"), ""); switch (mode){ case quoteHTML: str.replace(QRegExp("\n"), "
\n"); break; case quoteXML: str.replace(QRegExp("\n"), "
\n"); break; default: break; } QRegExp re(" +"); int len; int pos = 0; while ((pos = re.search(str, pos)) != -1) { len = re.matchedLength(); if (len == 1) continue; QString s = " "; for (int i = 1; i < len; i++) s += " "; str.replace(pos, len, s); } return str; } RTF2HTML::RTF2HTML() : cur_level(this) { rtf_ptr = NULL; bExplicitParagraph = false; } OutTag* RTF2HTML::getTopOutTag(TagEnum tagType) { vector::iterator it, it_end; for(it = oTags.begin(), it_end = oTags.end(); it != it_end; ++it) if (it->tag == tagType) return &(*it); return NULL; } void RTF2HTML::FlushOutTags() { vector::iterator iter; for (iter = oTags.begin(); iter != oTags.end(); iter++) { OutTag &t = *iter; switch (t.tag){ case TAG_FONT_COLOR: { // RTF colors are 1-based; colors[] is a 0-based array. if (t.param > colors.size() || t.param == 0) break; QColor &c = colors[t.param-1]; PrintUnquoted("", c.red(), c.green(), c.blue()); } break; case TAG_FONT_SIZE: PrintUnquoted("", t.param); break; case TAG_FONT_FAMILY: { if (t.param > fonts.size() || t.param == 0) break; FontDef &f = fonts[t.param-1]; string name = (!f.nonTaggedName.empty()) ? f.nonTaggedName : f.taggedName; PrintUnquoted("", name.c_str()); } break; case TAG_BG_COLOR:{ if (t.param > colors.size() || t.param == 0) break; QColor &c = colors[t.param-1]; PrintUnquoted("", c.red(), c.green(), c.blue()); break; } case TAG_BOLD: PrintUnquoted(""); break; case TAG_ITALIC: PrintUnquoted(""); break; case TAG_UNDERLINE: PrintUnquoted(""); break; default: break; } } oTags.clear(); } // This function will close the already-opened tag 'tag'. It will take // care of closing the tags which 'tag' contains first (ie. it will unroll // the stack till the point where 'tag' is). void Level::resetTag(TagEnum tag) { // A stack which'll keep tags we had to close in order to reach 'tag'. // After we close 'tag', we will reopen them. stack s; while (p->tags.size() > m_nTagsStartPos){ // Don't go further than the point where this level starts. TagEnum nTag = p->tags.top(); /* A tag will be located in oTags if it still wasn't printed out. A tag will get printed out only if necessary (e.g. will be optimized away). Thus, for each tag we remove from the actual tag stack, we also try to remove a yet-to-be-printed tag, and only if there are no yet-to-be-printed tags left, we start closing the tags we pop. The tags have one space - needed for umlaute (�) and .utf8() */ if (p->oTags.empty()){ switch (nTag){ case TAG_FONT_COLOR: case TAG_FONT_SIZE: case TAG_BG_COLOR: case TAG_FONT_FAMILY: p->PrintUnquoted(" "); break; case TAG_BOLD: p->PrintUnquoted(" "); break; case TAG_ITALIC: p->PrintUnquoted(" "); break; case TAG_UNDERLINE: p->PrintUnquoted(" "); break; default: break; } }else{ p->oTags.pop_back(); } p->tags.pop(); if (nTag == tag) break; // if we reached the tag we were looking to close. s.push(nTag); // remember to reopen this tag } if (tag == TAG_ALL) return; while (!s.empty()){ TagEnum nTag = s.top(); switch (nTag){ case TAG_FONT_COLOR:{ unsigned nFontColor = m_nFontColor; m_nFontColor = 0; setFontColor(nFontColor); break; } case TAG_FONT_SIZE:{ unsigned nFontSize = m_nFontSize; m_nFontSize = 0; setFontSize(nFontSize); break; } case TAG_BG_COLOR:{ unsigned nFontBgColor = m_nFontBgColor; m_nFontBgColor = 0; setFontBgColor(nFontBgColor); break; } case TAG_FONT_FAMILY:{ unsigned nFont = m_nFont; m_nFont = 0; setFont(nFont); break; } case TAG_BOLD:{ bool nBold = m_bBold; m_bBold = false; setBold(nBold); break; } case TAG_ITALIC:{ bool nItalic = m_bItalic; m_bItalic = false; setItalic(nItalic); break; } case TAG_UNDERLINE:{ bool nUnderline = m_bUnderline; m_bUnderline = false; setUnderline(nUnderline); break; } default: break; } s.pop(); } } Level::Level(RTF2HTML *_p) : p(_p), m_bFontTbl(false), m_bColors(false), m_bFontName(false), m_bTaggedFontNameOk(false), m_nFont(0), m_nEncoding(0) { m_nTagsStartPos = p->tags.size(); Init(); } Level::Level(const Level &l) : p(l.p), m_bFontTbl(l.m_bFontTbl), m_bColors(l.m_bColors), m_bFontName(false), m_bTaggedFontNameOk(l.m_bTaggedFontNameOk), m_nFont(l.m_nFont), m_nEncoding(l.m_nEncoding) { m_nTagsStartPos = p->tags.size(); Init(); } void Level::Init() { m_nFontColor = 0; m_nFontBgColor = 0; m_nFontSize = 0; m_bFontName = false; m_bBold = false; m_bItalic = false; m_bUnderline = false; } void RTF2HTML::PrintUnquoted(const char *str, ...) { char buff[1024]; va_list ap; va_start(ap, str); vsnprintf(buff, sizeof(buff), str, ap); va_end(ap); sParagraph += buff; } void RTF2HTML::PrintQuoted(const QString &str) { sParagraph += quoteString(str); } void RTF2HTML::FlushParagraph() { if (!bExplicitParagraph || sParagraph.isEmpty()) return; /* s += "

"; s += sParagraph; s += "

"; */ s += sParagraph; s += "
"; // Clear up the paragraph members sParagraph = ""; bExplicitParagraph = false; } void Level::setFont(unsigned nFont) { if (nFont <= 0) return; if (m_bFontTbl){ if (nFont > p->fonts.size() +1){ kdDebug(14200) << "Invalid font index (" << nFont << ") while parsing font table." << endl; return; } if (nFont > p->fonts.size()){ FontDef f; f.charset = 0; p->fonts.push_back(f); } m_nFont = nFont; } else { if (nFont > p->fonts.size()) { kdDebug(14200) << "Invalid font index (" << nFont << ")." << endl; return; } if (m_nFont == nFont) return; m_nFont = nFont; if (m_nFont) resetTag(TAG_FONT_FAMILY); m_nEncoding = p->fonts[nFont-1].charset; p->oTags.push_back(OutTag(TAG_FONT_FAMILY, nFont)); p->PutTag(TAG_FONT_FAMILY); } } void Level::setFontName() { // This function is only valid during font table parsing. if (m_bFontTbl){ if ((m_nFont > 0) && (m_nFont <= p->fonts.size())) // Be prepared to accept a font name. m_bFontName = true; } } void Level::setEncoding(unsigned nEncoding) { if (m_bFontTbl){ if ((m_nFont > 0) && (m_nFont <= p->fonts.size())) p->fonts[m_nFont-1].charset = nEncoding; return; } m_nEncoding = nEncoding; } void Level::setBold(bool bBold) { if (m_bBold == bBold) return; if (m_bBold) resetTag(TAG_BOLD); m_bBold = bBold; if (!m_bBold) return; p->oTags.push_back(OutTag(TAG_BOLD, 0)); p->PutTag(TAG_BOLD); } void Level::setItalic(bool bItalic) { if (m_bItalic == bItalic) return; if (m_bItalic) resetTag(TAG_ITALIC); m_bItalic = bItalic; if (!m_bItalic) return; p->oTags.push_back(OutTag(TAG_ITALIC, 0)); p->PutTag(TAG_ITALIC); } void Level::setUnderline(bool bUnderline) { if (m_bUnderline == bUnderline) return; if (m_bUnderline) resetTag(TAG_UNDERLINE); m_bUnderline = bUnderline; if (!m_bUnderline) return; p->oTags.push_back(OutTag(TAG_UNDERLINE, 0)); p->PutTag(TAG_UNDERLINE); } void Level::setFontColor(unsigned short nColor) { if (m_nFontColor == nColor) return; if (m_nFontColor) resetTag(TAG_FONT_COLOR); if (nColor > p->colors.size()) return; m_nFontColor = nColor; p->oTags.push_back(OutTag(TAG_FONT_COLOR, m_nFontColor)); p->PutTag(TAG_FONT_COLOR); } void Level::setFontBgColor(unsigned short nColor) { if (m_nFontBgColor == nColor) return; if (m_nFontBgColor != 0) resetTag(TAG_BG_COLOR); if (nColor > p->colors.size()) return; m_nFontBgColor = nColor; p->oTags.push_back(OutTag(TAG_BG_COLOR, m_nFontBgColor)); p->PutTag(TAG_BG_COLOR); } void Level::setFontSizeHalfPoints(unsigned short nSize) { setFontSize(nSize / 2); } void Level::setFontSize(unsigned short nSize) { if (m_nFontSize == nSize) return; if (m_nFontSize) resetTag(TAG_FONT_SIZE); p->oTags.push_back(OutTag(TAG_FONT_SIZE, nSize)); p->PutTag(TAG_FONT_SIZE); m_nFontSize = nSize; } void Level::startParagraph() { // Whatever tags we have open now, close them. // We cannot carry let character formatting tags wrap paragraphs, // since a formatting tag can close at any time and we cannot // close the paragraph any time we want. resetTag(TAG_ALL); // Flush the current paragraph HTML to the document HTML. p->FlushParagraph(); // Mark this new paragraph as an explicit one (from \par etc.). p->bExplicitParagraph = true; // Restore character formatting p->oTags.push_back(OutTag(TAG_FONT_SIZE, m_nFontSize)); p->PutTag(TAG_FONT_SIZE); p->oTags.push_back(OutTag(TAG_FONT_COLOR, m_nFontColor)); p->PutTag(TAG_FONT_COLOR); p->oTags.push_back(OutTag(TAG_FONT_FAMILY, m_nFont)); p->PutTag(TAG_FONT_FAMILY); if (m_nFontBgColor != 0) { p->oTags.push_back(OutTag(TAG_BG_COLOR, m_nFontBgColor)); p->PutTag(TAG_BG_COLOR); } if (m_bBold) { p->oTags.push_back(OutTag(TAG_BOLD, 0)); p->PutTag(TAG_BOLD); } if (m_bItalic) { p->PutTag(TAG_ITALIC); p->oTags.push_back(OutTag(TAG_ITALIC, 0)); } if (m_bUnderline) { p->oTags.push_back(OutTag(TAG_UNDERLINE, 0)); p->PutTag(TAG_UNDERLINE); } } bool Level::isParagraphOpen() const { return p->bExplicitParagraph; } void Level::clearParagraphFormatting() { // implicitly start a paragraph if (!isParagraphOpen()) startParagraph(); // Since we don't implement any of the paragraph formatting tags (e.g. alignment), // we don't clean up anything here. Note that \pard does NOT clean character // formatting (such as font size, font weight, italics...). p->parStyle.clearFormatting(); } void Level::setParagraphDirLTR() { // implicitly start a paragraph if (!isParagraphOpen()) startParagraph(); p->parStyle.dir = ParStyle::DirLTR; } void Level::setParagraphDirRTL() { // implicitly start a paragraph if (!isParagraphOpen()) startParagraph(); p->parStyle.dir = ParStyle::DirRTL; } void Level::addLineBreak() { p->PrintUnquoted("
"); } void Level::reset() { resetTag(TAG_ALL); if (m_bColors){ if (m_bColorInit){ QColor c(m_nRed, m_nGreen, m_nBlue); p->colors.push_back(c); resetColors(); } return; } } void Level::setText(const char *str) { if (m_bColors) { reset(); } else if (m_bFontTbl) { if ((m_nFont <= 0) || (m_nFont > p->fonts.size())) return; FontDef& def = p->fonts[m_nFont-1]; char *pp = strchr(str, ';'); unsigned size; if (pp != NULL) size = (pp - str); else size = strlen(str); if (m_bFontName) { def.nonTaggedName.append(str, size); // We know we have the entire name if (pp != NULL) m_bFontName = false; } else if (!m_bTaggedFontNameOk) { def.taggedName.append(str, size); if (pp != NULL) m_bTaggedFontNameOk = true; } } else { for (; *str; str++) if ((unsigned char)(*str) >= ' ') break; if (!*str) return; p->FlushOutTags(); text += str; } } void Level::flush() { if (text.length() == 0) return; // TODO: Make encoding work in Kopete /* const char *encoding = NULL; if (m_nEncoding){ for (const ENCODING *c = ICQPlugin::core->encodings; c->language; c++){ if (!c->bMain) continue; if ((unsigned)c->rtf_code == m_nEncoding){ encoding = c->codec; break; } } } if (encoding == NULL) encoding = p->encoding; QTextCodec *codec = ICQClient::_getCodec(encoding); */ //p->PrintQuoted(codec->toUnicode(text.c_str(), text.length())); p->PrintQuoted(text.c_str()); text = ""; } const unsigned FONTTBL = 0; const unsigned COLORTBL = 1; const unsigned RED = 2; const unsigned GREEN = 3; const unsigned BLUE = 4; const unsigned CF = 5; const unsigned FS = 6; const unsigned HIGHLIGHT = 7; const unsigned PARD = 8; const unsigned PAR = 9; const unsigned I = 10; const unsigned B = 11; const unsigned UL = 12; const unsigned F = 13; const unsigned FCHARSET = 14; const unsigned FNAME = 15; const unsigned ULNONE = 16; const unsigned LTRPAR = 17; const unsigned RTLPAR = 18; const unsigned LINE = 19; static char cmds[] = "fonttbl\x00" "colortbl\x00" "red\x00" "green\x00" "blue\x00" "cf\x00" "fs\x00" "highlight\x00" "pard\x00" "par\x00" "i\x00" "b\x00" "ul\x00" "f\x00" "fcharset\x00" "fname\x00" "ulnone\x00" "ltrpar\x00" "rtlpar\x00" "line\x00" "\x00"; int yywrap() { return 1; } static char h2d(char c) { if ((c >= '0') && (c <= '9')) return c - '0'; if ((c >= 'A') && (c <= 'F')) return (c - 'A') + 10; if ((c >= 'a') && (c <= 'f')) return (c - 'a') + 10; return 0; } QString RTF2HTML::Parse(const char *rtf, const char *_encoding) { encoding = _encoding; YY_BUFFER_STATE yy_current_buffer = yy_scan_string(rtf); rtf_ptr = rtf; for (;;){ int res = yylex(); if (!res) break; switch (res){ case UP:{ cur_level.flush(); levels.push(cur_level); break; } case DOWN:{ if (!levels.empty()){ cur_level.flush(); cur_level.reset(); cur_level = levels.top(); levels.pop(); } break; } case IMG:{ cur_level.flush(); const char ICQIMAGE[] = "icqimage"; const char *smiles[] = { ":-)" , ":-O" , ":-|" , ":-/" , // 0-3 ":-(" , ":-*" , ":-/" , ":'(" , // 4-7 ";-)" , ":-@" , ":-$" , ":-X" , // 8-B ":-P" , "8-)" , "O:)" , ":-D" }; // C-F const char *p = yytext + 3; if ((strlen(p) > strlen(ICQIMAGE)) && !memcmp(p, ICQIMAGE, strlen(ICQIMAGE))){ unsigned n = 0; for (p += strlen(ICQIMAGE); *p; p++){ if ((*p >= '0') && (*p <= '9')){ n = n << 4; n += (*p - '0'); continue; } if ((*p >= 'A') && (*p <= 'F')){ n = n << 4; n += (*p - 'A') + 10; continue; } if ((*p >= 'a') && (*p <= 'f')){ n = n << 4; n += (*p - 'a') + 10; continue; } break; } if (n < 16) PrintUnquoted(" %s ", smiles[n] ); }else{ kdDebug(14200) << "Unknown image " << yytext << endl; } break; } case SKIP: break; case SLASH: cur_level.setText(yytext+1); break; case TXT: cur_level.setText(yytext); break; case UNICODE_CHAR:{ cur_level.flush(); sParagraph += QChar((unsigned short)(atol(yytext + 2))); break; } case HEX:{ char s[2]; s[0] = (h2d(yytext[2]) << 4) + h2d(yytext[3]); s[1] = 0; cur_level.setText(s); break; } case CMD: { cur_level.flush(); const char *cmd = yytext + 1; unsigned n_cmd = 0; unsigned cmd_size = 0; int cmd_value = -1; const char *p; for (p = cmd; *p; p++, cmd_size++) if (((*p >= '0') && (*p <= '9')) || (*p == ' ')) break; if (*p && (*p != ' ')) cmd_value = atol(p); for (p = cmds; *p; p += strlen(p) + 1, n_cmd++){ if (strlen(p) > cmd_size) continue; if (!memcmp(p, cmd, cmd_size)) break; } cmd += strlen(p); switch (n_cmd){ case FONTTBL: // fonttbl cur_level.setFontTbl(); break; case COLORTBL: cur_level.setColors(); break; case RED: cur_level.setRed(cmd_value); break; case GREEN: cur_level.setGreen(cmd_value); break; case BLUE: cur_level.setBlue(cmd_value); break; case CF: cur_level.setFontColor(cmd_value); break; case FS: cur_level.setFontSizeHalfPoints(cmd_value); break; case HIGHLIGHT: cur_level.setFontBgColor(cmd_value); break; case PARD: cur_level.clearParagraphFormatting(); break; case PAR: cur_level.startParagraph(); break; case I: cur_level.setItalic(cmd_value != 0); break; case B: cur_level.setBold(cmd_value != 0); break; case UL: cur_level.setUnderline(cmd_value != 0); break; case ULNONE: cur_level.setUnderline(false); break; case F: // RTF fonts are 0-based; our font index is 1-based. cur_level.setFont(cmd_value+1); break; case FCHARSET: cur_level.setEncoding(cmd_value); break; case FNAME: cur_level.setFontName(); break; case LTRPAR: cur_level.setParagraphDirLTR(); break; case RTLPAR: cur_level.setParagraphDirRTL(); break; case LINE: cur_level.addLineBreak(); } break; } } } yy_delete_buffer(yy_current_buffer); yy_current_buffer = NULL; FlushParagraph(); return s; } /* bool ICQClient::parseRTF(const char *rtf, const char *encoding, QString &res) { char _RTF[] = "{\\rtf"; if ((strlen(rtf) > strlen(_RTF)) && !memcmp(rtf, _RTF, strlen(_RTF))){ RTF2HTML p; res = p.Parse(rtf, encoding); return true; } QTextCodec *codec = ICQClient::_getCodec(encoding); res = codec->toUnicode(rtf, strlen(rtf)); return false; } */