#include #include #include #include #include #include #include #include static XEP393Element * CreateElementVessel(XEP393Element *parent, XEP393Type type) { XEP393Element *ret = Malloc(sizeof(*ret)); ret->parent = parent; ret->type = type; ret->children = ArrayCreate(); ret->text_data = NULL; if (parent) { ArrayAdd(parent->children, ret); } return ret; } static void XEP393FreeElementBase(XEP393Element *element, bool unlink) { size_t i, len; if (!element) { return; } len = ArraySize(element->children); for (i = 0; i < len; i++) { XEP393FreeElement(ArrayGet(element->children, i)); } if (element->parent && unlink) { XEP393Element *parent = element->parent; len = ArraySize(parent->children); for (i = 0; i < len; i++) { XEP393Element *c = ArrayGet(parent->children, i); if (c == element) { ArrayDelete(parent->children, i); break; } } } ArrayFree(element->children); Free(element->text_data); Free(element); } void XEP393FreeElement(XEP393Element *element) { XEP393FreeElementBase(element, false); } static StringRect DecodeQuote(StringRect rect, size_t *skip) { StringRect ret = StrFullRect(NULL); int lines = 0; /* C abuse of chaining operations */ while ((StrGet(&rect, lines, 0) == '>') && ++lines) { if (!ret.source_lines) { int shift_by = 1, ch; ret = rect; ret.end_line--; /* TODO: You can easily craft strings that conceal data( * > Please mind the whitespace * > hidden we're concealing data to Matrix * > star in Well, you can still stare at the body and XML * > four but that's for Nerds * > seasons See, Touhou reference! * concealing!) */ while ((ch = StrGet(&rect, lines - 1, shift_by)) && isspace(ch)) { shift_by++; } ret = StrShift(ret, shift_by); continue; } } if (!lines) { return StrFullRect(NULL); } ret.end_line = ret.start_line + lines - 1; if (skip) { *skip = lines; } return ret; } static StringRect DecodeSpan(StringRect rect, char del, size_t *skip) { StringRect ret = StrFullRect(NULL); int chars = 0; char c; if (StrGet(&rect, 0, 0) != del) { return ret; } rect = StrShift(rect, 1); /* C abuse of chaining operations */ while (((c = StrGet(&rect, 0, chars)) != del) && ++chars) { if (!c) { return StrFullRect(NULL); } if (!ret.source_lines && isspace(c)) { return StrFullRect(NULL); } if (!ret.source_lines) { ret = rect; ret.end_char = ret.start_char; continue; } ret.end_char++; } ret.end_char++; if (!chars) { return StrFullRect(NULL); } { char *temp, *gen = NULL, chara[2] = { 0, '\0' }; size_t i; for (i = 0; i < StrViewChars(ret, 0); i++) { *chara = StrGet(&ret, 0, i); if (!*chara) { break; } temp = gen; gen = StrConcat(2, gen, chara); Free(temp); } Free(gen); } if (skip) { *skip = chars; } return ret; } #define BLOCK_QUOTE (1 << 0) #define BLOCK_CODES (1 << 1) static void ParseLine(XEP393Element *elem, StringRect line) { XEP393Element *span_item, *line_item; StringRect shifted; size_t ch_idx, chars = StrViewChars(line, 0); size_t text_start = 0; size_t i; for (ch_idx = 0; ch_idx < chars; ch_idx++) { char curr = StrGet(&line, 0, ch_idx); StringRect span; shifted = line; shifted.start_char += ch_idx; #define HandleSpan(del, sym) \ if (curr == del && \ (span = DecodeSpan(shifted, del, NULL)).source_lines) \ { \ size_t text_end = ch_idx; \ \ { \ char *temp, *gen = NULL, chara[2] = { 0, '\0' }; \ for (i = text_start; i < text_end; i++) \ { \ *chara = StrGet(&line, 0, i); \ \ temp = gen; \ gen = StrConcat(2, gen, chara); \ Free(temp); \ } \ line_item = CreateElementVessel(elem, XEP393_TEXT); \ line_item->text_data = gen; \ } \ \ span_item = CreateElementVessel(elem, sym); \ ParseLine(span_item, span); \ text_start = span.end_char - line.start_char + 1; \ ch_idx = span.end_char; \ continue; \ } HandleSpan('*', XEP393_EMPH); HandleSpan('_', XEP393_ITALIC); HandleSpan('~', XEP393_SRKE); HandleSpan('`', XEP393_MONO); /* TODO: Can we troll and introduce | as a Parsee extension? */ } { char *temp, *gen = NULL, chara[2] = { 0, '\0' }; for (i = text_start; i < chars; i++) { *chara = StrGet(&line, 0, i); temp = gen; gen = StrConcat(2, gen, chara); Free(temp); } line_item = CreateElementVessel(elem, XEP393_TEXT); line_item->text_data = gen; } } static void XEP393Parse(XEP393Element *root, StringRect region, int flags) { size_t i, lines = StrViewLines(region); for (i = 0; i < lines; i++) { StringRect extend_line = StrGetl(®ion, i, true); StringRect single_line = StrGetl(®ion, i, false); size_t jump_by = 0; XEP393Element *sub; if ((flags & BLOCK_QUOTE) && (StrGet(&single_line, 0, 0) == '>')) { StringRect quote = DecodeQuote(extend_line, &jump_by); if (quote.source_lines) { sub = CreateElementVessel(root, XEP393_QUOT); XEP393Parse(sub, quote, flags); i += jump_by - 1; continue; } } /* TODO: Codeblocks, this shouldn't be too hard. */ if (!(flags & BLOCK_CODES)) { sub = CreateElementVessel(root, XEP393_LINE); ParseLine(sub, single_line); continue; } } } XEP393Element * XEP393(char *message) { char **lines = StrSplitLines(message); StringRect view = StrFullRect(lines); XEP393Element *root = CreateElementVessel(NULL, XEP393_ROOT); XEP393Parse(root, view, BLOCK_QUOTE); StrFreeLines(lines); return root; } #include static void ShoveXML(XEP393Element *element, XMLElement *xmlparent) { XMLElement *head = xmlparent; size_t i; bool par = false; if (!element || !xmlparent) { return; } if (element->type == XEP393_CODE) { XMLElement *pre, *code, *text; pre = XMLCreateTag("pre"); code = XMLCreateTag("code"); text = XMLCreateText(element->text_data); XMLAddChild(code, text); XMLAddChild(pre, code); XMLAddChild(xmlparent, pre); return; } switch (element->type) { case XEP393_ITALIC: head = XMLCreateTag("i"); XMLAddChild(xmlparent, head); XMLAddChild(head, XMLCreateText("_")); break; case XEP393_MONO: head = XMLCreateTag("code"); XMLAddChild(xmlparent, head); XMLAddChild(head, XMLCreateText("`")); break; case XEP393_SRKE: head = XMLCreateTag("s"); XMLAddChild(xmlparent, head); XMLAddChild(head, XMLCreateText("~")); break; case XEP393_EMPH: head = XMLCreateTag("strong"); XMLAddChild(xmlparent, head); XMLAddChild(head, XMLCreateText("*")); break; case XEP393_LINE: head = XMLCreateTag("p"); XMLAddChild(xmlparent, head); par = true; break; case XEP393_QUOT: head = XMLCreateTag("blockquote"); XMLAddChild(xmlparent, head); break; default: break; } if (ArraySize(element->children) == 0 && element->text_data) { XMLElement *text = XMLCreateText(element->text_data); XMLAddChild(head, text); } for (i = 0; i < ArraySize(element->children); i++) { XEP393Element *sub = ArrayGet(element->children, i); ShoveXML(sub, head); } switch (element->type) { case XEP393_ITALIC: XMLAddChild(head, XMLCreateText("_")); break; case XEP393_MONO: XMLAddChild(head, XMLCreateText("`")); break; case XEP393_SRKE: XMLAddChild(head, XMLCreateText("~")); break; case XEP393_EMPH: XMLAddChild(head, XMLCreateText("*")); break; default: break; } /* NOTE: Hack to get rid of stranded

-tags with no * children. */ if (par && ArraySize(head->children) == 0) { ArrayDelete( xmlparent->children, ArraySize(xmlparent->children) - 1 ); XMLFreeElement(head); } } #include char * XEP393ToXMLString(XEP393Element *xepd) { XMLElement *root; Stream *writer; char *ret = NULL; if (!xepd) { return NULL; } root = XMLCreateTag("span"); ShoveXML(xepd, root); writer = StrStreamWriter(&ret); XMLEncode(writer, root); XMLFreeElement(root); StreamFlush(writer); StreamClose(writer); return ret; }