kramdown 2.1.0 → 2.4.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.
- checksums.yaml +4 -4
- data/CONTRIBUTERS +19 -3
- data/README.md +8 -2
- data/VERSION +1 -1
- data/lib/kramdown/converter/base.rb +2 -1
- data/lib/kramdown/converter/html.rb +37 -30
- data/lib/kramdown/converter/kramdown.rb +20 -10
- data/lib/kramdown/converter/latex.rb +2 -2
- data/lib/kramdown/converter/math_engine/mathjax.rb +7 -33
- data/lib/kramdown/converter/syntax_highlighter/rouge.rb +17 -9
- data/lib/kramdown/converter/syntax_highlighter.rb +1 -1
- data/lib/kramdown/element.rb +24 -0
- data/lib/kramdown/options.rb +62 -12
- data/lib/kramdown/parser/base.rb +3 -1
- data/lib/kramdown/parser/html.rb +16 -9
- data/lib/kramdown/parser/kramdown/abbreviation.rb +1 -1
- data/lib/kramdown/parser/kramdown/autolink.rb +2 -2
- data/lib/kramdown/parser/kramdown/codespan.rb +18 -4
- data/lib/kramdown/parser/kramdown/emphasis.rb +1 -1
- data/lib/kramdown/parser/kramdown/extensions.rb +6 -0
- data/lib/kramdown/parser/kramdown/header.rb +3 -2
- data/lib/kramdown/parser/kramdown/html.rb +4 -10
- data/lib/kramdown/parser/kramdown/list.rb +37 -9
- data/lib/kramdown/parser/kramdown/math.rb +1 -1
- data/lib/kramdown/parser/kramdown/table.rb +2 -2
- data/lib/kramdown/parser/kramdown.rb +8 -1
- data/lib/kramdown/utils/html.rb +9 -0
- data/lib/kramdown/version.rb +1 -1
- data/man/man1/kramdown.1 +23 -0
- data/test/test_files.rb +28 -18
- data/test/test_location.rb +2 -2
- data/test/test_string_scanner_kramdown.rb +1 -1
- data/test/testcases/block/03_paragraph/standalone_image.html +5 -0
- data/test/testcases/block/03_paragraph/standalone_image.text +3 -0
- data/test/testcases/block/03_paragraph/to_kramdown.kramdown +7 -0
- data/test/testcases/block/03_paragraph/to_kramdown.text +5 -0
- data/test/testcases/block/04_header/atx_header.html +6 -0
- data/test/testcases/block/04_header/atx_header.text +6 -0
- data/test/testcases/block/06_codeblock/rouge/multiple.html +1 -1
- data/test/testcases/block/06_codeblock/rouge/simple.html +1 -1
- data/test/testcases/block/09_html/processing_instruction.html +5 -6
- data/test/testcases/block/09_html/standalone_image_in_div.htmlinput +7 -0
- data/test/testcases/block/09_html/standalone_image_in_div.text +8 -0
- data/test/testcases/block/09_html/table.kramdown +8 -0
- data/test/testcases/block/09_html/table.text +7 -0
- data/test/testcases/block/12_extension/options.html +4 -4
- data/test/testcases/block/12_extension/options.text +2 -0
- data/test/testcases/block/12_extension/options2.html +4 -4
- data/test/testcases/block/14_table/table_with_footnote.html +4 -4
- data/test/testcases/block/15_math/gh_128.html +1 -2
- data/test/testcases/block/15_math/normal.html +16 -15
- data/test/testcases/block/16_toc/toc_with_footnotes.html +4 -4
- data/test/testcases/cjk-line-break.html +4 -0
- data/test/testcases/cjk-line-break.options +1 -0
- data/test/testcases/cjk-line-break.text +12 -0
- data/test/testcases/man/example.man +1 -1
- data/test/testcases/man/example.text +1 -1
- data/test/testcases/span/02_emphasis/normal.html +4 -0
- data/test/testcases/span/02_emphasis/normal.text +4 -0
- data/test/testcases/span/03_codespan/normal.html +4 -0
- data/test/testcases/span/03_codespan/normal.text +4 -0
- data/test/testcases/span/04_footnote/backlink_inline.html +21 -21
- data/test/testcases/span/04_footnote/backlink_text.html +4 -4
- data/test/testcases/span/04_footnote/footnote_nr.html +6 -6
- data/test/testcases/span/04_footnote/footnote_prefix.html +6 -6
- data/test/testcases/span/04_footnote/inside_footnote.html +9 -9
- data/test/testcases/span/04_footnote/markers.html +16 -16
- data/test/testcases/span/04_footnote/placement.html +4 -4
- data/test/testcases/span/04_footnote/regexp_problem.html +4 -4
- data/test/testcases/span/04_footnote/without_backlink.html +3 -3
- data/test/testcases/span/05_html/normal.html +1 -1
- data/test/testcases/span/abbreviations/abbrev_in_html.html +9 -0
- data/test/testcases/span/abbreviations/abbrev_in_html.text +10 -0
- data/test/testcases/span/abbreviations/in_footnote.html +4 -4
- data/test/testcases/span/math/normal.html +4 -4
- data/test/testcases/span/text_substitutions/entities.html +1 -1
- data/test/testcases/span/text_substitutions/entities.text +1 -1
- metadata +36 -15
- data/test/testcases/block/15_math/mathjax_preview.html +0 -4
- data/test/testcases/block/15_math/mathjax_preview.options +0 -2
- data/test/testcases/block/15_math/mathjax_preview.text +0 -5
- data/test/testcases/block/15_math/mathjax_preview_as_code.html +0 -4
- data/test/testcases/block/15_math/mathjax_preview_as_code.options +0 -3
- data/test/testcases/block/15_math/mathjax_preview_as_code.text +0 -5
- data/test/testcases/block/15_math/mathjax_preview_simple.html +0 -4
- data/test/testcases/block/15_math/mathjax_preview_simple.options +0 -2
- data/test/testcases/block/15_math/mathjax_preview_simple.text +0 -5
data/lib/kramdown/options.rb
CHANGED
|
@@ -39,6 +39,7 @@ module Kramdown
|
|
|
39
39
|
ALLOWED_TYPES = [String, Integer, Float, Symbol, Boolean, Object]
|
|
40
40
|
|
|
41
41
|
@options = {}
|
|
42
|
+
@cached_defaults = nil
|
|
42
43
|
|
|
43
44
|
# Define a new option called +name+ (a Symbol) with the given +type+ (String, Integer, Float,
|
|
44
45
|
# Symbol, Boolean, Object), default value +default+ and the description +desc+. If a block is
|
|
@@ -54,6 +55,7 @@ module Kramdown
|
|
|
54
55
|
raise ArgumentError, "Invalid type for default value" if !(type === default) && !default.nil?
|
|
55
56
|
raise ArgumentError, "Missing validator block" if type == Object && block.nil?
|
|
56
57
|
@options[name] = Definition.new(name, type, default, desc, block)
|
|
58
|
+
@cached_defaults = nil
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
# Return all option definitions.
|
|
@@ -68,15 +70,17 @@ module Kramdown
|
|
|
68
70
|
|
|
69
71
|
# Return a Hash with the default values for all options.
|
|
70
72
|
def self.defaults
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
@cached_defaults ||= begin
|
|
74
|
+
temp = {}
|
|
75
|
+
@options.each {|_n, o| temp[o.name] = o.default }
|
|
76
|
+
temp.freeze
|
|
77
|
+
end
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
# Merge the #defaults Hash with the *parsed* options from the given Hash, i.e. only valid option
|
|
77
81
|
# names are considered and their value is run through the #parse method.
|
|
78
82
|
def self.merge(hash)
|
|
79
|
-
temp = defaults
|
|
83
|
+
temp = defaults.dup
|
|
80
84
|
hash.each do |k, v|
|
|
81
85
|
k = k.to_sym
|
|
82
86
|
temp[k] = @options.key?(k) ? parse(k, v) : v
|
|
@@ -328,7 +332,11 @@ module Kramdown
|
|
|
328
332
|
Used by: HTML converter, kramdown converter
|
|
329
333
|
EOF
|
|
330
334
|
|
|
331
|
-
|
|
335
|
+
TOC_LEVELS_RANGE = (1..6).freeze
|
|
336
|
+
TOC_LEVELS_ARRAY = TOC_LEVELS_RANGE.to_a.freeze
|
|
337
|
+
private_constant :TOC_LEVELS_RANGE, :TOC_LEVELS_ARRAY
|
|
338
|
+
|
|
339
|
+
define(:toc_levels, Object, TOC_LEVELS_ARRAY, <<~EOF) do |val|
|
|
332
340
|
Defines the levels that are used for the table of contents
|
|
333
341
|
|
|
334
342
|
The individual levels can be specified by separating them with commas
|
|
@@ -347,12 +355,20 @@ module Kramdown
|
|
|
347
355
|
else
|
|
348
356
|
raise Kramdown::Error, "Invalid syntax for option toc_levels"
|
|
349
357
|
end
|
|
350
|
-
when Array
|
|
351
|
-
|
|
358
|
+
when Array
|
|
359
|
+
unless val.eql?(TOC_LEVELS_ARRAY)
|
|
360
|
+
val = val.map(&:to_i).uniq
|
|
361
|
+
end
|
|
362
|
+
when Range
|
|
363
|
+
if val.eql?(TOC_LEVELS_RANGE)
|
|
364
|
+
val = TOC_LEVELS_ARRAY
|
|
365
|
+
else
|
|
366
|
+
val = val.map(&:to_i).uniq
|
|
367
|
+
end
|
|
352
368
|
else
|
|
353
369
|
raise Kramdown::Error, "Invalid type #{val.class} for option toc_levels"
|
|
354
370
|
end
|
|
355
|
-
if val.any? {|i| !
|
|
371
|
+
if val.any? {|i| !TOC_LEVELS_RANGE.cover?(i) }
|
|
356
372
|
raise Kramdown::Error, "Level numbers for option toc_levels have to be integers from 1 to 6"
|
|
357
373
|
end
|
|
358
374
|
val
|
|
@@ -377,7 +393,11 @@ module Kramdown
|
|
|
377
393
|
simple_array_validator(val, :latex_headers, 6)
|
|
378
394
|
end
|
|
379
395
|
|
|
380
|
-
|
|
396
|
+
SMART_QUOTES_ENTITIES = %w[lsquo rsquo ldquo rdquo].freeze
|
|
397
|
+
SMART_QUOTES_STR = SMART_QUOTES_ENTITIES.join(',').freeze
|
|
398
|
+
private_constant :SMART_QUOTES_ENTITIES, :SMART_QUOTES_STR
|
|
399
|
+
|
|
400
|
+
define(:smart_quotes, Object, SMART_QUOTES_ENTITIES, <<~EOF) do |val|
|
|
381
401
|
Defines the HTML entity names or code points for smart quote output
|
|
382
402
|
|
|
383
403
|
The entities identified by entity name or code point that should be
|
|
@@ -388,9 +408,13 @@ module Kramdown
|
|
|
388
408
|
Default: lsquo,rsquo,ldquo,rdquo
|
|
389
409
|
Used by: HTML/Latex converter
|
|
390
410
|
EOF
|
|
391
|
-
val
|
|
392
|
-
|
|
393
|
-
|
|
411
|
+
if val == SMART_QUOTES_STR || val == SMART_QUOTES_ENTITIES
|
|
412
|
+
SMART_QUOTES_ENTITIES
|
|
413
|
+
else
|
|
414
|
+
val = simple_array_validator(val, :smart_quotes, 4)
|
|
415
|
+
val.map! {|v| Integer(v) rescue v }
|
|
416
|
+
val
|
|
417
|
+
end
|
|
394
418
|
end
|
|
395
419
|
|
|
396
420
|
define(:typographic_symbols, Object, {}, <<~EOF) do |val|
|
|
@@ -562,6 +586,32 @@ module Kramdown
|
|
|
562
586
|
Used by: HTML
|
|
563
587
|
EOF
|
|
564
588
|
|
|
589
|
+
define(:remove_line_breaks_for_cjk, Boolean, false, <<~EOF)
|
|
590
|
+
Specifies whether line breaks should be removed between CJK characters
|
|
591
|
+
|
|
592
|
+
Default: false
|
|
593
|
+
Used by: HTML converter
|
|
594
|
+
EOF
|
|
595
|
+
|
|
596
|
+
define(:forbidden_inline_options, Object, %w[template], <<~EOF) do |val|
|
|
597
|
+
Defines the options that may not be set using the {::options} extension
|
|
598
|
+
|
|
599
|
+
The value needs to be an array of option names.
|
|
600
|
+
|
|
601
|
+
Default: [template]
|
|
602
|
+
Used by: HTML converter
|
|
603
|
+
EOF
|
|
604
|
+
val.map! {|item| item.kind_of?(String) ? str_to_sym(item) : item }
|
|
605
|
+
simple_array_validator(val, :forbidden_inline_options)
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
define(:list_indent, Integer, 2, <<~EOF)
|
|
609
|
+
Sets the number of spaces to use for list indentation
|
|
610
|
+
|
|
611
|
+
Default: 2
|
|
612
|
+
Used by: Kramdown converter
|
|
613
|
+
EOF
|
|
614
|
+
|
|
565
615
|
end
|
|
566
616
|
|
|
567
617
|
end
|
data/lib/kramdown/parser/base.rb
CHANGED
|
@@ -93,7 +93,9 @@ module Kramdown
|
|
|
93
93
|
raise "The source text contains invalid characters for the used encoding #{source.encoding}"
|
|
94
94
|
end
|
|
95
95
|
source = source.encode('UTF-8')
|
|
96
|
-
source.gsub(/\r\n?/, "\n")
|
|
96
|
+
source.gsub!(/\r\n?/, "\n")
|
|
97
|
+
source.chomp!
|
|
98
|
+
source << "\n"
|
|
97
99
|
end
|
|
98
100
|
|
|
99
101
|
# This helper method adds the given +text+ either to the last element in the +tree+ if it is a
|
data/lib/kramdown/parser/html.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Kramdown
|
|
|
16
16
|
|
|
17
17
|
module Parser
|
|
18
18
|
|
|
19
|
-
# Used for parsing
|
|
19
|
+
# Used for parsing an HTML document.
|
|
20
20
|
#
|
|
21
21
|
# The parsing code is in the Parser module that can also be used by other parsers.
|
|
22
22
|
class Html < Base
|
|
@@ -240,7 +240,14 @@ module Kramdown
|
|
|
240
240
|
return
|
|
241
241
|
when :html_element
|
|
242
242
|
when :root
|
|
243
|
-
el.children.
|
|
243
|
+
el.children.map! do |c|
|
|
244
|
+
if c.type == :text
|
|
245
|
+
process_text(c.value, !do_conversion)
|
|
246
|
+
else
|
|
247
|
+
process(c)
|
|
248
|
+
c
|
|
249
|
+
end
|
|
250
|
+
end.flatten!
|
|
244
251
|
remove_whitespace_children(el)
|
|
245
252
|
return
|
|
246
253
|
else return
|
|
@@ -286,7 +293,7 @@ module Kramdown
|
|
|
286
293
|
src = Kramdown::Utils::StringScanner.new(raw)
|
|
287
294
|
result = []
|
|
288
295
|
until src.eos?
|
|
289
|
-
if (tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/))
|
|
296
|
+
if (tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/o))
|
|
290
297
|
result << Element.new(:text, tmp)
|
|
291
298
|
src.scan(HTML_ENTITY_RE)
|
|
292
299
|
val = src[1] || (src[2]&.to_i) || src[3].hex
|
|
@@ -324,7 +331,7 @@ module Kramdown
|
|
|
324
331
|
tmp = []
|
|
325
332
|
last_is_p = false
|
|
326
333
|
el.children.each do |c|
|
|
327
|
-
if
|
|
334
|
+
if !c.block? || c.type == :text
|
|
328
335
|
unless last_is_p
|
|
329
336
|
tmp << Element.new(:p, nil, nil, transparent: true)
|
|
330
337
|
last_is_p = true
|
|
@@ -354,8 +361,8 @@ module Kramdown
|
|
|
354
361
|
el.children = el.children.reject do |c|
|
|
355
362
|
i += 1
|
|
356
363
|
c.type == :text && c.value.strip.empty? &&
|
|
357
|
-
(i == 0 || i == el.children.length - 1 || (
|
|
358
|
-
|
|
364
|
+
(i == 0 || i == el.children.length - 1 || ((el.children[i - 1]).block? &&
|
|
365
|
+
(el.children[i + 1]).block?))
|
|
359
366
|
end
|
|
360
367
|
end
|
|
361
368
|
|
|
@@ -581,11 +588,11 @@ module Kramdown
|
|
|
581
588
|
@src = Kramdown::Utils::StringScanner.new(adapt_source(source))
|
|
582
589
|
|
|
583
590
|
while true
|
|
584
|
-
if (result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/))
|
|
591
|
+
if (result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/o))
|
|
585
592
|
@tree.children << Element.new(:xml_pi, result.strip, nil, category: :block)
|
|
586
|
-
elsif (result = @src.scan(/\s*#{HTML_DOCTYPE_RE}/))
|
|
593
|
+
elsif (result = @src.scan(/\s*#{HTML_DOCTYPE_RE}/o))
|
|
587
594
|
# ignore the doctype
|
|
588
|
-
elsif (result = @src.scan(/\s*#{HTML_COMMENT_RE}/))
|
|
595
|
+
elsif (result = @src.scan(/\s*#{HTML_COMMENT_RE}/o))
|
|
589
596
|
@tree.children << Element.new(:xml_comment, result.strip, nil, category: :block)
|
|
590
597
|
else
|
|
591
598
|
break
|
|
@@ -46,7 +46,7 @@ module Kramdown
|
|
|
46
46
|
regexps << /(?=(?:\W|^)#{regexps.first}(?!\w))/ # regexp should only match on word boundaries
|
|
47
47
|
end
|
|
48
48
|
el.children.map! do |child|
|
|
49
|
-
if child.type == :text
|
|
49
|
+
if child.type == :text && el.options[:content_model] != :raw
|
|
50
50
|
if child.value =~ regexps.first
|
|
51
51
|
result = []
|
|
52
52
|
strscan = Kramdown::Utils::StringScanner.new(child.value, child.options[:location])
|
|
@@ -11,8 +11,8 @@ module Kramdown
|
|
|
11
11
|
module Parser
|
|
12
12
|
class Kramdown
|
|
13
13
|
|
|
14
|
-
ACHARS = '[[:alnum:]]_'
|
|
15
|
-
AUTOLINK_START_STR = "<((mailto|https?|ftps?):.+?|[
|
|
14
|
+
ACHARS = '[[:alnum:]]-_.'
|
|
15
|
+
AUTOLINK_START_STR = "<((mailto|https?|ftps?):.+?|[#{ACHARS}]+?@[#{ACHARS}]+?)>"
|
|
16
16
|
AUTOLINK_START = /#{AUTOLINK_START_STR}/u
|
|
17
17
|
|
|
18
18
|
# Parse the autolink at the current location.
|
|
@@ -20,18 +20,32 @@ module Kramdown
|
|
|
20
20
|
simple = (result.length == 1)
|
|
21
21
|
saved_pos = @src.save_pos
|
|
22
22
|
|
|
23
|
-
if simple && @src.pre_match =~ /\s\Z/ && @src.match?(/\s/)
|
|
23
|
+
if simple && @src.pre_match =~ /\s\Z|\A\Z/ && @src.match?(/\s/)
|
|
24
24
|
add_text(result)
|
|
25
25
|
return
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
# assign static regex to avoid allocating the same on every instance
|
|
29
|
+
# where +result+ equals a single-backtick. Interpolate otherwise.
|
|
30
|
+
if result == '`'
|
|
31
|
+
scan_pattern = /`/
|
|
32
|
+
str_sub_pattern = /`\Z/
|
|
33
|
+
else
|
|
34
|
+
scan_pattern = /#{result}/
|
|
35
|
+
str_sub_pattern = /#{result}\Z/
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if (text = @src.scan_until(scan_pattern))
|
|
39
|
+
text.sub!(str_sub_pattern, '')
|
|
30
40
|
unless simple
|
|
31
41
|
text = text[1..-1] if text[0..0] == ' '
|
|
32
42
|
text = text[0..-2] if text[-1..-1] == ' '
|
|
33
43
|
end
|
|
34
|
-
@tree.children << Element.new(:codespan, text, nil,
|
|
44
|
+
@tree.children << Element.new(:codespan, text, nil, {
|
|
45
|
+
codespan_delimiter: result,
|
|
46
|
+
location: start_line_number
|
|
47
|
+
})
|
|
48
|
+
|
|
35
49
|
else
|
|
36
50
|
@src.revert_pos(saved_pos)
|
|
37
51
|
add_text(result)
|
|
@@ -22,7 +22,7 @@ module Kramdown
|
|
|
22
22
|
element = (result.length == 2 ? :strong : :em)
|
|
23
23
|
type = result[0..0]
|
|
24
24
|
|
|
25
|
-
if (type == '_' && @src.pre_match =~ /[[:alpha:]
|
|
25
|
+
if (type == '_' && @src.pre_match =~ /[[:alpha:]]-?[[:alpha:]]*\z/) || @src.check(/\s/) ||
|
|
26
26
|
@tree.type == element || @stack.any? {|el, _| el.type == element }
|
|
27
27
|
add_text(result)
|
|
28
28
|
return
|
|
@@ -110,6 +110,12 @@ module Kramdown
|
|
|
110
110
|
opts.select do |k, v|
|
|
111
111
|
k = k.to_sym
|
|
112
112
|
if Kramdown::Options.defined?(k)
|
|
113
|
+
if @options[:forbidden_inline_options].include?(k) ||
|
|
114
|
+
k == :forbidden_inline_options
|
|
115
|
+
warning("Option #{k} may not be set inline")
|
|
116
|
+
next false
|
|
117
|
+
end
|
|
118
|
+
|
|
113
119
|
begin
|
|
114
120
|
val = Kramdown::Options.parse(k, v)
|
|
115
121
|
@options[k] = val
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#
|
|
9
9
|
|
|
10
10
|
require 'kramdown/parser/kramdown/block_boundary'
|
|
11
|
+
require 'rexml/xmltokens'
|
|
11
12
|
|
|
12
13
|
module Kramdown
|
|
13
14
|
module Parser
|
|
@@ -31,7 +32,7 @@ module Kramdown
|
|
|
31
32
|
def parse_atx_header
|
|
32
33
|
return false unless after_block_boundary?
|
|
33
34
|
text, id = parse_header_contents
|
|
34
|
-
text.sub!(/
|
|
35
|
+
text.sub!(/(?<!\\)#+\z/, '') && text.rstrip!
|
|
35
36
|
return false if text.empty?
|
|
36
37
|
add_header(@src["level"].length, text, id)
|
|
37
38
|
true
|
|
@@ -40,7 +41,7 @@ module Kramdown
|
|
|
40
41
|
|
|
41
42
|
protected
|
|
42
43
|
|
|
43
|
-
HEADER_ID = /[\t ]{#(?<id
|
|
44
|
+
HEADER_ID = /[\t ]{#(?<id>#{REXML::XMLTokens::NAME_START_CHAR}#{REXML::XMLTokens::NAME_CHAR}*)}\z/
|
|
44
45
|
|
|
45
46
|
# Returns header text and optional ID.
|
|
46
47
|
def parse_header_contents
|
|
@@ -65,7 +65,7 @@ module Kramdown
|
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
HTML_BLOCK_START = /^#{OPT_SPACE}<(#{REXML::Parsers::BaseParser::UNAME_STR}
|
|
68
|
+
HTML_BLOCK_START = /^#{OPT_SPACE}<(#{REXML::Parsers::BaseParser::UNAME_STR}|!--|\/)/
|
|
69
69
|
|
|
70
70
|
# Parse the HTML at the current position as block-level HTML.
|
|
71
71
|
def parse_block_html
|
|
@@ -74,17 +74,13 @@ module Kramdown
|
|
|
74
74
|
@tree.children << Element.new(:xml_comment, result, nil, category: :block, location: line)
|
|
75
75
|
@src.scan(TRAILING_WHITESPACE)
|
|
76
76
|
true
|
|
77
|
-
elsif (result = @src.scan(HTML_INSTRUCTION_RE))
|
|
78
|
-
@tree.children << Element.new(:xml_pi, result, nil, category: :block, location: line)
|
|
79
|
-
@src.scan(TRAILING_WHITESPACE)
|
|
80
|
-
true
|
|
81
77
|
else
|
|
82
|
-
if @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
|
|
78
|
+
if @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
|
|
83
79
|
@src.pos += @src.matched_size
|
|
84
80
|
handle_html_start_tag(line, &method(:handle_kramdown_html_tag))
|
|
85
81
|
Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native]
|
|
86
82
|
true
|
|
87
|
-
elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
|
|
83
|
+
elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
|
|
88
84
|
name = @src[1].downcase
|
|
89
85
|
|
|
90
86
|
if @tree.type == :html_element && @tree.value == name
|
|
@@ -100,15 +96,13 @@ module Kramdown
|
|
|
100
96
|
end
|
|
101
97
|
define_parser(:block_html, HTML_BLOCK_START)
|
|
102
98
|
|
|
103
|
-
HTML_SPAN_START = /<(#{REXML::Parsers::BaseParser::UNAME_STR}
|
|
99
|
+
HTML_SPAN_START = /<(#{REXML::Parsers::BaseParser::UNAME_STR}|!--|\/)/
|
|
104
100
|
|
|
105
101
|
# Parse the HTML at the current position as span-level HTML.
|
|
106
102
|
def parse_span_html
|
|
107
103
|
line = @src.current_line_number
|
|
108
104
|
if (result = @src.scan(HTML_COMMENT_RE))
|
|
109
105
|
@tree.children << Element.new(:xml_comment, result, nil, category: :span, location: line)
|
|
110
|
-
elsif (result = @src.scan(HTML_INSTRUCTION_RE))
|
|
111
|
-
@tree.children << Element.new(:xml_pi, result, nil, category: :span, location: line)
|
|
112
106
|
elsif (result = @src.scan(HTML_TAG_CLOSE_RE))
|
|
113
107
|
warning("Found invalidly used HTML closing tag for '#{@src[1]}' on line #{line}")
|
|
114
108
|
add_text(result)
|
|
@@ -44,8 +44,10 @@ module Kramdown
|
|
|
44
44
|
[content, indentation, *PARSE_FIRST_LIST_LINE_REGEXP_CACHE[indentation]]
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
PATTERN_TAIL = /[\t| ].*?\n/
|
|
48
|
+
|
|
49
|
+
LIST_START_UL = /^(#{OPT_SPACE}[+*-])(#{PATTERN_TAIL})/
|
|
50
|
+
LIST_START_OL = /^(#{OPT_SPACE}\d+\.)(#{PATTERN_TAIL})/
|
|
49
51
|
LIST_START = /#{LIST_START_UL}|#{LIST_START_OL}/
|
|
50
52
|
|
|
51
53
|
# Parse the ordered or unordered list at the current location.
|
|
@@ -67,6 +69,7 @@ module Kramdown
|
|
|
67
69
|
eob_found = true
|
|
68
70
|
break
|
|
69
71
|
elsif @src.scan(list_start_re)
|
|
72
|
+
list.options[:first_list_marker] ||= @src[1].strip
|
|
70
73
|
item = Element.new(:li, nil, nil, location: start_line_number)
|
|
71
74
|
item.value, indentation, content_re, lazy_re, indent_re =
|
|
72
75
|
parse_first_list_line(@src[1].length, @src[2])
|
|
@@ -77,11 +80,7 @@ module Kramdown
|
|
|
77
80
|
''
|
|
78
81
|
end
|
|
79
82
|
|
|
80
|
-
list_start_re =
|
|
81
|
-
/^( {0,#{[3, indentation - 1].min}}[+*-])([\t| ].*?\n)/
|
|
82
|
-
else
|
|
83
|
-
/^( {0,#{[3, indentation - 1].min}}\d+\.)([\t| ].*?\n)/
|
|
84
|
-
end
|
|
83
|
+
list_start_re = fetch_pattern(type, indentation)
|
|
85
84
|
nested_list_found = (item.value =~ LIST_START)
|
|
86
85
|
last_is_blank = false
|
|
87
86
|
item.value = [item.value]
|
|
@@ -148,7 +147,7 @@ module Kramdown
|
|
|
148
147
|
end
|
|
149
148
|
define_parser(:list, LIST_START)
|
|
150
149
|
|
|
151
|
-
DEFINITION_LIST_START = /^(#{OPT_SPACE}:)(
|
|
150
|
+
DEFINITION_LIST_START = /^(#{OPT_SPACE}:)(#{PATTERN_TAIL})/
|
|
152
151
|
|
|
153
152
|
# Parse the ordered or unordered list at the current location.
|
|
154
153
|
def parse_definition_list
|
|
@@ -198,7 +197,7 @@ module Kramdown
|
|
|
198
197
|
''
|
|
199
198
|
end
|
|
200
199
|
|
|
201
|
-
def_start_re =
|
|
200
|
+
def_start_re = fetch_pattern(:dl, indentation)
|
|
202
201
|
first_as_para = false
|
|
203
202
|
last_is_blank = false
|
|
204
203
|
elsif @src.check(EOB_MARKER)
|
|
@@ -252,6 +251,35 @@ module Kramdown
|
|
|
252
251
|
end
|
|
253
252
|
define_parser(:definition_list, DEFINITION_LIST_START)
|
|
254
253
|
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
# precomputed patterns for indentations 1..4 and fallback expression
|
|
257
|
+
# to compute pattern when indentation is outside the 1..4 range.
|
|
258
|
+
def fetch_pattern(type, indentation)
|
|
259
|
+
if type == :ul
|
|
260
|
+
case indentation
|
|
261
|
+
when 1 then %r/^( {0}[+*-])(#{PATTERN_TAIL})/o
|
|
262
|
+
when 2 then %r/^( {0,1}[+*-])(#{PATTERN_TAIL})/o
|
|
263
|
+
when 3 then %r/^( {0,2}[+*-])(#{PATTERN_TAIL})/o
|
|
264
|
+
else %r/^( {0,3}[+*-])(#{PATTERN_TAIL})/o
|
|
265
|
+
end
|
|
266
|
+
elsif type == :ol
|
|
267
|
+
case indentation
|
|
268
|
+
when 1 then %r/^( {0}\d+\.)(#{PATTERN_TAIL})/o
|
|
269
|
+
when 2 then %r/^( {0,1}\d+\.)(#{PATTERN_TAIL})/o
|
|
270
|
+
when 3 then %r/^( {0,2}\d+\.)(#{PATTERN_TAIL})/o
|
|
271
|
+
else %r/^( {0,3}\d+\.)(#{PATTERN_TAIL})/o
|
|
272
|
+
end
|
|
273
|
+
elsif type == :dl
|
|
274
|
+
case indentation
|
|
275
|
+
when 1 then %r/^( {0}:)(#{PATTERN_TAIL})/o
|
|
276
|
+
when 2 then %r/^( {0,1}:)(#{PATTERN_TAIL})/o
|
|
277
|
+
when 3 then %r/^( {0,2}:)(#{PATTERN_TAIL})/o
|
|
278
|
+
else %r/^( {0,3}:)(#{PATTERN_TAIL})/o
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
255
283
|
end
|
|
256
284
|
end
|
|
257
285
|
end
|
|
@@ -132,8 +132,8 @@ module Kramdown
|
|
|
132
132
|
pipe_on_line = false
|
|
133
133
|
end
|
|
134
134
|
else
|
|
135
|
-
break if lines.size > 1 && !pipe_on_line && lines.first !~ /^#{TABLE_PIPE_CHECK}/
|
|
136
|
-
pipe_on_line = (lines.size > 1 ? false : pipe_on_line) || (lines.last =~ /^#{TABLE_PIPE_CHECK}/)
|
|
135
|
+
break if lines.size > 1 && !pipe_on_line && lines.first !~ /^#{TABLE_PIPE_CHECK}/o
|
|
136
|
+
pipe_on_line = (lines.size > 1 ? false : pipe_on_line) || (lines.last =~ /^#{TABLE_PIPE_CHECK}/o)
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
@src.revert_pos(saved_pos) and return false unless pipe_on_line
|
|
@@ -79,6 +79,8 @@ module Kramdown
|
|
|
79
79
|
@span_parsers = [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link,
|
|
80
80
|
:smart_quotes, :inline_math, :span_extensions, :html_entity,
|
|
81
81
|
:typographic_syms, :line_break, :escaped_chars]
|
|
82
|
+
|
|
83
|
+
@span_pattern_cache ||= Hash.new { |h, k| h[k] = {} }
|
|
82
84
|
end
|
|
83
85
|
private_class_method(:new, :allocate)
|
|
84
86
|
|
|
@@ -195,6 +197,11 @@ module Kramdown
|
|
|
195
197
|
end.flatten!
|
|
196
198
|
end
|
|
197
199
|
|
|
200
|
+
def span_pattern_cache(stop_re, span_start)
|
|
201
|
+
@span_pattern_cache[stop_re][span_start] ||= /(?=#{Regexp.union(stop_re, span_start)})/
|
|
202
|
+
end
|
|
203
|
+
private :span_pattern_cache
|
|
204
|
+
|
|
198
205
|
# Parse all span-level elements in the source string of @src into +el+.
|
|
199
206
|
#
|
|
200
207
|
# If the parameter +stop_re+ (a regexp) is used, parsing is immediately stopped if the regexp
|
|
@@ -213,7 +220,7 @@ module Kramdown
|
|
|
213
220
|
span_start, span_start_re = span_parser_regexps(parsers) if parsers
|
|
214
221
|
parsers ||= @span_parsers
|
|
215
222
|
|
|
216
|
-
used_re = (stop_re.nil? ? span_start_re :
|
|
223
|
+
used_re = (stop_re.nil? ? span_start_re : span_pattern_cache(stop_re, span_start))
|
|
217
224
|
stop_re_found = false
|
|
218
225
|
while !@src.eos? && !stop_re_found
|
|
219
226
|
if (result = @src.scan_until(used_re))
|
data/lib/kramdown/utils/html.rb
CHANGED
|
@@ -42,6 +42,8 @@ module Kramdown
|
|
|
42
42
|
|
|
43
43
|
# Return the HTML representation of the attributes +attr+.
|
|
44
44
|
def html_attributes(attr)
|
|
45
|
+
return '' if attr.empty?
|
|
46
|
+
|
|
45
47
|
attr.map do |k, v|
|
|
46
48
|
v.nil? || (k == 'id' && v.strip.empty?) ? '' : " #{k}=\"#{escape_html(v.to_s, :attribute)}\""
|
|
47
49
|
end.join('')
|
|
@@ -68,6 +70,13 @@ module Kramdown
|
|
|
68
70
|
str.gsub(ESCAPE_RE_FROM_TYPE[type]) {|m| ESCAPE_MAP[m] || m }
|
|
69
71
|
end
|
|
70
72
|
|
|
73
|
+
REDUNDANT_LINE_BREAK_REGEX = /([\p{Han}\p{Hiragana}\p{Katakana}]+)\n([\p{Han}\p{Hiragana}\p{Katakana}]+)/u
|
|
74
|
+
def fix_cjk_line_break(str)
|
|
75
|
+
while str.gsub!(REDUNDANT_LINE_BREAK_REGEX, '\1\2')
|
|
76
|
+
end
|
|
77
|
+
str
|
|
78
|
+
end
|
|
79
|
+
|
|
71
80
|
end
|
|
72
81
|
|
|
73
82
|
end
|
data/lib/kramdown/version.rb
CHANGED
data/man/man1/kramdown.1
CHANGED
|
@@ -118,6 +118,15 @@ This option can be used to set a prefix for footnote IDs\. This is useful when r
|
|
|
118
118
|
Default: \[u2018]\[u2019] Used by: HTML
|
|
119
119
|
.RE
|
|
120
120
|
.TP
|
|
121
|
+
\fB\-\-forbidden\-inline\-options\fP \fIARG\fP
|
|
122
|
+
Defines the options that may not be set using the {::options} extension
|
|
123
|
+
.RS
|
|
124
|
+
.P
|
|
125
|
+
The value needs to be an array of option names\.
|
|
126
|
+
.P
|
|
127
|
+
Default: [template] Used by: HTML converter
|
|
128
|
+
.RE
|
|
129
|
+
.TP
|
|
121
130
|
\fB\-\-header\-offset\fP \fIARG\fP
|
|
122
131
|
Sets the output offset for headers
|
|
123
132
|
.RS
|
|
@@ -165,6 +174,13 @@ If the value is a String, it has to contain a valid YAML hash and the hash has t
|
|
|
165
174
|
Default: {} Used by: kramdown parser
|
|
166
175
|
.RE
|
|
167
176
|
.TP
|
|
177
|
+
\fB\-\-list\-indent\fP \fIARG\fP
|
|
178
|
+
Sets the number of spaces to use for list indentation
|
|
179
|
+
.RS
|
|
180
|
+
.P
|
|
181
|
+
Default: 2 Used by: Kramdown converter
|
|
182
|
+
.RE
|
|
183
|
+
.TP
|
|
168
184
|
\fB\-\-math\-engine\fP \fIARG\fP
|
|
169
185
|
Set the math engine
|
|
170
186
|
.RS
|
|
@@ -214,6 +230,13 @@ If this option is \fBtrue\fP, the RemoveHtmlTags converter removes block HTML ta
|
|
|
214
230
|
Default: true Used by: RemoveHtmlTags converter
|
|
215
231
|
.RE
|
|
216
232
|
.TP
|
|
233
|
+
\fB\-\-[no\-]remove\-line\-breaks\-for\-cjk\fP
|
|
234
|
+
Specifies whether line breaks should be removed between CJK characters
|
|
235
|
+
.RS
|
|
236
|
+
.P
|
|
237
|
+
Default: false Used by: HTML converter
|
|
238
|
+
.RE
|
|
239
|
+
.TP
|
|
217
240
|
\fB\-\-[no\-]remove\-span\-html\-tags\fP
|
|
218
241
|
Remove span HTML tags
|
|
219
242
|
.RS
|