#include "Markdown.h" #define LLOG(x) // DLOG("MarkdownConverter: " << x) namespace Upp { class sMarkdownContext : NoCopy { struct Block { MD_BLOCKTYPE type; String text; Value data; int level; Block *parent; Array children; Block(MD_BLOCKTYPE t, Block *p); }; enum NestedBlockPosition { BLOCK_IN_OLIST = 1, BLOCK_IN_ULIST = 2, BLOCK_IN_QUOTE = 4, BLOCK_IN_THEAD = 8, BLOCK_IN_TBODY = 16 }; Array document; Block* current_block; int current_level; String Compose(const Array& doc, int data = 0, bool notext = false, dword flags = 0) const; public: void BeginBlock(MD_BLOCKTYPE type, void *detail); void EndBlock(MD_BLOCKTYPE type, void *detail); String ToQtf() { return Compose(document); } sMarkdownContext& operator<<(const String& s) { ASSERT(current_block); current_block->text << s; return *this; } sMarkdownContext& operator<<(const char *s) { ASSERT(current_block); current_block->text << s; return *this; } sMarkdownContext() : current_block(nullptr) , current_level(0) {} }; sMarkdownContext::Block::Block(MD_BLOCKTYPE t, Block *p) : type(t) , level(0) , parent(p) { } void sMarkdownContext::BeginBlock(MD_BLOCKTYPE type, void *detail) { if(!current_block) { current_block = &document.Create(type, current_block); } else { current_block = ¤t_block->children.Create(type, current_block); } switch(type) { case MD_BLOCK_UL: current_block->data = reinterpret_cast(detail)->mark; current_block->level = ++current_level; break; case MD_BLOCK_OL: current_block->data = '1'; current_block->level = ++current_level; break; case MD_BLOCK_H: current_block->data = (int) reinterpret_cast(detail)->level; current_block->level = current_level; break; default: current_block->level = current_level; break; } } void sMarkdownContext::EndBlock(MD_BLOCKTYPE type, void *detail) { if(findarg(type, MD_BLOCK_UL, MD_BLOCK_OL) >= 0) --current_level; if(current_block) current_block = current_block->parent; } String sMarkdownContext::Compose(const Array& doc, int data, bool notext, dword flags) const { // TODO: // 1) Refactor this method. // 2) Make certain block styles and page properties (e.g. margins, indentation, etc.) configurable. String txt; for(int i = 0; i < doc.GetCount(); i++) { const Block& b = doc[i]; switch(b.type) { case MD_BLOCK_DOC: { txt << "[G;2 " << Compose(b.children, data, false, flags) << "&]"; break; } case MD_BLOCK_HR: { txt << "[H1;L0;h(220.220.220) &]"; break; } case MD_BLOCK_H: { txt << "&[*;" << clamp(6 - b.data.To(), 1, 6) << " " << b.text << "&]"; break; } case MD_BLOCK_UL: { txt << Compose(b.children, b.data, false, flags|BLOCK_IN_ULIST); break; } case MD_BLOCK_OL: { txt << "[N! " << Compose(b.children, b.data, false, flags|BLOCK_IN_OLIST) << "&]"; break; } case MD_BLOCK_LI: { bool q = b.text.IsEmpty(); if(!q) { txt << "[a20;b20;l" << b.level * ((flags & BLOCK_IN_QUOTE) ? 100 : 200) << ";i200;" << decode(data, '*', "OO ", '-', "O1 ", '+', "O2 ", "N1 ") << b.text << "&]"; } txt << Compose(b.children, data, q, flags); break; } case MD_BLOCK_P: { if(!b.level) { txt << "[a50;b50 " << b.text << " &]"; } else if(notext && !i) { txt << "[a20;b20;l" << b.level * ((flags & BLOCK_IN_QUOTE) ? 20 : 200) << ";i200;" << decode(data, '*', "OO ", '-', "O1 ", '+', "O2 ", "N1 ") << b.text << "&]"; } else { txt << "[a20;b20;l" << b.level * ((flags & BLOCK_IN_QUOTE) ? 40 : 400) << ";O_ " << b.text << "&]"; } break; } case MD_BLOCK_CODE: case MD_BLOCK_HTML: // Treat HTML as code block.... { txt << "{{10000;<" << b.level * ((flags & BLOCK_IN_QUOTE) ? 10 : 400) << ";@(250.250.250);F(230.230.230) [i10;C;1;@5;< " << b.text << " ]}}&"; break; } case MD_BLOCK_QUOTE: { txt << "{{1:500;G4;g20;F0;f0;<" << b.level * ((flags & BLOCK_IN_QUOTE) ? 10 : 400) << " :: [i10;<; " << Nvl(b.text, "") << Compose(b.children, data, false, flags|BLOCK_IN_QUOTE) << " ]}}&"; break; } case MD_BLOCK_TABLE: { txt << "{{" << Compose(b.children, data, false, flags) << "}}&"; break; } case MD_BLOCK_THEAD: { txt << Compose(b.children, data, false, flags|BLOCK_IN_THEAD); break; } case MD_BLOCK_TBODY: { txt << Compose(b.children, data, false, flags|BLOCK_IN_TBODY); break; } case MD_BLOCK_TR: { int n = b.children.GetCount(); if(flags & BLOCK_IN_THEAD) { for(int j = 0; j < n; j++) { txt << '1'; if(j < n - 1) txt << ':'; else { txt << "@(220.225.230);G(220.220.220);<" << b.level * ((flags & BLOCK_IN_QUOTE) ? 10 : 400) << " "; } } } for(int j = 0; j < n; j++) { const Block& bb = b.children[j]; if(j == 0 && bb.type == MD_BLOCK_TD) txt << "::@2 "; txt << bb.text; if(j < n - 1) txt << "||"; } break; } default: //txt << b.text; break; } } return txt; } static String sDeQtfMd(const char *s) { // Here we duplicate DeQtf() function, because we don't want to bring in the RichText package. StringBuffer r; for(; *s; s++) { if(*s == '\n') r.Cat('&'); else { if((byte) *s > ' ' && !IsDigit(*s) && !IsAlpha(*s) && (byte) *s < 128) r.Cat('`'); r.Cat(*s); } } return String(r); // Make compilers happy... } String MarkdownConverter::ToQtf(const String& mdtext) { MD_PARSER parser; parser.abi_version = 0; parser.flags = flags; parser.debug_log = nullptr; parser.syntax = nullptr; parser.enter_block = [](MD_BLOCKTYPE type, void *detail, void *udata) -> int { reinterpret_cast(udata)->BeginBlock(type, detail); return 0; }; parser.leave_block = [](MD_BLOCKTYPE type, void *detail, void *udata) -> int { reinterpret_cast(udata)->EndBlock(type, detail); return 0; }; parser.enter_span = [](MD_SPANTYPE type, void *detail, void *udata) -> int { auto& ctx = *reinterpret_cast(udata); switch(type) { case MD_SPAN_A: { auto *q = reinterpret_cast(detail); ctx << "[^" << String(q->href.text, q->href.size) << "^ "; break; } case MD_SPAN_IMG: { auto *q = reinterpret_cast(detail); ctx << "[^" << String(q->src.text, q->src.size) << "^ "; break; } case MD_SPAN_WIKILINK: { auto *q = reinterpret_cast(detail); ctx << "[^" << String(q->target.text, q->target.size) << "^ "; break; } default: { ctx << decode(type, MD_SPAN_U, "[_ ", MD_SPAN_EM, "[/ ", MD_SPAN_DEL, "[- ", MD_SPAN_CODE, "[C;@5;$(245.245.245) ", MD_SPAN_STRONG, "[* ", ""); break; }} return 0; }; parser.leave_span = [](MD_SPANTYPE type, void *detail, void *udata) -> int { auto& ctx = *reinterpret_cast(udata); if(findarg(type, MD_SPAN_A, MD_SPAN_U, MD_SPAN_EM, MD_SPAN_IMG, MD_SPAN_DEL, MD_SPAN_CODE, MD_SPAN_STRONG, MD_SPAN_WIKILINK) >= 0) ctx << "]"; return 0; }; parser.text = [](MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void* udata) -> int { auto& ctx = *reinterpret_cast(udata); ctx << decode(type, MD_TEXT_NULLCHAR, "?", MD_TEXT_BR, "&", MD_TEXT_SOFTBR, " ", // TODO: See if there is a way to properly imitate this in qtf... (const char*) ~sDeQtfMd(String((const char*) text, size))); return 0; }; #ifdef _DEBUG parser.debug_log = [](const char *msg, void *udata) -> void { LLOG(msg); }; #endif sMarkdownContext ctx; int rc = md_parse((const MD_CHAR*)~mdtext, (MD_SIZE) mdtext.GetLength(), &parser, &ctx); return rc ? String::GetVoid() : ctx.ToQtf(); } }