static void S_process_line(cmark_parser *parser, const unsigned char *buffer, size_t bytes) { cmark_node* last_matched_container; int offset = 0; int matched = 0; int lev = 0; int i; cmark_list *data = NULL; bool all_matched = true; cmark_node* container; bool blank = false; int first_nonspace; int indent; bool indented; cmark_chunk input; bool maybe_lazy; int trim = 0; bool cr = false; bool lf = false; utf8proc_detab(parser->curline, buffer, bytes); // Add a newline to the end if not present: // TODO this breaks abstraction: if (parser->curline->size > trim && parser->curline->ptr[parser->curline->size - 1 - trim] == '\n') { trim += 1; lf = true; } if (parser->curline->size > trim && parser->curline->ptr[parser->curline->size - 1 - trim] == '\r') { trim += 1; cr = true; } if (cr) { cmark_strbuf_truncate(parser->curline, parser->curline->size - trim); } if (cr || !lf) { cmark_strbuf_putc(parser->curline, '\n'); } input.data = parser->curline->ptr; input.len = parser->curline->size; // container starts at the document root. container = parser->root; parser->line_number++; // for each containing node, try to parse the associated line start. // bail out on failure: container will point to the last matching node. while (container->last_child && container->last_child->open) { container = container->last_child; first_nonspace = offset; while (peek_at(&input, first_nonspace) == ' ') { first_nonspace++; } indent = first_nonspace - offset; blank = peek_at(&input, first_nonspace) == '\n' || peek_at(&input, first_nonspace) == '\r'; if (container->type == NODE_BLOCK_QUOTE) { matched = indent <= 3 && peek_at(&input, first_nonspace) == '>'; if (matched) { offset = first_nonspace + 1; if (peek_at(&input, offset) == ' ') offset++; } else { all_matched = false; } } else if (container->type == NODE_ITEM) { if (indent >= container->as.list.marker_offset + container->as.list.padding) { offset += container->as.list.marker_offset + container->as.list.padding; } else if (blank) { offset = first_nonspace; } else { all_matched = false; } } else if (container->type == NODE_CODE_BLOCK) { if (!container->as.code.fenced) { // indented if (indent >= CODE_INDENT) { offset += CODE_INDENT; } else if (blank) { offset = first_nonspace; } else { all_matched = false; } } else { // fenced matched = 0; if (indent <= 3 && (peek_at(&input, first_nonspace) == container->as.code.fence_char)) { matched = scan_close_code_fence(&input, first_nonspace); } if (matched >= container->as.code.fence_length) { // closing fence - and since we're at // the end of a line, we can return: all_matched = false; offset += matched; parser->current = finalize(parser, container); goto finished; } else { // skip opt. spaces of fence offset i = container->as.code.fence_offset; while (i > 0 && peek_at(&input, offset) == ' ') { offset++; i--; } } } } else if (container->type == NODE_HEADER) { // a header can never contain more than one line all_matched = false; } else if (container->type == NODE_HTML) { if (blank) { all_matched = false; } } else if (container->type == NODE_PARAGRAPH) { if (blank) { all_matched = false; } } if (!all_matched) { container = container->parent; // back up to last matching node break; } } last_matched_container = container; // check to see if we've hit 2nd blank line, break out of list: if (blank && container->last_line_blank) { break_out_of_lists(parser, &container); } maybe_lazy = parser->current->type == NODE_PARAGRAPH; // try new container starts: while (container->type != NODE_CODE_BLOCK && container->type != NODE_HTML) { first_nonspace = offset; while (peek_at(&input, first_nonspace) == ' ') first_nonspace++; indent = first_nonspace - offset; indented = indent >= CODE_INDENT; blank = peek_at(&input, first_nonspace) == '\n' || peek_at(&input, first_nonspace) == '\r'; if (indented && !maybe_lazy && !blank) { offset += CODE_INDENT; container = add_child(parser, container, NODE_CODE_BLOCK, offset + 1); container->as.code.fenced = false; container->as.code.fence_char = 0; container->as.code.fence_length = 0; container->as.code.fence_offset = 0; container->as.code.info = cmark_chunk_literal(""); } else if (!indented && peek_at(&input, first_nonspace) == '>') { offset = first_nonspace + 1; // optional following character if (peek_at(&input, offset) == ' ') offset++; container = add_child(parser, container, NODE_BLOCK_QUOTE, offset + 1); } else if (!indented && (matched = scan_atx_header_start(&input, first_nonspace))) { offset = first_nonspace + matched; container = add_child(parser, container, NODE_HEADER, offset + 1); int hashpos = cmark_chunk_strchr(&input, '#', first_nonspace); int level = 0; while (peek_at(&input, hashpos) == '#') { level++; hashpos++; } container->as.header.level = level; container->as.header.setext = false; } else if (!indented && (matched = scan_open_code_fence(&input, first_nonspace))) { container = add_child(parser, container, NODE_CODE_BLOCK, first_nonspace + 1); container->as.code.fenced = true; container->as.code.fence_char = peek_at(&input, first_nonspace); container->as.code.fence_length = matched; container->as.code.fence_offset = first_nonspace - offset; container->as.code.info = cmark_chunk_literal(""); offset = first_nonspace + matched; } else if (!indented && (matched = scan_html_block_tag(&input, first_nonspace))) { container = add_child(parser, container, NODE_HTML, first_nonspace + 1); // note, we don't adjust offset because the tag is part of the text } else if (!indented && container->type == NODE_PARAGRAPH && (lev = scan_setext_header_line(&input, first_nonspace)) && // check that there is only one line in the paragraph: (cmark_strbuf_strrchr(&container->string_content, '\n', cmark_strbuf_len(&container->string_content) - 2) < 0 && cmark_strbuf_strrchr(&container->string_content, '\r', cmark_strbuf_len(&container->string_content) - 2) < 0)) { container->type = NODE_HEADER; container->as.header.level = lev; container->as.header.setext = true; offset = input.len - 1; } else if (!indented && !(container->type == NODE_PARAGRAPH && !all_matched) && (matched = scan_hrule(&input, first_nonspace))) { // it's only now that we know the line is not part of a setext header: container = add_child(parser, container, NODE_HRULE, first_nonspace + 1); container = finalize(parser, container); offset = input.len - 1; } else if ((matched = parse_list_marker(&input, first_nonspace, &data))) { // compute padding: offset = first_nonspace + matched; i = 0; while (i <= 5 && peek_at(&input, offset + i) == ' ') { i++; } // i = number of spaces after marker, up to 5 if (i >= 5 || i < 1 || peek_at(&input, offset) == '\n' || peek_at(&input, offset) == '\r') { data->padding = matched + 1; if (i > 0) { offset += 1; } } else { data->padding = matched + i; offset += i; } // check container; if it's a list, see if this list item // can continue the list; otherwise, create a list container. data->marker_offset = indent; if (container->type != NODE_LIST || !lists_match(&container->as.list, data)) { container = add_child(parser, container, NODE_LIST, first_nonspace + 1); memcpy(&container->as.list, data, sizeof(*data)); } // add the list item container = add_child(parser, container, NODE_ITEM, first_nonspace + 1); /* TODO: static */ memcpy(&container->as.list, data, sizeof(*data)); free(data); } else { break; } if (accepts_lines(container->type)) { // if it's a line container, it can't contain other containers break; } maybe_lazy = false; } // what remains at offset is a text line. add the text to the // appropriate container. first_nonspace = offset; while (peek_at(&input, first_nonspace) == ' ') first_nonspace++; indent = first_nonspace - offset; blank = peek_at(&input, first_nonspace) == '\n' || peek_at(&input, first_nonspace) == '\r'; if (blank && container->last_child) { container->last_child->last_line_blank = true; } // block quote lines are never blank as they start with > // and we don't count blanks in fenced code for purposes of tight/loose // lists or breaking out of lists. we also don't set last_line_blank // on an empty list item. container->last_line_blank = (blank && container->type != NODE_BLOCK_QUOTE && container->type != NODE_HEADER && !(container->type == NODE_CODE_BLOCK && container->as.code.fenced) && !(container->type == NODE_ITEM && container->first_child == NULL && container->start_line == parser->line_number)); cmark_node *cont = container; while (cont->parent) { cont->parent->last_line_blank = false; cont = cont->parent; } if (parser->current != last_matched_container && container == last_matched_container && !blank && parser->current->type == NODE_PARAGRAPH && cmark_strbuf_len(&parser->current->string_content) > 0) { add_line(parser->current, &input, offset); } else { // not a lazy continuation // finalize any blocks that were not matched and set cur to container: while (parser->current != last_matched_container) { parser->current = finalize(parser, parser->current); assert(parser->current != NULL); } if (container->type == NODE_CODE_BLOCK || container->type == NODE_HTML) { add_line(container, &input, offset); } else if (blank) { // ??? do nothing } else if (accepts_lines(container->type)) { if (container->type == NODE_HEADER && container->as.header.setext == false) { chop_trailing_hashtags(&input); } add_line(container, &input, first_nonspace); } else { // create paragraph container for line container = add_child(parser, container, NODE_PARAGRAPH, first_nonspace + 1); add_line(container, &input, first_nonspace); } parser->current = container; } finished: parser->last_line_length = parser->curline->size; if (parser->last_line_length && parser->curline->ptr[parser->last_line_length - 1] == '\n') parser->last_line_length--; if (parser->last_line_length && parser->curline->ptr[parser->last_line_length - 1] == '\r') parser->last_line_length--; cmark_strbuf_clear(parser->curline); }
// Attempts to parse a list item marker (bullet or enumerated). // On success, returns length of the marker, and populates // data with the details. On failure, returns 0. static int parse_list_marker(cmark_chunk *input, int pos, cmark_list **dataptr) { unsigned char c; int startpos; cmark_list *data; startpos = pos; c = peek_at(input, pos); if ((c == '*' || c == '-' || c == '+') && !scan_hrule(input, pos)) { pos++; if (!cmark_isspace(peek_at(input, pos))) { return 0; } data = (cmark_list *)calloc(1, sizeof(*data)); if(data == NULL) { return 0; } else { data->marker_offset = 0; // will be adjusted later data->list_type = CMARK_BULLET_LIST; data->bullet_char = c; data->start = 1; data->delimiter = CMARK_PERIOD_DELIM; data->tight = false; } } else if (cmark_isdigit(c)) { int start = 0; do { start = (10 * start) + (peek_at(input, pos) - '0'); pos++; } while (cmark_isdigit(peek_at(input, pos))); c = peek_at(input, pos); if (c == '.' || c == ')') { pos++; if (!cmark_isspace(peek_at(input, pos))) { return 0; } data = (cmark_list *)calloc(1, sizeof(*data)); if(data == NULL) { return 0; } else { data->marker_offset = 0; // will be adjusted later data->list_type = CMARK_ORDERED_LIST; data->bullet_char = 0; data->start = start; data->delimiter = (c == '.' ? CMARK_PERIOD_DELIM : CMARK_PAREN_DELIM); data->tight = false; } } else { return 0; } } else { return 0; } *dataptr = data; return (pos - startpos); }