#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); } typedef struct StrView { char *start; char *end; bool heap_free; } StrView; #define ViewLength(v) ((size_t) ((v.end) - (v.start))) static char * StringifyView(StrView v) { char *r; size_t len; if (!v.start || v.start > v.end) { return NULL; } len = ViewLength(v); r = Malloc(len + 1); memcpy(r, v.start, len); r[len] = '\0'; return r; } static StrView CreateStaticView(char *str) { StrView view = { .start = str, .end = str + strlen(str), .heap_free = false }; return view; } static bool IdentifySpan(char span_tag, StrView in, StrView *view) { size_t length; bool found = false; char prev = '\0'; if (in.start >= in.end) { return false; } if (ViewLength(in) < 2) { return false; } if (*in.start != span_tag || isspace(*(in.start + 1))) { /* The opening styling directive MUST NOT be followed * by a whitespace character */ return false; } view->start = in.start + 1; in.start += 1; for (length = 0; ViewLength(in) > 0; length++, in.start++) { if (*in.start == span_tag) { found = true; break; } prev = *in.start; } if (!found || !length || (prev && isspace(prev))) { /* the closing styling directive MUST NOT be preceeded * by a whitespace character. */ return false; } view->end = in.start; return true; } static void XEP393Decode(StrView view, XEP393Element *root) { StrView subview = view; StrView textview = view; XEP393Element *text, *span; bool managed = false; char prev = '\0', curr = '\0'; textview.end = subview.start; for (; subview.start < subview.end; subview.start++) { StrView span_view; managed = false; curr = *subview.start; if (prev == '\0' || prev == '\n') { /* TODO: Start of line, start parsing blocks. */ } #define Spanify(xep_symbol) \ managed = true; \ textview.end = subview.start; \ text = CreateElementVessel( \ root, XEP393_TEXT \ ); \ text->text_data = StringifyView(textview); \ \ /* Found a span. */ \ span = CreateElementVessel( \ root, xep_symbol \ ); \ \ XEP393Decode(span_view, span); \ \ /* Update subview */ \ subview.start = span_view.end + 1; \ \ /* Update textview */ \ textview.start = subview.start; \ textview.end = subview.start if (IdentifySpan('_', subview, &span_view)) { Spanify(XEP393_ITALIC); } else if (IdentifySpan('*', subview, &span_view)) { Spanify(XEP393_EMPH); } else if (IdentifySpan('`', subview, &span_view)) { Spanify(XEP393_MONO); } else { /* Text character: update end */ textview.end = subview.start; } prev = curr; } if (!managed) { textview.end = subview.start; text = CreateElementVessel( root, XEP393_TEXT ); text->text_data = StringifyView(textview); } } XEP393Element * XEP393(char *message) { StrView view = CreateStaticView(message); XEP393Element *root = CreateElementVessel(NULL, XEP393_ROOT); /* TODO: Parse blocks first, *then* spans. Considering the * current architecture, this shouldn't be too hard to integrate, * given how string views already manage boundaries, and elements * can already be used to contain blocks I think. * * Actually, nevermind, these would be pure pain. Nested blocks, * unterminated ones, QUOTES. Just hell. I hate parsing this shit. */ XEP393Decode(view, root); return root; } #include static void ShoveXML(XEP393Element *element, XMLElement *xmlparent) { XMLElement *head = xmlparent; size_t i; if (!element || !xmlparent) { return; } switch (element->type) { case XEP393_ITALIC: head = XMLCreateTag("i"); XMLAddChild(xmlparent, head); break; case XEP393_EMPH: head = XMLCreateTag("strong"); XMLAddChild(xmlparent, head); break; case XEP393_MONO: head = XMLCreateTag("code"); 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); } } #include char * XEP393ToXMLString(XEP393Element *xepd) { XMLElement *root; Stream *writer; char *ret = NULL; size_t i; if (!xepd) { return NULL; } root = XMLCreateTag("ROOT"); ShoveXML(xepd, root); writer = StrStreamWriter(&ret); for (i = 0; i < ArraySize(root->children); i++) { XMLEncode(writer, ArrayGet(root->children, i)); } XMLFreeElement(root); StreamFlush(writer); StreamClose(writer); return ret; }