kramdown 2.4.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[]
|