mirror of
https://github.com/swiftlang/swift-cmark.git
synced 2026-01-18 17:31:20 +01:00
Table extension from c068469 reworked
Note this includes a hack to the core code to escape pipes in the 'commonmark' renderer. This is to fix test cases with the table extension; i.e. we treat pipes as special characters that need escaping. We use the cmark_mem of the parser in order to ensure we use the arena allocator when necessary. A very flexible table format is supported; see test/extensions.txt for examples. Leading and trailing pipes can be omitted, and alignment specifiers can be used in the separator between the header and body. Table bodies don't need to be a consistent width. Embedded HTML is OK. Note we reuse the inline parser from cmark to parse tables -- this is to ensure pipes e.g. in the middle of an inline code block don't prematurely terminate a table cell.
This commit is contained in:
30
Makefile
30
Makefile
@@ -1,10 +1,12 @@
|
||||
SRCDIR=src
|
||||
EXTDIR=extensions
|
||||
DATADIR=data
|
||||
BUILDDIR?=build
|
||||
GENERATOR?=Unix Makefiles
|
||||
MINGW_BUILDDIR?=build-mingw
|
||||
MINGW_INSTALLDIR?=windows
|
||||
SPEC=test/spec.txt
|
||||
EXTENSIONS_SPEC=test/extensions.txt
|
||||
SITE=_site
|
||||
SPECVERSION=$(shell perl -ne 'print $$1 if /^version: *([0-9.]+)/' $(SPEC))
|
||||
FUZZCHARS?=2000000 # for fuzztest
|
||||
@@ -80,7 +82,7 @@ afl:
|
||||
-o test/afl_results \
|
||||
-x test/fuzzing_dictionary \
|
||||
-t 100 \
|
||||
$(CMARK) $(CMARK_OPTS)
|
||||
$(CMARK) -e table $(CMARK_OPTS)
|
||||
|
||||
libFuzzer:
|
||||
@[ -n "$(LIB_FUZZER_PATH)" ] || { echo '$$LIB_FUZZER_PATH not set'; false; }
|
||||
@@ -126,6 +128,19 @@ $(SRCDIR)/scanners.c: $(SRCDIR)/scanners.re
|
||||
--encoding-policy substitute -o $@ $<
|
||||
$(CLANG_FORMAT) $@
|
||||
|
||||
# We include scanners.c in the repository, so this shouldn't
|
||||
# normally need to be generated.
|
||||
$(EXTDIR)/ext_scanners.c: $(EXTDIR)/ext_scanners.re
|
||||
@case "$$(re2c -v)" in \
|
||||
*\ 0.13.*|*\ 0.14|*\ 0.14.1) \
|
||||
echo "re2c >= 0.14.2 is required"; \
|
||||
false; \
|
||||
;; \
|
||||
esac
|
||||
re2c --case-insensitive -b -i --no-generation-date -8 \
|
||||
--encoding-policy substitute -o $@ $<
|
||||
clang-format -style llvm -i $@
|
||||
|
||||
# We include entities.inc in the repository, so normally this
|
||||
# doesn't need to be regenerated:
|
||||
$(SRCDIR)/entities.inc: tools/make_entities_inc.py
|
||||
@@ -138,14 +153,19 @@ update-spec:
|
||||
test: $(SPEC) cmake_build
|
||||
$(MAKE) -C $(BUILDDIR) test || (cat $(BUILDDIR)/Testing/Temporary/LastTest.log && exit 1)
|
||||
|
||||
$(ALLTESTS): $(SPEC)
|
||||
python3 test/spec_tests.py --spec $< --dump-tests | python3 -c 'import json; import sys; tests = json.loads(sys.stdin.read()); print("\n".join([test["markdown"] for test in tests]))' > $@
|
||||
$(ALLTESTS): $(SPEC) $(EXTENSIONS_SPEC)
|
||||
( \
|
||||
python3 test/spec_tests.py --spec $(SPEC) --dump-tests | \
|
||||
python3 -c 'import json; import sys; tests = json.loads(sys.stdin.read()); print("\n".join([test["markdown"] for test in tests]))'; \
|
||||
python3 test/spec_tests.py --spec $(EXTENSIONS_SPEC) --dump-tests | \
|
||||
python3 -c 'import json; import sys; tests = json.loads(sys.stdin.read()); print("\n".join([test["markdown"] for test in tests]))'; \
|
||||
) > $@
|
||||
|
||||
leakcheck: $(ALLTESTS)
|
||||
for format in html man xml latex commonmark; do \
|
||||
for opts in "" "--smart"; do \
|
||||
echo "cmark -t $$format $$opts" ; \
|
||||
valgrind -q --leak-check=full --dsymutil=yes --error-exitcode=1 $(PROG) -t $$format $$opts $(ALLTESTS) >/dev/null || exit 1;\
|
||||
echo "cmark -t $$format -e table $$opts" ; \
|
||||
valgrind -q --leak-check=full --dsymutil=yes --suppressions=suppressions --error-exitcode=1 $(PROG) -t $$format -e table $$opts $(ALLTESTS) >/dev/null || exit 1;\
|
||||
done; \
|
||||
done;
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 2.8)
|
||||
set(STATICLIBRARY "libcmarkextensions_static")
|
||||
set(LIBRARY_SOURCES
|
||||
core-extensions.c
|
||||
table.c
|
||||
ext_scanners.c
|
||||
ext_scanners.re
|
||||
ext_scanners.h
|
||||
)
|
||||
|
||||
include_directories(
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#include "core-extensions.h"
|
||||
#include "table.h"
|
||||
|
||||
int core_extensions_registration(cmark_plugin *plugin) { return 1; }
|
||||
int core_extensions_registration(cmark_plugin *plugin) {
|
||||
cmark_plugin_register_syntax_extension(plugin, create_table_extension());
|
||||
return 1;
|
||||
}
|
||||
|
||||
382
extensions/ext_scanners.c
Normal file
382
extensions/ext_scanners.c
Normal file
@@ -0,0 +1,382 @@
|
||||
/* Generated by re2c 0.14.3 */
|
||||
#include "ext_scanners.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *),
|
||||
unsigned char *ptr, int len, bufsize_t offset) {
|
||||
bufsize_t res;
|
||||
|
||||
if (ptr == NULL || offset > len) {
|
||||
return 0;
|
||||
} else {
|
||||
unsigned char lim = ptr[len];
|
||||
|
||||
ptr[len] = '\0';
|
||||
res = scanner(ptr + offset);
|
||||
ptr[len] = lim;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bufsize_t _scan_table_start(const unsigned char *p) {
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
|
||||
{
|
||||
unsigned char yych;
|
||||
static const unsigned char yybm[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
yych = *(marker = p);
|
||||
if (yych <= '{') {
|
||||
if (yych <= 0x1F) {
|
||||
if (yych <= '\t') {
|
||||
if (yych <= 0x08)
|
||||
goto yy6;
|
||||
goto yy3;
|
||||
} else {
|
||||
if (yych <= '\n')
|
||||
goto yy2;
|
||||
if (yych <= '\f')
|
||||
goto yy3;
|
||||
goto yy6;
|
||||
}
|
||||
} else {
|
||||
if (yych <= '-') {
|
||||
if (yych <= ' ')
|
||||
goto yy3;
|
||||
if (yych <= ',')
|
||||
goto yy6;
|
||||
goto yy5;
|
||||
} else {
|
||||
if (yych == ':')
|
||||
goto yy4;
|
||||
goto yy6;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (yych <= 0xEC) {
|
||||
if (yych <= 0xC1) {
|
||||
if (yych <= '|')
|
||||
goto yy3;
|
||||
if (yych <= 0x7F)
|
||||
goto yy6;
|
||||
} else {
|
||||
if (yych <= 0xDF)
|
||||
goto yy7;
|
||||
if (yych <= 0xE0)
|
||||
goto yy9;
|
||||
goto yy10;
|
||||
}
|
||||
} else {
|
||||
if (yych <= 0xF0) {
|
||||
if (yych <= 0xED)
|
||||
goto yy14;
|
||||
if (yych <= 0xEF)
|
||||
goto yy10;
|
||||
goto yy11;
|
||||
} else {
|
||||
if (yych <= 0xF3)
|
||||
goto yy12;
|
||||
if (yych <= 0xF4)
|
||||
goto yy13;
|
||||
}
|
||||
}
|
||||
}
|
||||
yy2 : { return 0; }
|
||||
yy3:
|
||||
yych = *(marker = ++p);
|
||||
if (yybm[0 + yych] & 128) {
|
||||
goto yy22;
|
||||
}
|
||||
if (yych <= '\f') {
|
||||
if (yych == '\t')
|
||||
goto yy29;
|
||||
if (yych <= '\n')
|
||||
goto yy2;
|
||||
goto yy29;
|
||||
} else {
|
||||
if (yych <= ' ') {
|
||||
if (yych <= 0x1F)
|
||||
goto yy2;
|
||||
goto yy29;
|
||||
} else {
|
||||
if (yych == ':')
|
||||
goto yy31;
|
||||
goto yy2;
|
||||
}
|
||||
}
|
||||
yy4:
|
||||
yych = *(marker = ++p);
|
||||
if (yybm[0 + yych] & 128) {
|
||||
goto yy22;
|
||||
}
|
||||
goto yy2;
|
||||
yy5:
|
||||
yych = *(marker = ++p);
|
||||
if (yybm[0 + yych] & 128) {
|
||||
goto yy22;
|
||||
}
|
||||
if (yych <= ' ') {
|
||||
if (yych <= 0x08)
|
||||
goto yy2;
|
||||
if (yych <= '\r')
|
||||
goto yy16;
|
||||
if (yych <= 0x1F)
|
||||
goto yy2;
|
||||
goto yy16;
|
||||
} else {
|
||||
if (yych <= ':') {
|
||||
if (yych <= '9')
|
||||
goto yy2;
|
||||
goto yy15;
|
||||
} else {
|
||||
if (yych == '|')
|
||||
goto yy16;
|
||||
goto yy2;
|
||||
}
|
||||
}
|
||||
yy6:
|
||||
yych = *++p;
|
||||
goto yy2;
|
||||
yy7:
|
||||
yych = *++p;
|
||||
if (yych <= 0x7F)
|
||||
goto yy8;
|
||||
if (yych <= 0xBF)
|
||||
goto yy6;
|
||||
yy8:
|
||||
p = marker;
|
||||
goto yy2;
|
||||
yy9:
|
||||
yych = *++p;
|
||||
if (yych <= 0x9F)
|
||||
goto yy8;
|
||||
if (yych <= 0xBF)
|
||||
goto yy7;
|
||||
goto yy8;
|
||||
yy10:
|
||||
yych = *++p;
|
||||
if (yych <= 0x7F)
|
||||
goto yy8;
|
||||
if (yych <= 0xBF)
|
||||
goto yy7;
|
||||
goto yy8;
|
||||
yy11:
|
||||
yych = *++p;
|
||||
if (yych <= 0x8F)
|
||||
goto yy8;
|
||||
if (yych <= 0xBF)
|
||||
goto yy10;
|
||||
goto yy8;
|
||||
yy12:
|
||||
yych = *++p;
|
||||
if (yych <= 0x7F)
|
||||
goto yy8;
|
||||
if (yych <= 0xBF)
|
||||
goto yy10;
|
||||
goto yy8;
|
||||
yy13:
|
||||
yych = *++p;
|
||||
if (yych <= 0x7F)
|
||||
goto yy8;
|
||||
if (yych <= 0x8F)
|
||||
goto yy10;
|
||||
goto yy8;
|
||||
yy14:
|
||||
yych = *++p;
|
||||
if (yych <= 0x7F)
|
||||
goto yy8;
|
||||
if (yych <= 0x9F)
|
||||
goto yy7;
|
||||
goto yy8;
|
||||
yy15:
|
||||
++p;
|
||||
yych = *p;
|
||||
yy16:
|
||||
if (yybm[0 + yych] & 64) {
|
||||
goto yy15;
|
||||
}
|
||||
if (yych <= '\r') {
|
||||
if (yych <= 0x08)
|
||||
goto yy8;
|
||||
if (yych <= '\n')
|
||||
goto yy20;
|
||||
goto yy19;
|
||||
} else {
|
||||
if (yych != '|')
|
||||
goto yy8;
|
||||
}
|
||||
yy17:
|
||||
++p;
|
||||
yych = *p;
|
||||
if (yych <= 0x1F) {
|
||||
if (yych <= '\n') {
|
||||
if (yych <= 0x08)
|
||||
goto yy8;
|
||||
if (yych <= '\t')
|
||||
goto yy17;
|
||||
goto yy20;
|
||||
} else {
|
||||
if (yych <= '\f')
|
||||
goto yy17;
|
||||
if (yych >= 0x0E)
|
||||
goto yy8;
|
||||
}
|
||||
} else {
|
||||
if (yych <= '-') {
|
||||
if (yych <= ' ')
|
||||
goto yy17;
|
||||
if (yych <= ',')
|
||||
goto yy8;
|
||||
goto yy25;
|
||||
} else {
|
||||
if (yych == ':')
|
||||
goto yy24;
|
||||
goto yy8;
|
||||
}
|
||||
}
|
||||
yy19:
|
||||
yych = *++p;
|
||||
if (yych != '\n')
|
||||
goto yy8;
|
||||
yy20:
|
||||
++p;
|
||||
{ return (bufsize_t)(p - start); }
|
||||
yy22:
|
||||
++p;
|
||||
yych = *p;
|
||||
if (yybm[0 + yych] & 128) {
|
||||
goto yy22;
|
||||
}
|
||||
if (yych <= 0x1F) {
|
||||
if (yych <= '\n') {
|
||||
if (yych <= 0x08)
|
||||
goto yy8;
|
||||
if (yych <= '\t')
|
||||
goto yy15;
|
||||
goto yy20;
|
||||
} else {
|
||||
if (yych <= '\f')
|
||||
goto yy15;
|
||||
if (yych <= '\r')
|
||||
goto yy19;
|
||||
goto yy8;
|
||||
}
|
||||
} else {
|
||||
if (yych <= ':') {
|
||||
if (yych <= ' ')
|
||||
goto yy15;
|
||||
if (yych <= '9')
|
||||
goto yy8;
|
||||
goto yy15;
|
||||
} else {
|
||||
if (yych == '|')
|
||||
goto yy17;
|
||||
goto yy8;
|
||||
}
|
||||
}
|
||||
yy24:
|
||||
++p;
|
||||
yych = *p;
|
||||
if (yych != '-')
|
||||
goto yy8;
|
||||
yy25:
|
||||
++p;
|
||||
yych = *p;
|
||||
if (yych <= ' ') {
|
||||
if (yych <= '\n') {
|
||||
if (yych <= 0x08)
|
||||
goto yy8;
|
||||
if (yych >= '\n')
|
||||
goto yy20;
|
||||
} else {
|
||||
if (yych <= '\f')
|
||||
goto yy27;
|
||||
if (yych <= '\r')
|
||||
goto yy19;
|
||||
if (yych <= 0x1F)
|
||||
goto yy8;
|
||||
}
|
||||
} else {
|
||||
if (yych <= '9') {
|
||||
if (yych == '-')
|
||||
goto yy25;
|
||||
goto yy8;
|
||||
} else {
|
||||
if (yych <= ':')
|
||||
goto yy27;
|
||||
if (yych == '|')
|
||||
goto yy17;
|
||||
goto yy8;
|
||||
}
|
||||
}
|
||||
yy27:
|
||||
++p;
|
||||
yych = *p;
|
||||
if (yych <= '\r') {
|
||||
if (yych <= '\t') {
|
||||
if (yych <= 0x08)
|
||||
goto yy8;
|
||||
goto yy27;
|
||||
} else {
|
||||
if (yych <= '\n')
|
||||
goto yy20;
|
||||
if (yych <= '\f')
|
||||
goto yy27;
|
||||
goto yy19;
|
||||
}
|
||||
} else {
|
||||
if (yych <= ' ') {
|
||||
if (yych <= 0x1F)
|
||||
goto yy8;
|
||||
goto yy27;
|
||||
} else {
|
||||
if (yych == '|')
|
||||
goto yy17;
|
||||
goto yy8;
|
||||
}
|
||||
}
|
||||
yy29:
|
||||
++p;
|
||||
yych = *p;
|
||||
if (yybm[0 + yych] & 128) {
|
||||
goto yy22;
|
||||
}
|
||||
if (yych <= '\f') {
|
||||
if (yych == '\t')
|
||||
goto yy29;
|
||||
if (yych <= '\n')
|
||||
goto yy8;
|
||||
goto yy29;
|
||||
} else {
|
||||
if (yych <= ' ') {
|
||||
if (yych <= 0x1F)
|
||||
goto yy8;
|
||||
goto yy29;
|
||||
} else {
|
||||
if (yych != ':')
|
||||
goto yy8;
|
||||
}
|
||||
}
|
||||
yy31:
|
||||
++p;
|
||||
if (yybm[0 + (yych = *p)] & 128) {
|
||||
goto yy22;
|
||||
}
|
||||
goto yy8;
|
||||
}
|
||||
}
|
||||
16
extensions/ext_scanners.h
Normal file
16
extensions/ext_scanners.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "chunk.h"
|
||||
#include "cmark.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *),
|
||||
unsigned char *ptr, int len, bufsize_t offset);
|
||||
bufsize_t _scan_table_start(const unsigned char *p);
|
||||
|
||||
#define scan_table_start(c, l, n) _ext_scan_at(&_scan_table_start, c, l, n)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
42
extensions/ext_scanners.re
Normal file
42
extensions/ext_scanners.re
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <stdlib.h>
|
||||
#include "ext_scanners.h"
|
||||
|
||||
bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *), unsigned char *ptr, int len, bufsize_t offset)
|
||||
{
|
||||
bufsize_t res;
|
||||
|
||||
if (ptr == NULL || offset > len) {
|
||||
return 0;
|
||||
} else {
|
||||
unsigned char lim = ptr[len];
|
||||
|
||||
ptr[len] = '\0';
|
||||
res = scanner(ptr + offset);
|
||||
ptr[len] = lim;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*!re2c
|
||||
re2c:define:YYCTYPE = "unsigned char";
|
||||
re2c:define:YYCURSOR = p;
|
||||
re2c:define:YYMARKER = marker;
|
||||
re2c:define:YYCTXMARKER = marker;
|
||||
re2c:yyfill:enable = 0;
|
||||
|
||||
spacechar = [ \t\v\f];
|
||||
newline = [\r]?[\n];
|
||||
|
||||
table_marker = (spacechar*[:]?[-]+[:]?spacechar*);
|
||||
*/
|
||||
|
||||
bufsize_t _scan_table_start(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[|]? table_marker ([|] table_marker)* [|]? spacechar* newline { return (bufsize_t)(p - start); }
|
||||
.? { return 0; }
|
||||
*/
|
||||
}
|
||||
687
extensions/table.c
Normal file
687
extensions/table.c
Normal file
@@ -0,0 +1,687 @@
|
||||
#include <html.h>
|
||||
#include <inlines.h>
|
||||
#include <parser.h>
|
||||
#include <references.h>
|
||||
|
||||
#include "ext_scanners.h"
|
||||
#include "table.h"
|
||||
|
||||
static cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW,
|
||||
CMARK_NODE_TABLE_CELL;
|
||||
|
||||
typedef struct {
|
||||
uint16_t n_columns;
|
||||
cmark_llist *cells;
|
||||
} table_row;
|
||||
|
||||
typedef struct {
|
||||
uint16_t n_columns;
|
||||
uint8_t *alignments;
|
||||
} node_table;
|
||||
|
||||
typedef enum {
|
||||
ALIGN_NONE,
|
||||
ALIGN_LEFT,
|
||||
ALIGN_CENTER,
|
||||
ALIGN_RIGHT
|
||||
} table_column_alignment;
|
||||
|
||||
typedef struct { bool is_header; } node_table_row;
|
||||
|
||||
static void free_node_table(cmark_mem *mem, void *ptr) {
|
||||
node_table *t = ptr;
|
||||
mem->free(t->alignments);
|
||||
mem->free(t);
|
||||
}
|
||||
|
||||
static void free_node_table_row(cmark_mem *mem, void *ptr) { mem->free(ptr); }
|
||||
|
||||
static uint16_t get_n_table_columns(cmark_node *node) {
|
||||
if (!node || node->type != CMARK_NODE_TABLE)
|
||||
return -1;
|
||||
|
||||
return ((node_table *)node->user_data)->n_columns;
|
||||
}
|
||||
|
||||
static int set_n_table_columns(cmark_node *node, uint16_t n_columns) {
|
||||
if (!node || node->type != CMARK_NODE_TABLE)
|
||||
return 0;
|
||||
|
||||
((node_table *)node->user_data)->n_columns = n_columns;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint8_t *get_table_alignments(cmark_node *node) {
|
||||
if (!node || node->type != CMARK_NODE_TABLE)
|
||||
return 0;
|
||||
|
||||
return ((node_table *)node->user_data)->alignments;
|
||||
}
|
||||
|
||||
static int set_table_alignments(cmark_node *node, uint8_t *alignments) {
|
||||
if (!node || node->type != CMARK_NODE_TABLE)
|
||||
return 0;
|
||||
|
||||
((node_table *)node->user_data)->alignments = alignments;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int is_table_header(cmark_node *node, int is_table_header) {
|
||||
if (!node || node->type != CMARK_NODE_TABLE_ROW)
|
||||
return 0;
|
||||
|
||||
((node_table_row *)node->user_data)->is_header = is_table_header;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void free_table_cell(cmark_mem *mem, void *data) {
|
||||
cmark_node_free((cmark_node *)data);
|
||||
}
|
||||
|
||||
static void free_table_row(cmark_mem *mem, table_row *row) {
|
||||
if (!row)
|
||||
return;
|
||||
|
||||
cmark_llist_free_full(mem, row->cells, (cmark_free_func)free_table_cell);
|
||||
|
||||
mem->free(row);
|
||||
}
|
||||
|
||||
static void reescape_pipes(cmark_strbuf *strbuf, cmark_mem *mem,
|
||||
unsigned char *string, bufsize_t len) {
|
||||
bufsize_t r;
|
||||
|
||||
cmark_strbuf_init(mem, strbuf, len * 2);
|
||||
for (r = 0; r < len; ++r) {
|
||||
if (string[r] == '\\' && r + 1 < len &&
|
||||
(string[r + 1] == '|' || string[r + 1] == '\\'))
|
||||
cmark_strbuf_putc(strbuf, '\\');
|
||||
|
||||
cmark_strbuf_putc(strbuf, string[r]);
|
||||
}
|
||||
}
|
||||
|
||||
static void maybe_consume_pipe(cmark_node **n, int *offset) {
|
||||
if (*n && (*n)->type == CMARK_NODE_TEXT && *offset < (*n)->as.literal.len &&
|
||||
(*n)->as.literal.data[*offset] == '|')
|
||||
++(*offset);
|
||||
}
|
||||
|
||||
static const char *find_unescaped_pipe(const char *cstr, size_t len) {
|
||||
bool escaping = false;
|
||||
for (; len; --len, ++cstr) {
|
||||
if (escaping)
|
||||
escaping = false;
|
||||
else if (*cstr == '\\')
|
||||
escaping = true;
|
||||
else if (*cstr == '|')
|
||||
return cstr;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static cmark_node *consume_until_pipe_or_eol(cmark_syntax_extension *self,
|
||||
cmark_parser *parser,
|
||||
cmark_node **n, int *offset) {
|
||||
cmark_node *result =
|
||||
cmark_node_new_with_mem(CMARK_NODE_TABLE_CELL, parser->mem);
|
||||
cmark_node_set_syntax_extension(result, self);
|
||||
bool was_escape = false;
|
||||
|
||||
while (*n) {
|
||||
if ((*n)->type == CMARK_NODE_TEXT) {
|
||||
cmark_node *child = cmark_parser_add_child(
|
||||
parser, result, CMARK_NODE_TEXT, cmark_parser_get_offset(parser));
|
||||
|
||||
const char *cstr = cmark_chunk_to_cstr(parser->mem, &(*n)->as.literal);
|
||||
|
||||
if (was_escape) {
|
||||
child->as.literal = cmark_chunk_dup(&(*n)->as.literal, *offset, 1);
|
||||
cmark_node_own(child);
|
||||
++*offset;
|
||||
was_escape = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(cstr + *offset, "\\") == 0 && (*n)->next &&
|
||||
(*n)->next->type == CMARK_NODE_TEXT) {
|
||||
was_escape = true;
|
||||
*n = (*n)->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *pipe =
|
||||
find_unescaped_pipe(cstr + *offset, (*n)->as.literal.len - *offset);
|
||||
|
||||
if (!pipe) {
|
||||
child->as.literal = cmark_chunk_dup(&(*n)->as.literal, *offset,
|
||||
(*n)->as.literal.len - *offset);
|
||||
cmark_node_own(child);
|
||||
} else {
|
||||
int len = pipe - cstr - *offset;
|
||||
child->as.literal = cmark_chunk_dup(&(*n)->as.literal, *offset, len);
|
||||
cmark_node_own(child);
|
||||
*offset += len + 1;
|
||||
if (*offset >= (*n)->as.literal.len) {
|
||||
*offset = 0;
|
||||
*n = (*n)->next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
*n = (*n)->next;
|
||||
*offset = 0;
|
||||
} else {
|
||||
cmark_node *next = (*n)->next;
|
||||
cmark_node_append_child(result, *n);
|
||||
cmark_node_own(*n);
|
||||
*n = next;
|
||||
*offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result->first_child) {
|
||||
cmark_node_free(result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static table_row *row_from_string(cmark_syntax_extension *self,
|
||||
cmark_parser *parser, unsigned char *string,
|
||||
int len) {
|
||||
table_row *row = NULL;
|
||||
|
||||
cmark_node *temp_container =
|
||||
cmark_node_new_with_mem(CMARK_NODE_PARAGRAPH, parser->mem);
|
||||
reescape_pipes(&temp_container->content, parser->mem, string, len);
|
||||
|
||||
cmark_manage_extensions_special_characters(parser, true);
|
||||
cmark_parse_inlines(parser, temp_container, parser->refmap, parser->options);
|
||||
cmark_manage_extensions_special_characters(parser, false);
|
||||
|
||||
if (!temp_container->first_child) {
|
||||
cmark_node_free(temp_container);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
row = (table_row *)parser->mem->calloc(1, sizeof(table_row));
|
||||
row->n_columns = 0;
|
||||
row->cells = NULL;
|
||||
|
||||
cmark_node *node = temp_container->first_child;
|
||||
int offset = 0;
|
||||
|
||||
maybe_consume_pipe(&node, &offset);
|
||||
cmark_node *child;
|
||||
while ((child = consume_until_pipe_or_eol(self, parser, &node, &offset)) !=
|
||||
NULL) {
|
||||
++row->n_columns;
|
||||
row->cells = cmark_llist_append(parser->mem, row->cells, child);
|
||||
}
|
||||
|
||||
cmark_node_free(temp_container);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
|
||||
cmark_parser *parser,
|
||||
cmark_node *parent_container,
|
||||
unsigned char *input, int len) {
|
||||
bufsize_t matched =
|
||||
scan_table_start(input, len, cmark_parser_get_first_nonspace(parser));
|
||||
cmark_node *table_header, *child;
|
||||
table_row *header_row = NULL;
|
||||
table_row *marker_row = NULL;
|
||||
const char *parent_string;
|
||||
uint16_t i;
|
||||
|
||||
if (!matched)
|
||||
goto done;
|
||||
|
||||
parent_string = cmark_node_get_string_content(parent_container);
|
||||
|
||||
header_row = row_from_string(self, parser, (unsigned char *)parent_string,
|
||||
strlen(parent_string));
|
||||
|
||||
if (!header_row) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
marker_row = row_from_string(self, parser,
|
||||
input + cmark_parser_get_first_nonspace(parser),
|
||||
len - cmark_parser_get_first_nonspace(parser));
|
||||
|
||||
assert(marker_row);
|
||||
|
||||
if (header_row->n_columns != marker_row->n_columns) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
cmark_node_set_syntax_extension(parent_container, self);
|
||||
|
||||
cmark_node_set_user_data(parent_container,
|
||||
parser->mem->calloc(1, sizeof(node_table)));
|
||||
cmark_node_set_user_data_free_func(parent_container, free_node_table);
|
||||
|
||||
set_n_table_columns(parent_container, header_row->n_columns);
|
||||
|
||||
uint8_t *alignments =
|
||||
parser->mem->calloc(header_row->n_columns, sizeof(uint8_t));
|
||||
cmark_llist *it = marker_row->cells;
|
||||
for (i = 0; it; it = it->next, ++i) {
|
||||
cmark_node *node = it->data;
|
||||
assert(node->type == CMARK_NODE_TABLE_CELL);
|
||||
|
||||
cmark_strbuf strbuf;
|
||||
cmark_strbuf_init(parser->mem, &strbuf, 0);
|
||||
for (child = node->first_child; child; child = child->next) {
|
||||
assert(child->type == CMARK_NODE_TEXT);
|
||||
cmark_strbuf_put(&strbuf, child->as.literal.data, child->as.literal.len);
|
||||
}
|
||||
cmark_strbuf_trim(&strbuf);
|
||||
char const *text = cmark_strbuf_cstr(&strbuf);
|
||||
|
||||
bool left = text[0] == ':', right = text[strbuf.size - 1] == ':';
|
||||
cmark_strbuf_free(&strbuf);
|
||||
|
||||
if (left && right)
|
||||
alignments[i] = ALIGN_CENTER;
|
||||
else if (left)
|
||||
alignments[i] = ALIGN_LEFT;
|
||||
else if (right)
|
||||
alignments[i] = ALIGN_RIGHT;
|
||||
}
|
||||
set_table_alignments(parent_container, alignments);
|
||||
|
||||
table_header =
|
||||
cmark_parser_add_child(parser, parent_container, CMARK_NODE_TABLE_ROW,
|
||||
cmark_parser_get_offset(parser));
|
||||
cmark_node_set_syntax_extension(table_header, self);
|
||||
|
||||
cmark_node_set_user_data(table_header,
|
||||
parser->mem->calloc(1, sizeof(node_table_row)));
|
||||
cmark_node_set_user_data_free_func(table_header, free_node_table_row);
|
||||
is_table_header(table_header, true);
|
||||
|
||||
{
|
||||
cmark_llist *tmp, *next;
|
||||
|
||||
for (tmp = header_row->cells; tmp; tmp = next) {
|
||||
cmark_node *header_cell = tmp->data;
|
||||
cmark_node_append_child(table_header, header_cell);
|
||||
next = header_row->cells = tmp->next;
|
||||
parser->mem->free(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
cmark_parser_advance_offset(
|
||||
parser, (char *)input,
|
||||
strlen((char *)input) - 1 - cmark_parser_get_offset(parser), false);
|
||||
done:
|
||||
free_table_row(parser->mem, header_row);
|
||||
free_table_row(parser->mem, marker_row);
|
||||
return parent_container;
|
||||
}
|
||||
|
||||
static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
|
||||
cmark_parser *parser,
|
||||
cmark_node *parent_container,
|
||||
unsigned char *input, int len) {
|
||||
cmark_node *table_row_block;
|
||||
table_row *row;
|
||||
|
||||
if (cmark_parser_is_blank(parser))
|
||||
return NULL;
|
||||
|
||||
table_row_block =
|
||||
cmark_parser_add_child(parser, parent_container, CMARK_NODE_TABLE_ROW,
|
||||
cmark_parser_get_offset(parser));
|
||||
|
||||
cmark_node_set_syntax_extension(table_row_block, self);
|
||||
cmark_node_set_user_data(table_row_block,
|
||||
parser->mem->calloc(1, sizeof(node_table_row)));
|
||||
cmark_node_set_user_data_free_func(table_row_block, free_node_table_row);
|
||||
|
||||
/* We don't advance the offset here */
|
||||
|
||||
row = row_from_string(self, parser,
|
||||
input + cmark_parser_get_first_nonspace(parser),
|
||||
len - cmark_parser_get_first_nonspace(parser));
|
||||
|
||||
{
|
||||
cmark_llist *tmp, *next;
|
||||
int i;
|
||||
int table_columns = get_n_table_columns(parent_container);
|
||||
|
||||
for (tmp = row->cells, i = 0; tmp && i < table_columns; tmp = next, ++i) {
|
||||
cmark_node *cell = tmp->data;
|
||||
assert(cell->type == CMARK_NODE_TABLE_CELL);
|
||||
cmark_node_append_child(table_row_block, cell);
|
||||
row->cells = next = tmp->next;
|
||||
parser->mem->free(tmp);
|
||||
}
|
||||
|
||||
for (; i < table_columns; ++i) {
|
||||
cmark_node *cell =
|
||||
cmark_parser_add_child(parser, table_row_block, CMARK_NODE_TABLE_CELL,
|
||||
cmark_parser_get_offset(parser));
|
||||
cmark_node_set_syntax_extension(cell, self);
|
||||
}
|
||||
}
|
||||
|
||||
free_table_row(parser->mem, row);
|
||||
|
||||
cmark_parser_advance_offset(parser, (char *)input,
|
||||
len - 1 - cmark_parser_get_offset(parser), false);
|
||||
|
||||
return table_row_block;
|
||||
}
|
||||
|
||||
static cmark_node *try_opening_table_block(cmark_syntax_extension *self,
|
||||
int indented, cmark_parser *parser,
|
||||
cmark_node *parent_container,
|
||||
unsigned char *input, int len) {
|
||||
cmark_node_type parent_type = cmark_node_get_type(parent_container);
|
||||
|
||||
if (!indented && parent_type == CMARK_NODE_PARAGRAPH) {
|
||||
return try_opening_table_header(self, parser, parent_container, input, len);
|
||||
} else if (!indented && parent_type == CMARK_NODE_TABLE) {
|
||||
return try_opening_table_row(self, parser, parent_container, input, len);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int matches(cmark_syntax_extension *self, cmark_parser *parser,
|
||||
unsigned char *input, int len,
|
||||
cmark_node *parent_container) {
|
||||
int res = 0;
|
||||
|
||||
if (cmark_node_get_type(parent_container) == CMARK_NODE_TABLE) {
|
||||
table_row *new_row = row_from_string(
|
||||
self, parser, input + cmark_parser_get_first_nonspace(parser),
|
||||
len - cmark_parser_get_first_nonspace(parser));
|
||||
if (new_row && new_row->n_columns)
|
||||
res = 1;
|
||||
free_table_row(parser->mem, new_row);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static const char *get_type_string(cmark_syntax_extension *ext,
|
||||
cmark_node *node) {
|
||||
if (node->type == CMARK_NODE_TABLE) {
|
||||
return "table";
|
||||
} else if (node->type == CMARK_NODE_TABLE_ROW) {
|
||||
if (((node_table_row *)node->user_data)->is_header)
|
||||
return "table_header";
|
||||
else
|
||||
return "table_row";
|
||||
} else if (node->type == CMARK_NODE_TABLE_CELL) {
|
||||
return "table_cell";
|
||||
}
|
||||
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
|
||||
cmark_node_type child_type) {
|
||||
if (node->type == CMARK_NODE_TABLE) {
|
||||
return child_type == CMARK_NODE_TABLE_ROW;
|
||||
} else if (node->type == CMARK_NODE_TABLE_ROW) {
|
||||
return child_type == CMARK_NODE_TABLE_CELL;
|
||||
} else if (node->type == CMARK_NODE_TABLE_CELL) {
|
||||
return child_type == CMARK_NODE_TEXT || child_type == CMARK_NODE_CODE ||
|
||||
child_type == CMARK_NODE_EMPH || child_type == CMARK_NODE_STRONG ||
|
||||
child_type == CMARK_NODE_LINK || child_type == CMARK_NODE_IMAGE ||
|
||||
child_type == CMARK_NODE_HTML_INLINE;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int contains_inlines(cmark_syntax_extension *extension,
|
||||
cmark_node *node) {
|
||||
return node->type == CMARK_NODE_TABLE_CELL;
|
||||
}
|
||||
|
||||
static void commonmark_render(cmark_syntax_extension *extension,
|
||||
cmark_renderer *renderer, cmark_node *node,
|
||||
cmark_event_type ev_type, int options) {
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
|
||||
if (node->type == CMARK_NODE_TABLE) {
|
||||
renderer->blankline(renderer);
|
||||
} else if (node->type == CMARK_NODE_TABLE_ROW) {
|
||||
if (entering) {
|
||||
renderer->cr(renderer);
|
||||
renderer->out(renderer, "|", false, LITERAL);
|
||||
}
|
||||
} else if (node->type == CMARK_NODE_TABLE_CELL) {
|
||||
if (entering) {
|
||||
} else {
|
||||
renderer->out(renderer, " |", false, LITERAL);
|
||||
if (((node_table_row *)node->parent->user_data)->is_header &&
|
||||
!node->next) {
|
||||
int i;
|
||||
uint8_t *alignments = get_table_alignments(node->parent->parent);
|
||||
uint16_t n_cols =
|
||||
((node_table *)node->parent->parent->user_data)->n_columns;
|
||||
renderer->cr(renderer);
|
||||
renderer->out(renderer, "|", false, LITERAL);
|
||||
for (i = 0; i < n_cols; i++) {
|
||||
if (alignments[i] == ALIGN_NONE)
|
||||
renderer->out(renderer, " --- |", false, LITERAL);
|
||||
else if (alignments[i] == ALIGN_LEFT)
|
||||
renderer->out(renderer, " :-- |", false, LITERAL);
|
||||
else if (alignments[i] == ALIGN_CENTER)
|
||||
renderer->out(renderer, " :-: |", false, LITERAL);
|
||||
else if (alignments[i] == ALIGN_RIGHT)
|
||||
renderer->out(renderer, " --: |", false, LITERAL);
|
||||
}
|
||||
renderer->cr(renderer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void latex_render(cmark_syntax_extension *extension,
|
||||
cmark_renderer *renderer, cmark_node *node,
|
||||
cmark_event_type ev_type, int options) {
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
|
||||
if (node->type == CMARK_NODE_TABLE) {
|
||||
if (entering) {
|
||||
int i;
|
||||
uint16_t n_cols;
|
||||
renderer->cr(renderer);
|
||||
renderer->out(renderer, "\\begin{table}", false, LITERAL);
|
||||
renderer->cr(renderer);
|
||||
renderer->out(renderer, "\\begin{tabular}{", false, LITERAL);
|
||||
|
||||
n_cols = ((node_table *)node->user_data)->n_columns;
|
||||
for (i = 0; i < n_cols; i++) {
|
||||
renderer->out(renderer, "l", false, LITERAL);
|
||||
}
|
||||
renderer->out(renderer, "}", false, LITERAL);
|
||||
renderer->cr(renderer);
|
||||
} else {
|
||||
renderer->out(renderer, "\\end{tabular}", false, LITERAL);
|
||||
renderer->cr(renderer);
|
||||
renderer->out(renderer, "\\end{table}", false, LITERAL);
|
||||
renderer->cr(renderer);
|
||||
}
|
||||
} else if (node->type == CMARK_NODE_TABLE_ROW) {
|
||||
if (!entering) {
|
||||
renderer->cr(renderer);
|
||||
}
|
||||
} else if (node->type == CMARK_NODE_TABLE_CELL) {
|
||||
if (!entering) {
|
||||
if (node->next) {
|
||||
renderer->out(renderer, " & ", false, LITERAL);
|
||||
} else {
|
||||
renderer->out(renderer, " \\\\", false, LITERAL);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void man_render(cmark_syntax_extension *extension,
|
||||
cmark_renderer *renderer, cmark_node *node,
|
||||
cmark_event_type ev_type, int options) {
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
|
||||
if (node->type == CMARK_NODE_TABLE) {
|
||||
if (entering) {
|
||||
int i;
|
||||
uint16_t n_cols;
|
||||
renderer->cr(renderer);
|
||||
renderer->out(renderer, ".TS", false, LITERAL);
|
||||
renderer->cr(renderer);
|
||||
renderer->out(renderer, "tab(@);", false, LITERAL);
|
||||
renderer->cr(renderer);
|
||||
|
||||
n_cols = ((node_table *)node->user_data)->n_columns;
|
||||
|
||||
for (i = 0; i < n_cols; i++) {
|
||||
renderer->out(renderer, "c", false, LITERAL);
|
||||
}
|
||||
|
||||
if (n_cols) {
|
||||
renderer->out(renderer, ".", false, LITERAL);
|
||||
renderer->cr(renderer);
|
||||
}
|
||||
} else {
|
||||
renderer->out(renderer, ".TE", false, LITERAL);
|
||||
renderer->cr(renderer);
|
||||
}
|
||||
} else if (node->type == CMARK_NODE_TABLE_ROW) {
|
||||
if (!entering) {
|
||||
renderer->cr(renderer);
|
||||
}
|
||||
} else if (node->type == CMARK_NODE_TABLE_CELL) {
|
||||
if (!entering && node->next) {
|
||||
renderer->out(renderer, "@", false, LITERAL);
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
struct html_table_state {
|
||||
unsigned need_closing_table_body : 1;
|
||||
unsigned in_table_header : 1;
|
||||
};
|
||||
|
||||
static void html_render(cmark_syntax_extension *extension,
|
||||
cmark_html_renderer *renderer, cmark_node *node,
|
||||
cmark_event_type ev_type, int options) {
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
cmark_strbuf *html = renderer->html;
|
||||
cmark_node *n;
|
||||
|
||||
// XXX: we just monopolise renderer->opaque.
|
||||
struct html_table_state *table_state =
|
||||
(struct html_table_state *)&renderer->opaque;
|
||||
|
||||
if (node->type == CMARK_NODE_TABLE) {
|
||||
if (entering) {
|
||||
cmark_html_render_cr(html);
|
||||
cmark_strbuf_puts(html, "<table");
|
||||
cmark_html_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_putc(html, '>');
|
||||
table_state->need_closing_table_body = false;
|
||||
} else {
|
||||
if (table_state->need_closing_table_body)
|
||||
cmark_strbuf_puts(html, "</tbody>");
|
||||
table_state->need_closing_table_body = false;
|
||||
cmark_strbuf_puts(html, "</table>\n");
|
||||
}
|
||||
} else if (node->type == CMARK_NODE_TABLE_ROW) {
|
||||
if (entering) {
|
||||
cmark_html_render_cr(html);
|
||||
if (((node_table_row *)node->user_data)->is_header) {
|
||||
table_state->in_table_header = 1;
|
||||
cmark_strbuf_puts(html, "<thead>");
|
||||
cmark_html_render_cr(html);
|
||||
}
|
||||
cmark_strbuf_puts(html, "<tr");
|
||||
cmark_html_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_putc(html, '>');
|
||||
} else {
|
||||
cmark_html_render_cr(html);
|
||||
cmark_strbuf_puts(html, "</tr>");
|
||||
if (((node_table_row *)node->user_data)->is_header) {
|
||||
cmark_html_render_cr(html);
|
||||
cmark_strbuf_puts(html, "</thead>");
|
||||
cmark_html_render_cr(html);
|
||||
cmark_strbuf_puts(html, "<tbody>");
|
||||
table_state->need_closing_table_body = 1;
|
||||
table_state->in_table_header = false;
|
||||
}
|
||||
}
|
||||
} else if (node->type == CMARK_NODE_TABLE_CELL) {
|
||||
uint8_t *alignments = get_table_alignments(node->parent->parent);
|
||||
if (entering) {
|
||||
cmark_html_render_cr(html);
|
||||
if (table_state->in_table_header) {
|
||||
cmark_strbuf_puts(html, "<th");
|
||||
} else {
|
||||
cmark_strbuf_puts(html, "<td");
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (n = node->parent->first_child; n; n = n->next, ++i)
|
||||
if (n == node)
|
||||
break;
|
||||
|
||||
if (alignments[i] == ALIGN_LEFT)
|
||||
cmark_strbuf_puts(html, " align=\"left\"");
|
||||
else if (alignments[i] == ALIGN_CENTER)
|
||||
cmark_strbuf_puts(html, " align=\"center\"");
|
||||
else if (alignments[i] == ALIGN_RIGHT)
|
||||
cmark_strbuf_puts(html, " align=\"right\"");
|
||||
|
||||
cmark_html_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_putc(html, '>');
|
||||
} else {
|
||||
if (table_state->in_table_header) {
|
||||
cmark_strbuf_puts(html, "</th>");
|
||||
} else {
|
||||
cmark_strbuf_puts(html, "</td>");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
cmark_syntax_extension *create_table_extension(void) {
|
||||
cmark_syntax_extension *ext = cmark_syntax_extension_new("table");
|
||||
|
||||
cmark_syntax_extension_set_match_block_func(ext, matches);
|
||||
cmark_syntax_extension_set_open_block_func(ext, try_opening_table_block);
|
||||
cmark_syntax_extension_set_get_type_string_func(ext, get_type_string);
|
||||
cmark_syntax_extension_set_can_contain_func(ext, can_contain);
|
||||
cmark_syntax_extension_set_contains_inlines_func(ext, contains_inlines);
|
||||
cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
|
||||
cmark_syntax_extension_set_latex_render_func(ext, latex_render);
|
||||
cmark_syntax_extension_set_man_render_func(ext, man_render);
|
||||
cmark_syntax_extension_set_html_render_func(ext, html_render);
|
||||
CMARK_NODE_TABLE = cmark_syntax_extension_add_node(0);
|
||||
CMARK_NODE_TABLE_ROW = cmark_syntax_extension_add_node(0);
|
||||
CMARK_NODE_TABLE_CELL = cmark_syntax_extension_add_node(0);
|
||||
|
||||
return ext;
|
||||
}
|
||||
8
extensions/table.h
Normal file
8
extensions/table.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef TABLE_H
|
||||
#define TABLE_H
|
||||
|
||||
#include "core-extensions.h"
|
||||
|
||||
cmark_syntax_extension *create_table_extension(void);
|
||||
|
||||
#endif
|
||||
@@ -130,6 +130,51 @@ memory. Memory in these slabs is not reused at all.
|
||||
Resets the arena allocator, quickly returning all used memory to the
|
||||
operating system.
|
||||
|
||||
.PP
|
||||
\fItypedef\f[] \fBvoid\f[](\fI*cmark_free_func\f[])
|
||||
|
||||
.PP
|
||||
Callback for freeing user data with a \f[I]cmark_mem\f[] context.
|
||||
|
||||
.SS
|
||||
Linked list
|
||||
|
||||
.PP
|
||||
.nf
|
||||
\fC
|
||||
.RS 0n
|
||||
typedef struct _cmark_llist
|
||||
{
|
||||
struct _cmark_llist *next;
|
||||
void *data;
|
||||
} cmark_llist;
|
||||
.RE
|
||||
\f[]
|
||||
.fi
|
||||
|
||||
.PP
|
||||
A generic singly linked list.
|
||||
|
||||
.PP
|
||||
\fIcmark_llist *\f[] \fBcmark_llist_append\f[](\fIcmark_mem * mem\f[], \fIcmark_llist * head\f[], \fIvoid * data\f[])
|
||||
|
||||
.PP
|
||||
Append an element to the linked list, return the possibly modified head
|
||||
of the list.
|
||||
|
||||
.PP
|
||||
\fIvoid\f[] \fBcmark_llist_free_full\f[](\fIcmark_mem * mem\f[], \fIcmark_llist * head\f[], \fIcmark_free_func free_func\f[])
|
||||
|
||||
.PP
|
||||
Free the list starting with \f[I]head\f[], calling \f[I]free_func\f[]
|
||||
with the data pointer of each of its elements
|
||||
|
||||
.PP
|
||||
\fIvoid\f[] \fBcmark_llist_free\f[](\fIcmark_mem * mem\f[], \fIcmark_llist * head\f[])
|
||||
|
||||
.PP
|
||||
Free the list starting with \f[I]head\f[]
|
||||
|
||||
.SS
|
||||
Creating and Destroying Nodes
|
||||
|
||||
@@ -328,6 +373,12 @@ Returns the user data of \f[I]node\f[].
|
||||
Sets arbitrary user data for \f[I]node\f[]. Returns 1 on success, 0 on
|
||||
failure.
|
||||
|
||||
.PP
|
||||
\fIint\f[] \fBcmark_node_set_user_data_free_func\f[](\fIcmark_node *node\f[], \fIcmark_free_func free_func\f[])
|
||||
|
||||
.PP
|
||||
Set free function for user data */
|
||||
|
||||
.PP
|
||||
\fIcmark_node_type\f[] \fBcmark_node_get_type\f[](\fIcmark_node *node\f[])
|
||||
|
||||
@@ -437,6 +488,18 @@ Returns the info string from a fenced code block.
|
||||
Sets the info string in a fenced code block, returning 1 on success
|
||||
and 0 on failure.
|
||||
|
||||
.PP
|
||||
\fIint\f[] \fBcmark_node_set_fenced\f[](\fIcmark_node * node\f[], \fIint fenced\f[], \fIint length\f[], \fIint offset\f[], \fIchar character\f[])
|
||||
|
||||
.PP
|
||||
Sets code blocks fencing details
|
||||
|
||||
.PP
|
||||
\fIint\f[] \fBcmark_node_get_fenced\f[](\fIcmark_node *node\f[], \fIint *length\f[], \fIint *offset\f[], \fIchar *character\f[])
|
||||
|
||||
.PP
|
||||
Returns code blocks fencing details
|
||||
|
||||
.PP
|
||||
\fIconst char *\f[] \fBcmark_node_get_url\f[](\fIcmark_node *node\f[])
|
||||
|
||||
@@ -575,6 +638,12 @@ Returns 1 on success, 0 on failure.
|
||||
.PP
|
||||
Consolidates adjacent text nodes.
|
||||
|
||||
.PP
|
||||
\fIvoid\f[] \fBcmark_node_own\f[](\fIcmark_node *root\f[])
|
||||
|
||||
.PP
|
||||
Ensures a node and all its children own their own chunk memory.
|
||||
|
||||
.SS
|
||||
Parsing
|
||||
.PP
|
||||
@@ -669,7 +738,7 @@ As for \f[I]cmark_render_xml\f[], but specifying the allocator to use
|
||||
for the resulting string.
|
||||
|
||||
.PP
|
||||
\fIchar *\f[] \fBcmark_render_html\f[](\fIcmark_node *root\f[], \fIint options\f[])
|
||||
\fIchar *\f[] \fBcmark_render_html\f[](\fIcmark_node *root\f[], \fIint options\f[], \fIcmark_llist *extensions\f[])
|
||||
|
||||
.PP
|
||||
Render a \f[I]node\f[] tree as an HTML fragment. It is up to the user to
|
||||
@@ -677,7 +746,7 @@ add an appropriate header and footer. It is the caller's responsibility
|
||||
to free the returned buffer.
|
||||
|
||||
.PP
|
||||
\fIchar *\f[] \fBcmark_render_html_with_mem\f[](\fIcmark_node *root\f[], \fIint options\f[], \fIcmark_mem *mem\f[])
|
||||
\fIchar *\f[] \fBcmark_render_html_with_mem\f[](\fIcmark_node *root\f[], \fIint options\f[], \fIcmark_llist *extensions\f[], \fIcmark_mem *mem\f[])
|
||||
|
||||
.PP
|
||||
As for \f[I]cmark_render_html\f[], but specifying the allocator to use
|
||||
|
||||
@@ -34,7 +34,7 @@ static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_escaping escape,
|
||||
c < 0x80 && escape != LITERAL &&
|
||||
((escape == NORMAL &&
|
||||
(c == '*' || c == '_' || c == '[' || c == ']' || c == '#' || c == '<' ||
|
||||
c == '>' || c == '\\' || c == '`' || c == '!' ||
|
||||
c == '>' || c == '\\' || c == '`' || c == '!' || c == '|' ||
|
||||
(c == '&' && cmark_isalpha(nextc)) || (c == '!' && nextc == '[') ||
|
||||
(renderer->begin_content && (c == '-' || c == '+' || c == '=') &&
|
||||
// begin_content doesn't get set to false til we've passed digits
|
||||
|
||||
10
suppressions
Normal file
10
suppressions
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
.
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:__smakebuf
|
||||
fun:__srefill0
|
||||
fun:__fread
|
||||
fun:fread
|
||||
fun:main
|
||||
}
|
||||
@@ -41,16 +41,15 @@ IF (PYTHONINTERP_FOUND)
|
||||
"--library-dir" "${CMAKE_CURRENT_BINARY_DIR}/../src"
|
||||
)
|
||||
|
||||
add_test(roundtriptest_executable
|
||||
add_test(roundtriptest_library
|
||||
${PYTHON_EXECUTABLE}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/roundtrip_tests.py"
|
||||
"--spec" "${CMAKE_CURRENT_SOURCE_DIR}/spec.txt"
|
||||
"--library-dir" "${CMAKE_CURRENT_BINARY_DIR}/../src"
|
||||
)
|
||||
|
||||
add_test(entity_executable
|
||||
${PYTHON_EXECUTABLE}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/entity_tests.py"
|
||||
add_test(entity_library
|
||||
${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/entity_tests.py"
|
||||
"--library-dir" "${CMAKE_CURRENT_BINARY_DIR}/../src"
|
||||
)
|
||||
endif()
|
||||
@@ -63,6 +62,17 @@ IF (PYTHONINTERP_FOUND)
|
||||
${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/spec_tests.py" "--no-normalize" "--spec" "${CMAKE_CURRENT_SOURCE_DIR}/smart_punct.txt" "--program" "${CMAKE_CURRENT_BINARY_DIR}/../src/cmark --smart"
|
||||
)
|
||||
|
||||
add_test(extensions_executable
|
||||
${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/spec_tests.py" "--no-normalize" "--spec" "${CMAKE_CURRENT_SOURCE_DIR}/extensions.txt" "--program" "${CMAKE_CURRENT_BINARY_DIR}/../src/cmark -e table"
|
||||
)
|
||||
|
||||
add_test(roundtrip_extensions_executable
|
||||
${PYTHON_EXECUTABLE}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/roundtrip_tests.py"
|
||||
"--spec" "${CMAKE_CURRENT_SOURCE_DIR}/extensions.txt"
|
||||
"--program" "${CMAKE_CURRENT_BINARY_DIR}/../src/cmark -e table"
|
||||
)
|
||||
|
||||
add_test(regressiontest_executable
|
||||
${PYTHON_EXECUTABLE}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/spec_tests.py" "--no-normalize" "--spec"
|
||||
@@ -70,6 +80,7 @@ IF (PYTHONINTERP_FOUND)
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/../src/cmark"
|
||||
)
|
||||
|
||||
|
||||
ELSE(PYTHONINTERP_FOUND)
|
||||
|
||||
message("\n*** A python 3 interpreter is required to run the spec tests.\n")
|
||||
|
||||
@@ -32,5 +32,8 @@ cb
|
||||
<b>x</b>
|
||||
</div>
|
||||
|
||||
[f]: /u "t"
|
||||
| a | b |
|
||||
| --- | --- |
|
||||
| c | `d|` \| e |
|
||||
|
||||
[f]: /u "t"
|
||||
|
||||
360
test/extensions.txt
Normal file
360
test/extensions.txt
Normal file
@@ -0,0 +1,360 @@
|
||||
---
|
||||
title: Extensions test
|
||||
author: Yuki Izumi
|
||||
version: 0.1
|
||||
date: '2016-08-31'
|
||||
license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)'
|
||||
...
|
||||
|
||||
## Tables
|
||||
|
||||
Here's a well-formed table, doing everything it should.
|
||||
|
||||
```````````````````````````````` example
|
||||
| abc | def |
|
||||
| --- | --- |
|
||||
| ghi | jkl |
|
||||
| mno | pqr |
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> abc </th>
|
||||
<th> def </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> ghi </td>
|
||||
<td> jkl </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> mno </td>
|
||||
<td> pqr </td>
|
||||
</tr></tbody></table>
|
||||
````````````````````````````````
|
||||
|
||||
We're going to mix up the table now; we'll demonstrate that inline formatting
|
||||
works fine, but block elements don't. You can also have empty cells, and the
|
||||
textual alignment of the columns is shown to be irrelevant.
|
||||
|
||||
```````````````````````````````` example
|
||||
Hello!
|
||||
|
||||
| _abc_ | セン |
|
||||
| ----- | ---- |
|
||||
| 1. Block elements inside cells don't work. | |
|
||||
| But **_inline elements do_**. | x |
|
||||
|
||||
Hi!
|
||||
.
|
||||
<p>Hello!</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> <em>abc</em> </th>
|
||||
<th> セン </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> 1. Block elements inside cells don't work. </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> But <strong><em>inline elements do</em></strong>. </td>
|
||||
<td> x </td>
|
||||
</tr></tbody></table>
|
||||
<p>Hi!</p>
|
||||
````````````````````````````````
|
||||
|
||||
Here we demonstrate some edge cases about what is and isn't a table.
|
||||
|
||||
```````````````````````````````` example
|
||||
| Not enough table | to be considered table |
|
||||
|
||||
| Not enough table | to be considered table |
|
||||
| Not enough table | to be considered table |
|
||||
|
||||
| Just enough table | to be considered table |
|
||||
| ----------------- | ---------------------- |
|
||||
|
||||
| ---- | --- |
|
||||
|
||||
|x|
|
||||
|-|
|
||||
|
||||
| xyz |
|
||||
| --- |
|
||||
.
|
||||
<p>| Not enough table | to be considered table |</p>
|
||||
<p>| Not enough table | to be considered table |
|
||||
| Not enough table | to be considered table |</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> Just enough table </th>
|
||||
<th> to be considered table </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody></table>
|
||||
<p>| ---- | --- |</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>x</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody></table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> xyz </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody></table>
|
||||
````````````````````````````````
|
||||
|
||||
A "simpler" table, GFM style:
|
||||
|
||||
```````````````````````````````` example
|
||||
abc | def
|
||||
--- | ---
|
||||
xyz | ghi
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>abc </th>
|
||||
<th> def</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>xyz </td>
|
||||
<td> ghi</td>
|
||||
</tr></tbody></table>
|
||||
````````````````````````````````
|
||||
|
||||
We are making the parser slighly more lax here. Here is a table with spaces at
|
||||
the end:
|
||||
|
||||
```````````````````````````````` example
|
||||
Hello!
|
||||
|
||||
| _abc_ | セン |
|
||||
| ----- | ---- |
|
||||
| this row has a space at the end | |
|
||||
| But **_inline elements do_**. | x |
|
||||
|
||||
Hi!
|
||||
.
|
||||
<p>Hello!</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> <em>abc</em> </th>
|
||||
<th> セン </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> this row has a space at the end </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> But <strong><em>inline elements do</em></strong>. </td>
|
||||
<td> x </td>
|
||||
</tr></tbody></table>
|
||||
<p>Hi!</p>
|
||||
````````````````````````````````
|
||||
|
||||
Table alignment:
|
||||
|
||||
```````````````````````````````` example
|
||||
aaa | bbb | ccc | ddd | eee
|
||||
:-- | --- | :-: | --- | --:
|
||||
fff | ggg | hhh | iii | jjj
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">aaa </th>
|
||||
<th> bbb </th>
|
||||
<th align="center"> ccc </th>
|
||||
<th> ddd </th>
|
||||
<th align="right"> eee</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left">fff </td>
|
||||
<td> ggg </td>
|
||||
<td align="center"> hhh </td>
|
||||
<td> iii </td>
|
||||
<td align="right"> jjj</td>
|
||||
</tr></tbody></table>
|
||||
````````````````````````````````
|
||||
|
||||
### Table cell count mismatches
|
||||
|
||||
The header and marker row must match.
|
||||
|
||||
```````````````````````````````` example
|
||||
| a | b | c |
|
||||
| --- | --- |
|
||||
| this | isn't | okay |
|
||||
.
|
||||
<p>| a | b | c |
|
||||
| --- | --- |
|
||||
| this | isn't | okay |</p>
|
||||
````````````````````````````````
|
||||
|
||||
But any of the body rows can be shorter. Rows longer
|
||||
than the header are truncated.
|
||||
|
||||
```````````````````````````````` example
|
||||
| a | b | c |
|
||||
| --- | --- | ---
|
||||
| x
|
||||
| a | b
|
||||
| 1 | 2 | 3 | 4 | 5 |
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> a </th>
|
||||
<th> b </th>
|
||||
<th> c </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> x</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> a </td>
|
||||
<td> b</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> 1 </td>
|
||||
<td> 2 </td>
|
||||
<td> 3 </td>
|
||||
</tr></tbody></table>
|
||||
````````````````````````````````
|
||||
|
||||
### Embedded pipes
|
||||
|
||||
Tables with embedded pipes could be tricky.
|
||||
|
||||
```````````````````````````````` example
|
||||
| a | b |
|
||||
| --- | --- |
|
||||
| Escaped pipes are \|okay\|. | Like \| this. |
|
||||
| Within `|code| is okay` too. |
|
||||
| **_`c|`_** \| complex
|
||||
| don't **\_reparse\_**
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> a </th>
|
||||
<th> b </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> Escaped pipes are |okay|. </td>
|
||||
<td> Like | this. </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> Within <code>|code| is okay</code> too. </td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <strong><em><code>c|</code></em></strong> | complex</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> don't <strong>_reparse_</strong></td>
|
||||
<td></td>
|
||||
</tr></tbody></table>
|
||||
````````````````````````````````
|
||||
|
||||
### Oddly-formatted markers
|
||||
|
||||
This shouldn't assert.
|
||||
|
||||
```````````````````````````````` example
|
||||
| a |
|
||||
--- |
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> a </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody></table>
|
||||
````````````````````````````````
|
||||
|
||||
### Escaping
|
||||
|
||||
```````````````````````````````` example
|
||||
| a |
|
||||
| --- |
|
||||
| \\ |
|
||||
| \\\\ |
|
||||
| \_ |
|
||||
| \| |
|
||||
| \a |
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> a </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> \ </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> \\ </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> _ </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> | </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> a </td>
|
||||
</tr></tbody></table>
|
||||
````````````````````````````````
|
||||
|
||||
### Embedded HTML
|
||||
|
||||
```````````````````````````````` example
|
||||
| a |
|
||||
| --- |
|
||||
| <strong>hello</strong> |
|
||||
| ok <br> sure |
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> a </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> <strong>hello</strong> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> ok <br> sure </td>
|
||||
</tr></tbody></table>
|
||||
````````````````````````````````
|
||||
Reference in New Issue
Block a user