markly 0.15.3 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/context/getting-started.md +36 -0
  4. data/context/index.yaml +3 -3
  5. data/ext/markly/blocks.c +24 -6
  6. data/ext/markly/cmark-gfm.h +24 -0
  7. data/ext/markly/cmark-gfm_export.h +1 -0
  8. data/ext/markly/cmark-gfm_version.h +2 -2
  9. data/ext/markly/cmark.c +1 -1
  10. data/ext/markly/cmark_ctype.c +9 -0
  11. data/ext/markly/cmark_ctype.h +5 -0
  12. data/ext/markly/commonmark.c +21 -1
  13. data/ext/markly/config.h +0 -4
  14. data/ext/markly/extconf.rb +6 -1
  15. data/ext/markly/{table.c → extensions/table.c} +91 -46
  16. data/ext/markly/front_matter.c +141 -0
  17. data/ext/markly/front_matter.h +24 -0
  18. data/ext/markly/html.c +47 -23
  19. data/ext/markly/inlines.c +85 -3
  20. data/ext/markly/iterator.c +6 -1
  21. data/ext/markly/latex.c +3 -0
  22. data/ext/markly/man.c +3 -0
  23. data/ext/markly/markly.c +288 -201
  24. data/ext/markly/node.c +65 -5
  25. data/ext/markly/node.h +1 -0
  26. data/ext/markly/parser.h +19 -0
  27. data/ext/markly/plaintext.c +3 -0
  28. data/ext/markly/xml.c +19 -1
  29. data/lib/markly/flags.rb +10 -1
  30. data/lib/markly/node/inspect.rb +10 -3
  31. data/lib/markly/node.rb +53 -28
  32. data/lib/markly/renderer/generic.rb +42 -12
  33. data/lib/markly/renderer/headings.rb +24 -19
  34. data/lib/markly/renderer/html.rb +111 -10
  35. data/lib/markly/version.rb +4 -2
  36. data/lib/markly.rb +14 -13
  37. data/readme.md +22 -6
  38. data/releases.md +16 -0
  39. data.tar.gz.sig +0 -0
  40. metadata +23 -21
  41. metadata.gz.sig +0 -0
  42. /data/ext/markly/{autolink.c → extensions/autolink.c} +0 -0
  43. /data/ext/markly/{autolink.h → extensions/autolink.h} +0 -0
  44. /data/ext/markly/{cmark-gfm-core-extensions.h → extensions/cmark-gfm-core-extensions.h} +0 -0
  45. /data/ext/markly/{cmark-gfm-extensions_export.h → extensions/cmark-gfm-extensions_export.h} +0 -0
  46. /data/ext/markly/{core-extensions.c → extensions/core-extensions.c} +0 -0
  47. /data/ext/markly/{ext_scanners.c → extensions/ext_scanners.c} +0 -0
  48. /data/ext/markly/{ext_scanners.h → extensions/ext_scanners.h} +0 -0
  49. /data/ext/markly/{strikethrough.c → extensions/strikethrough.c} +0 -0
  50. /data/ext/markly/{strikethrough.h → extensions/strikethrough.h} +0 -0
  51. /data/ext/markly/{table.h → extensions/table.h} +0 -0
  52. /data/ext/markly/{tagfilter.c → extensions/tagfilter.c} +0 -0
  53. /data/ext/markly/{tagfilter.h → extensions/tagfilter.h} +0 -0
  54. /data/ext/markly/{tasklist.c → extensions/tasklist.c} +0 -0
  55. /data/ext/markly/{tasklist.h → extensions/tasklist.h} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76b9702e08b93aa97c2fe8b02d0ac1faea07b8037cf093c48d87b4b7e260806a
4
- data.tar.gz: b3cad63b1de5e67e2160c72731f28cea94d02fbe999c5b2f2201f3e94b6c84b7
3
+ metadata.gz: b86cb2d3095458e69fe2d6fc2b9d6eb7dbee37b15edf5cc7064104ef12b5cb98
4
+ data.tar.gz: 1ddb2cac9ff79ee9fd2012e67949ddd5886930f6c5ecb28b54edf2eeffbbf110
5
5
  SHA512:
6
- metadata.gz: 701981fe8e9c6a229cc93042a40b4600e9d627903dd5f50e80089e2766c506ceb780c9feb3cb0ca3cdb1621be0e58db5cff0795925c8b42324d4b763312abaf0
7
- data.tar.gz: 5500f4778efd6753dd6ffc9a5717c372c4a0d422db38695466bb2ddd9bb16a3a1ba138c34382f9ed3278c4a69ff75a29172ce581ce0138a20e4ab3b6c9b2be8a
6
+ metadata.gz: fbc478782839f1c5d20abd6f9697da6e870f4620873d1b1450b96e7f8e21c31f345d0461fbc6536da9f3a0b33b19ef7dd69a70c5f1edd40bc7e08b42cab66ac1
7
+ data.tar.gz: ef38f140dd12fbd5788e17f124f280fe25cb1a3af67963d0b8e1780b851adf75609f9da6f38e682ca4ff2a6f59d7d756dc5a7469ffaf76e99c9ea6a9f03b115f
checksums.yaml.gz.sig CHANGED
Binary file
@@ -42,7 +42,9 @@ Markly accepts integer flags which control how the Markdown is parsed and render
42
42
  | ------------------------------------ | -----------
43
43
  | `Markly::DEFAULT` | The default parsing system.
44
44
  | `Markly::UNSAFE` | Allow raw/custom HTML and unsafe links.
45
+ | `Markly::FRONT_MATTER` | Parse front matter at the start of the document.
45
46
  | `Markly::FOOTNOTES` | Parse footnotes.
47
+ | `Markly::INLINE_CODE_INFO` | Parse language prefixes such as `ruby:` on inline code spans.
46
48
  | `Markly::LIBERAL_HTML_TAG` | Support liberal parsing of inline HTML tags.
47
49
  | `Markly::SMART` | Use smart punctuation (curly quotes, etc.).
48
50
  | `Markly::STRIKETHROUGH_DOUBLE_TILDE` | Parse strikethroughs by double tildes (compatibility with [redcarpet](https://github.com/vmg/redcarpet))
@@ -76,6 +78,40 @@ To have multiple options applied, `|` (or) the flags together:
76
78
  Markly.render_html("\"'Shelob' is my name.\"", flags: Markly::HARD_BREAKS|Markly::SOURCE_POSITION)
77
79
  ```
78
80
 
81
+ Inline code language prefixes are opt-in. The language is available through
82
+ `Node#code_info` and is rendered as a `language-...` class:
83
+
84
+ ``` ruby
85
+ document = Markly.parse("ruby:`Object.new`", flags: Markly::INLINE_CODE_INFO)
86
+ code = document.first_child.first_child
87
+
88
+ code.code_info
89
+ # => "ruby"
90
+
91
+ code.code_language
92
+ # => "ruby"
93
+
94
+ document.to_html
95
+ # => <p><code class="language-ruby">Object.new</code></p>
96
+ ```
97
+
98
+ `Node#code_info` is also the general info-string accessor for fenced code
99
+ blocks and front matter. `Node#code_language` returns the first token of that
100
+ info string, while `Node#fence_info` remains available for compatibility on
101
+ those block nodes.
102
+
103
+ For a fenced code block, `Node#fence` returns a `Node::Fence` structure with
104
+ the fence character, length, and indentation:
105
+
106
+ ``` ruby
107
+ block = Markly.parse(" ~~~~ ruby\n Object.new\n ~~~~").first_child
108
+
109
+ block.fence
110
+ # => #<struct Markly::Node::Fence character="~", length=4, indent=2>
111
+ ```
112
+
113
+ Indented code blocks and other node types return `nil`.
114
+
79
115
  ## Extensions
80
116
 
81
117
  Both `render_html` and `parse` take an optional `extensions:` argument defining the extensions you want enabled as your CommonMark document is being processed:
data/context/index.yaml CHANGED
@@ -3,9 +3,9 @@
3
3
  ---
4
4
  description: CommonMark parser and renderer. Written in C, wrapped in Ruby.
5
5
  metadata:
6
- documentation_uri: https://ioquatix.github.io/markly/
7
- funding_uri: https://github.com/sponsors/ioquatix/
8
- source_code_uri: https://github.com/ioquatix/markly.git
6
+ documentation_uri: https://socketry.github.io/markly/
7
+ funding_uri: https://github.com/sponsors/socketry/
8
+ source_code_uri: https://github.com/socketry/markly.git
9
9
  files:
10
10
  - path: getting-started.md
11
11
  title: Getting Started
data/ext/markly/blocks.c CHANGED
@@ -23,6 +23,7 @@
23
23
  #include "houdini.h"
24
24
  #include "buffer.h"
25
25
  #include "footnotes.h"
26
+ #include "front_matter.h"
26
27
 
27
28
  #define CODE_INDENT 4
28
29
  #define TAB_STOP 4
@@ -68,16 +69,12 @@ static CMARK_INLINE bool S_is_line_end_char(char c) {
68
69
  return (c == '\n' || c == '\r');
69
70
  }
70
71
 
71
- static CMARK_INLINE bool S_is_space_or_tab(char c) {
72
- return (c == ' ' || c == '\t');
73
- }
74
-
75
72
  // Returns true if block is being finalized on the same line it ends.
76
73
  // This happens for:
77
74
  // - Document node (special case)
78
75
  // - Fenced code blocks (end on the closing fence line)
79
76
  // - Setext headings (end on the underline)
80
- // - HTML blocks types 1-5 per CommonMark spec §4.6 (end on the line
77
+ // - HTML block types 1-5 per CommonMark spec §4.6 (end on the line
81
78
  // containing the closing marker)
82
79
  // - Any block finalized on the same line it started (e.g., single-line blocks)
83
80
  static CMARK_INLINE bool S_ends_on_current_line(cmark_parser *parser, cmark_node *b) {
@@ -100,6 +97,10 @@ static CMARK_INLINE bool S_ends_on_current_line(cmark_parser *parser, cmark_node
100
97
  b->start_line == parser->line_number;
101
98
  }
102
99
 
100
+ static CMARK_INLINE bool S_is_space_or_tab(char c) {
101
+ return (c == ' ' || c == '\t');
102
+ }
103
+
103
104
  static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
104
105
  size_t len, bool eof);
105
106
 
@@ -159,6 +160,8 @@ static void cmark_parser_reset(cmark_parser *parser) {
159
160
 
160
161
  cmark_strbuf_init(parser->mem, &parser->curline, 256);
161
162
  cmark_strbuf_init(parser->mem, &parser->linebuf, 0);
163
+ cmark_strbuf_init(parser->mem, &parser->front_matter_buf, 0);
164
+ cmark_strbuf_init(parser->mem, &parser->front_matter_info, 0);
162
165
 
163
166
  cmark_node *document = make_document(parser->mem);
164
167
 
@@ -189,6 +192,8 @@ void cmark_parser_free(cmark_parser *parser) {
189
192
  cmark_parser_dispose(parser);
190
193
  cmark_strbuf_free(&parser->curline);
191
194
  cmark_strbuf_free(&parser->linebuf);
195
+ cmark_strbuf_free(&parser->front_matter_buf);
196
+ cmark_strbuf_free(&parser->front_matter_info);
192
197
  cmark_llist_free(parser->mem, parser->syntax_extensions);
193
198
  cmark_llist_free(parser->mem, parser->inline_syntax_extensions);
194
199
  mem->free(parser);
@@ -1244,7 +1249,8 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
1244
1249
  parser->first_nonspace + 1);
1245
1250
  S_advance_offset(parser, input, input->len - 1 - parser->offset, false);
1246
1251
  } else if (!indented &&
1247
- parser->options & CMARK_OPT_FOOTNOTES &&
1252
+ (parser->options & CMARK_OPT_FOOTNOTES) &&
1253
+ depth < MAX_LIST_DEPTH &&
1248
1254
  (matched = scan_footnote_definition(input, parser->first_nonspace))) {
1249
1255
  cmark_chunk c = cmark_chunk_dup(input, parser->first_nonspace + 2, matched - 2);
1250
1256
 
@@ -1516,6 +1522,10 @@ static void S_process_line(cmark_parser *parser, const unsigned char *buffer,
1516
1522
 
1517
1523
  parser->line_number++;
1518
1524
 
1525
+ if ((parser->options & CMARK_OPT_FRONT_MATTER) &&
1526
+ cmark_front_matter_process_line(parser, &input))
1527
+ goto finished;
1528
+
1519
1529
  last_matched_container = check_open_blocks(parser, &input, &all_matched);
1520
1530
 
1521
1531
  if (!last_matched_container)
@@ -1556,12 +1566,20 @@ cmark_node *cmark_parser_finish(cmark_parser *parser) {
1556
1566
  cmark_strbuf_clear(&parser->linebuf);
1557
1567
  }
1558
1568
 
1569
+ // If front matter scanning was still active when the document ended, no
1570
+ // closing delimiter was found. The entire document (after the opening ---)
1571
+ // is treated as front matter.
1572
+ if ((parser->options & CMARK_OPT_FRONT_MATTER) && parser->front_matter_scanning)
1573
+ cmark_front_matter_process_line(parser, NULL);
1574
+
1559
1575
  finalize_document(parser);
1560
1576
 
1561
1577
  cmark_consolidate_text_nodes(parser->root);
1562
1578
 
1563
1579
  cmark_strbuf_free(&parser->curline);
1564
1580
  cmark_strbuf_free(&parser->linebuf);
1581
+ cmark_strbuf_free(&parser->front_matter_buf);
1582
+ cmark_strbuf_free(&parser->front_matter_info);
1565
1583
 
1566
1584
  #if CMARK_DEBUG_NODES
1567
1585
  if (cmark_node_check(parser->root, stderr)) {
@@ -53,6 +53,7 @@ typedef enum {
53
53
  CMARK_NODE_HEADING = CMARK_NODE_TYPE_BLOCK | 0x0009,
54
54
  CMARK_NODE_THEMATIC_BREAK = CMARK_NODE_TYPE_BLOCK | 0x000a,
55
55
  CMARK_NODE_FOOTNOTE_DEFINITION = CMARK_NODE_TYPE_BLOCK | 0x000b,
56
+ CMARK_NODE_FRONT_MATTER = CMARK_NODE_TYPE_BLOCK | 0x000c,
56
57
 
57
58
  /* Inline */
58
59
  CMARK_NODE_TEXT = CMARK_NODE_TYPE_INLINE | 0x0001,
@@ -424,6 +425,18 @@ CMARK_GFM_EXPORT int cmark_node_get_item_index(cmark_node *node);
424
425
  */
425
426
  CMARK_GFM_EXPORT int cmark_node_set_item_index(cmark_node *node, int idx);
426
427
 
428
+ /** Returns the info string from a code block or the language identifier from
429
+ * an inline code span.
430
+ */
431
+ CMARK_GFM_EXPORT const char *cmark_node_get_code_info(cmark_node *node);
432
+
433
+ /** Sets the info string in a code block or the language identifier on an
434
+ * inline code span, returning 1 on success and 0 on failure. Inline code
435
+ * language identifiers must begin with an alphanumeric character and may
436
+ * additionally contain '_', '-', '+', '#', and '.'.
437
+ */
438
+ CMARK_GFM_EXPORT int cmark_node_set_code_info(cmark_node *node, const char *info);
439
+
427
440
  /** Returns the info string from a fenced code block.
428
441
  */
429
442
  CMARK_GFM_EXPORT const char *cmark_node_get_fence_info(cmark_node *node);
@@ -768,6 +781,17 @@ char *cmark_render_latex_with_mem(cmark_node *root, int options, int width, cmar
768
781
  */
769
782
  #define CMARK_OPT_FULL_INFO_STRING (1 << 16)
770
783
 
784
+ /** Parse front matter ("---" delimited block at the start of the document)
785
+ * and expose it as a CMARK_NODE_FRONT_MATTER node. The raw content between
786
+ * the delimiters is available via cmark_node_get_literal(); how it is
787
+ * interpreted (e.g. as YAML, TOML, JSON) is left to the caller.
788
+ */
789
+ #define CMARK_OPT_FRONT_MATTER (1 << 18)
790
+
791
+ /** Parse inline code language prefixes, e.g. ruby:`code`.
792
+ */
793
+ #define CMARK_OPT_INLINE_CODE_INFO (1 << 19)
794
+
771
795
  /**
772
796
  * ## Version information
773
797
  */
@@ -33,6 +33,7 @@
33
33
  # define CMARK_GFM_DEPRECATED_NO_EXPORT CMARK_GFM_NO_EXPORT CMARK_GFM_DEPRECATED
34
34
  #endif
35
35
 
36
+ /* NOLINTNEXTLINE(readability-avoid-unconditional-preprocessor-if) */
36
37
  #if 0 /* DEFINE_NO_DEPRECATED */
37
38
  # ifndef CMARK_GFM_NO_DEPRECATED
38
39
  # define CMARK_GFM_NO_DEPRECATED
@@ -1,7 +1,7 @@
1
1
  #ifndef CMARK_GFM_VERSION_H
2
2
  #define CMARK_GFM_VERSION_H
3
3
 
4
- #define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 2)
5
- #define CMARK_GFM_VERSION_STRING "0.29.0.gfm.2"
4
+ #define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 13)
5
+ #define CMARK_GFM_VERSION_STRING "0.29.0.gfm.13"
6
6
 
7
7
  #endif
data/ext/markly/cmark.c CHANGED
@@ -7,7 +7,7 @@
7
7
  #include "cmark-gfm.h"
8
8
  #include "buffer.h"
9
9
 
10
- cmark_node_type CMARK_NODE_LAST_BLOCK = CMARK_NODE_FOOTNOTE_DEFINITION;
10
+ cmark_node_type CMARK_NODE_LAST_BLOCK = CMARK_NODE_FRONT_MATTER;
11
11
  cmark_node_type CMARK_NODE_LAST_INLINE = CMARK_NODE_FOOTNOTE_REFERENCE;
12
12
 
13
13
  int cmark_version(void) { return CMARK_GFM_VERSION; }
@@ -42,3 +42,12 @@ int cmark_isalnum(char c) {
42
42
  int cmark_isdigit(char c) { return cmark_ctype_class[(uint8_t)c] == 3; }
43
43
 
44
44
  int cmark_isalpha(char c) { return cmark_ctype_class[(uint8_t)c] == 4; }
45
+
46
+ int cmark_is_inline_code_info_start_char(char c) {
47
+ return cmark_isalnum(c);
48
+ }
49
+
50
+ int cmark_is_inline_code_info_char(char c) {
51
+ return cmark_is_inline_code_info_start_char(c) || c == '_' || c == '-' ||
52
+ c == '+' || c == '#' || c == '.';
53
+ }
@@ -26,6 +26,11 @@ int cmark_isdigit(char c);
26
26
  CMARK_GFM_EXPORT
27
27
  int cmark_isalpha(char c);
28
28
 
29
+ /* Character classes for inline code info identifiers. */
30
+ int cmark_is_inline_code_info_start_char(char c);
31
+
32
+ int cmark_is_inline_code_info_char(char c);
33
+
29
34
  #ifdef __cplusplus
30
35
  }
31
36
  #endif
@@ -271,7 +271,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
271
271
  if (!first_in_list_item) {
272
272
  BLANKLINE();
273
273
  }
274
- info = cmark_node_get_fence_info(node);
274
+ info = cmark_node_get_code_info(node);
275
275
  info_len = strlen(info);
276
276
  fencechar[0] = strchr(info, '`') == NULL ? '`' : '~';
277
277
  code = cmark_node_get_literal(node);
@@ -363,6 +363,10 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
363
363
  extra_spaces = code_len == 0 ||
364
364
  code[0] == '`' || code[code_len - 1] == '`' ||
365
365
  code[0] == ' ' || code[code_len - 1] == ' ';
366
+ if (node->as.code.info.len > 0) {
367
+ OUT(cmark_node_get_code_info(node), false, LITERAL);
368
+ LIT(":");
369
+ }
366
370
  for (i = 0; i < numticks; i++) {
367
371
  LIT("`");
368
372
  }
@@ -492,6 +496,22 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
492
496
  }
493
497
  break;
494
498
 
499
+ case CMARK_NODE_FRONT_MATTER:
500
+ if (entering) {
501
+ const char *front_matter_info = cmark_node_get_fence_info(node);
502
+ BLANKLINE();
503
+ LIT("---");
504
+ if (front_matter_info && *front_matter_info) {
505
+ LIT(" ");
506
+ OUT(front_matter_info, false, LITERAL);
507
+ }
508
+ LIT("\n");
509
+ OUT(cmark_node_get_literal(node), false, LITERAL);
510
+ LIT("---\n");
511
+ BLANKLINE();
512
+ }
513
+ break;
514
+
495
515
  default:
496
516
  assert(false);
497
517
  break;
data/ext/markly/config.h CHANGED
@@ -5,9 +5,7 @@
5
5
  extern "C" {
6
6
  #endif
7
7
 
8
- #ifndef HAVE_STDBOOL_H
9
8
  #define HAVE_STDBOOL_H
10
- #endif
11
9
 
12
10
  #ifdef HAVE_STDBOOL_H
13
11
  #include <stdbool.h>
@@ -71,8 +69,6 @@ CMARK_INLINE int c99_snprintf(char *outBuf, size_t size, const char *format, ...
71
69
 
72
70
  #endif
73
71
 
74
- #define CMARK_DEBUG_NODES 0
75
-
76
72
  #ifdef __cplusplus
77
73
  }
78
74
  #endif
@@ -6,12 +6,17 @@
6
6
  # Copyright, 2015-2019, by Garen Torikian.
7
7
  # Copyright, 2016-2017, by Yuki Izumi.
8
8
  # Copyright, 2017, by Ashe Connor.
9
- # Copyright, 2020-2025, by Samuel Williams.
9
+ # Copyright, 2020-2026, by Samuel Williams.
10
10
 
11
11
  require "mkmf"
12
12
 
13
13
  append_cflags(["-O3", "-Wall", "-Wno-unknown-pragmas", "-std=c99"])
14
14
 
15
+ # Include the extensions/ subdirectory, mirroring the cmark-gfm source layout.
16
+ $INCFLAGS << " -I$(srcdir)/extensions"
17
+ $VPATH << "$(srcdir)/extensions"
18
+ $srcs = (Dir["#{$srcdir}/*.c"] + Dir["#{$srcdir}/extensions/*.c"]).map{|f| File.basename(f)}
19
+
15
20
  gem_name = File.basename(__dir__)
16
21
  extension_name = "markly"
17
22
 
@@ -11,6 +11,9 @@
11
11
  #include "table.h"
12
12
  #include "cmark-gfm-core-extensions.h"
13
13
 
14
+ // Limit to prevent a malicious input from causing a denial of service.
15
+ #define MAX_AUTOCOMPLETED_CELLS 0x80000
16
+
14
17
  // Custom node flag, initialized in `create_table_extension`.
15
18
  static cmark_node_internal_flags CMARK_NODE__TABLE_VISITED;
16
19
 
@@ -31,6 +34,8 @@ typedef struct {
31
34
  typedef struct {
32
35
  uint16_t n_columns;
33
36
  uint8_t *alignments;
37
+ int n_rows;
38
+ int n_nonempty_cells;
34
39
  } node_table;
35
40
 
36
41
  typedef struct {
@@ -83,6 +88,33 @@ static int set_n_table_columns(cmark_node *node, uint16_t n_columns) {
83
88
  return 1;
84
89
  }
85
90
 
91
+ // Increment the number of rows in the table. Also update n_nonempty_cells,
92
+ // which keeps track of the number of cells which were parsed from the
93
+ // input file. (If one of the rows is too short, then the trailing cells
94
+ // are autocompleted. Autocompleted cells are not counted in n_nonempty_cells.)
95
+ // The purpose of this is to prevent a malicious input from generating a very
96
+ // large number of autocompleted cells, which could cause a denial of service
97
+ // vulnerability.
98
+ static int incr_table_row_count(cmark_node *node, int i) {
99
+ if (!node || node->type != CMARK_NODE_TABLE) {
100
+ return 0;
101
+ }
102
+
103
+ ((node_table *)node->as.opaque)->n_rows++;
104
+ ((node_table *)node->as.opaque)->n_nonempty_cells += i;
105
+ return 1;
106
+ }
107
+
108
+ // Calculate the number of autocompleted cells.
109
+ static int get_n_autocompleted_cells(cmark_node *node) {
110
+ if (!node || node->type != CMARK_NODE_TABLE) {
111
+ return 0;
112
+ }
113
+
114
+ const node_table *nt = (node_table *)node->as.opaque;
115
+ return (nt->n_columns * nt->n_rows) - nt->n_nonempty_cells;
116
+ }
117
+
86
118
  static uint8_t *get_table_alignments(cmark_node *node) {
87
119
  if (!node || node->type != CMARK_NODE_TABLE)
88
120
  return 0;
@@ -98,6 +130,23 @@ static int set_table_alignments(cmark_node *node, uint8_t *alignments) {
98
130
  return 1;
99
131
  }
100
132
 
133
+ static uint8_t get_cell_alignment(cmark_node *node) {
134
+ if (!node || node->type != CMARK_NODE_TABLE_CELL)
135
+ return 0;
136
+
137
+ const uint8_t *alignments = get_table_alignments(node->parent->parent);
138
+ int i = node->as.cell_index;
139
+ return alignments[i];
140
+ }
141
+
142
+ static int set_cell_index(cmark_node *node, int i) {
143
+ if (!node || node->type != CMARK_NODE_TABLE_CELL)
144
+ return 0;
145
+
146
+ node->as.cell_index = i;
147
+ return 1;
148
+ }
149
+
101
150
  static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsize_t len)
102
151
  {
103
152
  cmark_strbuf *res = (cmark_strbuf *)mem->calloc(1, sizeof(cmark_strbuf));
@@ -257,7 +306,7 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
257
306
  unsigned char *input, int len) {
258
307
  cmark_node *table_header;
259
308
  table_row *header_row = NULL;
260
- table_row *marker_row = NULL;
309
+ table_row *delimiter_row = NULL;
261
310
  node_table_row *ntr;
262
311
  const char *parent_string;
263
312
  uint16_t i;
@@ -270,16 +319,16 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
270
319
  return parent_container;
271
320
  }
272
321
 
273
- // Since scan_table_start was successful, we must have a marker row.
274
- marker_row = row_from_string(self, parser,
275
- input + cmark_parser_get_first_nonspace(parser),
276
- len - cmark_parser_get_first_nonspace(parser));
322
+ // Since scan_table_start was successful, we must have a delimiter row.
323
+ delimiter_row = row_from_string(
324
+ self, parser, input + cmark_parser_get_first_nonspace(parser),
325
+ len - cmark_parser_get_first_nonspace(parser));
277
326
  // assert may be optimized out, don't rely on it for security boundaries
278
- if (!marker_row) {
327
+ if (!delimiter_row) {
279
328
  return parent_container;
280
329
  }
281
-
282
- assert(marker_row);
330
+
331
+ assert(delimiter_row);
283
332
 
284
333
  cmark_arena_push();
285
334
 
@@ -289,8 +338,8 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
289
338
  parent_string = cmark_node_get_string_content(parent_container);
290
339
  header_row = row_from_string(self, parser, (unsigned char *)parent_string,
291
340
  (int)strlen(parent_string));
292
- if (!header_row || header_row->n_columns != marker_row->n_columns) {
293
- free_table_row(parser->mem, marker_row);
341
+ if (!header_row || header_row->n_columns != delimiter_row->n_columns) {
342
+ free_table_row(parser->mem, delimiter_row);
294
343
  free_table_row(parser->mem, header_row);
295
344
  cmark_arena_pop();
296
345
  parent_container->flags |= CMARK_NODE__TABLE_VISITED;
@@ -298,14 +347,14 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
298
347
  }
299
348
 
300
349
  if (cmark_arena_pop()) {
301
- marker_row = row_from_string(
350
+ delimiter_row = row_from_string(
302
351
  self, parser, input + cmark_parser_get_first_nonspace(parser),
303
352
  len - cmark_parser_get_first_nonspace(parser));
304
353
  header_row = row_from_string(self, parser, (unsigned char *)parent_string,
305
354
  (int)strlen(parent_string));
306
355
  // row_from_string can return NULL, add additional check to ensure n_columns match
307
- if (!marker_row || !header_row || header_row->n_columns != marker_row->n_columns) {
308
- free_table_row(parser->mem, marker_row);
356
+ if (!delimiter_row || !header_row || header_row->n_columns != delimiter_row->n_columns) {
357
+ free_table_row(parser->mem, delimiter_row);
309
358
  free_table_row(parser->mem, header_row);
310
359
  return parent_container;
311
360
  }
@@ -313,7 +362,7 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
313
362
 
314
363
  if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) {
315
364
  free_table_row(parser->mem, header_row);
316
- free_table_row(parser->mem, marker_row);
365
+ free_table_row(parser->mem, delimiter_row);
317
366
  return parent_container;
318
367
  }
319
368
 
@@ -326,12 +375,12 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
326
375
  parent_container->as.opaque = parser->mem->calloc(1, sizeof(node_table));
327
376
  set_n_table_columns(parent_container, header_row->n_columns);
328
377
 
329
- // allocate alignments based on marker_row->n_columns
330
- // since we populate the alignments array based on marker_row->cells
378
+ // allocate alignments based on delimiter_row->n_columns
379
+ // since we populate the alignments array based on delimiter_row->cells
331
380
  uint8_t *alignments =
332
- (uint8_t *)parser->mem->calloc(marker_row->n_columns, sizeof(uint8_t));
333
- for (i = 0; i < marker_row->n_columns; ++i) {
334
- node_cell *node = &marker_row->cells[i];
381
+ (uint8_t *)parser->mem->calloc(delimiter_row->n_columns, sizeof(uint8_t));
382
+ for (i = 0; i < delimiter_row->n_columns; ++i) {
383
+ node_cell *node = &delimiter_row->cells[i];
335
384
  bool left = node->buf->ptr[0] == ':', right = node->buf->ptr[node->buf->size - 1] == ':';
336
385
 
337
386
  if (left && right)
@@ -353,25 +402,26 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
353
402
  table_header->as.opaque = ntr = (node_table_row *)parser->mem->calloc(1, sizeof(node_table_row));
354
403
  ntr->is_header = true;
355
404
 
356
- {
357
- for (i = 0; i < header_row->n_columns; ++i) {
358
- node_cell *cell = &header_row->cells[i];
359
- cmark_node *header_cell = cmark_parser_add_child(parser, table_header,
360
- CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
361
- header_cell->start_line = header_cell->end_line = parent_container->start_line;
362
- header_cell->internal_offset = cell->internal_offset;
363
- header_cell->end_column = parent_container->start_column + cell->end_offset;
364
- cmark_node_set_string_content(header_cell, (char *) cell->buf->ptr);
365
- cmark_node_set_syntax_extension(header_cell, self);
366
- }
405
+ for (i = 0; i < header_row->n_columns; ++i) {
406
+ node_cell *cell = &header_row->cells[i];
407
+ cmark_node *header_cell = cmark_parser_add_child(parser, table_header,
408
+ CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
409
+ header_cell->start_line = header_cell->end_line = parent_container->start_line;
410
+ header_cell->internal_offset = cell->internal_offset;
411
+ header_cell->end_column = parent_container->start_column + cell->end_offset;
412
+ cmark_node_set_string_content(header_cell, (char *) cell->buf->ptr);
413
+ cmark_node_set_syntax_extension(header_cell, self);
414
+ set_cell_index(header_cell, i);
367
415
  }
368
416
 
417
+ incr_table_row_count(parent_container, i);
418
+
369
419
  cmark_parser_advance_offset(
370
420
  parser, (char *)input,
371
421
  (int)strlen((char *)input) - 1 - cmark_parser_get_offset(parser), false);
372
422
 
373
423
  free_table_row(parser->mem, header_row);
374
- free_table_row(parser->mem, marker_row);
424
+ free_table_row(parser->mem, delimiter_row);
375
425
  return parent_container;
376
426
  }
377
427
 
@@ -385,6 +435,10 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
385
435
  if (cmark_parser_is_blank(parser))
386
436
  return NULL;
387
437
 
438
+ if (get_n_autocompleted_cells(parent_container) > MAX_AUTOCOMPLETED_CELLS) {
439
+ return NULL;
440
+ }
441
+
388
442
  table_row_block =
389
443
  cmark_parser_add_child(parser, parent_container, CMARK_NODE_TABLE_ROW,
390
444
  parent_container->start_column);
@@ -412,12 +466,16 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
412
466
  node->end_column = parent_container->start_column + cell->end_offset;
413
467
  cmark_node_set_string_content(node, (char *) cell->buf->ptr);
414
468
  cmark_node_set_syntax_extension(node, self);
469
+ set_cell_index(node, i);
415
470
  }
416
471
 
472
+ incr_table_row_count(parent_container, i);
473
+
417
474
  for (; i < table_columns; ++i) {
418
475
  cmark_node *node = cmark_parser_add_child(
419
476
  parser, table_row_block, CMARK_NODE_TABLE_CELL, 0);
420
477
  cmark_node_set_syntax_extension(node, self);
478
+ set_cell_index(node, i);
421
479
  }
422
480
  }
423
481
 
@@ -602,13 +660,7 @@ static const char *xml_attr(cmark_syntax_extension *extension,
602
660
  cmark_node *node) {
603
661
  if (node->type == CMARK_NODE_TABLE_CELL) {
604
662
  if (cmark_gfm_extensions_get_table_row_is_header(node->parent)) {
605
- uint8_t *alignments = get_table_alignments(node->parent->parent);
606
- int i = 0;
607
- cmark_node *n;
608
- for (n = node->parent->first_child; n; n = n->next, ++i)
609
- if (n == node)
610
- break;
611
- switch (alignments[i]) {
663
+ switch (get_cell_alignment(node)) {
612
664
  case 'l': return " align=\"left\"";
613
665
  case 'c': return " align=\"center\"";
614
666
  case 'r': return " align=\"right\"";
@@ -696,7 +748,6 @@ static void html_render(cmark_syntax_extension *extension,
696
748
  cmark_event_type ev_type, int options) {
697
749
  bool entering = (ev_type == CMARK_EVENT_ENTER);
698
750
  cmark_strbuf *html = renderer->html;
699
- cmark_node *n;
700
751
 
701
752
  // XXX: we just monopolise renderer->opaque.
702
753
  struct html_table_state *table_state =
@@ -745,7 +796,6 @@ static void html_render(cmark_syntax_extension *extension,
745
796
  }
746
797
  }
747
798
  } else if (node->type == CMARK_NODE_TABLE_CELL) {
748
- uint8_t *alignments = get_table_alignments(node->parent->parent);
749
799
  if (entering) {
750
800
  cmark_html_render_cr(html);
751
801
  if (table_state->in_table_header) {
@@ -754,12 +804,7 @@ static void html_render(cmark_syntax_extension *extension,
754
804
  cmark_strbuf_puts(html, "<td");
755
805
  }
756
806
 
757
- int i = 0;
758
- for (n = node->parent->first_child; n; n = n->next, ++i)
759
- if (n == node)
760
- break;
761
-
762
- switch (alignments[i]) {
807
+ switch (get_cell_alignment(node)) {
763
808
  case 'l': html_table_add_align(html, "left", options); break;
764
809
  case 'c': html_table_add_align(html, "center", options); break;
765
810
  case 'r': html_table_add_align(html, "right", options); break;