kramdown 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTERS +10 -1
  3. data/VERSION +1 -1
  4. data/bin/kramdown +15 -12
  5. data/lib/kramdown/converter/base.rb +2 -2
  6. data/lib/kramdown/converter/html.rb +22 -14
  7. data/lib/kramdown/converter/kramdown.rb +25 -20
  8. data/lib/kramdown/converter/latex.rb +11 -10
  9. data/lib/kramdown/converter/man.rb +3 -3
  10. data/lib/kramdown/converter/math_engine/mathjax.rb +1 -1
  11. data/lib/kramdown/converter/remove_html_tags.rb +5 -4
  12. data/lib/kramdown/document.rb +1 -1
  13. data/lib/kramdown/element.rb +1 -1
  14. data/lib/kramdown/options.rb +43 -15
  15. data/lib/kramdown/parser/base.rb +6 -0
  16. data/lib/kramdown/parser/html.rb +24 -18
  17. data/lib/kramdown/parser/kramdown/abbreviation.rb +4 -2
  18. data/lib/kramdown/parser/kramdown/codespan.rb +1 -1
  19. data/lib/kramdown/parser/kramdown/emphasis.rb +6 -1
  20. data/lib/kramdown/parser/kramdown/escaped_chars.rb +1 -1
  21. data/lib/kramdown/parser/kramdown/extensions.rb +6 -6
  22. data/lib/kramdown/parser/kramdown/html.rb +26 -23
  23. data/lib/kramdown/parser/kramdown/html_entity.rb +1 -1
  24. data/lib/kramdown/parser/kramdown/link.rb +4 -4
  25. data/lib/kramdown/parser/kramdown/list.rb +19 -18
  26. data/lib/kramdown/parser/kramdown/smart_quotes.rb +1 -1
  27. data/lib/kramdown/parser/kramdown.rb +5 -4
  28. data/lib/kramdown/utils/entities.rb +661 -5
  29. data/lib/kramdown/utils/html.rb +1 -1
  30. data/lib/kramdown/utils/unidecoder.rb +9 -13
  31. data/lib/kramdown/version.rb +1 -1
  32. data/man/man1/kramdown.1 +18 -0
  33. data/test/test_files.rb +8 -0
  34. data/test/testcases/block/04_header/with_header_links.html +7 -0
  35. data/test/testcases/block/04_header/with_header_links.options +2 -0
  36. data/test/testcases/block/04_header/with_header_links.text +8 -0
  37. data/test/testcases/block/04_header/with_line_break.html +1 -0
  38. data/test/testcases/block/04_header/with_line_break.text +1 -0
  39. data/test/testcases/block/08_list/escaping.html +4 -0
  40. data/test/testcases/block/08_list/escaping.text +4 -0
  41. data/test/testcases/block/08_list/nested_compact.kramdown +7 -0
  42. data/test/testcases/block/08_list/nested_compact.text +6 -0
  43. data/test/testcases/block/08_list/special_cases.html +10 -0
  44. data/test/testcases/block/08_list/special_cases.text +9 -0
  45. data/test/testcases/block/09_html/cdata_section.html +10 -0
  46. data/test/testcases/block/09_html/cdata_section.text +10 -0
  47. data/test/testcases/block/09_html/html_to_native/table_simple.html +3 -0
  48. data/test/testcases/block/09_html/html_to_native/table_simple.text +3 -0
  49. data/test/testcases/block/12_extension/options.html +2 -2
  50. data/test/testcases/block/12_extension/options2.html +2 -2
  51. data/test/testcases/block/14_table/table_with_footnote.html +2 -2
  52. data/test/testcases/block/16_toc/toc_with_footnotes.html +2 -2
  53. data/test/testcases/span/02_emphasis/normal.html +6 -1
  54. data/test/testcases/span/02_emphasis/normal.text +5 -0
  55. data/test/testcases/span/04_footnote/backlink_inline.html +10 -10
  56. data/test/testcases/span/04_footnote/backlink_text.html +2 -2
  57. data/test/testcases/span/04_footnote/footnote_link_text.html +12 -0
  58. data/test/testcases/span/04_footnote/footnote_link_text.options +1 -0
  59. data/test/testcases/span/04_footnote/footnote_link_text.text +4 -0
  60. data/test/testcases/span/04_footnote/footnote_nr.html +3 -3
  61. data/test/testcases/span/04_footnote/footnote_prefix.html +3 -3
  62. data/test/testcases/span/04_footnote/inside_footnote.html +6 -6
  63. data/test/testcases/span/04_footnote/markers.html +10 -10
  64. data/test/testcases/span/04_footnote/placement.html +2 -2
  65. data/test/testcases/span/04_footnote/regexp_problem.html +2 -2
  66. data/test/testcases/span/04_footnote/without_backlink.html +2 -2
  67. data/test/testcases/span/abbreviations/abbrev.html +4 -0
  68. data/test/testcases/span/abbreviations/abbrev.text +7 -0
  69. data/test/testcases/span/abbreviations/in_footnote.html +2 -2
  70. metadata +21 -9
@@ -39,7 +39,7 @@ module Kramdown
39
39
  ALLOWED_TYPES = [String, Integer, Float, Symbol, Boolean, Object]
40
40
 
41
41
  @options = {}
42
- @cached_defaults = nil
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
- @cached_defaults = nil
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
- @cached_defaults ||= begin
74
- temp = {}
75
- @options.each {|_n, o| temp[o.name] = o.default }
76
- temp.freeze
77
- end
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 =~ /^\d(?:,\d)*$/
354
- val = val.split(/,/).map(&:to_i).uniq
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
- val = TOC_LEVELS_ARRAY
365
- else
366
- val = val.map(&:to_i).uniq
367
- end
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
 
@@ -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
@@ -24,15 +24,16 @@ module Kramdown
24
24
  # Contains all constants that are used when parsing.
25
25
  module Constants
26
26
 
27
- #:stopdoc:
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:][\-\w\.:]*);|&#(\d+);|&\#x([0-9a-fA-F]+);/
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}|\/|!--|\?))/ # :nodoc:
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] || (src[2]&.to_i) || src[3].hex
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 || ((el.children[i - 1]).block? &&
365
- (el.children[i + 1]).block?))
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
- if c.type == :text
427
+ case c.type
428
+ when :text
424
429
  mem << c.value
425
- elsif c.type == :entity
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
- elsif c.type == :smart_quote || c.type == :typographic_sym
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.select {|cc| cc.value == 'th' || cc.value == 'td' }.length
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? {|r| (r.value == 'tr' || r.type == :text) && r.children.all? {|c| c.value == type || c.type == :text }}
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 !is_math_tag?(el)
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'] =~ /mode=display/ ? :block : :span))
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 {|k| /#{Regexp.escape(k)}/ })]
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 =~ regexps.first
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
@@ -43,7 +43,7 @@ module Kramdown
43
43
  end
44
44
  @tree.children << Element.new(:codespan, text, nil, {
45
45
  codespan_delimiter: result,
46
- location: start_line_number
46
+ location: start_line_number,
47
47
  })
48
48
 
49
49
  else
@@ -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 !handle_extension(ext, opts, body, type, start_line_number)
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|[^\}\2])*?)\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
- if content_model == :block
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
- elsif content_model == :span
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, &method(:handle_kramdown_html_tag))
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
- else
78
- if @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
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
- handle_html_start_tag(line, &method(:handle_kramdown_html_tag))
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
- if val == :block
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
- elsif val == :span
133
+ when :span
131
134
  do_parsing = true
132
- elsif val == :default
135
+ when :default
133
136
  do_parsing = HTML_CONTENT_MODEL[tag_name] != :raw
134
- elsif val == :raw
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: !!@src[4])
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] || (@src[2]&.to_i) || @src[3].hex)
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(/[\s]+/, ' ').downcase
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 =~ /[ \t]+["']/
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 =~ /^!/ ? :img : :a)
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.select {|c| c.type == :img }.size == 0
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 =~ self.class::LIST_ITEM_IAL_CHECK
33
+ if content.match?(self.class::LIST_ITEM_IAL_CHECK)
34
34
  indentation = 4
35
35
  else
36
- while content =~ /^ *\t/
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(/\n/).each do |term|
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
- if type == :ul
259
+ case type
260
+ when :ul
260
261
  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
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
- elsif type == :ol
267
+ when :ol
267
268
  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
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
- elsif type == :dl
274
+ when :dl
274
275
  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
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
@@ -119,7 +119,7 @@ module Kramdown
119
119
  class Kramdown
120
120
 
121
121
  SQ_PUNCT = '[!"#\$\%\'()*+,\-.\/:;<=>?\@\[\\\\\]\^_`{|}~]'
122
- SQ_CLOSE = %![^\ \\\\\t\r\n\\[{(-]!
122
+ SQ_CLOSE = %![^ \\\\\t\r\n\\[{(-]!
123
123
 
124
124
  SQ_RULES = [
125
125
  [/("|')(?=[_*]{1,2}\S)/, [:lquote1]],
@@ -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 { |h, k| h[k] = {} }
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
- if child.type == :raw_text
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
- elsif child.type == :eob
176
+ when :eob
176
177
  update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
177
178
  []
178
- elsif child.type == :blank
179
+ when :blank
179
180
  if last_blank
180
181
  last_blank.value << child.value
181
182
  []