commonmarker 0.21.2 → 0.23.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of commonmarker might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac3365deb1dd8288503331626fc20ec583a5f96a23e25d330f4ff47e526bfc54
4
- data.tar.gz: 975bcb958eb2e994a15090311088ff95870af95b1fc2d73bc1c4eb216fe69388
3
+ metadata.gz: bc6d71b6e85fb61abc438252b07369d3264982cf8fb327d96e5e8333bb23841e
4
+ data.tar.gz: 1911965561ddf20104db09c44c4e5d25f65a277a28b825bc0c03bb172779a623
5
5
  SHA512:
6
- metadata.gz: 531cda0280a75a40c139969287106646c8b2cef181b48249055f3a896bc4329485a0f70ac59f6f3a85eed00247c15f4a6785f478b957ebd3594be3074a9d25a9
7
- data.tar.gz: 3bb88a326ea1e0e7f99da2b459b90cac2ea748e57d933029c11278f230064a433a00889c93d65851dfef4b131ba46562a33d4597ef677f3e382325a712b0ffe6
6
+ metadata.gz: 59737cb433c3f4f3d53e06d3466f1f3dcd8a40e201e8b0f26be4d553f45667137e05d980100304e720d0e8fda178da3eaa9e421608cf4e53c6d159ff9261ae3e
7
+ data.tar.gz: c3088c3219da29bdb0356f46a5b7a7f2881410c825e101f7ced3964760bfcfcb5cef57e7f3ebf5bd9f686b974ad80887fc93899e616afb211dc0e648d6352cd2
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # CommonMarker
2
2
 
3
- [![Build Status](https://travis-ci.org/gjtorikian/commonmarker.svg)](https://travis-ci.org/gjtorikian/commonmarker) [![Gem Version](https://badge.fury.io/rb/commonmarker.svg)](http://badge.fury.io/rb/commonmarker)
3
+ ![Build Status](https://github.com/gjtorikian/commonmarker/workflows/CI/badge.svg) [![Gem Version](https://badge.fury.io/rb/commonmarker.svg)](http://badge.fury.io/rb/commonmarker)
4
4
 
5
5
  Ruby wrapper for [libcmark-gfm](https://github.com/github/cmark),
6
6
  GitHub's fork of the reference parser for CommonMark. It passes all of the C tests, and is therefore spec-complete. It also includes extensions to the CommonMark spec as documented in the [GitHub Flavored Markdown spec](http://github.github.com/gfm/), such as support for tables, strikethroughs, and autolinking.
@@ -130,26 +130,31 @@ CommonMarker accepts the same options that CMark does, as symbols. Note that the
130
130
  | Name | Description
131
131
  | ----------------------------- | -----------
132
132
  | `:DEFAULT` | The default parsing system.
133
+ | `:SOURCEPOS` | Include source position in nodes
133
134
  | `:UNSAFE` | Allow raw/custom HTML and unsafe links.
134
- | `:FOOTNOTES` | Parse footnotes.
135
- | `:LIBERAL_HTML_TAG` | Support liberal parsing of inline HTML tags.
135
+ | `:VALIDATE_UTF8` | Replace illegal sequences with the replacement character `U+FFFD`.
136
136
  | `:SMART` | Use smart punctuation (curly quotes, etc.).
137
+ | `:LIBERAL_HTML_TAG` | Support liberal parsing of inline HTML tags.
138
+ | `:FOOTNOTES` | Parse footnotes.
137
139
  | `:STRIKETHROUGH_DOUBLE_TILDE` | Parse strikethroughs by double tildes (compatibility with [redcarpet](https://github.com/vmg/redcarpet))
138
- | `:VALIDATE_UTF8` | Replace illegal sequences with the replacement character `U+FFFD`.
139
140
 
140
141
  ### Render options
141
142
 
142
143
  | Name | Description |
143
144
  | ------------------ | ----------- |
144
145
  | `:DEFAULT` | The default rendering system. |
145
- | `:UNSAFE` | Allow raw/custom HTML and unsafe links. |
146
- | `:GITHUB_PRE_LANG` | Use GitHub-style `<pre lang>` for fenced code blocks. |
146
+ | `:SOURCEPOS` | Include source position in rendered HTML. |
147
147
  | `:HARDBREAKS` | Treat `\n` as hardbreaks (by adding `<br/>`). |
148
+ | `:UNSAFE` | Allow raw/custom HTML and unsafe links. |
148
149
  | `:NOBREAKS` | Translate `\n` in the source to a single whitespace. |
149
- | `:SOURCEPOS` | Include source position in rendered HTML. |
150
+ | `:VALIDATE_UTF8` | Replace illegal sequences with the replacement character `U+FFFD`. |
151
+ | `:SMART` | Use smart punctuation (curly quotes, etc.). |
152
+ | `:GITHUB_PRE_LANG` | Use GitHub-style `<pre lang>` for fenced code blocks. |
153
+ | `:LIBERAL_HTML_TAG` | Support liberal parsing of inline HTML tags. |
154
+ | `:FOOTNOTES` | Render footnotes. |
155
+ | `:STRIKETHROUGH_DOUBLE_TILDE` | Parse strikethroughs by double tildes (compatibility with [redcarpet](https://github.com/vmg/redcarpet)) |
150
156
  | `:TABLE_PREFER_STYLE_ATTRIBUTES` | Use `style` insted of `align` for table cells. |
151
157
  | `:FULL_INFO_STRING` | Include full info strings of code blocks in separate attribute. |
152
- | `:FOOTNOTES` | Render footnotes. |
153
158
 
154
159
  ### Passing options
155
160
 
@@ -180,6 +185,76 @@ The available extensions are:
180
185
  * `:autolink` - This provides support for automatically converting URLs to anchor tags.
181
186
  * `:tagfilter` - This escapes [several "unsafe" HTML tags](https://github.github.com/gfm/#disallowed-raw-html-extension-), causing them to not have any effect.
182
187
 
188
+ ## Output formats
189
+
190
+ Like CMark, CommonMarker can generate output in several formats: HTML, XML, plaintext, and commonmark are currently supported.
191
+
192
+ ### HTML
193
+
194
+ The default output format, HTML, will be generated when calling `to_html` or using `--to=html` on the command line.
195
+
196
+ ```ruby
197
+ doc = CommonMarker.render_doc('*Hello* world!', :DEFAULT)
198
+ puts(doc.to_html)
199
+
200
+ <p><em>Hello</em> world!</p>
201
+ ```
202
+
203
+ ### XML
204
+
205
+ XML will be generated when calling `to_xml` or using `--to=xml` on the command line.
206
+
207
+ ```ruby
208
+ doc = CommonMarker.render_doc('*Hello* world!', :DEFAULT)
209
+ puts(doc.to_xml)
210
+
211
+ <?xml version="1.0" encoding="UTF-8"?>
212
+ <!DOCTYPE document SYSTEM "CommonMark.dtd">
213
+ <document xmlns="http://commonmark.org/xml/1.0">
214
+ <paragraph>
215
+ <emph>
216
+ <text xml:space="preserve">Hello</text>
217
+ </emph>
218
+ <text xml:space="preserve"> world!</text>
219
+ </paragraph>
220
+ </document>
221
+ ```
222
+
223
+ ### Plaintext
224
+
225
+ Plaintext will be generated when calling `to_plaintext` or using `--to=plaintext` on the command line.
226
+
227
+ ```ruby
228
+ doc = CommonMarker.render_doc('*Hello* world!', :DEFAULT)
229
+ puts(doc.to_plaintext)
230
+
231
+ Hello world!
232
+ ```
233
+
234
+ ### Commonmark
235
+
236
+ Commonmark will be generated when calling `to_commonmark` or using `--to=commonmark` on the command line.
237
+
238
+ ``` ruby
239
+ text = <<-TEXT
240
+ 1. I am a numeric list.
241
+ 2. I continue the list.
242
+ * Suddenly, an unordered list!
243
+ * What fun!
244
+ TEXT
245
+
246
+ doc = CommonMarker.render_doc(text, :DEFAULT)
247
+ puts(doc.to_commonmark)
248
+
249
+ 1. I am a numeric list.
250
+ 2. I continue the list.
251
+
252
+ <!-- end list -->
253
+
254
+ - Suddenly, an unordered list\!
255
+ - What fun\!
256
+ ```
257
+
183
258
  ## Developing locally
184
259
 
185
260
  After cloning the repo:
data/bin/commonmarker CHANGED
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'optparse'
5
- require 'ostruct'
6
5
 
7
6
  $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
8
7
  require 'commonmarker'
@@ -11,17 +10,16 @@ root = File.expand_path('..', __dir__)
11
10
  $LOAD_PATH.unshift File.expand_path('lib', root)
12
11
 
13
12
  def parse_options
14
- options = OpenStruct.new
13
+ options = Struct.new(:active_extensions, :active_parse_options, :active_render_options, :output_format, :renderer)
14
+ .new([], [:DEFAULT], [:DEFAULT], :html)
15
15
  extensions = CommonMarker.extensions
16
- parse_options = CommonMarker::Config::Parse
17
- render_options = CommonMarker::Config::Render
18
-
19
- options.active_extensions = []
20
- options.active_parse_options = [:DEFAULT]
21
- options.active_render_options = [:DEFAULT]
16
+ parse_options = CommonMarker::Config::OPTS.fetch(:parse)
17
+ render_options = CommonMarker::Config::OPTS.fetch(:render)
18
+ format_options = CommonMarker::Config::OPTS.fetch(:format)
22
19
 
23
20
  option_parser = OptionParser.new do |opts|
24
21
  opts.banner = 'Usage: commonmarker [--html-renderer] [--extension=EXTENSION]'
22
+ opts.separator ' [--to=FORMAT]'
25
23
  opts.separator ' [--parse-option=OPTION]'
26
24
  opts.separator ' [--render-option=OPTION]'
27
25
  opts.separator ' [FILE..]'
@@ -43,6 +41,7 @@ def parse_options
43
41
  opts.on('-h', '--help', 'Prints this help') do
44
42
  puts opts
45
43
  puts
44
+ puts "Available formats: #{format_options.join(', ')}"
46
45
  puts "Available extentions: #{extensions.join(', ')}"
47
46
  puts "Available parse options: #{parse_options.keys.join(', ')}"
48
47
  puts "Available render options: #{render_options.keys.join(', ')}"
@@ -51,7 +50,16 @@ def parse_options
51
50
  exit
52
51
  end
53
52
 
54
- opts.on('--html-renderer', 'Use the HtmlRenderer renderer rather than the native C renderer') do
53
+ opts.on('-tFORMAT', '--to=FORMAT', String, 'Specify output FORMAT') do |value|
54
+ value = value.to_sym
55
+ if format_options.include?(value)
56
+ options.output_format = value
57
+ else
58
+ abort("format '#{value}' not found")
59
+ end
60
+ end
61
+
62
+ opts.on('--html-renderer', 'Use the HtmlRenderer renderer rather than the native C renderer (only valid when format is html)') do
55
63
  options.renderer = true
56
64
  end
57
65
 
@@ -88,11 +96,23 @@ end
88
96
 
89
97
  options = parse_options
90
98
 
99
+ abort("format '#{options.output_format}' does not support using the HtmlRenderer renderer") if
100
+ options.renderer && options.output_format != :html
101
+
91
102
  doc = CommonMarker.render_doc(ARGF.read, options.active_parse_options, options.active_extensions)
92
103
 
93
- if options.renderer
94
- renderer = CommonMarker::HtmlRenderer.new(extensions: options.active_extensions)
95
- $stdout.write(renderer.render(doc))
96
- else
97
- $stdout.write(doc.to_html(options.active_render_options, options.active_extensions))
104
+ case options.output_format
105
+ when :html
106
+ if options.renderer
107
+ renderer = CommonMarker::HtmlRenderer.new(options: options.active_render_options, extensions: options.active_extensions)
108
+ $stdout.write(renderer.render(doc))
109
+ else
110
+ $stdout.write(doc.to_html(options.active_render_options, options.active_extensions))
111
+ end
112
+ when :xml
113
+ $stdout.write(doc.to_xml(options.active_render_options))
114
+ when :commonmark
115
+ $stdout.write(doc.to_commonmark(options.active_render_options))
116
+ when :plaintext
117
+ $stdout.write(doc.to_plaintext(options.active_render_options))
98
118
  end
data/commonmarker.gemspec CHANGED
@@ -21,11 +21,11 @@ Gem::Specification.new do |s|
21
21
 
22
22
  s.executables = ['commonmarker']
23
23
  s.require_paths = %w[lib ext]
24
- s.required_ruby_version = ['>= 2.4.10', '< 4.0']
24
+ s.required_ruby_version = ['>= 2.6', '< 4.0']
25
25
 
26
- s.rdoc_options += ['-x', 'ext/commonmarker/cmark/.*']
26
+ s.metadata['rubygems_mfa_required'] = 'true'
27
27
 
28
- s.add_dependency 'ruby-enum', '~> 0.5'
28
+ s.rdoc_options += ['-x', 'ext/commonmarker/cmark/.*']
29
29
 
30
30
  s.add_development_dependency 'awesome_print'
31
31
  s.add_development_dependency 'json', '~> 2.3'
@@ -468,7 +468,6 @@ static void process_footnotes(cmark_parser *parser) {
468
468
  while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
469
469
  cur = cmark_iter_get_node(iter);
470
470
  if (ev_type == CMARK_EVENT_EXIT && cur->type == CMARK_NODE_FOOTNOTE_DEFINITION) {
471
- cmark_node_unlink(cur);
472
471
  cmark_footnote_create(map, cur);
473
472
  }
474
473
  }
@@ -485,6 +484,15 @@ static void process_footnotes(cmark_parser *parser) {
485
484
  if (!footnote->ix)
486
485
  footnote->ix = ++ix;
487
486
 
487
+ // store a reference to this footnote reference's footnote definition
488
+ // this is used by renderers when generating label ids
489
+ cur->parent_footnote_def = footnote->node;
490
+
491
+ // keep track of a) count of how many times this footnote def has been
492
+ // referenced, and b) which reference index this footnote ref is at.
493
+ // this is used by renderers when generating links and backreferences.
494
+ cur->footnote.ref_ix = ++footnote->node->footnote.def_count;
495
+
488
496
  char n[32];
489
497
  snprintf(n, sizeof(n), "%d", footnote->ix);
490
498
  cmark_chunk_free(parser->mem, &cur->as.literal);
@@ -515,13 +523,16 @@ static void process_footnotes(cmark_parser *parser) {
515
523
  qsort(map->sorted, map->size, sizeof(cmark_map_entry *), sort_footnote_by_ix);
516
524
  for (unsigned int i = 0; i < map->size; ++i) {
517
525
  cmark_footnote *footnote = (cmark_footnote *)map->sorted[i];
518
- if (!footnote->ix)
526
+ if (!footnote->ix) {
527
+ cmark_node_unlink(footnote->node);
519
528
  continue;
529
+ }
520
530
  cmark_node_append_child(parser->root, footnote->node);
521
531
  footnote->node = NULL;
522
532
  }
523
533
  }
524
534
 
535
+ cmark_unlink_footnotes_map(map);
525
536
  cmark_map_free(map);
526
537
  }
527
538
 
@@ -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) | 0)
5
- #define CMARK_GFM_VERSION_STRING "0.29.0.gfm.0"
4
+ #define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 3)
5
+ #define CMARK_GFM_VERSION_STRING "0.29.0.gfm.3"
6
6
 
7
7
  #endif
@@ -477,7 +477,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
477
477
  case CMARK_NODE_FOOTNOTE_REFERENCE:
478
478
  if (entering) {
479
479
  LIT("[^");
480
- OUT(cmark_chunk_to_cstr(renderer->mem, &node->as.literal), false, LITERAL);
480
+
481
+ char *footnote_label = renderer->mem->calloc(node->parent_footnote_def->as.literal.len + 1, sizeof(char));
482
+ memmove(footnote_label, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);
483
+
484
+ OUT(footnote_label, false, LITERAL);
485
+ renderer->mem->free(footnote_label);
486
+
481
487
  LIT("]");
482
488
  }
483
489
  break;
@@ -486,9 +492,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
486
492
  if (entering) {
487
493
  renderer->footnote_ix += 1;
488
494
  LIT("[^");
489
- char n[32];
490
- snprintf(n, sizeof(n), "%d", renderer->footnote_ix);
491
- OUT(n, false, LITERAL);
495
+
496
+ char *footnote_label = renderer->mem->calloc(node->as.literal.len + 1, sizeof(char));
497
+ memmove(footnote_label, node->as.literal.data, node->as.literal.len);
498
+
499
+ OUT(footnote_label, false, LITERAL);
500
+ renderer->mem->free(footnote_label);
501
+
492
502
  LIT("]:\n");
493
503
 
494
504
  cmark_strbuf_puts(renderer->prefix, " ");
@@ -45,6 +45,13 @@ static VALUE encode_utf8_string(const char *c_string) {
45
45
  return string;
46
46
  }
47
47
 
48
+ /* Encode a C string using the encoding from Ruby string +source+. */
49
+ static VALUE encode_source_string(const char *c_string, VALUE source) {
50
+ VALUE string = rb_str_new2(c_string);
51
+ rb_enc_copy(string, source);
52
+ return string;
53
+ }
54
+
48
55
  static void rb_mark_c_struct(void *data) {
49
56
  cmark_node *node = data;
50
57
  cmark_node *child;
@@ -108,25 +115,23 @@ static void rb_parent_removed(VALUE val) {
108
115
  RDATA(val)->dfree = rb_free_c_struct;
109
116
  }
110
117
 
111
- static cmark_parser *prepare_parser(VALUE rb_options, VALUE rb_extensions, cmark_mem *mem) {
118
+ static cmark_parser *prepare_parser(VALUE rb_options, VALUE rb_extensions) {
112
119
  int options;
113
- int extensions_len;
114
120
  VALUE rb_ext_name;
115
121
  int i;
116
122
 
117
- Check_Type(rb_options, T_FIXNUM);
123
+ FIXNUM_P(rb_options);
124
+ options = FIX2INT(rb_options);
125
+
118
126
  Check_Type(rb_extensions, T_ARRAY);
119
127
 
120
- options = FIX2INT(rb_options);
121
- extensions_len = RARRAY_LEN(rb_extensions);
128
+ cmark_parser *parser = cmark_parser_new(options);
122
129
 
123
- cmark_parser *parser = cmark_parser_new_with_mem(options, mem);
124
- for (i = 0; i < extensions_len; ++i) {
125
- rb_ext_name = RARRAY_PTR(rb_extensions)[i];
130
+ for (i = 0; i < RARRAY_LEN(rb_extensions); ++i) {
131
+ rb_ext_name = rb_ary_entry(rb_extensions, i);
126
132
 
127
133
  if (!SYMBOL_P(rb_ext_name)) {
128
134
  cmark_parser_free(parser);
129
- cmark_arena_reset();
130
135
  rb_raise(rb_eTypeError, "extension names should be Symbols; got a %"PRIsVALUE"", rb_obj_class(rb_ext_name));
131
136
  }
132
137
 
@@ -135,7 +140,6 @@ static cmark_parser *prepare_parser(VALUE rb_options, VALUE rb_extensions, cmark
135
140
 
136
141
  if (!syntax_extension) {
137
142
  cmark_parser_free(parser);
138
- cmark_arena_reset();
139
143
  rb_raise(rb_eArgError, "extension %s not found", rb_id2name(SYM2ID(rb_ext_name)));
140
144
  }
141
145
 
@@ -150,33 +154,57 @@ static cmark_parser *prepare_parser(VALUE rb_options, VALUE rb_extensions, cmark
150
154
  *
151
155
  */
152
156
  static VALUE rb_markdown_to_html(VALUE self, VALUE rb_text, VALUE rb_options, VALUE rb_extensions) {
153
- char *str, *html;
154
- int len;
157
+ char *html;
155
158
  cmark_parser *parser;
156
159
  cmark_node *doc;
160
+
157
161
  Check_Type(rb_text, T_STRING);
158
- Check_Type(rb_options, T_FIXNUM);
159
162
 
160
- parser = prepare_parser(rb_options, rb_extensions, cmark_get_arena_mem_allocator());
163
+ parser = prepare_parser(rb_options, rb_extensions);
161
164
 
162
- str = (char *)RSTRING_PTR(rb_text);
163
- len = RSTRING_LEN(rb_text);
165
+ cmark_parser_feed(parser, StringValuePtr(rb_text), RSTRING_LEN(rb_text));
166
+ doc = cmark_parser_finish(parser);
164
167
 
165
- cmark_parser_feed(parser, str, len);
168
+ if (doc == NULL) {
169
+ cmark_parser_free(parser);
170
+ rb_raise(rb_eNodeError, "error parsing document");
171
+ }
172
+
173
+ html = cmark_render_html(doc, parser->options, parser->syntax_extensions);
174
+
175
+ cmark_parser_free(parser);
176
+ cmark_node_free(doc);
177
+
178
+ return rb_utf8_str_new_cstr(html);
179
+ }
180
+
181
+ /*
182
+ * Internal: Parses a Markdown string into an HTML string.
183
+ *
184
+ */
185
+ static VALUE rb_markdown_to_xml(VALUE self, VALUE rb_text, VALUE rb_options, VALUE rb_extensions) {
186
+ char *xml;
187
+ cmark_parser *parser;
188
+ cmark_node *doc;
189
+
190
+ Check_Type(rb_text, T_STRING);
191
+
192
+ parser = prepare_parser(rb_options, rb_extensions);
193
+
194
+ cmark_parser_feed(parser, StringValuePtr(rb_text), RSTRING_LEN(rb_text));
166
195
  doc = cmark_parser_finish(parser);
196
+
167
197
  if (doc == NULL) {
168
- cmark_arena_reset();
198
+ cmark_parser_free(parser);
169
199
  rb_raise(rb_eNodeError, "error parsing document");
170
200
  }
171
201
 
172
- cmark_mem *default_mem = cmark_get_default_mem_allocator();
173
- html = cmark_render_html_with_mem(doc, FIX2INT(rb_options), parser->syntax_extensions, default_mem);
174
- cmark_arena_reset();
202
+ xml = cmark_render_xml(doc, parser->options);
175
203
 
176
- VALUE ruby_html = rb_str_new2(html);
177
- default_mem->free(html);
204
+ cmark_parser_free(parser);
205
+ cmark_node_free(doc);
178
206
 
179
- return ruby_html;
207
+ return rb_utf8_str_new_cstr(xml);
180
208
  }
181
209
 
182
210
  /*
@@ -267,18 +295,17 @@ static VALUE rb_node_new(VALUE self, VALUE type) {
267
295
  static VALUE rb_parse_document(VALUE self, VALUE rb_text, VALUE rb_len,
268
296
  VALUE rb_options, VALUE rb_extensions) {
269
297
  char *text;
270
- int len, options;
298
+ int len;
271
299
  cmark_parser *parser;
272
300
  cmark_node *doc;
273
301
  Check_Type(rb_text, T_STRING);
274
302
  Check_Type(rb_len, T_FIXNUM);
275
303
  Check_Type(rb_options, T_FIXNUM);
276
304
 
277
- parser = prepare_parser(rb_options, rb_extensions, cmark_get_default_mem_allocator());
305
+ parser = prepare_parser(rb_options, rb_extensions);
278
306
 
279
307
  text = (char *)RSTRING_PTR(rb_text);
280
308
  len = FIX2INT(rb_len);
281
- options = FIX2INT(rb_options);
282
309
 
283
310
  cmark_parser_feed(parser, text, len);
284
311
  doc = cmark_parser_finish(parser);
@@ -567,6 +594,27 @@ static VALUE rb_render_html(VALUE self, VALUE rb_options, VALUE rb_extensions) {
567
594
  return ruby_html;
568
595
  }
569
596
 
597
+ /* Internal: Convert the node to an XML string.
598
+ *
599
+ * Returns a {String}.
600
+ */
601
+ static VALUE rb_render_xml(VALUE self, VALUE rb_options) {
602
+ int options;
603
+ cmark_node *node;
604
+ Check_Type(rb_options, T_FIXNUM);
605
+
606
+ options = FIX2INT(rb_options);
607
+
608
+ Data_Get_Struct(self, cmark_node, node);
609
+
610
+ char *xml = cmark_render_xml(node, options);
611
+ VALUE ruby_xml = rb_str_new2(xml);
612
+
613
+ free(xml);
614
+
615
+ return ruby_xml;
616
+ }
617
+
570
618
  /* Internal: Convert the node to a CommonMark string.
571
619
  *
572
620
  * Returns a {String}.
@@ -1130,7 +1178,8 @@ static VALUE rb_html_escape_href(VALUE self, VALUE rb_text) {
1130
1178
  if (houdini_escape_href(&buf, (const uint8_t *)RSTRING_PTR(rb_text),
1131
1179
  RSTRING_LEN(rb_text))) {
1132
1180
  result = (char *)cmark_strbuf_detach(&buf);
1133
- return rb_str_new2(result);
1181
+ return encode_source_string(result, rb_text);
1182
+
1134
1183
  }
1135
1184
 
1136
1185
  return rb_text;
@@ -1150,7 +1199,7 @@ static VALUE rb_html_escape_html(VALUE self, VALUE rb_text) {
1150
1199
  if (houdini_escape_html0(&buf, (const uint8_t *)RSTRING_PTR(rb_text),
1151
1200
  RSTRING_LEN(rb_text), 0)) {
1152
1201
  result = (char *)cmark_strbuf_detach(&buf);
1153
- return rb_str_new2(result);
1202
+ return encode_source_string(result, rb_text);
1154
1203
  }
1155
1204
 
1156
1205
  return rb_text;
@@ -1208,6 +1257,8 @@ __attribute__((visibility("default"))) void Init_commonmarker() {
1208
1257
  rb_cNode = rb_define_class_under(module, "Node", rb_cObject);
1209
1258
  rb_define_singleton_method(rb_cNode, "markdown_to_html", rb_markdown_to_html,
1210
1259
  3);
1260
+ rb_define_singleton_method(rb_cNode, "markdown_to_xml", rb_markdown_to_xml,
1261
+ 3);
1211
1262
  rb_define_singleton_method(rb_cNode, "new", rb_node_new, 1);
1212
1263
  rb_define_singleton_method(rb_cNode, "parse_document", rb_parse_document, 4);
1213
1264
  rb_define_method(rb_cNode, "string_content", rb_node_get_string_content, 0);
@@ -1220,6 +1271,7 @@ __attribute__((visibility("default"))) void Init_commonmarker() {
1220
1271
  rb_define_method(rb_cNode, "next", rb_node_next, 0);
1221
1272
  rb_define_method(rb_cNode, "insert_before", rb_node_insert_before, 1);
1222
1273
  rb_define_method(rb_cNode, "_render_html", rb_render_html, 2);
1274
+ rb_define_method(rb_cNode, "_render_xml", rb_render_xml, 1);
1223
1275
  rb_define_method(rb_cNode, "_render_commonmark", rb_render_commonmark, -1);
1224
1276
  rb_define_method(rb_cNode, "_render_plaintext", rb_render_plaintext, -1);
1225
1277
  rb_define_method(rb_cNode, "insert_after", rb_node_insert_after, 1);