/*
IEView Plugin for Miranda IM
Copyright (C) 2005-2010 Piotr Piastucki
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.
This program 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 for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "ieview_common.h"
TextToken::TextToken(int type, const char *text, int len) {
next = NULL;
tag = 0;
end = false;
this->type = type;
this->text = mir_strndup(text, len);
this->wtext = mir_a2t(this->text);
this->link = NULL;
this->wlink = NULL;
}
TextToken::TextToken(int type, const wchar_t *wtext, int len) {
next = NULL;
tag = 0;
end = false;
this->type = type;
this->wtext = mir_tstrndup(wtext, len);
this->text = mir_t2a(this->wtext);
this->link = NULL;
this->wlink = NULL;
}
TextToken::~TextToken() {
if (text!=NULL) {
mir_free(text);
}
if (wtext!=NULL) {
mir_free(wtext);
}
if (link!=NULL) {
mir_free(link);
}
if (wlink!=NULL) {
mir_free(wlink);
}
}
TextToken * TextToken::getNext() {
return next;
}
void TextToken::setNext(TextToken *ptr) {
next = ptr;
}
int TextToken::getType() {
return type;
}
const char *TextToken::getText() {
return text;
}
const wchar_t *TextToken::getTextW() {
return wtext;
}
int TextToken::getTag() {
return tag;
}
void TextToken::setTag(int tag) {
this->tag = tag;
}
bool TextToken::isEnd() {
return end;
}
void TextToken::setEnd(bool b) {
this->end = b;
}
const char *TextToken::getLink() {
return link;
}
const wchar_t *TextToken::getLinkW() {
return wlink;
}
void TextToken::setLink(const char *link) {
if (this->link != NULL) {
delete this->link;
}
if (this->wlink != NULL) {
delete this->wlink;
}
this->link = mir_strdup(link);
this->wlink = mir_a2t(link);
}
void TextToken::setLink(const wchar_t *link) {
if (this->link != NULL) {
delete this->link;
}
if (this->wlink != NULL) {
delete this->wlink;
}
this->link = mir_t2a(link);
this->wlink = mir_tstrdup(link);
}
static int countNoWhitespace(const wchar_t *str) {
int c;
for (c=0; *str!='\n' && *str!='\r' && *str!='\t' && *str!=' ' && *str!='\0'; str++, c++);
return c;
}
TextToken* TextToken::tokenizeBBCodes(const wchar_t *text) {
return tokenizeBBCodes(text, (int)wcslen(text));
}
TextToken* TextToken::tokenizeMath(const wchar_t *text) {
TextToken *firstToken = NULL, *lastToken = NULL, *mathToken = NULL;
static bool mathModInitialized = false;
static wchar_t *mathTagName[] = {NULL, NULL};
static int mathTagLen[] = {0, 0};
int i;
if (!mathModInitialized) {
if (ServiceExists(MATH_GET_PARAMS)) {
char* mthDelStart = (char *)CallService(MATH_GET_PARAMS, (WPARAM)MATH_PARAM_STARTDELIMITER, 0);
char* mthDelEnd = (char *)CallService(MATH_GET_PARAMS, (WPARAM)MATH_PARAM_ENDDELIMITER, 0);
if (mthDelStart!=NULL) {
mathTagName[0] = mir_a2t(mthDelStart);
mathTagLen[0] = (int)wcslen(mathTagName[0]);
}
if (mthDelEnd!=NULL) {
mathTagName[1] = mir_a2t(mthDelEnd);
mathTagLen[1] = (int)wcslen(mathTagName[1]);
}
CallService(MTH_FREE_MATH_BUFFER,0, (LPARAM) mthDelStart);
CallService(MTH_FREE_MATH_BUFFER,0, (LPARAM) mthDelEnd);
}
mathModInitialized = true;
}
int textLen = 0;
int l = (int)wcslen(text);
for (i=0; i<=l;) {
bool mathFound = false;
int k = 0, tagDataStart=0, newTokenType = 0, newTokenSize = 0;
if (mathTagName[0] != NULL && mathTagName[1] != NULL) {
if (!wcsnicmp(text+i, mathTagName[0], mathTagLen[0])) {
k = tagDataStart = i + mathTagLen[0];
for (; k < l; k++) {
if (!wcsnicmp(text+k, mathTagName[1], mathTagLen[1])) {
k += mathTagLen[1];
mathFound = true;
break;
}
}
}
}
if (mathFound) {
mathToken = new TextToken(MATH, text + tagDataStart, k - mathTagLen[1] - tagDataStart);
char* mathPath=(char*)CallService(MTH_GET_GIF_UNICODE, 0, (LPARAM) mathToken->getTextW());
if (mathPath!=NULL) {
mathToken->setLink(mathPath);
CallService(MTH_FREE_GIFPATH, 0, (LPARAM) mathPath);
} else {
mathToken->setLink("");
}
mathToken->setEnd(false);
newTokenType = MATH;
newTokenSize = k - i;
} else {
if (i==l) {
newTokenType = END;
newTokenSize = 1;
} else {
newTokenType = TEXT;
newTokenSize = 1;
}
}
if (newTokenType != TEXT) {
if (textLen >0 ) {
TextToken *newToken = new TextToken(TEXT, text+i-textLen, textLen);
textLen = 0;
if (lastToken == NULL) {
firstToken = newToken;
} else {
lastToken->setNext(newToken);
}
lastToken = newToken;
}
if (newTokenType == MATH) {
if (lastToken == NULL) {
firstToken = mathToken;
} else {
lastToken->setNext(mathToken);
}
lastToken = mathToken;
}
} else {
textLen += newTokenSize;
}
i += newTokenSize;
}
return firstToken;
}
// TODO: Add the following BBCodes: code
#define BB_TAG_NUM 10
TextToken* TextToken::tokenizeBBCodes(const wchar_t *text, int l) {
static const wchar_t *bbTagName[] = {L"b", L"i", L"u", L"s", L"img", L"color", L"size", L"bimg", L"url", L"code"};
static int bbTagNameLen[] = {1, 1, 1, 1, 3, 5, 4, 4, 3, 4};
static int bbTagArg[] = {0, 0, 0, 0, 0, 1, 1, 0, 1, 0};
static int bbTagId[] = {BB_B, BB_I, BB_U, BB_S, BB_IMG, BB_COLOR, BB_SIZE, BB_BIMG, BB_URL, BB_CODE};
static int bbTagEnd[BB_TAG_NUM];
static int bbTagCount[BB_TAG_NUM];
int i,j;
TextToken *firstToken = NULL, *lastToken = NULL, * bbToken = NULL;
int textLen = 0;
for (j = 0; j < BB_TAG_NUM; j++) {
bbTagCount[j] = 0;
bbTagEnd[j] = 0;
}
for (i = 0; i <= l;) {
int k, tagArgStart=0, tagArgEnd=0, tagDataStart=0, newTokenType = 0, newTokenSize = 0;
bool bbFound = false;
if (text[i] == '[') {
if (text[i+1] != '/') {
for (j = 0; j < BB_TAG_NUM; j++) {
k = i + 1;
if (!wcsnicmp(text+k, bbTagName[j], bbTagNameLen[j])) {
tagArgStart = tagArgEnd = 0;
k += bbTagNameLen[j];
if (bbTagArg[j]) {
if (text[k] != '=') continue;
k++;
tagArgStart = k;
for (; text[k]!='\0'; k++) {
if (text[k]==']') break;
}
tagArgEnd = k;
}
if (text[k] == ']') {
k++;
tagDataStart = k;
if (k < bbTagEnd[j]) k = bbTagEnd[j];
for (; k < l; k++) {
if (text[k] == '[' && text[k+1] == '/') {
k += 2;
if (!wcsnicmp(text+k, bbTagName[j], bbTagNameLen[j])) {
k += bbTagNameLen[j];
if (text[k] == ']') {
k++;
bbFound = true;
break;
}
}
}
}
if (bbFound) break;
}
}
}
if (bbFound) {
bbTagEnd[j] = k;
switch (bbTagId[j]) {
case BB_B:
case BB_I:
case BB_U:
case BB_S:
case BB_CODE:
case BB_COLOR:
case BB_SIZE:
bbTagCount[j]++;
if (bbTagArg[j]) {
bbToken = new TextToken(BBCODE, text + tagArgStart, tagArgEnd - tagArgStart);
} else {
bbToken = new TextToken(BBCODE, bbTagName[j], bbTagNameLen[j]);
}
bbToken->setTag(bbTagId[j]);
bbToken->setEnd(false);
newTokenType = BBCODE;
newTokenSize = tagDataStart - i;
break;
case BB_URL:
case BB_IMG:
case BB_BIMG:
bbToken = new TextToken(BBCODE, text + tagDataStart, k - bbTagNameLen[j] - 3 - tagDataStart);
bbToken->setTag(bbTagId[j]);
bbToken->setEnd(false);
newTokenType = BBCODE;
newTokenSize = k - i;
if (bbTagArg[j]) {
wchar_t *urlLink = mir_tstrndup(text + tagArgStart, tagArgEnd - tagArgStart);
bbToken->setLink(urlLink);
delete urlLink;
}
break;
}
}
} else {
for (j = 0; j < BB_TAG_NUM; j++) {
k = i + 2;
if (bbTagCount[j]>0 && !wcsnicmp(text+k, bbTagName[j], bbTagNameLen[j])) {
k += bbTagNameLen[j];
if (text[k] == ']') {
k++;
bbFound = true;
break;
}
}
}
if (bbFound) {
bbTagCount[j]--;
bbToken = new TextToken(BBCODE, bbTagName[j], bbTagNameLen[j]);
bbToken->setEnd(true);
bbToken->setTag(bbTagId[j]);
newTokenType = BBCODE;
newTokenSize = k - i;
}
}
}
if (!bbFound) {
if (i==l) {
newTokenType = END;
newTokenSize = 1;
} else {
newTokenType = TEXT;
newTokenSize = 1;
}
}
if (newTokenType != TEXT) {
if (textLen >0 ) {
TextToken *newToken = new TextToken(TEXT, text+i-textLen, textLen);
textLen = 0;
if (lastToken == NULL) {
firstToken = newToken;
} else {
lastToken->setNext(newToken);
}
lastToken = newToken;
}
if (newTokenType == BBCODE) {
if (lastToken == NULL) {
firstToken = bbToken;
} else {
lastToken->setNext(bbToken);
}
lastToken = bbToken;
}
} else {
textLen += newTokenSize;
}
i += newTokenSize;
}
return firstToken;
}
TextToken* TextToken::tokenizeLinks(const wchar_t *text) {
TextToken *firstToken = NULL, *lastToken = NULL;
int textLen = 0;
int l = (int)wcslen(text);
for (int i=0; i<=l;) {
int newTokenType, newTokenSize;
int urlLen = Utils::detectURL(text+i);
if (i == l) {
newTokenType = END;
newTokenSize = 1;
} else if (urlLen > 0) {
newTokenType = LINK;
newTokenSize = urlLen;
} else if (!wcsncmp(text+i, L"www.", 4)) {
newTokenType = WWWLINK;
newTokenSize = countNoWhitespace(text+i);
} else if (!wcsncmp(text+i, L"mailto:", 7)) {
newTokenType = LINK;
newTokenSize = countNoWhitespace(text+i);
} else {
newTokenType = TEXT;
newTokenSize = 1;
}
if (newTokenType != TEXT) {
if (textLen >0 ) {
TextToken *newToken = new TextToken(TEXT, text+i-textLen, textLen);
textLen = 0;
if (lastToken == NULL) {
firstToken = newToken;
} else {
lastToken->setNext(newToken);
}
lastToken = newToken;
}
if (newTokenType == WWWLINK || newTokenType == LINK) {
TextToken *newToken = new TextToken(newTokenType, text+i, newTokenSize);
newToken->setLink(newToken->getText());
if (lastToken == NULL) {
firstToken = newToken;
} else {
lastToken->setNext(newToken);
}
lastToken = newToken;
}
} else {
textLen += newTokenSize;
}
i += newTokenSize;
}
return firstToken;
}
TextToken* TextToken::tokenizeSmileys(HANDLE hContact, const char *proto, const wchar_t *text, bool isSent) {
TextToken *firstToken = NULL, *lastToken = NULL;
SMADD_BATCHPARSE2 sp;
SMADD_BATCHPARSERES *spRes;
int l = (int)wcslen(text);
if (!Options::isSmileyAdd()) {
return new TextToken(TEXT, text, l);
}
sp.cbSize = sizeof(sp);
sp.Protocolname = proto;
sp.flag = SAFL_PATH | SAFL_UNICODE | (isSent ? SAFL_OUTGOING : 0);
sp.wstr = (wchar_t *)text;
sp.hContact = hContact;
spRes = (SMADD_BATCHPARSERES *) CallService(MS_SMILEYADD_BATCHPARSE, 0, (LPARAM)&sp);
int last_pos = 0;
if (spRes != NULL) {
for (int i = 0; i < (int)sp.numSmileys; i++) {
if (spRes[i].filepath != NULL && strlen((char *)spRes[i].filepath) > 0) {
if ((int)spRes[i].startChar - last_pos > 0) {
TextToken *newToken = new TextToken(TEXT, text+last_pos, spRes[i].startChar-last_pos);
if (lastToken == NULL) {
firstToken = newToken;
} else {
lastToken->setNext(newToken);
}
lastToken = newToken;
}
TextToken *newToken = new TextToken(SMILEY, text+spRes[i].startChar, spRes[i].size);
if (sp.oflag & SAFL_UNICODE) {
newToken->setLink((wchar_t *)spRes[i].filepath);
} else {
newToken->setLink((char *)spRes[i].filepath);
}
if (lastToken == NULL) {
firstToken = newToken;
} else {
lastToken->setNext(newToken);
}
lastToken = newToken;
last_pos = spRes[i].startChar + spRes[i].size;
}
}
CallService(MS_SMILEYADD_BATCHFREE, 0, (LPARAM)spRes);
}
if (last_pos < l) {
TextToken *newToken = new TextToken(TEXT, text+last_pos, l-last_pos);
if (lastToken == NULL) {
firstToken = newToken;
} else {
lastToken->setNext(newToken);
}
lastToken = newToken;
}
return firstToken;
}
TextToken* TextToken::tokenizeChatFormatting(const wchar_t *text) {
TextToken *firstToken = NULL, *lastToken = NULL;
int textLen = 0;
int l = (int)wcslen(text);
wchar_t* tokenBuffer = new wchar_t[l + 1];
for (int i=0; i<=l;) {
int newTokenType = TEXT;
int newTokenSize = 1;
int newTokenTag = 0;
int newTokenTextLen = 0;
const wchar_t * newTokenText = NULL;
bool endToken = false;
if (i==l) {
newTokenType = END;
} else {
if (text[i] == '%') {
newTokenSize = 2;
switch (text[i + 1]) {
case '%':
break;
case 'B':
endToken = true;
case 'b':
newTokenType = BBCODE;
newTokenTag = BB_B;
break;
case 'U':
endToken = true;
case 'u':
newTokenType = BBCODE;
newTokenTag = BB_U;
break;
case 'I':
endToken = true;
case 'i':
newTokenType = BBCODE;
newTokenTag = BB_I;
break;
case 'C':
endToken = true;
case 'c':
newTokenType = BBCODE;
newTokenTag = BB_COLOR;
if (!endToken) {
newTokenText = text + i + 2;
newTokenTextLen = 7;
newTokenSize = 9;
}
break;
case 'F':
endToken = true;
case 'f':
newTokenType = BBCODE;
newTokenTag = BB_BACKGROUND;
if (!endToken) {
newTokenText = text + i + 2;
newTokenTextLen = 7;
newTokenSize = 9;
}
break;
default:
newTokenSize = 1;
}
}
}
if (newTokenType != TEXT) {
if (textLen >0 ) {
TextToken *newToken = new TextToken(TEXT, tokenBuffer, textLen);
textLen = 0;
if (lastToken == NULL) {
firstToken = newToken;
} else {
lastToken->setNext(newToken);
}
lastToken = newToken;
}
if (newTokenType == BBCODE) {
TextToken *newToken = new TextToken(newTokenType, newTokenText, newTokenTextLen);
newToken->setEnd(endToken);
newToken->setTag(newTokenTag);
if (lastToken == NULL) {
firstToken = newToken;
} else {
lastToken->setNext(newToken);
}
lastToken = newToken;
}
} else {
tokenBuffer[textLen] = text[i];
textLen++;
}
i += newTokenSize;
}
return firstToken;
}
wchar_t *TextToken::htmlEncode(const wchar_t *str) {
wchar_t *out;
const wchar_t *ptr;
bool wasSpace;
int c;
c = 0;
wasSpace = false;
for (ptr=str; *ptr!='\0'; ptr++) {
if (*ptr==' ' && wasSpace) {
wasSpace = true;
c += 6;
} else {
wasSpace = false;
switch (*ptr) {
case '\n': c += 4; break;
case '\r': break;
case '&': c += 5; break;
case '>': c += 4; break;
case '<': c += 4; break;
case '"': c += 6; break;
case ' ': wasSpace = true;
default: c += 1; break;
}
}
}
wchar_t *output = new wchar_t[c+1];
wasSpace = false;
for (out=output, ptr=str; *ptr!='\0'; ptr++) {
if (*ptr==' ' && wasSpace) {
wcscpy(out, L" ");
out += 6;
} else {
wasSpace = false;
switch (*ptr) {
case '\n': wcscpy(out, L"
"); out += 4; break;
case '\r': break;
case '&': wcscpy(out, L"&"); out += 5; break;
case '>': wcscpy(out, L">"); out += 4; break;
case '<': wcscpy(out, L"<"); out += 4; break;
case '"': wcscpy(out, L"""); out += 6; break;
case ' ': wasSpace = true;
default: *out = *ptr; out += 1; break;
}
}
}
*out = '\0';
return output;
}
void TextToken::toString(wchar_t **str, int *sizeAlloced) {
wchar_t *eText = NULL, *eLink = NULL;
switch (type) {
case TEXT:
eText = htmlEncode(wtext);
Utils::appendText(str, sizeAlloced, L"%s", eText);
break;
case WWWLINK:
case LINK:
{
eText = htmlEncode(wtext);
eLink = htmlEncode(wlink);
const wchar_t *linkPrefix = type == WWWLINK ? L"http://" : L"";
if ((Options::getGeneralFlags()&Options::GENERAL_ENABLE_EMBED)) {
wchar_t *match = wcsstr(wlink, L"youtube.com");
if (match != NULL) {
match = wcsstr(match + 11, L"v=");
if (match != NULL) {
match += 2;
wchar_t *match2 = wcsstr(match, L"&");
int len = match2 != NULL ? match2 - match : (int)wcslen(match);
match = mir_wstrdup(match);
match[len] = 0;
int width ;
int height;
int Embedsize = Options::getEmbedsize();
switch (Embedsize){
case 0:
width = 320;
height = 205;
break;
case 1:
width = 480;
height = 385;
break;
case 2:
width = 560;
height = 349;
break;
case 3:
width = 640;
height = 390;
break;
};
Utils::appendText(str, sizeAlloced, L"
"); break; case BB_IMG: eText = htmlEncode(wtext); if ((Options::getGeneralFlags()&Options::GENERAL_ENABLE_FLASH) && (wcsstr(eText, L".swf")!=NULL)) { Utils::appendText(str, sizeAlloced, L"", eText); } else if ((Options::getGeneralFlags()&Options::GENERAL_ENABLE_PNGHACK) && (wcsstr(eText, L".png")!=NULL)) { Utils::appendText(str, sizeAlloced, L"", eText); } else { if (wcsncmp(eText, L"http://", 7)) { Utils::appendText(str, sizeAlloced, L"this.width ? 'auto' : maxw);\" src=\"file://%s\" />", eText); } else { Utils::appendText(str, sizeAlloced, L"this.width ? 'auto' : maxw);\" src=\"%s\" />", eText); } } break; case BB_BIMG: { wchar_t *absolutePath = Utils::toAbsolute(wtext); eText = htmlEncode(absolutePath); delete absolutePath; } if ((Options::getGeneralFlags()&Options::GENERAL_ENABLE_FLASH) && (wcsstr(eText, L".swf")!=NULL)) { Utils::appendText(str, sizeAlloced, L"", eText); } else if ((Options::getGeneralFlags()&Options::GENERAL_ENABLE_PNGHACK) && (wcsstr(eText, L".png")!=NULL)) { Utils::appendText(str, sizeAlloced, L"", eText); } else { Utils::appendText(str, sizeAlloced, L"this.width ? 'auto' : maxw);\" src=\"file://%s\" />", eText); } break; case BB_URL: eText = htmlEncode(wtext); eLink = htmlEncode(wlink); Utils::appendText(str, sizeAlloced, L"%s", eLink, eText); break; case BB_COLOR: eText = htmlEncode(wtext); //Utils::appendText(str, sizeAlloced, L"", eText); Utils::appendText(str, sizeAlloced, L"", eText); break; case BB_BACKGROUND: eText = htmlEncode(wtext); Utils::appendText(str, sizeAlloced, L"", eText); break; case BB_SIZE: eText = htmlEncode(wtext); Utils::appendText(str, sizeAlloced, L"", eText); break; } } else { switch (tag) { case BB_B: Utils::appendText(str, sizeAlloced, L"