aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/renderer/MCHTMLRenderer.cpp
diff options
context:
space:
mode:
authorGravatar DINH Viet Hoa <dinh.viet.hoa@gmail.com>2013-02-02 21:00:20 -0800
committerGravatar DINH Viet Hoa <dinh.viet.hoa@gmail.com>2013-02-02 21:00:20 -0800
commit2319ee53c7318b31340ef8356693a5008385180b (patch)
tree4b6766fbc1e3d59d95700cd88417b77ac50870a5 /src/core/renderer/MCHTMLRenderer.cpp
parentb21a8e209974ad04e9cff02e225e772246c00c89 (diff)
Implemented HTML rendering of messages
Diffstat (limited to 'src/core/renderer/MCHTMLRenderer.cpp')
-rw-r--r--src/core/renderer/MCHTMLRenderer.cpp446
1 files changed, 446 insertions, 0 deletions
diff --git a/src/core/renderer/MCHTMLRenderer.cpp b/src/core/renderer/MCHTMLRenderer.cpp
new file mode 100644
index 00000000..10066fc4
--- /dev/null
+++ b/src/core/renderer/MCHTMLRenderer.cpp
@@ -0,0 +1,446 @@
+//
+// MCHTMLRenderer.cpp
+// testUI
+//
+// Created by DINH Viêt Hoà on 1/23/13.
+// Copyright (c) 2013 MailCore. All rights reserved.
+//
+
+#include "MCHTMLRenderer.h"
+
+#include <ctemplate/template.h>
+#include "MCAddressDisplay.h"
+#include "MCDateFormatter.h"
+#include "MCSizeFormatter.h"
+#include "MCHTMLRendererCallback.h"
+
+using namespace mailcore;
+
+enum {
+ RENDER_STATE_NONE,
+ RENDER_STATE_HAD_ATTACHMENT,
+ RENDER_STATE_HAD_ATTACHMENT_THEN_TEXT,
+};
+
+struct htmlRendererContext {
+ HTMLRendererIMAPCallback * dataCallback;
+ HTMLRendererTemplateCallback * htmlCallback;
+ int firstRendered;
+ String * folder;
+ int state;
+ int pass;
+ bool hasMixedTextAndAttachments;
+ bool firstAttachment;
+ bool hasTextPart;
+};
+
+class DefaultTemplateCallback : public Object, public HTMLRendererTemplateCallback {
+};
+
+static bool partContainsMimeType(AbstractPart * part, String * mimeType);
+static bool singlePartContainsMimeType(AbstractPart * part, String * mimeType);
+static bool multipartContainsMimeType(AbstractMultipart * part, String * mimeType);
+static bool messagePartContainsMimeType(AbstractMessagePart * part, String * mimeType);
+
+static String * htmlForAbstractPart(AbstractPart * part, htmlRendererContext * context);
+
+static String * renderTemplate(String * templateContent, HashMap * values);
+
+static String * htmlForAbstractMessage(String * folder, AbstractMessage * message,
+ HTMLRendererIMAPCallback * dataCallback,
+ HTMLRendererTemplateCallback * htmlCallback);
+
+static bool isTextPart(AbstractPart * part, htmlRendererContext * context)
+{
+ String * mimeType = part->mimeType()->lowercaseString();
+ MCAssert(mimeType != NULL);
+
+ if (!part->isInlineAttachment()) {
+ if ((part->filename() != NULL) && context->firstRendered) {
+ return false;
+ }
+ }
+
+ if (mimeType->isEqual(MCSTR("text/plain"))) {
+ return true;
+ }
+ else if (mimeType->isEqual(MCSTR("text/html"))) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+
+static AbstractPart * preferredPartInMultipartAlternative(AbstractMultipart * part)
+{
+ int htmlPart = -1;
+ int textPart = -1;
+
+ for(unsigned int i = 0 ; i < part->parts()->count() ; i ++) {
+ AbstractPart * subpart = (AbstractPart *) part->parts()->objectAtIndex(i);
+ if (partContainsMimeType(subpart, MCSTR("text/html"))) {
+ htmlPart = i;
+ }
+ else if (partContainsMimeType(subpart, MCSTR("text/plain"))) {
+ textPart = i;
+ }
+ }
+ if (htmlPart != -1) {
+ return (AbstractPart *) part->parts()->objectAtIndex(htmlPart);
+ }
+ else if (textPart != -1) {
+ return (AbstractPart *) part->parts()->objectAtIndex(textPart);
+ }
+ else if (part->parts()->count() > 0) {
+ return (AbstractPart *) part->parts()->objectAtIndex(0);
+ }
+ else {
+ return NULL;
+ }
+}
+
+static bool partContainsMimeType(AbstractPart * part, String * mimeType)
+{
+ switch (part->partType()) {
+ case PartTypeSingle:
+ return singlePartContainsMimeType(part, mimeType);
+ case PartTypeMessage:
+ return messagePartContainsMimeType((AbstractMessagePart *) part, mimeType);
+ case PartTypeMultipartMixed:
+ case PartTypeMultipartRelated:
+ case PartTypeMultipartAlternative:
+ return multipartContainsMimeType((AbstractMultipart *) part, mimeType);
+ default:
+ return false;
+ }
+}
+
+static bool singlePartContainsMimeType(AbstractPart * part, String * mimeType)
+{
+ return part->mimeType()->lowercaseString()->isEqual(mimeType);
+}
+
+static bool multipartContainsMimeType(AbstractMultipart * part, String * mimeType)
+{
+ for(unsigned int i = 0 ; i < part->parts()->count() ; i ++) {
+ AbstractPart * subpart = (AbstractPart *) part->parts()->objectAtIndex(i);
+ if (partContainsMimeType(subpart, mimeType)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool messagePartContainsMimeType(AbstractMessagePart * part, String * mimeType)
+{
+ return partContainsMimeType(part->mainPart(), mimeType);
+}
+
+static String * htmlForAbstractMessage(String * folder, AbstractMessage * message,
+ HTMLRendererIMAPCallback * dataCallback,
+ HTMLRendererTemplateCallback * htmlCallback)
+{
+ AbstractPart * mainPart = NULL;
+
+ if (htmlCallback == NULL) {
+ htmlCallback = new DefaultTemplateCallback();
+ ((DefaultTemplateCallback *) htmlCallback)->autorelease();
+ }
+
+ if (message->className()->isEqual(MCSTR("mailcore::IMAPMessage"))) {
+ mainPart = ((IMAPMessage *) message)->mainPart();
+ }
+ else if (message->className()->isEqual(MCSTR("mailcore::MessageParser"))) {
+ mainPart = ((MessageParser *) message)->mainPart();
+ }
+ MCAssert(mainPart != NULL);
+
+ htmlRendererContext context;
+ context.dataCallback = dataCallback;
+ context.htmlCallback = htmlCallback;
+ context.firstRendered = 0;
+ context.folder = folder;
+ context.state = RENDER_STATE_NONE;
+
+ context.hasMixedTextAndAttachments = false;
+ context.pass = 0;
+ context.firstAttachment = false;
+ context.hasTextPart = false;
+ htmlForAbstractPart(mainPart, &context);
+
+ context.hasMixedTextAndAttachments = (context.state == RENDER_STATE_HAD_ATTACHMENT_THEN_TEXT);
+ context.pass = 1;
+ context.firstAttachment = false;
+ context.hasTextPart = false;
+ String * content = htmlForAbstractPart(mainPart, &context);
+ if (content == NULL)
+ return NULL;
+
+ content = htmlCallback->filterHTMLForMessage(content);
+
+ HashMap * values = htmlCallback->templateValuesForHeader(message->header());
+ String * headerString = renderTemplate(htmlCallback->templateForMainHeader(), values);
+
+ HashMap * msgValues = new HashMap();
+ msgValues->setObjectForKey(MCSTR("HEADER"), headerString);
+ msgValues->setObjectForKey(MCSTR("BODY"), content);
+ String * result = renderTemplate(htmlCallback->templateForMessage(), msgValues);
+ msgValues->release();
+
+ return result;
+}
+
+String * htmlForAbstractSinglePart(AbstractPart * part, htmlRendererContext * context);
+String * htmlForAbstractMessagePart(AbstractMessagePart * part, htmlRendererContext * context);
+String * htmlForAbstractMultipartRelated(AbstractMultipart * part, htmlRendererContext * context);
+String * htmlForAbstractMultipartMixed(AbstractMultipart * part, htmlRendererContext * context);
+String * htmlForAbstractMultipartAlternative(AbstractMultipart * part, htmlRendererContext * context);
+
+String * htmlForAbstractPart(AbstractPart * part, htmlRendererContext * context)
+{
+ switch (part->partType()) {
+ case PartTypeSingle:
+ return htmlForAbstractSinglePart((AbstractPart *) part, context);
+ case PartTypeMessage:
+ return htmlForAbstractMessagePart((AbstractMessagePart *) part, context);
+ case PartTypeMultipartMixed:
+ return htmlForAbstractMultipartMixed((AbstractMultipart *) part, context);
+ case PartTypeMultipartRelated:
+ return htmlForAbstractMultipartRelated((AbstractMultipart *) part, context);
+ case PartTypeMultipartAlternative:
+ return htmlForAbstractMultipartAlternative((AbstractMultipart *) part, context);
+ default:
+ MCAssert(0);
+ }
+ return NULL;
+}
+
+String * htmlForAbstractSinglePart(AbstractPart * part, htmlRendererContext * context)
+{
+ String * mimeType = part->mimeType()->lowercaseString();
+ MCAssert(mimeType != NULL);
+
+ if (isTextPart(part, context)) {
+ if (context->pass == 0) {
+ if (context->state == RENDER_STATE_HAD_ATTACHMENT) {
+ context->state = RENDER_STATE_HAD_ATTACHMENT_THEN_TEXT;
+ }
+ return NULL;
+ }
+
+ context->hasTextPart = true;
+
+ if (mimeType->isEqual(MCSTR("text/plain"))) {
+ String * charset = part->charset();
+ Data * data = NULL;
+ if (part->className()->isEqual(MCSTR("mailcore::IMAPPart"))) {
+ data = context->dataCallback->dataForIMAPPart(context->folder, (IMAPPart *) part);
+ }
+ else if (part->className()->isEqual(MCSTR("mailcore::Attachment"))) {
+ data = ((Attachment *) part)->data();
+ MCAssert(data != NULL);
+ }
+ if (data == NULL)
+ return NULL;
+
+ String * str = data->stringWithDetectedCharset(charset, false);
+ context->firstRendered = true;
+ return str->htmlEncodedString();
+ }
+ else if (mimeType->isEqual(MCSTR("text/html"))) {
+ String * charset = part->charset();
+ Data * data = NULL;
+ if (part->className()->isEqual(MCSTR("mailcore::IMAPPart"))) {
+ data = context->dataCallback->dataForIMAPPart(context->folder, (IMAPPart *) part);
+ }
+ else if (part->className()->isEqual(MCSTR("mailcore::Attachment"))) {
+ data = ((Attachment *) part)->data();
+ MCAssert(data != NULL);
+ }
+ if (data == NULL)
+ return NULL;
+
+ String * str = data->stringWithDetectedCharset(charset, true);
+ str = str->cleanedHTMLString();
+ str = context->htmlCallback->filterHTMLForPart(str);
+ context->firstRendered = true;
+ return str;
+ }
+ else {
+ MCAssert(0);
+ return NULL;
+ }
+ }
+ else {
+ if (context->pass == 0) {
+ if (context->state == RENDER_STATE_NONE) {
+ context->state = RENDER_STATE_HAD_ATTACHMENT;
+ }
+ return NULL;
+ }
+
+ if (part->uniqueID() == NULL) {
+ part->setUniqueID(String::uuidString());
+ }
+
+ String * result = String::string();
+ String * separatorString;
+ String * content;
+
+ if (!context->firstAttachment && context->hasTextPart) {
+ separatorString = context->htmlCallback->templateForAttachmentSeparator();
+ }
+ else {
+ separatorString = MCSTR("");
+ }
+
+ context->firstAttachment = true;
+
+ if (context->htmlCallback->canPreviewPart(part)) {
+ if (part->className()->isEqual(MCSTR("mailcore::IMAPPart"))) {
+ context->dataCallback->prefetchImageIMAPPart(context->folder, (IMAPPart *) part);
+ }
+ String * url = String::stringWithUTF8Format("x-mailcore-image:%s",
+ part->uniqueID()->UTF8Characters());
+ HashMap * values = context->htmlCallback->templateValuesForPart(part);
+ values->setObjectForKey(MCSTR("URL"), url);
+ content = renderTemplate(context->htmlCallback->templateForImage(), values);
+ }
+ else {
+ if (part->className()->isEqual(MCSTR("mailcore::IMAPPart"))) {
+ context->dataCallback->prefetchAttachmentIMAPPart(context->folder, (IMAPPart *) part);
+ }
+ HashMap * values = context->htmlCallback->templateValuesForPart(part);
+ content = renderTemplate(context->htmlCallback->templateForAttachment(), values);
+ }
+
+ result->appendString(separatorString);
+ result->appendString(content);
+
+ return result;
+ }
+}
+
+String * htmlForAbstractMessagePart(AbstractMessagePart * part, htmlRendererContext * context)
+{
+ String * substring = htmlForAbstractPart(part->mainPart(), context);
+ if (context->pass == 0) {
+ return NULL;
+ }
+ MCAssert(substring != NULL);
+
+ String * result = String::string();
+ HashMap * values = context->htmlCallback->templateValuesForHeader(part->header());
+ String * headerString = renderTemplate(context->htmlCallback->templateForMainHeader(), values);
+ result->appendString(headerString);
+ result->appendString(substring);
+ return result;
+}
+
+String * htmlForAbstractMultipartAlternative(AbstractMultipart * part, htmlRendererContext * context)
+{
+ AbstractPart * preferredAlternative = preferredPartInMultipartAlternative(part);
+ if (preferredAlternative == NULL)
+ return MCSTR("");
+
+ return htmlForAbstractPart(preferredAlternative, context);
+}
+
+String * htmlForAbstractMultipartMixed(AbstractMultipart * part, htmlRendererContext * context)
+{
+ String * result = String::string();
+ for(unsigned int i = 0 ; i < part->parts()->count() ; i ++) {
+ AbstractPart * subpart = (AbstractPart *) part->parts()->objectAtIndex(i);
+ String * substring = htmlForAbstractPart(subpart, context);
+ if (context->pass != 0) {
+ if (substring == NULL)
+ return NULL;
+
+ result->appendString(substring);
+ }
+ }
+ return result;
+}
+
+String * htmlForAbstractMultipartRelated(AbstractMultipart * part, htmlRendererContext * context)
+{
+ if (part->parts()->count() == 0) {
+ if (context->pass == 0) {
+ return NULL;
+ }
+ else {
+ return MCSTR("");
+ }
+ }
+
+ AbstractPart * subpart = (AbstractPart *) part->parts()->objectAtIndex(0);
+ return htmlForAbstractPart(subpart, context);
+}
+
+void fillTemplateDictionaryFromMCHashMap(ctemplate::TemplateDictionary * dict, HashMap * mcHashMap)
+{
+ Array * keys = mcHashMap->allKeys();
+
+ for(unsigned int i = 0 ; i < keys->count() ; i ++) {
+ String * key = (String *) keys->objectAtIndex(i);
+ Object * value;
+
+ value = mcHashMap->objectForKey(key);
+ if (value->className()->isEqual(MCSTR("mailcore::String"))) {
+ String * str;
+
+ str = (String *) value;
+ dict->SetValue(key->UTF8Characters(), str->UTF8Characters());
+ }
+ else if (value->className()->isEqual(MCSTR("mailcore::Array"))) {
+ Array * array;
+
+ array = (Array *) value;
+ for(unsigned int k = 0 ; k < array->count() ; k ++) {
+ HashMap * item = (HashMap *) array->objectAtIndex(k);
+ ctemplate::TemplateDictionary * subDict = dict->AddSectionDictionary(key->UTF8Characters());
+ fillTemplateDictionaryFromMCHashMap(subDict, item);
+ }
+ }
+ else if (value->className()->isEqual(MCSTR("mailcore::HashMap"))) {
+ ctemplate::TemplateDictionary * subDict;
+ HashMap * item;
+
+ item = (HashMap *) value;
+ subDict = dict->AddSectionDictionary(key->UTF8Characters());
+ fillTemplateDictionaryFromMCHashMap(subDict, item);
+ }
+ }
+}
+
+String * renderTemplate(String * templateContent, HashMap * values)
+{
+ ctemplate::TemplateDictionary dict("template dict");
+ std::string output;
+ Data * data;
+
+ fillTemplateDictionaryFromMCHashMap(&dict, values);
+ data = templateContent->dataUsingEncoding("utf-8");
+ ctemplate::Template * tpl = ctemplate::Template::StringToTemplate(data->bytes(), data->length(), ctemplate::DO_NOT_STRIP);
+ if (!tpl->Expand(&output, &dict))
+ return NULL;
+ delete tpl;
+
+ return String::stringWithUTF8Characters(output.c_str());
+}
+
+String * HTMLRenderer::htmlForRFC822Message(MessageParser * message,
+ HTMLRendererTemplateCallback * htmlCallback)
+{
+ return htmlForAbstractMessage(NULL, message, NULL, htmlCallback);
+}
+
+String * HTMLRenderer::htmlForIMAPMessage(String * folder,
+ IMAPMessage * message,
+ HTMLRendererIMAPCallback * dataCallback,
+ HTMLRendererTemplateCallback * htmlCallback)
+{
+ return htmlForAbstractMessage(folder, message, dataCallback, htmlCallback);
+}