mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 15:15:10 +00:00
I need to make it so that NetBSD targets get their own correctness/build checks in CI. I also had to do some hacks with Cytoplasm, will make a PR for those soon.
437 lines
10 KiB
C
437 lines
10 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((int) 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((int) 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 <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, *act_root;
|
|
XMLElement *child;
|
|
|
|
Stream *writer;
|
|
char *ret = NULL;
|
|
size_t i, children;
|
|
if (!xepd)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
root = XMLCreateTag("span");
|
|
act_root = root;
|
|
ShoveXML(xepd, root);
|
|
|
|
writer = StrStreamWriter(&ret);
|
|
children = ArraySize(root->children);
|
|
|
|
child = ArrayGet(root->children, 0);
|
|
if (children == 1 && StrEquals(child->name, "p"))
|
|
{
|
|
children = ArraySize(child->children);
|
|
root = child;
|
|
}
|
|
for (i = 0; i < children; i++)
|
|
{
|
|
child = ArrayGet(root->children, i);
|
|
|
|
XMLEncode(writer, child);
|
|
}
|
|
XMLFreeElement(act_root);
|
|
StreamFlush(writer);
|
|
StreamClose(writer);
|
|
|
|
return ret;
|
|
}
|