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:
Yuki Izumi
2016-11-29 14:58:18 +11:00
committed by Yuki Izumi
parent 3e3761a26e
commit 0445d10bbe
14 changed files with 1630 additions and 14 deletions

View File

@@ -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;

View File

@@ -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(

View File

@@ -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
View 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
View 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

View 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
View 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
View File

@@ -0,0 +1,8 @@
#ifndef TABLE_H
#define TABLE_H
#include "core-extensions.h"
cmark_syntax_extension *create_table_extension(void);
#endif

View File

@@ -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

View File

@@ -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
View File

@@ -0,0 +1,10 @@
{
.
Memcheck:Leak
fun:malloc
fun:__smakebuf
fun:__srefill0
fun:__fread
fun:fread
fun:main
}

View File

@@ -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")

View File

@@ -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
View 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>
````````````````````````````````