Родитель
5a3f747222
Коммит
fb6356e3aa
|
@ -18,7 +18,6 @@ set(HEADERS
|
|||
houdini.h
|
||||
cmark_ctype.h
|
||||
render.h
|
||||
source_map.h
|
||||
)
|
||||
set(LIBRARY_SOURCES
|
||||
cmark.c
|
||||
|
@ -41,7 +40,6 @@ set(LIBRARY_SOURCES
|
|||
houdini_html_e.c
|
||||
houdini_html_u.c
|
||||
cmark_ctype.c
|
||||
source_map.c
|
||||
${HEADERS}
|
||||
)
|
||||
|
||||
|
|
186
src/blocks.c
186
src/blocks.c
|
@ -28,10 +28,6 @@
|
|||
#define MIN(x, y) ((x < y) ? x : y)
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(x, y) ((x > y) ? x : y)
|
||||
#endif
|
||||
|
||||
#define peek_at(i, n) (i)->data[n]
|
||||
|
||||
static bool S_last_line_blank(const cmark_node *node) {
|
||||
|
@ -97,7 +93,6 @@ cmark_parser *cmark_parser_new_with_mem(int options, cmark_mem *mem) {
|
|||
parser->root = document;
|
||||
parser->current = document;
|
||||
parser->line_number = 0;
|
||||
parser->line_offset = 0;
|
||||
parser->offset = 0;
|
||||
parser->column = 0;
|
||||
parser->first_nonspace = 0;
|
||||
|
@ -108,7 +103,6 @@ cmark_parser *cmark_parser_new_with_mem(int options, cmark_mem *mem) {
|
|||
parser->last_line_length = 0;
|
||||
parser->options = options;
|
||||
parser->last_buffer_ended_with_cr = false;
|
||||
parser->source_map = source_map_new(mem);
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
@ -122,7 +116,6 @@ void cmark_parser_free(cmark_parser *parser) {
|
|||
cmark_mem *mem = parser->mem;
|
||||
cmark_strbuf_free(&parser->curline);
|
||||
cmark_strbuf_free(&parser->linebuf);
|
||||
source_map_free(parser->source_map);
|
||||
cmark_reference_map_free(parser->refmap);
|
||||
mem->free(parser);
|
||||
}
|
||||
|
@ -262,13 +255,10 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
|
|||
|
||||
switch (S_type(b)) {
|
||||
case CMARK_NODE_PARAGRAPH:
|
||||
source_map_start_cursor(parser->source_map, parser->last_paragraph_extent);
|
||||
while (cmark_strbuf_at(node_content, 0) == '[' &&
|
||||
(pos = cmark_parse_reference_inline(parser->mem, node_content,
|
||||
parser->refmap, parser->root,
|
||||
parser->source_map))) {
|
||||
parser->last_paragraph_extent = parser->source_map->cursor;
|
||||
source_map_start_cursor(parser->source_map, parser->last_paragraph_extent);
|
||||
parser->refmap))) {
|
||||
|
||||
cmark_strbuf_drop(node_content, pos);
|
||||
}
|
||||
if (is_blank(node_content, 0)) {
|
||||
|
@ -276,6 +266,7 @@ static cmark_node *finalize(cmark_parser *parser, cmark_node *b) {
|
|||
cmark_node_free(b);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
if (!b->as.code.fenced) { // indented code
|
||||
remove_trailing_blank_lines(node_content);
|
||||
|
@ -370,32 +361,21 @@ static cmark_node *add_child(cmark_parser *parser, cmark_node *parent,
|
|||
|
||||
// Walk through node and all children, recursively, parsing
|
||||
// string content into inline content where appropriate.
|
||||
static void process_inlines(cmark_parser *parser) {
|
||||
cmark_iter *iter = cmark_iter_new(parser->root);
|
||||
static void process_inlines(cmark_mem *mem, cmark_node *root,
|
||||
cmark_reference_map *refmap, int options) {
|
||||
cmark_iter *iter = cmark_iter_new(root);
|
||||
cmark_node *cur;
|
||||
cmark_event_type ev_type;
|
||||
cmark_source_extent *cur_extent = parser->source_map->head;
|
||||
|
||||
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
|
||||
cur = cmark_iter_get_node(iter);
|
||||
if (ev_type == CMARK_EVENT_ENTER) {
|
||||
if (contains_inlines(S_type(cur))) {
|
||||
while (cur_extent && cur_extent->node != cur) {
|
||||
cur_extent = source_map_stitch_extent(parser->source_map, cur_extent, parser->root, parser->line_offset)->next;
|
||||
}
|
||||
|
||||
assert(cur_extent);
|
||||
|
||||
source_map_start_cursor(parser->source_map, cur_extent);
|
||||
cmark_parse_inlines(parser->mem, cur, parser->refmap, parser->options, parser->source_map, parser->line_offset);
|
||||
cmark_parse_inlines(mem, cur, refmap, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (cur_extent) {
|
||||
cur_extent = source_map_stitch_extent(parser->source_map, cur_extent, parser->root, parser->line_offset)->next;
|
||||
}
|
||||
|
||||
cmark_iter_free(iter);
|
||||
}
|
||||
|
||||
|
@ -502,10 +482,7 @@ static cmark_node *finalize_document(cmark_parser *parser) {
|
|||
}
|
||||
|
||||
finalize(parser, parser->root);
|
||||
|
||||
process_inlines(parser);
|
||||
|
||||
assert(source_map_check(parser->source_map, parser->line_offset));
|
||||
process_inlines(parser->mem, parser->root, parser->refmap, parser->options);
|
||||
|
||||
return parser->root;
|
||||
}
|
||||
|
@ -547,7 +524,6 @@ void cmark_parser_feed(cmark_parser *parser, const char *buffer, size_t len) {
|
|||
static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
|
||||
size_t len, bool eof) {
|
||||
const unsigned char *end = buffer + len;
|
||||
const unsigned char *skipped;
|
||||
static const uint8_t repl[] = {239, 191, 189};
|
||||
|
||||
if (parser->last_buffer_ended_with_cr && *buffer == '\n') {
|
||||
|
@ -558,7 +534,6 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
|
|||
while (buffer < end) {
|
||||
const unsigned char *eol;
|
||||
bufsize_t chunk_len;
|
||||
bufsize_t linebuf_size = 0;
|
||||
bool process = false;
|
||||
for (eol = buffer; eol < end; ++eol) {
|
||||
if (S_is_line_end_char(*eol)) {
|
||||
|
@ -576,7 +551,6 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
|
|||
chunk_len = (eol - buffer);
|
||||
if (process) {
|
||||
if (parser->linebuf.size > 0) {
|
||||
linebuf_size = cmark_strbuf_len(&parser->linebuf);
|
||||
cmark_strbuf_put(&parser->linebuf, buffer, chunk_len);
|
||||
S_process_line(parser, parser->linebuf.ptr, parser->linebuf.size);
|
||||
cmark_strbuf_clear(&parser->linebuf);
|
||||
|
@ -595,8 +569,6 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
|
|||
}
|
||||
|
||||
buffer += chunk_len;
|
||||
skipped = buffer;
|
||||
|
||||
if (buffer < end) {
|
||||
if (*buffer == '\0') {
|
||||
// skip over NULL
|
||||
|
@ -612,11 +584,6 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
|
|||
buffer++;
|
||||
}
|
||||
}
|
||||
chunk_len += buffer - skipped;
|
||||
chunk_len += linebuf_size;
|
||||
|
||||
if (process)
|
||||
parser->line_offset += chunk_len;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,13 +643,11 @@ static void S_find_first_nonspace(cmark_parser *parser, cmark_chunk *input) {
|
|||
// indicates a number of columns; otherwise, a number of bytes.
|
||||
// If advancing a certain number of columns partially consumes
|
||||
// a tab character, parser->partially_consumed_tab is set to true.
|
||||
static void S_advance_offset(cmark_parser *parser, cmark_node *container, cmark_extent_type type,
|
||||
cmark_chunk *input, bufsize_t count, bool columns) {
|
||||
static void S_advance_offset(cmark_parser *parser, cmark_chunk *input,
|
||||
bufsize_t count, bool columns) {
|
||||
char c;
|
||||
int chars_to_tab;
|
||||
int chars_to_advance;
|
||||
int initial_pos = parser->offset + parser->line_offset;
|
||||
|
||||
while (count > 0 && (c = peek_at(input, parser->offset))) {
|
||||
if (c == '\t') {
|
||||
chars_to_tab = TAB_STOP - (parser->column % TAB_STOP);
|
||||
|
@ -705,8 +670,6 @@ static void S_advance_offset(cmark_parser *parser, cmark_node *container, cmark_
|
|||
count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
source_map_append_extent(parser->source_map, initial_pos, parser->offset + parser->line_offset, container, type);
|
||||
}
|
||||
|
||||
static bool S_last_child_is_open(cmark_node *container) {
|
||||
|
@ -714,7 +677,7 @@ static bool S_last_child_is_open(cmark_node *container) {
|
|||
(container->last_child->flags & CMARK_NODE__OPEN);
|
||||
}
|
||||
|
||||
static bool parse_block_quote_prefix(cmark_parser *parser, cmark_chunk *input, cmark_node *container) {
|
||||
static bool parse_block_quote_prefix(cmark_parser *parser, cmark_chunk *input) {
|
||||
bool res = false;
|
||||
bufsize_t matched = 0;
|
||||
|
||||
|
@ -722,10 +685,10 @@ static bool parse_block_quote_prefix(cmark_parser *parser, cmark_chunk *input, c
|
|||
parser->indent <= 3 && peek_at(input, parser->first_nonspace) == '>';
|
||||
if (matched) {
|
||||
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_OPENER, input, parser->indent + 1, true);
|
||||
S_advance_offset(parser, input, parser->indent + 1, true);
|
||||
|
||||
if (S_is_space_or_tab(peek_at(input, parser->offset))) {
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_BLANK, input, 1, true);
|
||||
S_advance_offset(parser, input, 1, true);
|
||||
}
|
||||
|
||||
res = true;
|
||||
|
@ -739,7 +702,7 @@ static bool parse_node_item_prefix(cmark_parser *parser, cmark_chunk *input,
|
|||
|
||||
if (parser->indent >=
|
||||
container->as.list.marker_offset + container->as.list.padding) {
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_BLANK, input, container->as.list.marker_offset +
|
||||
S_advance_offset(parser, input, container->as.list.marker_offset +
|
||||
container->as.list.padding,
|
||||
true);
|
||||
res = true;
|
||||
|
@ -747,7 +710,7 @@ static bool parse_node_item_prefix(cmark_parser *parser, cmark_chunk *input,
|
|||
// if container->first_child is NULL, then the opening line
|
||||
// of the list item was blank after the list marker; in this
|
||||
// case, we are done with the list item.
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_BLANK, input, parser->first_nonspace - parser->offset,
|
||||
S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
|
||||
false);
|
||||
res = true;
|
||||
}
|
||||
|
@ -761,10 +724,10 @@ static bool parse_code_block_prefix(cmark_parser *parser, cmark_chunk *input,
|
|||
|
||||
if (!container->as.code.fenced) { // indented
|
||||
if (parser->indent >= CODE_INDENT) {
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_OPENER, input, CODE_INDENT, true);
|
||||
S_advance_offset(parser, input, CODE_INDENT, true);
|
||||
res = true;
|
||||
} else if (parser->blank) {
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_BLANK, input, parser->first_nonspace - parser->offset,
|
||||
S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
|
||||
false);
|
||||
res = true;
|
||||
}
|
||||
|
@ -780,14 +743,14 @@ static bool parse_code_block_prefix(cmark_parser *parser, cmark_chunk *input,
|
|||
// closing fence - and since we're at
|
||||
// the end of a line, we can stop processing it:
|
||||
*should_continue = false;
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_OPENER, input, matched, false);
|
||||
S_advance_offset(parser, input, matched, false);
|
||||
parser->current = finalize(parser, container);
|
||||
} else {
|
||||
// skip opt. spaces of fence parser->offset
|
||||
int i = container->as.code.fence_offset;
|
||||
|
||||
while (i > 0 && S_is_space_or_tab(peek_at(input, parser->offset))) {
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_BLANK, input, 1, true);
|
||||
S_advance_offset(parser, input, 1, true);
|
||||
i--;
|
||||
}
|
||||
res = true;
|
||||
|
@ -844,7 +807,7 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input,
|
|||
|
||||
switch (cont_type) {
|
||||
case CMARK_NODE_BLOCK_QUOTE:
|
||||
if (!parse_block_quote_prefix(parser, input, container))
|
||||
if (!parse_block_quote_prefix(parser, input))
|
||||
goto done;
|
||||
break;
|
||||
case CMARK_NODE_ITEM:
|
||||
|
@ -904,26 +867,29 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
|
|||
indented = parser->indent >= CODE_INDENT;
|
||||
|
||||
if (!indented && peek_at(input, parser->first_nonspace) == '>') {
|
||||
*container = add_child(parser, *container, CMARK_NODE_BLOCK_QUOTE,
|
||||
parser->first_nonspace + 1);
|
||||
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_OPENER, input,
|
||||
bufsize_t blockquote_startpos = parser->first_nonspace;
|
||||
|
||||
S_advance_offset(parser, input,
|
||||
parser->first_nonspace + 1 - parser->offset, false);
|
||||
// optional following character
|
||||
if (S_is_space_or_tab(peek_at(input, parser->offset))) {
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_BLANK, input, 1, true);
|
||||
S_advance_offset(parser, input, 1, true);
|
||||
}
|
||||
*container = add_child(parser, *container, CMARK_NODE_BLOCK_QUOTE,
|
||||
blockquote_startpos + 1);
|
||||
|
||||
} else if (!indented && (matched = scan_atx_heading_start(
|
||||
input, parser->first_nonspace))) {
|
||||
bufsize_t hashpos;
|
||||
int level = 0;
|
||||
bufsize_t heading_startpos = parser->first_nonspace;
|
||||
|
||||
*container = add_child(parser, *container, CMARK_NODE_HEADING,
|
||||
parser->first_nonspace + 1);
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_OPENER, input,
|
||||
S_advance_offset(parser, input,
|
||||
parser->first_nonspace + matched - parser->offset,
|
||||
false);
|
||||
*container = add_child(parser, *container, CMARK_NODE_HEADING,
|
||||
heading_startpos + 1);
|
||||
|
||||
hashpos = cmark_chunk_strchr(input, '#', parser->first_nonspace);
|
||||
|
||||
|
@ -945,7 +911,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
|
|||
(*container)->as.code.fence_offset =
|
||||
(int8_t)(parser->first_nonspace - parser->offset);
|
||||
(*container)->as.code.info = cmark_chunk_literal("");
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_OPENER, input,
|
||||
S_advance_offset(parser, input,
|
||||
parser->first_nonspace + matched - parser->offset,
|
||||
false);
|
||||
|
||||
|
@ -965,14 +931,14 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
|
|||
(*container)->type = (uint16_t)CMARK_NODE_HEADING;
|
||||
(*container)->as.heading.level = lev;
|
||||
(*container)->as.heading.setext = true;
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_CLOSER, input, input->len - 1 - parser->offset, false);
|
||||
S_advance_offset(parser, input, input->len - 1 - parser->offset, false);
|
||||
} else if (!indented &&
|
||||
!(cont_type == CMARK_NODE_PARAGRAPH && !all_matched) &&
|
||||
(matched = scan_thematic_break(input, parser->first_nonspace))) {
|
||||
// it's only now that we know the line is not part of a setext heading:
|
||||
*container = add_child(parser, *container, CMARK_NODE_THEMATIC_BREAK,
|
||||
parser->first_nonspace + 1);
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_CONTENT, input, input->len - 1 - parser->offset, false);
|
||||
S_advance_offset(parser, input, input->len - 1 - parser->offset, false);
|
||||
} else if ((!indented || cont_type == CMARK_NODE_LIST) &&
|
||||
(matched = parse_list_marker(
|
||||
parser->mem, input, parser->first_nonspace,
|
||||
|
@ -980,37 +946,20 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
|
|||
|
||||
// Note that we can have new list items starting with >= 4
|
||||
// spaces indent, as long as the list container is still open.
|
||||
cmark_node *list = NULL;
|
||||
cmark_node *item = NULL;
|
||||
cmark_source_extent *save_source_map_tail;
|
||||
int i = 0;
|
||||
|
||||
if (cont_type != CMARK_NODE_LIST ||
|
||||
!lists_match(&((*container)->as.list), data)) {
|
||||
*container = add_child(parser, *container, CMARK_NODE_LIST,
|
||||
parser->first_nonspace + 1);
|
||||
list = *container;
|
||||
|
||||
}
|
||||
|
||||
// add the list item
|
||||
*container = add_child(parser, *container, CMARK_NODE_ITEM,
|
||||
parser->first_nonspace + 1);
|
||||
item = *container;
|
||||
|
||||
// compute padding:
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_OPENER, input,
|
||||
S_advance_offset(parser, input,
|
||||
parser->first_nonspace + matched - parser->offset,
|
||||
false);
|
||||
|
||||
save_partially_consumed_tab = parser->partially_consumed_tab;
|
||||
save_offset = parser->offset;
|
||||
save_column = parser->column;
|
||||
save_source_map_tail = parser->source_map->tail;
|
||||
|
||||
while (parser->column - save_column <= 5 &&
|
||||
S_is_space_or_tab(peek_at(input, parser->offset))) {
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_BLANK, input, 1, true);
|
||||
S_advance_offset(parser, input, 1, true);
|
||||
}
|
||||
|
||||
i = parser->column - save_column;
|
||||
|
@ -1020,14 +969,9 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
|
|||
data->padding = matched + 1;
|
||||
parser->offset = save_offset;
|
||||
parser->column = save_column;
|
||||
if (save_source_map_tail) {
|
||||
cmark_source_extent *tmp_extent;
|
||||
for (tmp_extent = save_source_map_tail->next; tmp_extent; tmp_extent = source_map_free_extent(parser->source_map, tmp_extent));
|
||||
}
|
||||
|
||||
parser->partially_consumed_tab = save_partially_consumed_tab;
|
||||
if (i > 0) {
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_BLANK, input, 1, true);
|
||||
S_advance_offset(parser, input, 1, true);
|
||||
}
|
||||
} else {
|
||||
data->padding = matched + i;
|
||||
|
@ -1038,14 +982,22 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
|
|||
|
||||
data->marker_offset = parser->indent;
|
||||
|
||||
/* TODO: static */
|
||||
if (list)
|
||||
memcpy(&(list->as.list), data, sizeof(*data));
|
||||
if (item)
|
||||
memcpy(&(item->as.list), data, sizeof(*data));
|
||||
if (cont_type != CMARK_NODE_LIST ||
|
||||
!lists_match(&((*container)->as.list), data)) {
|
||||
*container = add_child(parser, *container, CMARK_NODE_LIST,
|
||||
parser->first_nonspace + 1);
|
||||
|
||||
memcpy(&((*container)->as.list), data, sizeof(*data));
|
||||
}
|
||||
|
||||
// add the list item
|
||||
*container = add_child(parser, *container, CMARK_NODE_ITEM,
|
||||
parser->first_nonspace + 1);
|
||||
/* TODO: static */
|
||||
memcpy(&((*container)->as.list), data, sizeof(*data));
|
||||
parser->mem->free(data);
|
||||
} else if (indented && !maybe_lazy && !parser->blank) {
|
||||
S_advance_offset(parser, input, CODE_INDENT, true);
|
||||
*container = add_child(parser, *container, CMARK_NODE_CODE_BLOCK,
|
||||
parser->offset + 1);
|
||||
(*container)->as.code.fenced = false;
|
||||
|
@ -1054,7 +1006,6 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
|
|||
(*container)->as.code.fence_offset = 0;
|
||||
(*container)->as.code.info = cmark_chunk_literal("");
|
||||
|
||||
S_advance_offset(parser, *container, CMARK_EXTENT_OPENER, input, CODE_INDENT, true);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@ -1119,11 +1070,6 @@ static void add_text_to_container(cmark_parser *parser, cmark_node *container,
|
|||
}
|
||||
|
||||
if (S_type(container) == CMARK_NODE_CODE_BLOCK) {
|
||||
source_map_append_extent(parser->source_map,
|
||||
parser->offset + parser->line_offset,
|
||||
parser->line_offset + input->len,
|
||||
container,
|
||||
CMARK_EXTENT_CONTENT);
|
||||
add_line(container, input, parser);
|
||||
} else if (S_type(container) == CMARK_NODE_HTML_BLOCK) {
|
||||
add_line(container, input, parser);
|
||||
|
@ -1164,43 +1110,22 @@ static void add_text_to_container(cmark_parser *parser, cmark_node *container,
|
|||
container = finalize(parser, container);
|
||||
assert(parser->current != NULL);
|
||||
}
|
||||
source_map_append_extent(parser->source_map,
|
||||
parser->offset + parser->line_offset,
|
||||
parser->line_offset + input->len,
|
||||
container,
|
||||
CMARK_EXTENT_CONTENT);
|
||||
} else if (parser->blank) {
|
||||
source_map_append_extent(parser->source_map,
|
||||
parser->line_offset + parser->offset,
|
||||
parser->line_offset + input->len,
|
||||
container,
|
||||
CMARK_EXTENT_BLANK);
|
||||
// ??? do nothing
|
||||
} else if (accepts_lines(S_type(container))) {
|
||||
bufsize_t initial_len = input->len;
|
||||
bool chopped = false;
|
||||
|
||||
if (S_type(container) == CMARK_NODE_HEADING &&
|
||||
container->as.heading.setext == false) {
|
||||
chop_trailing_hashtags(input);
|
||||
chopped = true;
|
||||
}
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_BLANK, input, parser->first_nonspace - parser->offset,
|
||||
S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
|
||||
false);
|
||||
add_line(container, input, parser);
|
||||
|
||||
if (chopped)
|
||||
source_map_append_extent(parser->source_map,
|
||||
MAX(parser->line_offset + parser->offset, parser->line_offset + input->len),
|
||||
parser->line_offset + initial_len,
|
||||
container,
|
||||
CMARK_EXTENT_CLOSER);
|
||||
} else {
|
||||
// create paragraph container for line
|
||||
container = add_child(parser, container, CMARK_NODE_PARAGRAPH,
|
||||
parser->first_nonspace + 1);
|
||||
S_advance_offset(parser, container, CMARK_EXTENT_OPENER, input, parser->first_nonspace - parser->offset,
|
||||
S_advance_offset(parser, input, parser->first_nonspace - parser->offset,
|
||||
false);
|
||||
parser->last_paragraph_extent = parser->source_map->tail;
|
||||
add_line(container, input, parser);
|
||||
}
|
||||
|
||||
|
@ -1262,7 +1187,6 @@ finished:
|
|||
cmark_node *cmark_parser_finish(cmark_parser *parser) {
|
||||
if (parser->linebuf.size) {
|
||||
S_process_line(parser, parser->linebuf.ptr, parser->linebuf.size);
|
||||
parser->line_offset += parser->linebuf.size;
|
||||
cmark_strbuf_clear(&parser->linebuf);
|
||||
}
|
||||
|
||||
|
@ -1281,9 +1205,3 @@ cmark_node *cmark_parser_finish(cmark_parser *parser) {
|
|||
#endif
|
||||
return parser->root;
|
||||
}
|
||||
|
||||
cmark_source_extent *
|
||||
cmark_parser_get_first_source_extent(cmark_parser *parser)
|
||||
{
|
||||
return parser->source_map->head;
|
||||
}
|
||||
|
|
|
@ -24,11 +24,6 @@ static void *xrealloc(void *ptr, size_t size) {
|
|||
return new_ptr;
|
||||
}
|
||||
|
||||
void cmark_default_mem_free(void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
cmark_mem DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, free};
|
||||
|
||||
char *cmark_markdown_to_html(const char *text, size_t len, int options) {
|
||||
|
|
60
src/cmark.h
60
src/cmark.h
|
@ -2,7 +2,6 @@
|
|||
#define CMARK_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <cmark_export.h>
|
||||
#include <cmark_version.h>
|
||||
|
||||
|
@ -66,21 +65,6 @@ typedef enum {
|
|||
CMARK_NODE_LAST_INLINE = CMARK_NODE_IMAGE,
|
||||
} cmark_node_type;
|
||||
|
||||
typedef enum {
|
||||
CMARK_EXTENT_NONE,
|
||||
CMARK_EXTENT_OPENER,
|
||||
CMARK_EXTENT_CLOSER,
|
||||
CMARK_EXTENT_BLANK,
|
||||
CMARK_EXTENT_CONTENT,
|
||||
CMARK_EXTENT_PUNCTUATION,
|
||||
CMARK_EXTENT_LINK_DESTINATION,
|
||||
CMARK_EXTENT_LINK_TITLE,
|
||||
CMARK_EXTENT_LINK_LABEL,
|
||||
CMARK_EXTENT_REFERENCE_DESTINATION,
|
||||
CMARK_EXTENT_REFERENCE_LABEL,
|
||||
CMARK_EXTENT_REFERENCE_TITLE,
|
||||
} cmark_extent_type;
|
||||
|
||||
/* For backwards compatibility: */
|
||||
#define CMARK_NODE_HEADER CMARK_NODE_HEADING
|
||||
#define CMARK_NODE_HRULE CMARK_NODE_THEMATIC_BREAK
|
||||
|
@ -102,7 +86,6 @@ typedef enum {
|
|||
typedef struct cmark_node cmark_node;
|
||||
typedef struct cmark_parser cmark_parser;
|
||||
typedef struct cmark_iter cmark_iter;
|
||||
typedef struct cmark_source_extent cmark_source_extent;
|
||||
|
||||
/**
|
||||
* ## Custom memory allocator support
|
||||
|
@ -117,11 +100,6 @@ typedef struct cmark_mem {
|
|||
void (*free)(void *);
|
||||
} cmark_mem;
|
||||
|
||||
/** Convenience function for bindings.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
void cmark_default_mem_free(void *ptr);
|
||||
|
||||
/**
|
||||
* ## Creating and Destroying Nodes
|
||||
*/
|
||||
|
@ -499,11 +477,6 @@ void cmark_parser_feed(cmark_parser *parser, const char *buffer, size_t len);
|
|||
CMARK_EXPORT
|
||||
cmark_node *cmark_parser_finish(cmark_parser *parser);
|
||||
|
||||
/** Return a pointer to the first extent of the parser's source map
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_source_extent *cmark_parser_get_first_source_extent(cmark_parser *parser);
|
||||
|
||||
/** Parse a CommonMark document in 'buffer' of length 'len'.
|
||||
* Returns a pointer to a tree of nodes. The memory allocated for
|
||||
* the node tree should be released using 'cmark_node_free'
|
||||
|
@ -519,39 +492,6 @@ cmark_node *cmark_parse_document(const char *buffer, size_t len, int options);
|
|||
CMARK_EXPORT
|
||||
cmark_node *cmark_parse_file(FILE *f, int options);
|
||||
|
||||
/**
|
||||
* ## Source map API
|
||||
*/
|
||||
|
||||
/* Return the index, in bytes, of the start of this extent */
|
||||
CMARK_EXPORT
|
||||
uint64_t cmark_source_extent_get_start(cmark_source_extent *extent);
|
||||
|
||||
/* Return the index, in bytes, of the stop of this extent. This
|
||||
* index is not included in the extent*/
|
||||
CMARK_EXPORT
|
||||
uint64_t cmark_source_extent_get_stop(cmark_source_extent *extent);
|
||||
|
||||
/* Return the extent immediately following 'extent' */
|
||||
CMARK_EXPORT
|
||||
cmark_source_extent *cmark_source_extent_get_next(cmark_source_extent *extent);
|
||||
|
||||
/* Return the extent immediately preceding 'extent' */
|
||||
CMARK_EXPORT
|
||||
cmark_source_extent *cmark_source_extent_get_previous(cmark_source_extent *extent);
|
||||
|
||||
/* Return the node 'extent' maps to */
|
||||
CMARK_EXPORT
|
||||
cmark_node *cmark_source_extent_get_node(cmark_source_extent *extent);
|
||||
|
||||
/* Return the type of 'extent' */
|
||||
CMARK_EXPORT
|
||||
cmark_extent_type cmark_source_extent_get_type(cmark_source_extent *extent);
|
||||
|
||||
/* Return a string representation of 'extent' */
|
||||
CMARK_EXPORT
|
||||
const char *cmark_source_extent_get_type_string(cmark_source_extent *extent);
|
||||
|
||||
/**
|
||||
* ## Rendering
|
||||
*/
|
||||
|
|
143
src/inlines.c
143
src/inlines.c
|
@ -13,10 +13,6 @@
|
|||
#include "scanners.h"
|
||||
#include "inlines.h"
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(x, y) ((x < y) ? x : y)
|
||||
#endif
|
||||
|
||||
static const char *EMDASH = "\xE2\x80\x94";
|
||||
static const char *ENDASH = "\xE2\x80\x93";
|
||||
static const char *ELLIPSES = "\xE2\x80\xA6";
|
||||
|
@ -44,7 +40,6 @@ typedef struct delimiter {
|
|||
unsigned char delim_char;
|
||||
bool can_open;
|
||||
bool can_close;
|
||||
cmark_source_extent *extent;
|
||||
} delimiter;
|
||||
|
||||
typedef struct bracket {
|
||||
|
@ -55,7 +50,6 @@ typedef struct bracket {
|
|||
bool image;
|
||||
bool active;
|
||||
bool bracket_after;
|
||||
cmark_source_extent *extent;
|
||||
} bracket;
|
||||
|
||||
typedef struct {
|
||||
|
@ -67,7 +61,6 @@ typedef struct {
|
|||
bracket *last_bracket;
|
||||
bufsize_t backticks[MAXBACKTICKS + 1];
|
||||
bool scanned_for_backticks;
|
||||
cmark_source_map *source_map;
|
||||
} subject;
|
||||
|
||||
static CMARK_INLINE bool S_is_line_end_char(char c) {
|
||||
|
@ -80,7 +73,7 @@ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
|
|||
static int parse_inline(subject *subj, cmark_node *parent, int options);
|
||||
|
||||
static void subject_from_buf(cmark_mem *mem, subject *e, cmark_strbuf *buffer,
|
||||
cmark_reference_map *refmap, cmark_source_map *source_map);
|
||||
cmark_reference_map *refmap);
|
||||
static bufsize_t subject_find_special_char(subject *subj, int options);
|
||||
|
||||
// Create an inline with a literal string value.
|
||||
|
@ -156,7 +149,7 @@ static CMARK_INLINE cmark_node *make_autolink(cmark_mem *mem, cmark_chunk url,
|
|||
}
|
||||
|
||||
static void subject_from_buf(cmark_mem *mem, subject *e, cmark_strbuf *buffer,
|
||||
cmark_reference_map *refmap, cmark_source_map *source_map) {
|
||||
cmark_reference_map *refmap) {
|
||||
int i;
|
||||
e->mem = mem;
|
||||
e->input.data = buffer->ptr;
|
||||
|
@ -166,8 +159,6 @@ static void subject_from_buf(cmark_mem *mem, subject *e, cmark_strbuf *buffer,
|
|||
e->refmap = refmap;
|
||||
e->last_delim = NULL;
|
||||
e->last_bracket = NULL;
|
||||
e->source_map = source_map;
|
||||
|
||||
for (i=0; i <= MAXBACKTICKS; i++) {
|
||||
e->backticks[i] = 0;
|
||||
}
|
||||
|
@ -415,7 +406,6 @@ static void push_delimiter(subject *subj, unsigned char c, bool can_open,
|
|||
if (delim->previous != NULL) {
|
||||
delim->previous->next = delim;
|
||||
}
|
||||
delim->extent = NULL;
|
||||
subj->last_delim = delim;
|
||||
}
|
||||
|
||||
|
@ -431,12 +421,11 @@ static void push_bracket(subject *subj, bool image, cmark_node *inl_text) {
|
|||
b->previous_delimiter = subj->last_delim;
|
||||
b->position = subj->pos;
|
||||
b->bracket_after = false;
|
||||
b->extent = NULL;
|
||||
subj->last_bracket = b;
|
||||
}
|
||||
|
||||
// Assumes the subject has a c at the current position.
|
||||
static cmark_node *handle_delim(subject *subj, unsigned char c, bool smart, bool *pushed) {
|
||||
static cmark_node *handle_delim(subject *subj, unsigned char c, bool smart) {
|
||||
bufsize_t numdelims;
|
||||
cmark_node *inl_text;
|
||||
bool can_open, can_close;
|
||||
|
@ -457,9 +446,6 @@ static cmark_node *handle_delim(subject *subj, unsigned char c, bool smart, bool
|
|||
|
||||
if ((can_open || can_close) && (!(c == '\'' || c == '"') || smart)) {
|
||||
push_delimiter(subj, c, can_open, can_close, inl_text);
|
||||
*pushed = true;
|
||||
} else {
|
||||
*pushed = false;
|
||||
}
|
||||
|
||||
return inl_text;
|
||||
|
@ -620,7 +606,6 @@ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
|
|||
bufsize_t opener_num_chars = opener_inl->as.literal.len;
|
||||
bufsize_t closer_num_chars = closer_inl->as.literal.len;
|
||||
cmark_node *tmp, *tmpnext, *emph;
|
||||
cmark_source_extent *tmp_extent;
|
||||
|
||||
// calculate the actual number of characters used from this closer
|
||||
if (closer_num_chars < 3 || opener_num_chars < 3) {
|
||||
|
@ -656,28 +641,9 @@ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
|
|||
}
|
||||
cmark_node_insert_after(opener_inl, emph);
|
||||
|
||||
tmp_extent = closer->extent->prev;
|
||||
|
||||
source_map_insert_extent(subj->source_map,
|
||||
opener->extent,
|
||||
opener->extent->stop - use_delims,
|
||||
opener->extent->stop,
|
||||
emph,
|
||||
CMARK_EXTENT_OPENER);
|
||||
opener->extent->stop -= use_delims;
|
||||
|
||||
source_map_insert_extent(subj->source_map,
|
||||
tmp_extent,
|
||||
closer->extent->start,
|
||||
closer->extent->start + use_delims,
|
||||
emph,
|
||||
CMARK_EXTENT_CLOSER);
|
||||
closer->extent->start += use_delims;
|
||||
|
||||
// if opener has 0 characters, remove it and its associated inline
|
||||
if (opener_num_chars == 0) {
|
||||
cmark_node_free(opener_inl);
|
||||
source_map_free_extent(subj->source_map, opener->extent);
|
||||
remove_delimiter(subj, opener);
|
||||
}
|
||||
|
||||
|
@ -687,7 +653,6 @@ static delimiter *S_insert_emph(subject *subj, delimiter *opener,
|
|||
cmark_node_free(closer_inl);
|
||||
// remove closer from list
|
||||
tmp_delim = closer->next;
|
||||
source_map_free_extent(subj->source_map, closer->extent);
|
||||
remove_delimiter(subj, closer);
|
||||
closer = tmp_delim;
|
||||
}
|
||||
|
@ -910,8 +875,6 @@ static cmark_node *handle_close_bracket(subject *subj) {
|
|||
int found_label;
|
||||
cmark_node *tmp, *tmpnext;
|
||||
bool is_image;
|
||||
bool is_inline = false;
|
||||
bool is_shortcut = false;
|
||||
|
||||
advance(subj); // advance past ]
|
||||
initial_pos = subj->pos;
|
||||
|
@ -962,7 +925,6 @@ static cmark_node *handle_close_bracket(subject *subj) {
|
|||
title = cmark_clean_title(subj->mem, &title_chunk);
|
||||
cmark_chunk_free(subj->mem, &url_chunk);
|
||||
cmark_chunk_free(subj->mem, &title_chunk);
|
||||
is_inline = true;
|
||||
goto match;
|
||||
|
||||
} else {
|
||||
|
@ -985,7 +947,6 @@ static cmark_node *handle_close_bracket(subject *subj) {
|
|||
cmark_chunk_free(subj->mem, &raw_label);
|
||||
raw_label = cmark_chunk_dup(&subj->input, opener->position,
|
||||
initial_pos - opener->position - 1);
|
||||
is_shortcut = true;
|
||||
found_label = true;
|
||||
}
|
||||
|
||||
|
@ -1015,28 +976,6 @@ match:
|
|||
cmark_node_insert_before(opener->inl_text, inl);
|
||||
// Add link text:
|
||||
tmp = opener->inl_text->next;
|
||||
assert(opener->extent);
|
||||
|
||||
opener->extent->node = inl;
|
||||
opener->extent->type = CMARK_EXTENT_PUNCTUATION;
|
||||
|
||||
source_map_splice_extent(subj->source_map, initial_pos - 1, initial_pos, inl, CMARK_EXTENT_PUNCTUATION);
|
||||
if (is_inline) {
|
||||
source_map_splice_extent(subj->source_map, after_link_text_pos, starturl, inl, CMARK_EXTENT_PUNCTUATION);
|
||||
source_map_splice_extent(subj->source_map, starturl, endurl, inl, CMARK_EXTENT_LINK_DESTINATION);
|
||||
if (endtitle != starttitle) {
|
||||
source_map_splice_extent(subj->source_map, endurl, starttitle, inl, CMARK_EXTENT_BLANK);
|
||||
source_map_splice_extent(subj->source_map, starttitle, endtitle, inl, CMARK_EXTENT_LINK_TITLE);
|
||||
source_map_splice_extent(subj->source_map, endtitle, subj->pos, inl, CMARK_EXTENT_BLANK);
|
||||
} else {
|
||||
source_map_splice_extent(subj->source_map, endurl, subj->pos, inl, CMARK_EXTENT_BLANK);
|
||||
}
|
||||
} else if (!is_shortcut) {
|
||||
source_map_splice_extent(subj->source_map, initial_pos, initial_pos + 1, inl, CMARK_EXTENT_PUNCTUATION);
|
||||
source_map_splice_extent(subj->source_map, initial_pos + 1, subj->pos - 1, inl, CMARK_EXTENT_LINK_LABEL);
|
||||
source_map_splice_extent(subj->source_map, subj->pos - 1, subj->pos, inl, CMARK_EXTENT_PUNCTUATION);
|
||||
}
|
||||
|
||||
while (tmp) {
|
||||
tmpnext = tmp->next;
|
||||
cmark_node_append_child(inl, tmp);
|
||||
|
@ -1140,10 +1079,6 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
|
|||
cmark_chunk contents;
|
||||
unsigned char c;
|
||||
bufsize_t endpos;
|
||||
bufsize_t startpos = subj->pos;
|
||||
bool add_extent_to_last_bracket = false;
|
||||
bool add_extent_to_last_delimiter = false;
|
||||
|
||||
c = peek_char(subj);
|
||||
if (c == 0) {
|
||||
return 0;
|
||||
|
@ -1169,7 +1104,7 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
|
|||
case '_':
|
||||
case '\'':
|
||||
case '"':
|
||||
new_inl = handle_delim(subj, c, (options & CMARK_OPT_SMART) != 0, &add_extent_to_last_delimiter);
|
||||
new_inl = handle_delim(subj, c, (options & CMARK_OPT_SMART) != 0);
|
||||
break;
|
||||
case '-':
|
||||
new_inl = handle_hyphen(subj, (options & CMARK_OPT_SMART) != 0);
|
||||
|
@ -1181,7 +1116,6 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
|
|||
advance(subj);
|
||||
new_inl = make_str(subj->mem, cmark_chunk_literal("["));
|
||||
push_bracket(subj, false, new_inl);
|
||||
add_extent_to_last_bracket = true;
|
||||
break;
|
||||
case ']':
|
||||
new_inl = handle_close_bracket(subj);
|
||||
|
@ -1192,7 +1126,6 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
|
|||
advance(subj);
|
||||
new_inl = make_str(subj->mem, cmark_chunk_literal("!["));
|
||||
push_bracket(subj, true, new_inl);
|
||||
add_extent_to_last_bracket = true;
|
||||
} else {
|
||||
new_inl = make_str(subj->mem, cmark_chunk_literal("!"));
|
||||
}
|
||||
|
@ -1209,17 +1142,7 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
|
|||
|
||||
new_inl = make_str(subj->mem, contents);
|
||||
}
|
||||
|
||||
if (new_inl != NULL) {
|
||||
cmark_source_extent *extent;
|
||||
|
||||
extent = source_map_splice_extent(subj->source_map, startpos, subj->pos, new_inl, CMARK_EXTENT_CONTENT);
|
||||
|
||||
if (add_extent_to_last_bracket)
|
||||
subj->last_bracket->extent = extent;
|
||||
else if (add_extent_to_last_delimiter)
|
||||
subj->last_delim->extent = extent;
|
||||
|
||||
cmark_node_append_child(parent, new_inl);
|
||||
}
|
||||
|
||||
|
@ -1228,11 +1151,9 @@ static int parse_inline(subject *subj, cmark_node *parent, int options) {
|
|||
|
||||
// Parse inlines from parent's string_content, adding as children of parent.
|
||||
extern void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent,
|
||||
cmark_reference_map *refmap, int options,
|
||||
cmark_source_map *source_map, uint64_t total_length) {
|
||||
cmark_reference_map *refmap, int options) {
|
||||
subject subj;
|
||||
subject_from_buf(mem, &subj, &parent->content, refmap, source_map);
|
||||
bufsize_t initial_len = subj.input.len;
|
||||
subject_from_buf(mem, &subj, &parent->content, refmap);
|
||||
cmark_chunk_rtrim(&subj.input);
|
||||
|
||||
while (!is_eof(&subj) && parse_inline(&subj, parent, options))
|
||||
|
@ -1246,13 +1167,6 @@ extern void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent,
|
|||
while (subj.last_bracket) {
|
||||
pop_bracket(&subj);
|
||||
}
|
||||
|
||||
source_map_insert_extent(source_map,
|
||||
source_map->cursor,
|
||||
source_map->cursor->stop,
|
||||
MIN(source_map->cursor->stop + initial_len - subj.input.len, total_length),
|
||||
parent,
|
||||
CMARK_EXTENT_BLANK);
|
||||
}
|
||||
|
||||
// Parse zero or more space characters, including at most one newline.
|
||||
|
@ -1268,30 +1182,22 @@ static void spnl(subject *subj) {
|
|||
// Return 0 if no reference found, otherwise position of subject
|
||||
// after reference is parsed.
|
||||
bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_strbuf *input,
|
||||
cmark_reference_map *refmap,
|
||||
cmark_node *root,
|
||||
cmark_source_map *source_map) {
|
||||
cmark_reference_map *refmap) {
|
||||
subject subj;
|
||||
cmark_node *container = source_map->cursor->node;
|
||||
cmark_source_extent *tmp_extent = source_map->cursor;
|
||||
|
||||
cmark_chunk lab;
|
||||
cmark_chunk url;
|
||||
cmark_chunk title;
|
||||
|
||||
bufsize_t matchlen = 0;
|
||||
bufsize_t starttitle, endtitle;
|
||||
bufsize_t endlabel;
|
||||
bufsize_t starturl, endurl;
|
||||
bufsize_t beforetitle;
|
||||
|
||||
subject_from_buf(mem, &subj, input, NULL, source_map);
|
||||
subject_from_buf(mem, &subj, input, NULL);
|
||||
|
||||
// parse label:
|
||||
if (!link_label(&subj, &lab) || lab.len == 0)
|
||||
return 0;
|
||||
|
||||
endlabel = subj.pos - 1;
|
||||
|
||||
// colon:
|
||||
if (peek_char(&subj) == ':') {
|
||||
advance(&subj);
|
||||
|
@ -1301,7 +1207,6 @@ bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_strbuf *input,
|
|||
|
||||
// parse link url:
|
||||
spnl(&subj);
|
||||
starturl = subj.pos;
|
||||
matchlen = manual_scan_link_url(&subj.input, subj.pos);
|
||||
if (matchlen > 0) {
|
||||
url = cmark_chunk_dup(&subj.input, subj.pos, matchlen);
|
||||
|
@ -1311,29 +1216,22 @@ bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_strbuf *input,
|
|||
}
|
||||
|
||||
// parse optional link_title
|
||||
endurl = subj.pos;
|
||||
beforetitle = subj.pos;
|
||||
spnl(&subj);
|
||||
starttitle = subj.pos;
|
||||
matchlen = scan_link_title(&subj.input, subj.pos);
|
||||
if (matchlen) {
|
||||
title = cmark_chunk_dup(&subj.input, subj.pos, matchlen);
|
||||
subj.pos += matchlen;
|
||||
} else {
|
||||
subj.pos = endurl;
|
||||
starttitle = endurl;
|
||||
endtitle = endurl;
|
||||
subj.pos = beforetitle;
|
||||
title = cmark_chunk_literal("");
|
||||
}
|
||||
|
||||
endtitle = subj.pos;
|
||||
|
||||
// parse final spaces and newline:
|
||||
skip_spaces(&subj);
|
||||
if (!skip_line_end(&subj)) {
|
||||
if (matchlen) { // try rewinding before title
|
||||
subj.pos = endurl;
|
||||
starttitle = endurl;
|
||||
endtitle = endurl;
|
||||
subj.pos = beforetitle;
|
||||
skip_spaces(&subj);
|
||||
if (!skip_line_end(&subj)) {
|
||||
return 0;
|
||||
|
@ -1344,22 +1242,5 @@ bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_strbuf *input,
|
|||
}
|
||||
// insert reference into refmap
|
||||
cmark_reference_create(refmap, &lab, &url, &title);
|
||||
|
||||
// Mark the extents of the reference
|
||||
source_map_splice_extent(source_map, 0, 1, root, CMARK_EXTENT_PUNCTUATION);
|
||||
source_map_splice_extent(source_map, 1, endlabel, root, CMARK_EXTENT_REFERENCE_LABEL);
|
||||
source_map_splice_extent(source_map, endlabel, endlabel + 2, root, CMARK_EXTENT_PUNCTUATION);
|
||||
source_map_splice_extent(source_map, endlabel + 2, starturl, root, CMARK_EXTENT_BLANK);
|
||||
source_map_splice_extent(source_map, starturl, endurl, root, CMARK_EXTENT_REFERENCE_DESTINATION);
|
||||
source_map_splice_extent(source_map, endurl, starttitle, root, CMARK_EXTENT_BLANK);
|
||||
source_map_splice_extent(source_map, starttitle, endtitle, root, CMARK_EXTENT_REFERENCE_TITLE);
|
||||
source_map_splice_extent(source_map, endtitle, subj.pos, root, CMARK_EXTENT_BLANK);
|
||||
|
||||
while (tmp_extent != source_map->cursor) {
|
||||
if (tmp_extent->node == container)
|
||||
tmp_extent->node = root;
|
||||
tmp_extent = tmp_extent->next;
|
||||
}
|
||||
|
||||
return subj.pos;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#ifndef CMARK_INLINES_H
|
||||
#define CMARK_INLINES_H
|
||||
|
||||
#include "chunk.h"
|
||||
#include "references.h"
|
||||
#include "source_map.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
@ -13,13 +9,10 @@ cmark_chunk cmark_clean_url(cmark_mem *mem, cmark_chunk *url);
|
|||
cmark_chunk cmark_clean_title(cmark_mem *mem, cmark_chunk *title);
|
||||
|
||||
void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent,
|
||||
cmark_reference_map *refmap, int options,
|
||||
cmark_source_map *source_map, uint64_t total_length);
|
||||
cmark_reference_map *refmap, int options);
|
||||
|
||||
bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_strbuf *input,
|
||||
cmark_reference_map *refmap,
|
||||
cmark_node *root,
|
||||
cmark_source_map *source_map);
|
||||
cmark_reference_map *refmap);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "node.h"
|
||||
#include "buffer.h"
|
||||
#include "memory.h"
|
||||
#include "source_map.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
@ -28,12 +27,9 @@ struct cmark_parser {
|
|||
bool partially_consumed_tab;
|
||||
cmark_strbuf curline;
|
||||
bufsize_t last_line_length;
|
||||
bufsize_t line_offset;
|
||||
cmark_strbuf linebuf;
|
||||
int options;
|
||||
bool last_buffer_ended_with_cr;
|
||||
cmark_source_map *source_map;
|
||||
cmark_source_extent *last_paragraph_extent;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
293
src/source_map.c
293
src/source_map.c
|
@ -1,293 +0,0 @@
|
|||
#include <assert.h>
|
||||
|
||||
#include "source_map.h"
|
||||
|
||||
cmark_source_map *
|
||||
source_map_new(cmark_mem *mem)
|
||||
{
|
||||
cmark_source_map *res = (cmark_source_map *) mem->calloc(1, sizeof(cmark_source_map));
|
||||
res->mem = mem;
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
source_map_free(cmark_source_map *self)
|
||||
{
|
||||
cmark_source_extent *tmp;
|
||||
for (tmp = self->head; tmp; tmp = source_map_free_extent(self, tmp));
|
||||
self->mem->free(self);
|
||||
}
|
||||
|
||||
cmark_source_extent *
|
||||
source_map_append_extent(cmark_source_map *self, uint64_t start, uint64_t stop, cmark_node *node, cmark_extent_type type)
|
||||
{
|
||||
assert (start <= stop);
|
||||
assert (!self->tail || self->tail->stop <= start);
|
||||
|
||||
cmark_source_extent *res = (cmark_source_extent *) self->mem->calloc(1, sizeof(cmark_source_extent));
|
||||
|
||||
res->start = start;
|
||||
res->stop = stop;
|
||||
res->node = node;
|
||||
res->type = type;
|
||||
|
||||
res->next = NULL;
|
||||
res->prev = self->tail;
|
||||
|
||||
if (!self->head)
|
||||
self->head = res;
|
||||
else
|
||||
self->tail->next = res;
|
||||
|
||||
self->tail = res;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
cmark_source_extent *
|
||||
source_map_insert_extent(cmark_source_map *self, cmark_source_extent *previous,
|
||||
uint64_t start, uint64_t stop, cmark_node *node, cmark_extent_type type)
|
||||
{
|
||||
if (start == stop)
|
||||
return previous;
|
||||
|
||||
cmark_source_extent *extent = (cmark_source_extent *) self->mem->calloc(1, sizeof(cmark_source_extent));
|
||||
|
||||
extent->start = start;
|
||||
extent->stop = stop;
|
||||
extent->node = node;
|
||||
extent->type = type;
|
||||
extent->next = previous->next;
|
||||
extent->prev = previous;
|
||||
previous->next = extent;
|
||||
|
||||
if (extent->next)
|
||||
extent->next->prev = extent;
|
||||
else
|
||||
self->tail = extent;
|
||||
|
||||
return extent;
|
||||
}
|
||||
|
||||
cmark_source_extent *
|
||||
source_map_free_extent(cmark_source_map *self, cmark_source_extent *extent)
|
||||
{
|
||||
cmark_source_extent *next = extent->next;
|
||||
|
||||
if (extent->prev)
|
||||
extent->prev->next = next;
|
||||
|
||||
if (extent->next)
|
||||
extent->next->prev = extent->prev;
|
||||
|
||||
if (extent == self->tail)
|
||||
self->tail = extent->prev;
|
||||
|
||||
if (extent == self->head)
|
||||
self->head = extent->next;
|
||||
|
||||
if (extent == self->cursor) {
|
||||
self->cursor = extent->prev;
|
||||
}
|
||||
|
||||
if (extent == self->next_cursor) {
|
||||
self->next_cursor = extent->next;
|
||||
}
|
||||
|
||||
self->mem->free(extent);
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
cmark_source_extent *
|
||||
source_map_stitch_extent(cmark_source_map *self, cmark_source_extent *extent,
|
||||
cmark_node *node, uint64_t total_length)
|
||||
{
|
||||
cmark_source_extent *next_extent = extent->next;
|
||||
cmark_source_extent *res;
|
||||
|
||||
while (next_extent && extent->start == extent->stop) {
|
||||
extent = source_map_free_extent(self, extent);
|
||||
extent = next_extent;
|
||||
next_extent = extent->next;
|
||||
}
|
||||
|
||||
if (next_extent) {
|
||||
res = source_map_insert_extent(self,
|
||||
extent,
|
||||
extent->stop,
|
||||
extent->next->start,
|
||||
node,
|
||||
CMARK_EXTENT_BLANK);
|
||||
} else {
|
||||
res = source_map_insert_extent(self,
|
||||
extent,
|
||||
extent->stop,
|
||||
total_length,
|
||||
node,
|
||||
CMARK_EXTENT_BLANK);
|
||||
}
|
||||
|
||||
if (extent->start == extent->stop)
|
||||
source_map_free_extent(self, extent);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
cmark_source_extent *
|
||||
source_map_splice_extent(cmark_source_map *self, uint64_t start, uint64_t stop,
|
||||
cmark_node *node, cmark_extent_type type)
|
||||
{
|
||||
if (!self->next_cursor) {
|
||||
self->cursor = source_map_insert_extent(self,
|
||||
self->cursor,
|
||||
start + self->cursor_offset,
|
||||
stop + self->cursor_offset, node, type);
|
||||
|
||||
return self->cursor;
|
||||
} else if (start + self->cursor_offset < self->next_cursor->start &&
|
||||
stop + self->cursor_offset <= self->next_cursor->start) {
|
||||
self->cursor = source_map_insert_extent(self,
|
||||
self->cursor,
|
||||
start + self->cursor_offset,
|
||||
stop + self->cursor_offset, node, type);
|
||||
|
||||
return self->cursor;
|
||||
} else if (start + self->cursor_offset < self->next_cursor->start) {
|
||||
uint64_t new_start = self->next_cursor->start - self->cursor_offset;
|
||||
|
||||
self->cursor = source_map_insert_extent(self,
|
||||
self->cursor,
|
||||
start + self->cursor_offset,
|
||||
self->next_cursor->start,
|
||||
node, type);
|
||||
|
||||
if (new_start == stop)
|
||||
return self->cursor;
|
||||
|
||||
start = new_start;
|
||||
}
|
||||
|
||||
while (self->next_cursor && start + self->cursor_offset >= self->next_cursor->start) {
|
||||
self->cursor_offset += self->next_cursor->stop - self->next_cursor->start;
|
||||
self->cursor = self->cursor->next;
|
||||
self->next_cursor = self->cursor->next;
|
||||
}
|
||||
|
||||
return source_map_splice_extent(self, start, stop, node, type);
|
||||
}
|
||||
|
||||
bool
|
||||
source_map_start_cursor(cmark_source_map *self, cmark_source_extent *cursor)
|
||||
{
|
||||
self->cursor = cursor ? cursor : self->head;
|
||||
|
||||
if (!self->cursor)
|
||||
return false;
|
||||
|
||||
self->next_cursor = self->cursor->next;
|
||||
self->cursor_offset = self->cursor->stop;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
source_map_pretty_print(cmark_source_map *self) {
|
||||
cmark_source_extent *tmp;
|
||||
|
||||
for (tmp = self->head; tmp; tmp = tmp->next) {
|
||||
printf ("%lu:%lu - %s, %s (%p)\n", tmp->start, tmp->stop,
|
||||
cmark_node_get_type_string(tmp->node),
|
||||
cmark_source_extent_get_type_string(tmp),
|
||||
(void *) tmp->node);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
source_map_check(cmark_source_map *self, uint64_t total_length)
|
||||
{
|
||||
uint64_t last_stop = 0;
|
||||
cmark_source_extent *tmp;
|
||||
|
||||
for (tmp = self->head; tmp; tmp = tmp->next) {
|
||||
if (tmp->start != last_stop) {
|
||||
return false;
|
||||
} if (tmp->start == tmp->stop)
|
||||
return false;
|
||||
last_stop = tmp->stop;
|
||||
}
|
||||
|
||||
if (last_stop != total_length)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
uint64_t
|
||||
cmark_source_extent_get_start(cmark_source_extent *extent)
|
||||
{
|
||||
return extent->start;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
cmark_source_extent_get_stop(cmark_source_extent *extent)
|
||||
{
|
||||
return extent->stop;
|
||||
}
|
||||
|
||||
cmark_node *
|
||||
cmark_source_extent_get_node(cmark_source_extent *extent)
|
||||
{
|
||||
return extent->node;
|
||||
}
|
||||
|
||||
cmark_source_extent *
|
||||
cmark_source_extent_get_next(cmark_source_extent *extent)
|
||||
{
|
||||
return extent->next;
|
||||
}
|
||||
|
||||
cmark_source_extent *
|
||||
cmark_source_extent_get_previous(cmark_source_extent *extent)
|
||||
{
|
||||
return extent->prev;
|
||||
}
|
||||
|
||||
cmark_extent_type
|
||||
cmark_source_extent_get_type(cmark_source_extent *extent)
|
||||
{
|
||||
return extent->type;
|
||||
}
|
||||
|
||||
const char *
|
||||
cmark_source_extent_get_type_string(cmark_source_extent *extent)
|
||||
{
|
||||
switch (extent->type) {
|
||||
case CMARK_EXTENT_NONE:
|
||||
return "unknown";
|
||||
case CMARK_EXTENT_OPENER:
|
||||
return "opener";
|
||||
case CMARK_EXTENT_CLOSER:
|
||||
return "closer";
|
||||
case CMARK_EXTENT_BLANK:
|
||||
return "blank";
|
||||
case CMARK_EXTENT_CONTENT:
|
||||
return "content";
|
||||
case CMARK_EXTENT_PUNCTUATION:
|
||||
return "punctuation";
|
||||
case CMARK_EXTENT_LINK_DESTINATION:
|
||||
return "link_destination";
|
||||
case CMARK_EXTENT_LINK_TITLE:
|
||||
return "link_title";
|
||||
case CMARK_EXTENT_LINK_LABEL:
|
||||
return "link_label";
|
||||
case CMARK_EXTENT_REFERENCE_DESTINATION:
|
||||
return "reference_destination";
|
||||
case CMARK_EXTENT_REFERENCE_LABEL:
|
||||
return "reference_label";
|
||||
case CMARK_EXTENT_REFERENCE_TITLE:
|
||||
return "reference_title";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
#ifndef CMARK_SOURCE_MAP_H
|
||||
#define CMARK_SOURCE_MAP_H
|
||||
|
||||
#include "cmark.h"
|
||||
#include "config.h"
|
||||
|
||||
typedef struct _cmark_source_map
|
||||
{
|
||||
cmark_source_extent *head;
|
||||
cmark_source_extent *tail;
|
||||
cmark_source_extent *cursor;
|
||||
cmark_source_extent *next_cursor;
|
||||
uint64_t cursor_offset;
|
||||
cmark_mem *mem;
|
||||
} cmark_source_map;
|
||||
|
||||
struct cmark_source_extent
|
||||
{
|
||||
uint64_t start;
|
||||
uint64_t stop;
|
||||
struct cmark_source_extent *next;
|
||||
struct cmark_source_extent *prev;
|
||||
cmark_node *node;
|
||||
cmark_extent_type type;
|
||||
};
|
||||
|
||||
cmark_source_map * source_map_new (cmark_mem *mem);
|
||||
|
||||
void source_map_free (cmark_source_map *self);
|
||||
|
||||
bool source_map_check (cmark_source_map *self,
|
||||
uint64_t total_length);
|
||||
|
||||
void source_map_pretty_print (cmark_source_map *self);
|
||||
|
||||
cmark_source_extent * source_map_append_extent(cmark_source_map *self,
|
||||
uint64_t start,
|
||||
uint64_t stop,
|
||||
cmark_node *node,
|
||||
cmark_extent_type type);
|
||||
|
||||
cmark_source_extent * source_map_insert_extent(cmark_source_map *self,
|
||||
cmark_source_extent *previous,
|
||||
uint64_t start,
|
||||
uint64_t stop,
|
||||
cmark_node *node,
|
||||
cmark_extent_type type);
|
||||
|
||||
cmark_source_extent * source_map_free_extent (cmark_source_map *self,
|
||||
cmark_source_extent *extent);
|
||||
|
||||
cmark_source_extent * source_map_stitch_extent(cmark_source_map *self,
|
||||
cmark_source_extent *extent,
|
||||
cmark_node *node,
|
||||
uint64_t total_length);
|
||||
|
||||
cmark_source_extent * source_map_splice_extent(cmark_source_map *self,
|
||||
uint64_t start,
|
||||
uint64_t stop,
|
||||
cmark_node *node,
|
||||
cmark_extent_type type);
|
||||
|
||||
bool source_map_start_cursor (cmark_source_map *self,
|
||||
cmark_source_extent *cursor);
|
||||
|
||||
#endif
|
|
@ -73,20 +73,3 @@ ELSE(PYTHONINTERP_FOUND)
|
|||
|
||||
ENDIF(PYTHONINTERP_FOUND)
|
||||
|
||||
if (PYTHON_BINDING_TESTS)
|
||||
find_package(PythonInterp 3 REQUIRED)
|
||||
else(PYTHON_BINDING_TESTS)
|
||||
find_package(PythonInterp 3)
|
||||
endif(PYTHON_BINDING_TESTS)
|
||||
|
||||
IF (PYTHONINTERP_FOUND)
|
||||
add_test(python3_bindings
|
||||
${PYTHON_EXECUTABLE}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/test_cmark.py"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/../src"
|
||||
)
|
||||
ELSE(PYTHONINTERP_FOUND)
|
||||
message("\n*** A python 3 interpreter is required to run the python binding tests.\n")
|
||||
add_test(skipping_python_binding_tests
|
||||
echo "Skipping python binding tests, because no python 3 interpreter is available.")
|
||||
ENDIF(PYTHONINTERP_FOUND)
|
||||
|
|
|
@ -1,490 +0,0 @@
|
|||
# -*- coding: utf8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import os
|
||||
import unittest
|
||||
import argparse
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
sys.path.append(os.path.join(here, os.pardir, 'wrappers'))
|
||||
from wrapper import *
|
||||
|
||||
class TestHighLevel(unittest.TestCase):
|
||||
def test_markdown_to_html(self):
|
||||
self.assertEqual(markdown_to_html('foo'), '<p>foo</p>\n')
|
||||
|
||||
def test_parse_document(self):
|
||||
doc = parse_document('foo')
|
||||
self.assertEqual(type(doc), Document)
|
||||
|
||||
class TestParser(unittest.TestCase):
|
||||
def test_lifecycle(self):
|
||||
parser = Parser()
|
||||
del parser
|
||||
|
||||
def test_feed(self):
|
||||
parser = Parser()
|
||||
parser.feed('‘')
|
||||
|
||||
def test_finish(self):
|
||||
parser = Parser()
|
||||
parser.feed('‘')
|
||||
doc = parser.finish()
|
||||
|
||||
def test_source_map(self):
|
||||
parser = Parser()
|
||||
parser.feed('‘')
|
||||
doc = parser.finish()
|
||||
source_map = parser.get_source_map()
|
||||
extents = [e for e in source_map]
|
||||
self.assertEqual(len(extents), 1)
|
||||
self.assertEqual(extents[0].type, ExtentType.CONTENT)
|
||||
self.assertEqual(extents[0].start, 0)
|
||||
self.assertEqual(extents[0].stop, 3)
|
||||
|
||||
def test_render_html(self):
|
||||
parser = Parser()
|
||||
parser.feed('‘')
|
||||
doc = parser.finish()
|
||||
res = doc.to_html()
|
||||
self.assertEqual(res, '<p>‘</p>\n')
|
||||
|
||||
def test_render_xml(self):
|
||||
parser = Parser()
|
||||
parser.feed('‘')
|
||||
doc = parser.finish()
|
||||
res = doc.to_xml()
|
||||
self.assertEqual(
|
||||
res,
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
'<!DOCTYPE document SYSTEM "CommonMark.dtd">\n'
|
||||
'<document xmlns="http://commonmark.org/xml/1.0">\n'
|
||||
' <paragraph>\n'
|
||||
' <text>‘</text>\n'
|
||||
' </paragraph>\n'
|
||||
'</document>\n')
|
||||
|
||||
def test_render_commonmark(self):
|
||||
parser = Parser()
|
||||
parser.feed('‘')
|
||||
doc = parser.finish()
|
||||
res = doc.to_commonmark()
|
||||
self.assertEqual(res, '‘\n')
|
||||
|
||||
def test_render_man(self):
|
||||
parser = Parser()
|
||||
parser.feed('‘')
|
||||
doc = parser.finish()
|
||||
res = doc.to_man()
|
||||
self.assertEqual(
|
||||
res,
|
||||
'.PP\n'
|
||||
'\[oq]\n')
|
||||
|
||||
def test_render_latex(self):
|
||||
parser = Parser()
|
||||
parser.feed('‘')
|
||||
doc = parser.finish()
|
||||
res = doc.to_latex()
|
||||
self.assertEqual(res, '`\n')
|
||||
|
||||
class TestNode(unittest.TestCase):
|
||||
def test_type(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo')
|
||||
doc = parser.finish()
|
||||
self.assertEqual(type(doc), Document)
|
||||
|
||||
def test_first_child(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo')
|
||||
doc = parser.finish()
|
||||
child1 = doc.first_child
|
||||
child2 = doc.first_child
|
||||
self.assertEqual(child1, child2)
|
||||
self.assertEqual((child1 != child2), False)
|
||||
|
||||
def test_last_child(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo')
|
||||
doc = parser.finish()
|
||||
child1 = doc.first_child
|
||||
child2 = doc.last_child
|
||||
self.assertEqual(child1, child2)
|
||||
self.assertEqual((child1 != child2), False)
|
||||
|
||||
def test_next(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo *bar*')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
self.assertEqual(type(para), Paragraph)
|
||||
text = para.first_child
|
||||
self.assertEqual(type(text), Text)
|
||||
emph = text.next
|
||||
self.assertEqual(type(emph), Emph)
|
||||
self.assertEqual(para.next, None)
|
||||
|
||||
def test_previous(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo *bar*')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
text = para.first_child
|
||||
emph = text.next
|
||||
self.assertEqual(emph.previous, text)
|
||||
self.assertEqual(para.previous, None)
|
||||
|
||||
def test_children(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo *bar*')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
children = [c for c in para]
|
||||
self.assertEqual(len(children), 2)
|
||||
self.assertEqual(type(children[0]), Text)
|
||||
self.assertEqual(type(children[1]), Emph)
|
||||
|
||||
def test_new(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
n = Node()
|
||||
|
||||
def test_unlink(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo *bar*')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
para.unlink()
|
||||
self.assertEqual(doc.to_html(), '')
|
||||
|
||||
def test_append_child(self):
|
||||
parser = Parser()
|
||||
parser.feed('')
|
||||
doc = parser.finish()
|
||||
doc.append_child(Paragraph())
|
||||
self.assertEqual(doc.to_html(), '<p></p>\n')
|
||||
with self.assertRaises(LibcmarkError):
|
||||
doc.append_child(Text(literal='foo'))
|
||||
|
||||
def test_prepend_child(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo')
|
||||
doc = parser.finish()
|
||||
doc.prepend_child(Paragraph())
|
||||
self.assertEqual(doc.to_html(), '<p></p>\n<p>foo</p>\n')
|
||||
with self.assertRaises(LibcmarkError):
|
||||
doc.prepend_child(Text(literal='foo'))
|
||||
|
||||
def test_insert_before(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
para.insert_before(Paragraph())
|
||||
self.assertEqual(doc.to_html(), '<p></p>\n<p>foo</p>\n')
|
||||
with self.assertRaises(LibcmarkError):
|
||||
para.insert_before(Text(literal='foo'))
|
||||
|
||||
def test_insert_after(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
para.insert_after(Paragraph())
|
||||
self.assertEqual(doc.to_html(), '<p>foo</p>\n<p></p>\n')
|
||||
with self.assertRaises(LibcmarkError):
|
||||
para.insert_after(Text(literal='foo'))
|
||||
|
||||
def test_consolidate_text_nodes(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo **bar*')
|
||||
doc = parser.finish()
|
||||
self.assertEqual(len([c for c in doc.first_child]), 3)
|
||||
doc.consolidate_text_nodes()
|
||||
self.assertEqual(len([c for c in doc.first_child]), 2)
|
||||
|
||||
class TestLiteral(unittest.TestCase):
|
||||
def test_text(self):
|
||||
parser = Parser()
|
||||
parser.feed('foo')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
self.assertEqual(type(para), Paragraph)
|
||||
text = para.first_child
|
||||
self.assertEqual(type(text), Text)
|
||||
self.assertEqual(text.literal, 'foo')
|
||||
text.literal = 'bar'
|
||||
self.assertEqual(text.to_html(), 'bar')
|
||||
|
||||
class TestDocument(unittest.TestCase):
|
||||
def test_new(self):
|
||||
doc = Document()
|
||||
self.assertEqual(doc.to_html(),
|
||||
'')
|
||||
|
||||
class TestBlockQuote(unittest.TestCase):
|
||||
def test_new(self):
|
||||
bq = BlockQuote()
|
||||
self.assertEqual(bq.to_html(),
|
||||
'<blockquote>\n</blockquote>\n')
|
||||
|
||||
class TestList(unittest.TestCase):
|
||||
def test_new(self):
|
||||
list_ = List()
|
||||
self.assertEqual(list_.to_html(),
|
||||
'<ul>\n</ul>\n')
|
||||
|
||||
def test_type(self):
|
||||
parser = Parser()
|
||||
parser.feed('* foo')
|
||||
doc = parser.finish()
|
||||
list_ = doc.first_child
|
||||
self.assertEqual(type(list_), List)
|
||||
self.assertEqual(list_.type, ListType.BULLET)
|
||||
list_.type = ListType.ORDERED
|
||||
self.assertEqual(doc.to_html(),
|
||||
'<ol>\n'
|
||||
'<li>foo</li>\n'
|
||||
'</ol>\n')
|
||||
|
||||
def test_start(self):
|
||||
parser = Parser()
|
||||
parser.feed('2. foo')
|
||||
doc = parser.finish()
|
||||
list_ = doc.first_child
|
||||
self.assertEqual(type(list_), List)
|
||||
self.assertEqual(list_.start, 2)
|
||||
list_.start = 1
|
||||
self.assertEqual(doc.to_commonmark(),
|
||||
'1. foo\n')
|
||||
with self.assertRaises(LibcmarkError):
|
||||
list_.start = -1
|
||||
list_.type = ListType.BULLET
|
||||
|
||||
def test_delim(self):
|
||||
parser = Parser()
|
||||
parser.feed('1. foo')
|
||||
doc = parser.finish()
|
||||
list_ = doc.first_child
|
||||
self.assertEqual(type(list_), List)
|
||||
self.assertEqual(list_.delim, '.')
|
||||
list_.delim = ')'
|
||||
self.assertEqual(doc.to_commonmark(),
|
||||
'1) foo\n')
|
||||
|
||||
def test_tight(self):
|
||||
parser = Parser()
|
||||
parser.feed('* foo\n'
|
||||
'\n'
|
||||
'* bar\n')
|
||||
doc = parser.finish()
|
||||
list_ = doc.first_child
|
||||
self.assertEqual(type(list_), List)
|
||||
self.assertEqual(list_.tight, False)
|
||||
self.assertEqual(doc.to_commonmark(),
|
||||
' - foo\n'
|
||||
'\n'
|
||||
' - bar\n')
|
||||
|
||||
list_.tight = True
|
||||
self.assertEqual(doc.to_commonmark(),
|
||||
' - foo\n'
|
||||
' - bar\n')
|
||||
|
||||
with self.assertRaises(LibcmarkError):
|
||||
list_.tight = 42
|
||||
|
||||
class TestItem(unittest.TestCase):
|
||||
def test_new(self):
|
||||
item = Item()
|
||||
self.assertEqual(item.to_html(),
|
||||
'<li></li>\n')
|
||||
|
||||
class TestCodeBlock(unittest.TestCase):
|
||||
def test_new(self):
|
||||
cb = CodeBlock(literal='foo', fence_info='python')
|
||||
self.assertEqual(cb.to_html(),
|
||||
'<pre><code class="language-python">foo</code></pre>\n')
|
||||
|
||||
def test_fence_info(self):
|
||||
parser = Parser()
|
||||
parser.feed('``` markdown\n'
|
||||
'hello\n'
|
||||
'```\n')
|
||||
doc = parser.finish()
|
||||
code_block = doc.first_child
|
||||
self.assertEqual(type(code_block), CodeBlock)
|
||||
self.assertEqual(code_block.fence_info, 'markdown')
|
||||
code_block.fence_info = 'python'
|
||||
self.assertEqual(doc.to_commonmark(),
|
||||
'``` python\n'
|
||||
'hello\n'
|
||||
'```\n')
|
||||
|
||||
class TestHtmlBlock(unittest.TestCase):
|
||||
def test_new(self):
|
||||
hb = HtmlBlock(literal='<p>foo</p>')
|
||||
self.assertEqual(hb.to_html(),
|
||||
'<p>foo</p>\n')
|
||||
|
||||
class TestCustomBlock(unittest.TestCase):
|
||||
def test_new(self):
|
||||
cb = CustomBlock()
|
||||
self.assertEqual(cb.to_html(),
|
||||
'')
|
||||
|
||||
class TestParagraph(unittest.TestCase):
|
||||
def test_new(self):
|
||||
para = Paragraph()
|
||||
self.assertEqual(para.to_html(),
|
||||
'<p></p>\n')
|
||||
|
||||
class TestHeading(unittest.TestCase):
|
||||
def test_new(self):
|
||||
heading = Heading(level=3)
|
||||
self.assertEqual(heading.to_html(),
|
||||
'<h3></h3>\n')
|
||||
|
||||
def test_level(self):
|
||||
parser = Parser()
|
||||
parser.feed('# foo')
|
||||
doc = parser.finish()
|
||||
heading = doc.first_child
|
||||
self.assertEqual(type(heading), Heading)
|
||||
self.assertEqual(heading.level, 1)
|
||||
heading.level = 3
|
||||
self.assertEqual(heading.level, 3)
|
||||
|
||||
self.assertEqual(doc.to_html(),
|
||||
'<h3>foo</h3>\n')
|
||||
|
||||
with self.assertRaises(LibcmarkError):
|
||||
heading.level = 10
|
||||
|
||||
class TestThematicBreak(unittest.TestCase):
|
||||
def test_new(self):
|
||||
tb = ThematicBreak()
|
||||
self.assertEqual(tb.to_html(),
|
||||
'<hr />\n')
|
||||
|
||||
class TestText(unittest.TestCase):
|
||||
def test_new(self):
|
||||
text = Text(literal='foo')
|
||||
self.assertEqual(text.to_html(),
|
||||
'foo')
|
||||
|
||||
class TestSoftBreak(unittest.TestCase):
|
||||
def test_new(self):
|
||||
sb = SoftBreak()
|
||||
self.assertEqual(sb.to_html(), '\n')
|
||||
self.assertEqual(sb.to_html(options=Parser.OPT_HARDBREAKS),
|
||||
'<br />\n')
|
||||
self.assertEqual(sb.to_html(options=Parser.OPT_NOBREAKS),
|
||||
' ')
|
||||
|
||||
class TestLineBreak(unittest.TestCase):
|
||||
def test_new(self):
|
||||
lb = LineBreak()
|
||||
self.assertEqual(lb.to_html(), '<br />\n')
|
||||
|
||||
class TestCode(unittest.TestCase):
|
||||
def test_new(self):
|
||||
code = Code(literal='bar')
|
||||
self.assertEqual(code.to_html(), '<code>bar</code>')
|
||||
|
||||
class TestHtmlInline(unittest.TestCase):
|
||||
def test_new(self):
|
||||
hi = HtmlInline(literal='<b>baz</b>')
|
||||
self.assertEqual(hi.to_html(), '<b>baz</b>')
|
||||
|
||||
class TestCustomInline(unittest.TestCase):
|
||||
def test_new(self):
|
||||
ci = CustomInline()
|
||||
self.assertEqual(ci.to_html(),
|
||||
'')
|
||||
|
||||
class TestEmph(unittest.TestCase):
|
||||
def test_new(self):
|
||||
emph = Emph()
|
||||
self.assertEqual(emph.to_html(),
|
||||
'<em></em>')
|
||||
|
||||
class TestStrong(unittest.TestCase):
|
||||
def test_new(self):
|
||||
strong = Strong()
|
||||
self.assertEqual(strong.to_html(),
|
||||
'<strong></strong>')
|
||||
|
||||
class TestLink(unittest.TestCase):
|
||||
def test_new(self):
|
||||
link = Link(url='http://foo.com', title='foo')
|
||||
self.assertEqual(link.to_html(),
|
||||
'<a href="http://foo.com" title="foo"></a>')
|
||||
|
||||
def test_url(self):
|
||||
parser = Parser()
|
||||
parser.feed('<http://foo.com>\n')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
self.assertEqual(type(para), Paragraph)
|
||||
link = para.first_child
|
||||
self.assertEqual(type(link), Link)
|
||||
self.assertEqual(link.url, 'http://foo.com')
|
||||
link.url = 'http://bar.net'
|
||||
# Yeah that's crappy behaviour but not our problem here
|
||||
self.assertEqual(doc.to_commonmark(),
|
||||
'[http://foo.com](http://bar.net)\n')
|
||||
|
||||
def test_title(self):
|
||||
parser = Parser()
|
||||
parser.feed('<http://foo.com>\n')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
self.assertEqual(type(para), Paragraph)
|
||||
link = para.first_child
|
||||
self.assertEqual(type(link), Link)
|
||||
self.assertEqual(link.title, '')
|
||||
link.title = 'foo'
|
||||
self.assertEqual(doc.to_html(),
|
||||
'<p><a href="http://foo.com" title="foo">http://foo.com</a></p>\n')
|
||||
|
||||
class TestImage(unittest.TestCase):
|
||||
def test_new(self):
|
||||
image = Image(url='http://foo.com', title='foo')
|
||||
self.assertEqual(image.to_html(),
|
||||
'<img src="http://foo.com" alt="" title="foo" />')
|
||||
|
||||
def test_url(self):
|
||||
parser = Parser()
|
||||
parser.feed('![image](image.com)\n')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
self.assertEqual(type(para), Paragraph)
|
||||
link = para.first_child
|
||||
self.assertEqual(type(link), Image)
|
||||
self.assertEqual(link.url, 'image.com')
|
||||
link.url = 'http://bar.net'
|
||||
self.assertEqual(doc.to_commonmark(),
|
||||
'![image](http://bar.net)\n')
|
||||
|
||||
def test_title(self):
|
||||
parser = Parser()
|
||||
parser.feed('![image](image.com "ze image")\n')
|
||||
doc = parser.finish()
|
||||
para = doc.first_child
|
||||
self.assertEqual(type(para), Paragraph)
|
||||
image = para.first_child
|
||||
self.assertEqual(type(image), Image)
|
||||
self.assertEqual(image.title, 'ze image')
|
||||
image.title = 'foo'
|
||||
self.assertEqual(doc.to_html(),
|
||||
'<p><img src="image.com" alt="image" title="foo" /></p>\n')
|
||||
|
||||
if __name__=='__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('libdir')
|
||||
args = parser.parse_known_args()
|
||||
conf.set_library_path(args[0].libdir)
|
||||
unittest.main(argv=[sys.argv[0]] + args[1])
|
|
@ -1,921 +1,37 @@
|
|||
from __future__ import unicode_literals
|
||||
#!/usr/bin/env python
|
||||
|
||||
from ctypes import *
|
||||
# Example for using the shared library from python
|
||||
# Will work with either python 2 or python 3
|
||||
# Requires cmark library to be installed
|
||||
|
||||
from ctypes import CDLL, c_char_p, c_long
|
||||
import sys
|
||||
import platform
|
||||
|
||||
c_object_p = POINTER(c_void_p)
|
||||
|
||||
sysname = platform.system()
|
||||
|
||||
if sysname == 'Windows':
|
||||
libc = CDLL('msvcrt.dll')
|
||||
if sysname == 'Darwin':
|
||||
libname = "libcmark.dylib"
|
||||
elif sysname == 'Windows':
|
||||
libname = "cmark.dll"
|
||||
else:
|
||||
libc = CDLL('libc.so.6')
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
def bytes_and_length(text):
|
||||
if type(text) == str:
|
||||
text = text.encode("utf8")
|
||||
return text, len(text)
|
||||
else:
|
||||
def bytes_and_length(text):
|
||||
if type(text) == unicode:
|
||||
text = text.encode("utf8")
|
||||
return text, len(text)
|
||||
|
||||
def unicode_from_char_p(res, fn, args):
|
||||
ret = res.decode("utf8")
|
||||
return ret
|
||||
|
||||
class owned_char_p(c_void_p):
|
||||
def __del__(self):
|
||||
conf.lib.cmark_default_mem_free(self.value)
|
||||
|
||||
def unicode_from_owned_char_p(res, fn, args):
|
||||
ret = cast(res, c_char_p).value.decode("utf8")
|
||||
return ret
|
||||
|
||||
def boolean_from_result(res, fn, args):
|
||||
return bool(res)
|
||||
|
||||
def delim_from_int(res, fn, args):
|
||||
if res == 0:
|
||||
return ''
|
||||
elif res == 1:
|
||||
return '.'
|
||||
elif res == 2:
|
||||
return ')'
|
||||
|
||||
class BaseEnumeration(object):
|
||||
def __init__(self, value):
|
||||
if value >= len(self.__class__._kinds):
|
||||
self.__class__._kinds += [None] * (value - len(self.__class__._kinds) + 1)
|
||||
if self.__class__._kinds[value] is not None:
|
||||
raise ValueError('{0} value {1} already loaded'.format(
|
||||
str(self.__class__), value))
|
||||
self.value = value
|
||||
self.__class__._kinds[value] = self
|
||||
self.__class__._name_map = None
|
||||
|
||||
def from_param(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_id(cls, id, fn, args):
|
||||
if id >= len(cls._kinds) or cls._kinds[id] is None:
|
||||
raise ValueError('Unknown template argument kind %d' % id)
|
||||
return cls._kinds[id]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Get the enumeration name of this cursor kind."""
|
||||
if self._name_map is None:
|
||||
self._name_map = {}
|
||||
for key, value in self.__class__.__dict__.items():
|
||||
if isinstance(value, self.__class__):
|
||||
self._name_map[value] = key
|
||||
return str(self._name_map[self])
|
||||
|
||||
def __repr__(self):
|
||||
return '%s.%s' % (self.__class__.__name__, self.name,)
|
||||
|
||||
class Parser(object):
|
||||
OPT_DEFAULT = 0
|
||||
OPT_SOURCEPOS = 1 << 1
|
||||
OPT_HARDBREAKS = 1 << 2
|
||||
OPT_SAFE = 1 << 3
|
||||
OPT_NOBREAKS = 1 << 4
|
||||
OPT_NORMALIZE = 1 << 8
|
||||
OPT_VALIDATE_UTF8 = 1 << 9
|
||||
OPT_SMART = 1 << 10
|
||||
|
||||
def __init__(self, options=0):
|
||||
self._parser = conf.lib.cmark_parser_new(options)
|
||||
|
||||
def __del__(self):
|
||||
conf.lib.cmark_parser_free(self._parser)
|
||||
|
||||
def feed(self, text):
|
||||
conf.lib.cmark_parser_feed(self._parser, *bytes_and_length(text))
|
||||
|
||||
def finish(self):
|
||||
return conf.lib.cmark_parser_finish(self._parser)
|
||||
|
||||
def get_source_map(self):
|
||||
return conf.lib.cmark_parser_get_first_source_extent(self._parser)
|
||||
|
||||
class LibcmarkError(Exception):
|
||||
def __init__(self, message):
|
||||
self.m = message
|
||||
|
||||
def __str__(self):
|
||||
return self.m
|
||||
|
||||
class NodeType(BaseEnumeration):
|
||||
_kinds = []
|
||||
_name_map = None
|
||||
|
||||
NodeType.NONE = NodeType(0)
|
||||
NodeType.DOCUMENT = NodeType(1)
|
||||
NodeType.BLOCK_QUOTE = NodeType(2)
|
||||
NodeType.LIST = NodeType(3)
|
||||
NodeType.ITEM = NodeType(4)
|
||||
NodeType.CODE_BLOCK = NodeType(5)
|
||||
NodeType.HTML_BLOCK = NodeType(6)
|
||||
NodeType.CUSTOM_BLOCK = NodeType(7)
|
||||
NodeType.PARAGRAPH = NodeType(8)
|
||||
NodeType.HEADING = NodeType(9)
|
||||
NodeType.THEMATIC_BREAK = NodeType(10)
|
||||
NodeType.TEXT = NodeType(11)
|
||||
NodeType.SOFTBREAK = NodeType(12)
|
||||
NodeType.LINEBREAK = NodeType(13)
|
||||
NodeType.CODE = NodeType(14)
|
||||
NodeType.HTML_INLINE = NodeType(15)
|
||||
NodeType.CUSTOM_INLINE = NodeType(16)
|
||||
NodeType.EMPH = NodeType(17)
|
||||
NodeType.STRONG = NodeType(18)
|
||||
NodeType.LINK = NodeType(19)
|
||||
NodeType.IMAGE = NodeType(20)
|
||||
|
||||
class ListType(BaseEnumeration):
|
||||
_kinds = []
|
||||
_name_map = None
|
||||
|
||||
ListType.BULLET = ListType(1)
|
||||
ListType.ORDERED = ListType(2)
|
||||
|
||||
class Node(object):
|
||||
__subclass_map = {}
|
||||
|
||||
def __init__(self):
|
||||
self._owned = False
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def from_result(res, fn=None, args=None):
|
||||
try:
|
||||
res.contents
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
cls = Node.get_subclass_map()[conf.lib.cmark_node_get_type(res)]
|
||||
|
||||
ret = cls.__new__(cls)
|
||||
ret._node = res
|
||||
ret._owned = False
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def get_subclass_map(cls):
|
||||
if cls.__subclass_map:
|
||||
return cls.__subclass_map
|
||||
|
||||
res = {c._node_type: c for c in cls.__subclasses__()}
|
||||
|
||||
for c in cls.__subclasses__():
|
||||
res.update(c.get_subclass_map())
|
||||
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
conf.lib.cmark_node_unlink(self._node)
|
||||
self._owned = True
|
||||
|
||||
def append_child(self, child):
|
||||
res = conf.lib.cmark_node_append_child(self._node, child._node)
|
||||
if not res:
|
||||
raise LibcmarkError("Can't append child %s to node %s" % (str(child), str(self)))
|
||||
child._owned = False
|
||||
|
||||
def prepend_child(self, child):
|
||||
res = conf.lib.cmark_node_prepend_child(self._node, child._node)
|
||||
if not res:
|
||||
raise LibcmarkError("Can't prepend child %s to node %s" % (str(child), str(self)))
|
||||
child._owned = False
|
||||
|
||||
def insert_before(self, sibling):
|
||||
res = conf.lib.cmark_node_insert_before(self._node, sibling._node)
|
||||
if not res:
|
||||
raise LibcmarkError("Can't insert sibling %s before node %s" % (str(sibling), str(self)))
|
||||
sibling._owned = False
|
||||
|
||||
def insert_after(self, sibling):
|
||||
res = conf.lib.cmark_node_insert_after(self._node, sibling._node)
|
||||
if not res:
|
||||
raise LibcmarkError("Can't insert sibling %s after node %s" % (str(sibling), str(self)))
|
||||
sibling._owned = False
|
||||
|
||||
def consolidate_text_nodes(self):
|
||||
conf.lib.cmark_consolidate_text_nodes(self._node)
|
||||
|
||||
def to_html(self, options=Parser.OPT_DEFAULT):
|
||||
return conf.lib.cmark_render_html(self._node, options)
|
||||
|
||||
def to_xml(self, options=Parser.OPT_DEFAULT):
|
||||
return conf.lib.cmark_render_xml(self._node, options)
|
||||
|
||||
def to_commonmark(self, options=Parser.OPT_DEFAULT, width=0):
|
||||
return conf.lib.cmark_render_commonmark(self._node, options, width)
|
||||
|
||||
def to_man(self, options=Parser.OPT_DEFAULT, width=0):
|
||||
return conf.lib.cmark_render_man(self._node, options, width)
|
||||
|
||||
def to_latex(self, options=Parser.OPT_DEFAULT, width=0):
|
||||
return conf.lib.cmark_render_latex(self._node, options, width)
|
||||
|
||||
@property
|
||||
def first_child(self):
|
||||
return conf.lib.cmark_node_first_child(self._node)
|
||||
|
||||
@property
|
||||
def last_child(self):
|
||||
return conf.lib.cmark_node_last_child(self._node)
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
return conf.lib.cmark_node_next(self._node)
|
||||
|
||||
@property
|
||||
def previous(self):
|
||||
return conf.lib.cmark_node_previous(self._node)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._node.contents.value == other._node.contents.value
|
||||
|
||||
def __ne__(self, other):
|
||||
return self._node.contents.value != other._node.contents.value
|
||||
|
||||
def __del__(self):
|
||||
if self._owned:
|
||||
conf.lib.cmark_node_free(self._node)
|
||||
|
||||
def __iter__(self):
|
||||
cur = self.first_child
|
||||
while (cur):
|
||||
yield cur
|
||||
cur = cur.next
|
||||
|
||||
class Literal(Node):
|
||||
_node_type = NodeType.NONE
|
||||
|
||||
@property
|
||||
def literal(self):
|
||||
return conf.lib.cmark_node_get_literal(self._node)
|
||||
|
||||
@literal.setter
|
||||
def literal(self, value):
|
||||
bytes_, _ = bytes_and_length(value)
|
||||
if not conf.lib.cmark_node_set_literal(self._node, bytes_):
|
||||
raise LibcmarkError("Invalid literal %s\n" % str(value))
|
||||
|
||||
class Document(Node):
|
||||
_node_type = NodeType.DOCUMENT
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
class BlockQuote(Node):
|
||||
_node_type = NodeType.BLOCK_QUOTE
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
class List(Node):
|
||||
_node_type = NodeType.LIST
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return conf.lib.cmark_node_get_list_type(self._node)
|
||||
|
||||
@type.setter
|
||||
def type(self, type_):
|
||||
if not conf.lib.cmark_node_set_list_type(self._node, type_.value):
|
||||
raise LibcmarkError("Invalid type %s" % str(type_))
|
||||
|
||||
@property
|
||||
def delim(self):
|
||||
return conf.lib.cmark_node_get_list_delim(self._node)
|
||||
|
||||
@delim.setter
|
||||
def delim(self, value):
|
||||
if value == '.':
|
||||
delim_type = 1
|
||||
elif value == ')':
|
||||
delim_type = 2
|
||||
else:
|
||||
raise LibcmarkError('Invalid delim type %s' % str(value))
|
||||
|
||||
conf.lib.cmark_node_set_list_delim(self._node, delim_type)
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
return conf.lib.cmark_node_get_list_start(self._node)
|
||||
|
||||
@start.setter
|
||||
def start(self, value):
|
||||
if not conf.lib.cmark_node_set_list_start(self._node, value):
|
||||
raise LibcmarkError("Invalid list start %s\n" % str(value))
|
||||
|
||||
@property
|
||||
def tight(self):
|
||||
return conf.lib.cmark_node_get_list_tight(self._node)
|
||||
|
||||
@tight.setter
|
||||
def tight(self, value):
|
||||
if value is True:
|
||||
tightness = 1
|
||||
elif value is False:
|
||||
tightness = 0
|
||||
else:
|
||||
raise LibcmarkError("Invalid list tightness %s\n" % str(value))
|
||||
if not conf.lib.cmark_node_set_list_tight(self._node, tightness):
|
||||
raise LibcmarkError("Invalid list tightness %s\n" % str(value))
|
||||
|
||||
class Item(Node):
|
||||
_node_type = NodeType.ITEM
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
class CodeBlock(Literal):
|
||||
_node_type = NodeType.CODE_BLOCK
|
||||
|
||||
def __init__(self, literal='', fence_info=''):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
self.literal = literal
|
||||
self.fence_info = fence_info
|
||||
|
||||
@property
|
||||
def fence_info(self):
|
||||
return conf.lib.cmark_node_get_fence_info(self._node)
|
||||
|
||||
@fence_info.setter
|
||||
def fence_info(self, value):
|
||||
bytes_, _ = bytes_and_length(value)
|
||||
if not conf.lib.cmark_node_set_fence_info(self._node, bytes_):
|
||||
raise LibcmarkError("Invalid fence info %s\n" % str(value))
|
||||
|
||||
class HtmlBlock(Literal):
|
||||
_node_type = NodeType.HTML_BLOCK
|
||||
|
||||
def __init__(self, literal=''):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
self.literal = literal
|
||||
|
||||
|
||||
class CustomBlock(Node):
|
||||
_node_type = NodeType.CUSTOM_BLOCK
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
|
||||
class Paragraph(Node):
|
||||
_node_type = NodeType.PARAGRAPH
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
class Heading(Node):
|
||||
_node_type = NodeType.HEADING
|
||||
|
||||
def __init__(self, level=1):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self.level = level
|
||||
self._owned = True
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
return int(conf.lib.cmark_node_get_heading_level(self._node))
|
||||
|
||||
@level.setter
|
||||
def level(self, value):
|
||||
res = conf.lib.cmark_node_set_heading_level(self._node, value)
|
||||
if (res == 0):
|
||||
raise LibcmarkError("Invalid heading level %s" % str(value))
|
||||
|
||||
class ThematicBreak(Node):
|
||||
_node_type = NodeType.THEMATIC_BREAK
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
|
||||
class Text(Literal):
|
||||
_node_type = NodeType.TEXT
|
||||
|
||||
def __init__(self, literal=''):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
self.literal = literal
|
||||
|
||||
|
||||
class SoftBreak(Node):
|
||||
_node_type = NodeType.SOFTBREAK
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
|
||||
class LineBreak(Node):
|
||||
_node_type = NodeType.LINEBREAK
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
|
||||
class Code(Literal):
|
||||
_node_type = NodeType.CODE
|
||||
|
||||
def __init__(self, literal=''):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
self.literal = literal
|
||||
|
||||
|
||||
class HtmlInline(Literal):
|
||||
_node_type = NodeType.HTML_INLINE
|
||||
|
||||
def __init__(self, literal=''):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
self.literal = literal
|
||||
|
||||
|
||||
class CustomInline(Node):
|
||||
_node_type = NodeType.CUSTOM_INLINE
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
class Emph(Node):
|
||||
_node_type = NodeType.EMPH
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
class Strong(Node):
|
||||
_node_type = NodeType.STRONG
|
||||
|
||||
def __init__(self):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
|
||||
|
||||
class Link(Node):
|
||||
_node_type = NodeType.LINK
|
||||
|
||||
def __init__(self, url='', title=''):
|
||||
self._node = conf.lib.cmark_node_new(self.__class__._node_type.value)
|
||||
self._owned = True
|
||||
self.url = url
|
||||
self.title = title
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return conf.lib.cmark_node_get_url(self._node)
|
||||
|
||||
@url.setter
|
||||
def url(self, value):
|
||||
bytes_, _ = bytes_and_length(value)
|
||||
if not conf.lib.cmark_node_set_url(self._node, bytes_):
|
||||
raise LibcmarkError("Invalid url %s\n" % str(value))
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return conf.lib.cmark_node_get_title(self._node)
|
||||
|
||||
@title.setter
|
||||
def title(self, value):
|
||||
bytes_, _ = bytes_and_length(value)
|
||||
if not conf.lib.cmark_node_set_title(self._node, bytes_):
|
||||
raise LibcmarkError("Invalid title %s\n" % str(value))
|
||||
|
||||
class Image(Link):
|
||||
_node_type = NodeType.IMAGE
|
||||
|
||||
class ExtentType(BaseEnumeration):
|
||||
_kinds = []
|
||||
_name_map = None
|
||||
|
||||
ExtentType.NONE = ExtentType(0)
|
||||
ExtentType.OPENER = ExtentType(1)
|
||||
ExtentType.CLOSER = ExtentType(2)
|
||||
ExtentType.BLANK = ExtentType(3)
|
||||
ExtentType.CONTENT = ExtentType(4)
|
||||
ExtentType.PUNCTUATION = ExtentType(5)
|
||||
ExtentType.LINK_DESTINATION = ExtentType(6)
|
||||
ExtentType.LINK_TITLE = ExtentType(7)
|
||||
ExtentType.LINK_LABEL = ExtentType(8)
|
||||
ExtentType.REFERENCE_DESTINATION = ExtentType(9)
|
||||
ExtentType.REFERENCE_LABEL = ExtentType(10)
|
||||
ExtentType.REFERENCE_TITLE = ExtentType(11)
|
||||
|
||||
class Extent(object):
|
||||
@staticmethod
|
||||
def from_result(res, fn=None, args=None):
|
||||
ret = Extent()
|
||||
ret._extent = res
|
||||
return ret
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
return conf.lib.cmark_source_extent_get_start(self._extent)
|
||||
|
||||
@property
|
||||
def stop(self):
|
||||
return conf.lib.cmark_source_extent_get_stop(self._extent)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return conf.lib.cmark_source_extent_get_type(self._extent)
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
return conf.lib.cmark_source_extent_get_node(self._extent)
|
||||
|
||||
class SourceMap(object):
|
||||
@staticmethod
|
||||
def from_result(res, fn, args):
|
||||
ret = SourceMap()
|
||||
ret._root = res
|
||||
return ret
|
||||
|
||||
def __iter__(self):
|
||||
cur = self._root
|
||||
while (cur):
|
||||
yield Extent.from_result(cur)
|
||||
cur = conf.lib.cmark_source_extent_get_next(cur)
|
||||
|
||||
def markdown_to_html(text, options=Parser.OPT_DEFAULT):
|
||||
bytes_, length = bytes_and_length(text)
|
||||
return conf.lib.cmark_markdown_to_html(bytes_, length, options)
|
||||
|
||||
def parse_document(text, options=Parser.OPT_DEFAULT):
|
||||
bytes_, length = bytes_and_length(text)
|
||||
return conf.lib.cmark_parse_document(bytes_, length, options)
|
||||
|
||||
functionList = [
|
||||
("cmark_default_mem_free",
|
||||
[c_void_p]),
|
||||
("cmark_markdown_to_html",
|
||||
[c_char_p, c_long, c_int],
|
||||
owned_char_p,
|
||||
unicode_from_owned_char_p),
|
||||
("cmark_parse_document",
|
||||
[c_char_p, c_long, c_int],
|
||||
c_object_p,
|
||||
Node.from_result),
|
||||
("cmark_parser_new",
|
||||
[c_int],
|
||||
c_object_p),
|
||||
("cmark_parser_free",
|
||||
[c_object_p]),
|
||||
("cmark_parser_feed",
|
||||
[c_object_p, c_char_p, c_long]),
|
||||
("cmark_parser_finish",
|
||||
[c_object_p],
|
||||
c_object_p,
|
||||
Node.from_result),
|
||||
("cmark_parser_get_first_source_extent",
|
||||
[c_object_p],
|
||||
c_object_p,
|
||||
SourceMap.from_result),
|
||||
("cmark_source_extent_get_next",
|
||||
[c_object_p],
|
||||
c_object_p),
|
||||
("cmark_source_extent_get_start",
|
||||
[c_object_p],
|
||||
c_ulonglong),
|
||||
("cmark_source_extent_get_stop",
|
||||
[c_object_p],
|
||||
c_ulonglong),
|
||||
("cmark_source_extent_get_type",
|
||||
[c_object_p],
|
||||
c_int,
|
||||
ExtentType.from_id),
|
||||
("cmark_source_extent_get_node",
|
||||
[c_object_p],
|
||||
c_object_p,
|
||||
Node.from_result),
|
||||
("cmark_render_html",
|
||||
[c_object_p, c_int],
|
||||
owned_char_p,
|
||||
unicode_from_owned_char_p),
|
||||
("cmark_render_xml",
|
||||
[c_object_p, c_int],
|
||||
owned_char_p,
|
||||
unicode_from_owned_char_p),
|
||||
("cmark_render_commonmark",
|
||||
[c_object_p, c_int, c_int],
|
||||
owned_char_p,
|
||||
unicode_from_owned_char_p),
|
||||
("cmark_render_man",
|
||||
[c_object_p, c_int, c_int],
|
||||
owned_char_p,
|
||||
unicode_from_owned_char_p),
|
||||
("cmark_render_latex",
|
||||
[c_object_p, c_int, c_int],
|
||||
owned_char_p,
|
||||
unicode_from_owned_char_p),
|
||||
("cmark_node_new",
|
||||
[c_int],
|
||||
c_object_p),
|
||||
("cmark_node_free",
|
||||
[c_object_p]),
|
||||
("cmark_node_get_type",
|
||||
[c_object_p],
|
||||
c_int,
|
||||
NodeType.from_id),
|
||||
("cmark_node_first_child",
|
||||
[c_object_p],
|
||||
c_object_p,
|
||||
Node.from_result),
|
||||
("cmark_node_last_child",
|
||||
[c_object_p],
|
||||
c_object_p,
|
||||
Node.from_result),
|
||||
("cmark_node_next",
|
||||
[c_object_p],
|
||||
c_object_p,
|
||||
Node.from_result),
|
||||
("cmark_node_previous",
|
||||
[c_object_p],
|
||||
c_object_p,
|
||||
Node.from_result),
|
||||
("cmark_node_unlink",
|
||||
[c_object_p]),
|
||||
("cmark_node_append_child",
|
||||
[c_object_p, c_object_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_prepend_child",
|
||||
[c_object_p, c_object_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_insert_before",
|
||||
[c_object_p, c_object_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_insert_after",
|
||||
[c_object_p, c_object_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_consolidate_text_nodes",
|
||||
[c_object_p]),
|
||||
("cmark_node_get_literal",
|
||||
[c_object_p],
|
||||
c_char_p,
|
||||
unicode_from_char_p),
|
||||
("cmark_node_set_literal",
|
||||
[c_object_p, c_char_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_get_heading_level",
|
||||
[c_object_p],
|
||||
c_int),
|
||||
("cmark_node_set_heading_level",
|
||||
[c_object_p, c_int],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_get_list_type",
|
||||
[c_object_p],
|
||||
c_int,
|
||||
ListType.from_id),
|
||||
("cmark_node_set_list_type",
|
||||
[c_object_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_get_list_delim",
|
||||
[c_object_p],
|
||||
c_int,
|
||||
delim_from_int),
|
||||
("cmark_node_set_list_delim",
|
||||
[c_object_p, c_int],
|
||||
c_int),
|
||||
("cmark_node_get_list_start",
|
||||
[c_object_p],
|
||||
c_int),
|
||||
("cmark_node_set_list_start",
|
||||
[c_object_p, c_int],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_get_list_tight",
|
||||
[c_object_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_set_list_tight",
|
||||
[c_object_p, c_int],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_get_fence_info",
|
||||
[c_object_p],
|
||||
c_char_p,
|
||||
unicode_from_char_p),
|
||||
("cmark_node_set_fence_info",
|
||||
[c_object_p, c_char_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_get_url",
|
||||
[c_object_p],
|
||||
c_char_p,
|
||||
unicode_from_char_p),
|
||||
("cmark_node_set_url",
|
||||
[c_object_p, c_char_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
("cmark_node_get_title",
|
||||
[c_object_p],
|
||||
c_char_p,
|
||||
unicode_from_char_p),
|
||||
("cmark_node_set_title",
|
||||
[c_object_p, c_char_p],
|
||||
c_int,
|
||||
boolean_from_result),
|
||||
]
|
||||
|
||||
# Taken from clang.cindex
|
||||
def register_function(lib, item, ignore_errors):
|
||||
# A function may not exist, if these bindings are used with an older or
|
||||
# incompatible version of libcmark.so.
|
||||
try:
|
||||
func = getattr(lib, item[0])
|
||||
except AttributeError as e:
|
||||
msg = str(e) + ". Please ensure that your python bindings are "\
|
||||
"compatible with your libcmark version."
|
||||
if ignore_errors:
|
||||
return
|
||||
raise LibcmarkError(msg)
|
||||
|
||||
if len(item) >= 2:
|
||||
func.argtypes = item[1]
|
||||
|
||||
if len(item) >= 3:
|
||||
func.restype = item[2]
|
||||
|
||||
if len(item) == 4:
|
||||
func.errcheck = item[3]
|
||||
|
||||
def register_functions(lib, ignore_errors):
|
||||
"""Register function prototypes with a libccmark library instance.
|
||||
|
||||
This must be called as part of library instantiation so Python knows how
|
||||
to call out to the shared library.
|
||||
"""
|
||||
|
||||
def register(item):
|
||||
return register_function(lib, item, ignore_errors)
|
||||
|
||||
for f in functionList:
|
||||
register(f)
|
||||
|
||||
class Config:
|
||||
library_path = None
|
||||
library_file = None
|
||||
compatibility_check = True
|
||||
loaded = False
|
||||
lib_ = None
|
||||
|
||||
@staticmethod
|
||||
def set_library_path(path):
|
||||
"""Set the path in which to search for libcmark"""
|
||||
if Config.loaded:
|
||||
raise Exception("library path must be set before before using " \
|
||||
"any other functionalities in libcmark.")
|
||||
|
||||
Config.library_path = path
|
||||
|
||||
@staticmethod
|
||||
def set_library_file(filename):
|
||||
"""Set the exact location of libcmark"""
|
||||
if Config.loaded:
|
||||
raise Exception("library file must be set before before using " \
|
||||
"any other functionalities in libcmark.")
|
||||
|
||||
Config.library_file = filename
|
||||
|
||||
@staticmethod
|
||||
def set_compatibility_check(check_status):
|
||||
""" Perform compatibility check when loading libcmark
|
||||
|
||||
The python bindings are only tested and evaluated with the version of
|
||||
libcmark they are provided with. To ensure correct behavior a (limited)
|
||||
compatibility check is performed when loading the bindings. This check
|
||||
will throw an exception, as soon as it fails.
|
||||
|
||||
In case these bindings are used with an older version of libcmark, parts
|
||||
that have been stable between releases may still work. Users of the
|
||||
python bindings can disable the compatibility check. This will cause
|
||||
the python bindings to load, even though they are written for a newer
|
||||
version of libcmark. Failures now arise if unsupported or incompatible
|
||||
features are accessed. The user is required to test themselves if the
|
||||
features they are using are available and compatible between different
|
||||
libcmark versions.
|
||||
"""
|
||||
if Config.loaded:
|
||||
raise Exception("compatibility_check must be set before before " \
|
||||
"using any other functionalities in libcmark.")
|
||||
|
||||
Config.compatibility_check = check_status
|
||||
|
||||
@property
|
||||
def lib(self):
|
||||
if self.lib_:
|
||||
return self.lib_
|
||||
lib = self.get_cmark_library()
|
||||
register_functions(lib, not Config.compatibility_check)
|
||||
Config.loaded = True
|
||||
self.lib_ = lib
|
||||
return lib
|
||||
|
||||
def get_filename(self):
|
||||
if Config.library_file:
|
||||
return Config.library_file
|
||||
|
||||
import platform
|
||||
name = platform.system()
|
||||
|
||||
if name == 'Darwin':
|
||||
file = 'libcmark.dylib'
|
||||
elif name == 'Windows':
|
||||
file = 'cmark.dll'
|
||||
else:
|
||||
file = 'libcmark.so'
|
||||
|
||||
if Config.library_path:
|
||||
file = Config.library_path + '/' + file
|
||||
|
||||
return file
|
||||
|
||||
def get_cmark_library(self):
|
||||
try:
|
||||
library = cdll.LoadLibrary(self.get_filename())
|
||||
except OSError as e:
|
||||
msg = str(e) + "(%s). To provide a path to libcmark use " \
|
||||
"Config.set_library_path() or " \
|
||||
"Config.set_library_file()." % self.get_filename()
|
||||
raise LibcmarkError(msg)
|
||||
|
||||
return library
|
||||
|
||||
def function_exists(self, name):
|
||||
try:
|
||||
getattr(self.lib, name)
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
conf = Config()
|
||||
|
||||
__alla__ = [
|
||||
'Parser',
|
||||
'LibcmarkError',
|
||||
'NodeType',
|
||||
'ListType',
|
||||
'Node',
|
||||
'Document',
|
||||
'BlockQuote',
|
||||
'List',
|
||||
'Item',
|
||||
'CodeBlock',
|
||||
'HtmlBlock',
|
||||
'CustomBlock',
|
||||
'Paragraph',
|
||||
'Heading',
|
||||
'ThematicBreak',
|
||||
'Text',
|
||||
'SoftBreak',
|
||||
'LineBreak',
|
||||
'Code',
|
||||
'HtmlInline',
|
||||
'CustomInline',
|
||||
'Emph',
|
||||
'Strong',
|
||||
'Link',
|
||||
'Image',
|
||||
'ExtentType',
|
||||
'Extent',
|
||||
'SourceMap',
|
||||
'markdown_to_html',
|
||||
'parse_document',
|
||||
'Config',
|
||||
'conf'
|
||||
]
|
||||
libname = "libcmark.so"
|
||||
cmark = CDLL(libname)
|
||||
|
||||
markdown = cmark.cmark_markdown_to_html
|
||||
markdown.restype = c_char_p
|
||||
markdown.argtypes = [c_char_p, c_long, c_long]
|
||||
|
||||
opts = 0 # defaults
|
||||
|
||||
def md2html(text):
|
||||
if sys.version_info >= (3,0):
|
||||
textbytes = text.encode('utf-8')
|
||||
textlen = len(textbytes)
|
||||
return markdown(textbytes, textlen, opts).decode('utf-8')
|
||||
else:
|
||||
textbytes = text
|
||||
textlen = len(text)
|
||||
return markdown(textbytes, textlen, opts)
|
||||
|
||||
sys.stdout.write(md2html(sys.stdin.read()))
|
||||
|
|
Загрузка…
Ссылка в новой задаче