kramdown 2.4.0 → 2.5.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 +10 -1
- data/VERSION +1 -1
- data/bin/kramdown +15 -12
- data/lib/kramdown/converter/base.rb +2 -2
- data/lib/kramdown/converter/html.rb +22 -14
- data/lib/kramdown/converter/kramdown.rb +25 -20
- data/lib/kramdown/converter/latex.rb +11 -10
- data/lib/kramdown/converter/man.rb +3 -3
- data/lib/kramdown/converter/math_engine/mathjax.rb +1 -1
- data/lib/kramdown/converter/remove_html_tags.rb +5 -4
- data/lib/kramdown/document.rb +1 -1
- data/lib/kramdown/element.rb +1 -1
- data/lib/kramdown/options.rb +43 -15
- data/lib/kramdown/parser/base.rb +6 -0
- data/lib/kramdown/parser/html.rb +24 -18
- data/lib/kramdown/parser/kramdown/abbreviation.rb +4 -2
- data/lib/kramdown/parser/kramdown/codespan.rb +1 -1
- data/lib/kramdown/parser/kramdown/emphasis.rb +6 -1
- data/lib/kramdown/parser/kramdown/escaped_chars.rb +1 -1
- data/lib/kramdown/parser/kramdown/extensions.rb +6 -6
- data/lib/kramdown/parser/kramdown/html.rb +26 -23
- data/lib/kramdown/parser/kramdown/html_entity.rb +1 -1
- data/lib/kramdown/parser/kramdown/link.rb +4 -4
- data/lib/kramdown/parser/kramdown/list.rb +19 -18
- data/lib/kramdown/parser/kramdown/smart_quotes.rb +1 -1
- data/lib/kramdown/parser/kramdown.rb +5 -4
- data/lib/kramdown/utils/entities.rb +661 -5
- data/lib/kramdown/utils/html.rb +1 -1
- data/lib/kramdown/utils/unidecoder.rb +9 -13
- data/lib/kramdown/version.rb +1 -1
- data/man/man1/kramdown.1 +18 -0
- data/test/test_files.rb +8 -0
- data/test/testcases/block/04_header/with_header_links.html +7 -0
- data/test/testcases/block/04_header/with_header_links.options +2 -0
- data/test/testcases/block/04_header/with_header_links.text +8 -0
- data/test/testcases/block/04_header/with_line_break.html +1 -0
- data/test/testcases/block/04_header/with_line_break.text +1 -0
- data/test/testcases/block/08_list/escaping.html +4 -0
- data/test/testcases/block/08_list/escaping.text +4 -0
- data/test/testcases/block/08_list/nested_compact.kramdown +7 -0
- data/test/testcases/block/08_list/nested_compact.text +6 -0
- data/test/testcases/block/08_list/special_cases.html +10 -0
- data/test/testcases/block/08_list/special_cases.text +9 -0
- data/test/testcases/block/09_html/cdata_section.html +10 -0
- data/test/testcases/block/09_html/cdata_section.text +10 -0
- data/test/testcases/block/09_html/html_to_native/table_simple.html +3 -0
- data/test/testcases/block/09_html/html_to_native/table_simple.text +3 -0
- data/test/testcases/block/12_extension/options.html +2 -2
- data/test/testcases/block/12_extension/options2.html +2 -2
- data/test/testcases/block/14_table/table_with_footnote.html +2 -2
- data/test/testcases/block/16_toc/toc_with_footnotes.html +2 -2
- data/test/testcases/span/02_emphasis/normal.html +6 -1
- data/test/testcases/span/02_emphasis/normal.text +5 -0
- data/test/testcases/span/04_footnote/backlink_inline.html +10 -10
- data/test/testcases/span/04_footnote/backlink_text.html +2 -2
- data/test/testcases/span/04_footnote/footnote_link_text.html +12 -0
- data/test/testcases/span/04_footnote/footnote_link_text.options +1 -0
- data/test/testcases/span/04_footnote/footnote_link_text.text +4 -0
- data/test/testcases/span/04_footnote/footnote_nr.html +3 -3
- data/test/testcases/span/04_footnote/footnote_prefix.html +3 -3
- data/test/testcases/span/04_footnote/inside_footnote.html +6 -6
- data/test/testcases/span/04_footnote/markers.html +10 -10
- data/test/testcases/span/04_footnote/placement.html +2 -2
- data/test/testcases/span/04_footnote/regexp_problem.html +2 -2
- data/test/testcases/span/04_footnote/without_backlink.html +2 -2
- data/test/testcases/span/abbreviations/abbrev.html +4 -0
- data/test/testcases/span/abbreviations/abbrev.text +7 -0
- data/test/testcases/span/abbreviations/in_footnote.html +2 -2
- metadata +21 -9
data/lib/kramdown/options.rb
CHANGED
@@ -39,7 +39,7 @@ module Kramdown
|
|
39
39
|
ALLOWED_TYPES = [String, Integer, Float, Symbol, Boolean, Object]
|
40
40
|
|
41
41
|
@options = {}
|
42
|
-
@
|
42
|
+
@defaults = nil
|
43
43
|
|
44
44
|
# Define a new option called +name+ (a Symbol) with the given +type+ (String, Integer, Float,
|
45
45
|
# Symbol, Boolean, Object), default value +default+ and the description +desc+. If a block is
|
@@ -55,7 +55,7 @@ module Kramdown
|
|
55
55
|
raise ArgumentError, "Invalid type for default value" if !(type === default) && !default.nil?
|
56
56
|
raise ArgumentError, "Missing validator block" if type == Object && block.nil?
|
57
57
|
@options[name] = Definition.new(name, type, default, desc, block)
|
58
|
-
@
|
58
|
+
@defaults = nil
|
59
59
|
end
|
60
60
|
|
61
61
|
# Return all option definitions.
|
@@ -70,11 +70,12 @@ module Kramdown
|
|
70
70
|
|
71
71
|
# Return a Hash with the default values for all options.
|
72
72
|
def self.defaults
|
73
|
-
@
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
73
|
+
@defaults ||=
|
74
|
+
begin
|
75
|
+
temp = {}
|
76
|
+
@options.each {|_n, o| temp[o.name] = o.default }
|
77
|
+
temp.freeze
|
78
|
+
end
|
78
79
|
end
|
79
80
|
|
80
81
|
# Merge the #defaults Hash with the *parsed* options from the given Hash, i.e. only valid option
|
@@ -140,7 +141,7 @@ module Kramdown
|
|
140
141
|
# Optionally, the array is checked for the correct size.
|
141
142
|
def self.simple_array_validator(val, name, size = nil)
|
142
143
|
if String === val
|
143
|
-
val = val.split(
|
144
|
+
val = val.split(",")
|
144
145
|
elsif !(Array === val)
|
145
146
|
raise Kramdown::Error, "Invalid type #{val.class} for option #{name}"
|
146
147
|
end
|
@@ -235,6 +236,15 @@ module Kramdown
|
|
235
236
|
Used by: HTML/Latex converter
|
236
237
|
EOF
|
237
238
|
|
239
|
+
define(:header_links, Boolean, false, <<~EOF)
|
240
|
+
Adds anchor tags within headers that can be used to generate permalinks
|
241
|
+
when not using a table of contents.
|
242
|
+
|
243
|
+
The anchor tags are empty, but can be styled to your liking.
|
244
|
+
|
245
|
+
Default: false
|
246
|
+
EOF
|
247
|
+
|
238
248
|
define(:transliterated_header_ids, Boolean, false, <<~EOF)
|
239
249
|
Transliterate the header text before generating the ID
|
240
250
|
|
@@ -350,8 +360,8 @@ module Kramdown
|
|
350
360
|
when String
|
351
361
|
if val =~ /^(\d)\.\.(\d)$/
|
352
362
|
val = Range.new($1.to_i, $2.to_i).to_a
|
353
|
-
elsif val
|
354
|
-
val = val.split(
|
363
|
+
elsif val.match?(/^\d(?:,\d)*$/)
|
364
|
+
val = val.split(",").map(&:to_i).uniq
|
355
365
|
else
|
356
366
|
raise Kramdown::Error, "Invalid syntax for option toc_levels"
|
357
367
|
end
|
@@ -360,11 +370,11 @@ module Kramdown
|
|
360
370
|
val = val.map(&:to_i).uniq
|
361
371
|
end
|
362
372
|
when Range
|
363
|
-
if val.eql?(TOC_LEVELS_RANGE)
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
373
|
+
val = if val.eql?(TOC_LEVELS_RANGE)
|
374
|
+
TOC_LEVELS_ARRAY
|
375
|
+
else
|
376
|
+
val.map(&:to_i).uniq
|
377
|
+
end
|
368
378
|
else
|
369
379
|
raise Kramdown::Error, "Invalid type #{val.class} for option toc_levels"
|
370
380
|
end
|
@@ -586,6 +596,24 @@ module Kramdown
|
|
586
596
|
Used by: HTML
|
587
597
|
EOF
|
588
598
|
|
599
|
+
define(:footnote_link_text, String, '%s', <<~EOF) do |val|
|
600
|
+
The text used for the footnote number in a footnote link
|
601
|
+
|
602
|
+
This option can be used to add additional text to the footnote
|
603
|
+
link. It should be a format string, and is passed the footnote
|
604
|
+
number as the only argument to the format string.
|
605
|
+
e.g. "[footnote %s]" would display as "[footnote 1]".
|
606
|
+
|
607
|
+
Default: '%s'
|
608
|
+
Used by: HTML
|
609
|
+
EOF
|
610
|
+
if !val.include?('%s')
|
611
|
+
raise Kramdown::Error, "option footnote_link_text needs to contain a '%s'"
|
612
|
+
end
|
613
|
+
val
|
614
|
+
end
|
615
|
+
|
616
|
+
|
589
617
|
define(:remove_line_breaks_for_cjk, Boolean, false, <<~EOF)
|
590
618
|
Specifies whether line breaks should be removed between CJK characters
|
591
619
|
|
data/lib/kramdown/parser/base.rb
CHANGED
@@ -54,6 +54,12 @@ module Kramdown
|
|
54
54
|
@options = Kramdown::Options.merge(options)
|
55
55
|
@root = Element.new(:root, nil, nil, encoding: (source.encoding rescue nil), location: 1,
|
56
56
|
options: {}, abbrev_defs: {}, abbrev_attr: {})
|
57
|
+
|
58
|
+
@root.options[:abbrev_defs].default_proc = @root.options[:abbrev_attr].default_proc =
|
59
|
+
lambda do |h, k|
|
60
|
+
k_mod = k.gsub(/[\s\p{Z}]+/, " ")
|
61
|
+
k != k_mod ? h[k_mod] : nil
|
62
|
+
end
|
57
63
|
@warnings = []
|
58
64
|
@text_type = :text
|
59
65
|
end
|
data/lib/kramdown/parser/html.rb
CHANGED
@@ -24,15 +24,16 @@ module Kramdown
|
|
24
24
|
# Contains all constants that are used when parsing.
|
25
25
|
module Constants
|
26
26
|
|
27
|
-
|
27
|
+
# :stopdoc:
|
28
28
|
# The following regexps are based on the ones used by REXML, with some slight modifications.
|
29
29
|
HTML_DOCTYPE_RE = /<!DOCTYPE.*?>/im
|
30
30
|
HTML_COMMENT_RE = /<!--(.*?)-->/m
|
31
31
|
HTML_INSTRUCTION_RE = /<\?(.*?)\?>/m
|
32
|
+
HTML_CDATA_RE = /<!\[CDATA\[(.*?)\]\]>/m
|
32
33
|
HTML_ATTRIBUTE_RE = /\s*(#{REXML::Parsers::BaseParser::UNAME_STR})(?:\s*=\s*(?:(\p{Word}+)|("|')(.*?)\3))?/m
|
33
34
|
HTML_TAG_RE = /<((?>#{REXML::Parsers::BaseParser::UNAME_STR}))\s*((?>\s+#{REXML::Parsers::BaseParser::UNAME_STR}(?:\s*=\s*(?:\p{Word}+|("|').*?\3))?)*)\s*(\/)?>/m
|
34
35
|
HTML_TAG_CLOSE_RE = /<\/(#{REXML::Parsers::BaseParser::UNAME_STR})\s*>/m
|
35
|
-
HTML_ENTITY_RE = /&([\w:][
|
36
|
+
HTML_ENTITY_RE = /&([\w:][\w.:-]*);|&#(\d+);|&\#x([0-9a-fA-F]+);/
|
36
37
|
|
37
38
|
HTML_CONTENT_MODEL_BLOCK = %w[address applet article aside blockquote body
|
38
39
|
dd details div dl fieldset figure figcaption
|
@@ -55,7 +56,7 @@ module Kramdown
|
|
55
56
|
# script, textarea
|
56
57
|
HTML_SPAN_ELEMENTS = %w[a abbr acronym b big bdo br button cite code del dfn em i img input
|
57
58
|
ins kbd label mark option q rb rbc rp rt rtc ruby samp select small
|
58
|
-
span strong sub sup tt u var]
|
59
|
+
span strong sub sup time tt u var]
|
59
60
|
HTML_BLOCK_ELEMENTS = %w[address article aside applet body blockquote caption col colgroup
|
60
61
|
dd div dl dt fieldset figcaption footer form h1 h2 h3 h4 h5 h6
|
61
62
|
header hgroup hr html head iframe legend menu li main map nav ol
|
@@ -136,7 +137,7 @@ module Kramdown
|
|
136
137
|
end
|
137
138
|
end
|
138
139
|
|
139
|
-
HTML_RAW_START = /(?=<(#{REXML::Parsers::BaseParser::UNAME_STR}
|
140
|
+
HTML_RAW_START = /(?=<(#{REXML::Parsers::BaseParser::UNAME_STR}|\/|!--|\?|!\[CDATA\[))/ # :nodoc:
|
140
141
|
|
141
142
|
# Parse raw HTML from the current source position, storing the found elements in +el+.
|
142
143
|
# Parsing continues until one of the following criteria are fulfilled:
|
@@ -160,6 +161,8 @@ module Kramdown
|
|
160
161
|
@tree.children << Element.new(:xml_comment, result, nil, category: :block, location: line)
|
161
162
|
elsif (result = @src.scan(HTML_INSTRUCTION_RE))
|
162
163
|
@tree.children << Element.new(:xml_pi, result, nil, category: :block, location: line)
|
164
|
+
elsif @src.scan(HTML_CDATA_RE)
|
165
|
+
@tree.children << Element.new(:text, @src[1], nil, cdata: true, location: line)
|
163
166
|
elsif @src.scan(HTML_TAG_RE)
|
164
167
|
if method(:handle_html_start_tag).arity.abs >= 1
|
165
168
|
handle_html_start_tag(line, &block)
|
@@ -239,6 +242,7 @@ module Kramdown
|
|
239
242
|
el.options.replace(category: (HTML_CONTENT_MODEL[ptype] == :span ? :span : :block))
|
240
243
|
return
|
241
244
|
when :html_element
|
245
|
+
# do nothing
|
242
246
|
when :root
|
243
247
|
el.children.map! do |c|
|
244
248
|
if c.type == :text
|
@@ -296,7 +300,7 @@ module Kramdown
|
|
296
300
|
if (tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/o))
|
297
301
|
result << Element.new(:text, tmp)
|
298
302
|
src.scan(HTML_ENTITY_RE)
|
299
|
-
val = src[1] ||
|
303
|
+
val = src[1] || src[2]&.to_i || src[3].hex
|
300
304
|
result << if %w[lsquo rsquo ldquo rdquo].include?(val)
|
301
305
|
Element.new(:smart_quote, val.intern)
|
302
306
|
elsif %w[mdash ndash hellip laquo raquo].include?(val)
|
@@ -361,8 +365,8 @@ module Kramdown
|
|
361
365
|
el.children = el.children.reject do |c|
|
362
366
|
i += 1
|
363
367
|
c.type == :text && c.value.strip.empty? &&
|
364
|
-
(i == 0 || i == el.children.length - 1 || (
|
365
|
-
|
368
|
+
(i == 0 || i == el.children.length - 1 || (el.children[i - 1].block? &&
|
369
|
+
el.children[i + 1].block?))
|
366
370
|
end
|
367
371
|
end
|
368
372
|
|
@@ -420,15 +424,16 @@ module Kramdown
|
|
420
424
|
result = process_text(raw, true)
|
421
425
|
begin
|
422
426
|
str = result.inject(+'') do |mem, c|
|
423
|
-
|
427
|
+
case c.type
|
428
|
+
when :text
|
424
429
|
mem << c.value
|
425
|
-
|
430
|
+
when :entity
|
426
431
|
mem << if [60, 62, 34, 38].include?(c.value.code_point)
|
427
432
|
c.value.code_point.chr
|
428
433
|
else
|
429
434
|
c.value.char
|
430
435
|
end
|
431
|
-
|
436
|
+
when :smart_quote, :typographic_sym
|
432
437
|
mem << entity(c.value.to_s).char
|
433
438
|
else
|
434
439
|
raise "Bug - please report"
|
@@ -518,7 +523,7 @@ module Kramdown
|
|
518
523
|
nr_cells = 0
|
519
524
|
check_nr_cells = lambda do |t|
|
520
525
|
if t.value == 'tr'
|
521
|
-
count = t.children.
|
526
|
+
count = t.children.count {|cc| cc.value == 'th' || cc.value == 'td' }
|
522
527
|
if count != nr_cells
|
523
528
|
if nr_cells == 0
|
524
529
|
nr_cells = count
|
@@ -532,7 +537,7 @@ module Kramdown
|
|
532
537
|
end
|
533
538
|
end
|
534
539
|
check_nr_cells.call(el)
|
535
|
-
return false if nr_cells == -1
|
540
|
+
return false if nr_cells == -1 || nr_cells == 0
|
536
541
|
|
537
542
|
alignment = nil
|
538
543
|
check_alignment = proc do |t|
|
@@ -551,7 +556,9 @@ module Kramdown
|
|
551
556
|
check_alignment.call(el)
|
552
557
|
|
553
558
|
check_rows = lambda do |t, type|
|
554
|
-
t.children.all?
|
559
|
+
t.children.all? do |r|
|
560
|
+
(r.value == 'tr' || r.type == :text) && r.children.all? {|c| c.value == type || c.type == :text }
|
561
|
+
end
|
555
562
|
end
|
556
563
|
check_rows.call(el, 'td') ||
|
557
564
|
(el.children.all? do |t|
|
@@ -561,10 +568,10 @@ module Kramdown
|
|
561
568
|
end
|
562
569
|
|
563
570
|
def convert_script(el)
|
564
|
-
if
|
565
|
-
process_html_element(el)
|
566
|
-
else
|
571
|
+
if is_math_tag?(el)
|
567
572
|
handle_math_tag(el)
|
573
|
+
else
|
574
|
+
process_html_element(el)
|
568
575
|
end
|
569
576
|
end
|
570
577
|
|
@@ -573,7 +580,7 @@ module Kramdown
|
|
573
580
|
end
|
574
581
|
|
575
582
|
def handle_math_tag(el)
|
576
|
-
set_basics(el, :math, category: (el.attr['type']
|
583
|
+
set_basics(el, :math, category: (el.attr['type'].include?("mode=display") ? :block : :span))
|
577
584
|
el.value = el.children.shift.value.sub(/\A(?:%\s*)?<!\[CDATA\[\n?(.*?)(?:\s%)?\]\]>\z/m, '\1')
|
578
585
|
el.attr.delete('type')
|
579
586
|
end
|
@@ -612,4 +619,3 @@ module Kramdown
|
|
612
619
|
end
|
613
620
|
|
614
621
|
end
|
615
|
-
|
@@ -42,12 +42,14 @@ module Kramdown
|
|
42
42
|
return if @root.options[:abbrev_defs].empty?
|
43
43
|
unless regexps
|
44
44
|
sorted_abbrevs = @root.options[:abbrev_defs].keys.sort {|a, b| b.length <=> a.length }
|
45
|
-
regexps = [Regexp.union(*sorted_abbrevs.map
|
45
|
+
regexps = [Regexp.union(*sorted_abbrevs.map do |k|
|
46
|
+
/#{Regexp.escape(k).gsub(/\\\s/, "[\\s\\p{Z}]+").force_encoding(Encoding::UTF_8)}/
|
47
|
+
end)]
|
46
48
|
regexps << /(?=(?:\W|^)#{regexps.first}(?!\w))/ # regexp should only match on word boundaries
|
47
49
|
end
|
48
50
|
el.children.map! do |child|
|
49
51
|
if child.type == :text && el.options[:content_model] != :raw
|
50
|
-
if child.value
|
52
|
+
if child.value.match?(regexps.first)
|
51
53
|
result = []
|
52
54
|
strscan = Kramdown::Utils::StringScanner.new(child.value, child.options[:location])
|
53
55
|
text_lineno = strscan.current_line_number
|
@@ -22,12 +22,13 @@ 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:]]-?[[:alpha:]]*\z/) || @src.check(/\s/) ||
|
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
|
29
29
|
end
|
30
30
|
|
31
|
+
warnings_pos = @warnings.size
|
31
32
|
sub_parse = lambda do |delim, elem|
|
32
33
|
el = Element.new(elem, nil, nil, location: start_line_number)
|
33
34
|
stop_re = /#{Regexp.escape(delim)}/
|
@@ -46,9 +47,13 @@ module Kramdown
|
|
46
47
|
found, el, stop_re = sub_parse.call(type, :em)
|
47
48
|
end
|
48
49
|
if found
|
50
|
+
# Useful for implementing underlines.
|
51
|
+
el.options[:char] = type
|
52
|
+
|
49
53
|
@src.scan(stop_re)
|
50
54
|
@tree.children << el
|
51
55
|
else
|
56
|
+
@warnings.slice!(0...warnings_pos)
|
52
57
|
@src.revert_pos(saved_pos)
|
53
58
|
@src.pos += result.length
|
54
59
|
add_text(result)
|
@@ -11,7 +11,7 @@ module Kramdown
|
|
11
11
|
module Parser
|
12
12
|
class Kramdown
|
13
13
|
|
14
|
-
ESCAPED_CHARS = /\\([\\.*_+`<>()\[\]{}#!:|"'
|
14
|
+
ESCAPED_CHARS = /\\([\\.*_+`<>()\[\]{}#!:|"'$=-])/
|
15
15
|
|
16
16
|
# Parse the backslash-escaped character at the current location.
|
17
17
|
def parse_escaped_chars
|
@@ -85,11 +85,11 @@ module Kramdown
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
if
|
88
|
+
if handle_extension(ext, opts, body, type, start_line_number)
|
89
|
+
true
|
90
|
+
else
|
89
91
|
error_block.call("Invalid extension with name '#{ext}' specified on line " \
|
90
92
|
"#{start_line_number} - ignoring it")
|
91
|
-
else
|
92
|
-
true
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
@@ -137,10 +137,10 @@ module Kramdown
|
|
137
137
|
end
|
138
138
|
|
139
139
|
ALD_ID_CHARS = /[\w-]/
|
140
|
-
ALD_ANY_CHARS = /\\\}|[
|
140
|
+
ALD_ANY_CHARS = /\\\}|[^}]/
|
141
141
|
ALD_ID_NAME = /\w#{ALD_ID_CHARS}*/
|
142
|
-
ALD_CLASS_NAME = /[^\s
|
143
|
-
ALD_TYPE_KEY_VALUE_PAIR = /(#{ALD_ID_NAME})=("|')((?:\\\}|\\\2|[
|
142
|
+
ALD_CLASS_NAME = /[^\s.#]+/
|
143
|
+
ALD_TYPE_KEY_VALUE_PAIR = /(#{ALD_ID_NAME})=("|')((?:\\\}|\\\2|[^}\2])*?)\2/
|
144
144
|
ALD_TYPE_CLASS_NAME = /\.(#{ALD_CLASS_NAME})/
|
145
145
|
ALD_TYPE_ID_NAME = /#([A-Za-z][\w:-]*)/
|
146
146
|
ALD_TYPE_ID_OR_CLASS = /#{ALD_TYPE_ID_NAME}|#{ALD_TYPE_CLASS_NAME}/
|
@@ -14,6 +14,7 @@ module Kramdown
|
|
14
14
|
class Kramdown
|
15
15
|
|
16
16
|
include Kramdown::Parser::Html::Parser
|
17
|
+
include Kramdown::Utils::Html
|
17
18
|
|
18
19
|
# Mapping of markdown attribute value to content model. I.e. :raw when "0", :default when "1"
|
19
20
|
# (use default content model for the HTML element), :span when "span", :block when block and
|
@@ -42,11 +43,12 @@ module Kramdown
|
|
42
43
|
el.options[:is_closed] = closed
|
43
44
|
|
44
45
|
if !closed && handle_body
|
45
|
-
|
46
|
+
case content_model
|
47
|
+
when :block
|
46
48
|
unless parse_blocks(el)
|
47
49
|
warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
|
48
50
|
end
|
49
|
-
|
51
|
+
when :span
|
50
52
|
curpos = @src.pos
|
51
53
|
if @src.scan_until(/(?=<\/#{el.value}\s*>)/mi)
|
52
54
|
add_text(extract_string(curpos...@src.pos, @src), el)
|
@@ -57,7 +59,7 @@ module Kramdown
|
|
57
59
|
warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
|
58
60
|
end
|
59
61
|
else
|
60
|
-
parse_raw_html(el,
|
62
|
+
parse_raw_html(el) {|iel, ic, ih| handle_kramdown_html_tag(iel, ic, ih) }
|
61
63
|
end
|
62
64
|
unless @tree.type == :html_element && @tree.options[:content_model] == :raw
|
63
65
|
@src.scan(TRAILING_WHITESPACE)
|
@@ -74,35 +76,35 @@ module Kramdown
|
|
74
76
|
@tree.children << Element.new(:xml_comment, result, nil, category: :block, location: line)
|
75
77
|
@src.scan(TRAILING_WHITESPACE)
|
76
78
|
true
|
77
|
-
|
78
|
-
|
79
|
+
elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
|
80
|
+
@src.pos += @src.matched_size
|
81
|
+
handle_html_start_tag(line) {|iel, ic, ih| handle_kramdown_html_tag(iel, ic, ih) }
|
82
|
+
Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native]
|
83
|
+
true
|
84
|
+
elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
|
85
|
+
name = @src[1].downcase
|
86
|
+
|
87
|
+
if @tree.type == :html_element && @tree.value == name
|
79
88
|
@src.pos += @src.matched_size
|
80
|
-
|
81
|
-
Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native]
|
82
|
-
true
|
83
|
-
elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
|
84
|
-
name = @src[1].downcase
|
85
|
-
|
86
|
-
if @tree.type == :html_element && @tree.value == name
|
87
|
-
@src.pos += @src.matched_size
|
88
|
-
throw :stop_block_parsing, :found
|
89
|
-
else
|
90
|
-
false
|
91
|
-
end
|
89
|
+
throw :stop_block_parsing, :found
|
92
90
|
else
|
93
91
|
false
|
94
92
|
end
|
93
|
+
else
|
94
|
+
false
|
95
95
|
end
|
96
96
|
end
|
97
97
|
define_parser(:block_html, HTML_BLOCK_START)
|
98
98
|
|
99
|
-
HTML_SPAN_START = /<(#{REXML::Parsers::BaseParser::UNAME_STR}
|
99
|
+
HTML_SPAN_START = /<(#{REXML::Parsers::BaseParser::UNAME_STR}|!--|\/|!\[CDATA\[)/
|
100
100
|
|
101
101
|
# Parse the HTML at the current position as span-level HTML.
|
102
102
|
def parse_span_html
|
103
103
|
line = @src.current_line_number
|
104
104
|
if (result = @src.scan(HTML_COMMENT_RE))
|
105
105
|
@tree.children << Element.new(:xml_comment, result, nil, category: :span, location: line)
|
106
|
+
elsif @src.scan(HTML_CDATA_RE)
|
107
|
+
add_text(escape_html(@src[1]))
|
106
108
|
elsif (result = @src.scan(HTML_TAG_CLOSE_RE))
|
107
109
|
warning("Found invalidly used HTML closing tag for '#{@src[1]}' on line #{line}")
|
108
110
|
add_text(result)
|
@@ -124,20 +126,21 @@ module Kramdown
|
|
124
126
|
@options[:parse_span_html]
|
125
127
|
end
|
126
128
|
if (val = HTML_MARKDOWN_ATTR_MAP[attrs.delete('markdown')])
|
127
|
-
|
129
|
+
case val
|
130
|
+
when :block
|
128
131
|
warning("Cannot use block-level parsing in span-level HTML tag (line #{line}) " \
|
129
132
|
"- using default mode")
|
130
|
-
|
133
|
+
when :span
|
131
134
|
do_parsing = true
|
132
|
-
|
135
|
+
when :default
|
133
136
|
do_parsing = HTML_CONTENT_MODEL[tag_name] != :raw
|
134
|
-
|
137
|
+
when :raw
|
135
138
|
do_parsing = false
|
136
139
|
end
|
137
140
|
end
|
138
141
|
|
139
142
|
el = Element.new(:html_element, tag_name, attrs, category: :span, location: line,
|
140
|
-
content_model: (do_parsing ? :span : :raw), is_closed:
|
143
|
+
content_model: (do_parsing ? :span : :raw), is_closed: !@src[4].nil?)
|
141
144
|
@tree.children << el
|
142
145
|
stop_re = /<\/#{Regexp.escape(tag_name)}\s*>/
|
143
146
|
stop_re = Regexp.new(stop_re.source, Regexp::IGNORECASE) if HTML_ELEMENT[tag_name]
|
@@ -18,7 +18,7 @@ module Kramdown
|
|
18
18
|
start_line_number = @src.current_line_number
|
19
19
|
@src.pos += @src.matched_size
|
20
20
|
begin
|
21
|
-
value = ::Kramdown::Utils::Entities.entity(@src[1] ||
|
21
|
+
value = ::Kramdown::Utils::Entities.entity(@src[1] || @src[2]&.to_i || @src[3].hex)
|
22
22
|
@tree.children << Element.new(:entity, value,
|
23
23
|
nil, original: @src.matched, location: start_line_number)
|
24
24
|
rescue ::Kramdown::Error
|
@@ -15,14 +15,14 @@ module Kramdown
|
|
15
15
|
|
16
16
|
# Normalize the link identifier.
|
17
17
|
def normalize_link_id(id)
|
18
|
-
id.gsub(
|
18
|
+
id.gsub(/\s+/, ' ').downcase
|
19
19
|
end
|
20
20
|
|
21
21
|
LINK_DEFINITION_START = /^#{OPT_SPACE}\[([^\n\]]+)\]:[ \t]*(?:<(.*?)>|([^\n]*?\S[^\n]*?))(?:(?:[ \t]*?\n|[ \t]+?)[ \t]*?(["'])(.+?)\4)?[ \t]*?\n/
|
22
22
|
|
23
23
|
# Parse the link definition at the current location.
|
24
24
|
def parse_link_definition
|
25
|
-
return false if @src[3].to_s
|
25
|
+
return false if @src[3].to_s.match?(/[ \t]+["']/)
|
26
26
|
@src.pos += @src.matched_size
|
27
27
|
link_id, link_url, link_title = normalize_link_id(@src[1]), @src[2] || @src[3], @src[5]
|
28
28
|
if @link_defs[link_id]
|
@@ -64,7 +64,7 @@ module Kramdown
|
|
64
64
|
cur_pos = @src.pos
|
65
65
|
saved_pos = @src.save_pos
|
66
66
|
|
67
|
-
link_type = (result
|
67
|
+
link_type = (result.match?(/^!/) ? :img : :a)
|
68
68
|
|
69
69
|
# no nested links allowed
|
70
70
|
if link_type == :a && (@tree.type == :img || @tree.type == :a ||
|
@@ -77,7 +77,7 @@ module Kramdown
|
|
77
77
|
count = 1
|
78
78
|
found = parse_spans(el, LINK_BRACKET_STOP_RE) do
|
79
79
|
count += (@src[1] ? -1 : 1)
|
80
|
-
count - el.children.
|
80
|
+
count - el.children.count {|c| c.type == :img } == 0
|
81
81
|
end
|
82
82
|
unless found
|
83
83
|
@src.revert_pos(saved_pos)
|
@@ -30,10 +30,10 @@ module Kramdown
|
|
30
30
|
# Used for parsing the first line of a list item or a definition, i.e. the line with list item
|
31
31
|
# marker or the definition marker.
|
32
32
|
def parse_first_list_line(indentation, content)
|
33
|
-
if content
|
33
|
+
if content.match?(self.class::LIST_ITEM_IAL_CHECK)
|
34
34
|
indentation = 4
|
35
35
|
else
|
36
|
-
while content
|
36
|
+
while content.match?(/^ *\t/)
|
37
37
|
temp = content.scan(/^ */).first.length + indentation
|
38
38
|
content.sub!(/^( *)(\t+)/) { $1 << " " * (4 - (temp % 4) + ($2.length - 1) * 4) }
|
39
39
|
end
|
@@ -167,7 +167,7 @@ module Kramdown
|
|
167
167
|
end
|
168
168
|
# take location from preceding para which is the first definition term
|
169
169
|
deflist.options[:location] = para.options[:location]
|
170
|
-
para.children.first.value.split(
|
170
|
+
para.children.first.value.split("\n").each do |term|
|
171
171
|
el = Element.new(:dt, nil, nil, location: @src.current_line_number)
|
172
172
|
term.sub!(self.class::LIST_ITEM_IAL) do
|
173
173
|
parse_attribute_list($1, el.options[:ial] ||= {})
|
@@ -256,26 +256,27 @@ module Kramdown
|
|
256
256
|
# precomputed patterns for indentations 1..4 and fallback expression
|
257
257
|
# to compute pattern when indentation is outside the 1..4 range.
|
258
258
|
def fetch_pattern(type, indentation)
|
259
|
-
|
259
|
+
case type
|
260
|
+
when :ul
|
260
261
|
case indentation
|
261
|
-
when 1 then
|
262
|
-
when 2 then
|
263
|
-
when 3 then
|
264
|
-
else
|
262
|
+
when 1 then /^( {0}[+*-])(#{PATTERN_TAIL})/o
|
263
|
+
when 2 then /^( {0,1}[+*-])(#{PATTERN_TAIL})/o
|
264
|
+
when 3 then /^( {0,2}[+*-])(#{PATTERN_TAIL})/o
|
265
|
+
else /^( {0,3}[+*-])(#{PATTERN_TAIL})/o
|
265
266
|
end
|
266
|
-
|
267
|
+
when :ol
|
267
268
|
case indentation
|
268
|
-
when 1 then
|
269
|
-
when 2 then
|
270
|
-
when 3 then
|
271
|
-
else
|
269
|
+
when 1 then /^( {0}\d+\.)(#{PATTERN_TAIL})/o
|
270
|
+
when 2 then /^( {0,1}\d+\.)(#{PATTERN_TAIL})/o
|
271
|
+
when 3 then /^( {0,2}\d+\.)(#{PATTERN_TAIL})/o
|
272
|
+
else /^( {0,3}\d+\.)(#{PATTERN_TAIL})/o
|
272
273
|
end
|
273
|
-
|
274
|
+
when :dl
|
274
275
|
case indentation
|
275
|
-
when 1 then
|
276
|
-
when 2 then
|
277
|
-
when 3 then
|
278
|
-
else
|
276
|
+
when 1 then /^( {0}:)(#{PATTERN_TAIL})/o
|
277
|
+
when 2 then /^( {0,1}:)(#{PATTERN_TAIL})/o
|
278
|
+
when 3 then /^( {0,2}:)(#{PATTERN_TAIL})/o
|
279
|
+
else /^( {0,3}:)(#{PATTERN_TAIL})/o
|
279
280
|
end
|
280
281
|
end
|
281
282
|
end
|
@@ -80,7 +80,7 @@ module Kramdown
|
|
80
80
|
:smart_quotes, :inline_math, :span_extensions, :html_entity,
|
81
81
|
:typographic_syms, :line_break, :escaped_chars]
|
82
82
|
|
83
|
-
@span_pattern_cache ||= Hash.new {
|
83
|
+
@span_pattern_cache ||= Hash.new {|h, k| h[k] = {} }
|
84
84
|
end
|
85
85
|
private_class_method(:new, :allocate)
|
86
86
|
|
@@ -166,16 +166,17 @@ module Kramdown
|
|
166
166
|
def update_tree(element)
|
167
167
|
last_blank = nil
|
168
168
|
element.children.map! do |child|
|
169
|
-
|
169
|
+
case child.type
|
170
|
+
when :raw_text
|
170
171
|
last_blank = nil
|
171
172
|
reset_env(src: ::Kramdown::Utils::StringScanner.new(child.value, element.options[:location]),
|
172
173
|
text_type: :text)
|
173
174
|
parse_spans(child)
|
174
175
|
child.children
|
175
|
-
|
176
|
+
when :eob
|
176
177
|
update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
|
177
178
|
[]
|
178
|
-
|
179
|
+
when :blank
|
179
180
|
if last_blank
|
180
181
|
last_blank.value << child.value
|
181
182
|
[]
|