Parsee/src/XEP-0393.c

421 lines
9.9 KiB
C

#include <XEP393.h>
#include <StringSplit.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <string.h>
#include <ctype.h>
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(&region, i, true);
StringRect single_line = StrGetl(&region, 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 <XML.h>
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 <p/>-tags with no
* children. */
if (par && ArraySize(head->children) == 0)
{
ArrayDelete(
xmlparent->children,
ArraySize(xmlparent->children) - 1
);
XMLFreeElement(head);
}
}
#include <StringStream.h>
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;
}