kramdown 2.0.0.beta2 → 2.3.0

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

Potentially problematic release.


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

Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTERS +14 -3
  3. data/VERSION +1 -1
  4. data/bin/kramdown +48 -19
  5. data/lib/kramdown/converter/base.rb +2 -1
  6. data/lib/kramdown/converter/html.rb +47 -25
  7. data/lib/kramdown/converter/kramdown.rb +11 -6
  8. data/lib/kramdown/converter/latex.rb +1 -1
  9. data/lib/kramdown/converter/math_engine/mathjax.rb +7 -33
  10. data/lib/kramdown/converter/syntax_highlighter.rb +1 -1
  11. data/lib/kramdown/converter/syntax_highlighter/rouge.rb +20 -9
  12. data/lib/kramdown/element.rb +30 -3
  13. data/lib/kramdown/options.rb +57 -8
  14. data/lib/kramdown/parser/base.rb +3 -1
  15. data/lib/kramdown/parser/html.rb +8 -8
  16. data/lib/kramdown/parser/kramdown.rb +11 -2
  17. data/lib/kramdown/parser/kramdown/autolink.rb +2 -2
  18. data/lib/kramdown/parser/kramdown/blank_line.rb +2 -2
  19. data/lib/kramdown/parser/kramdown/block_boundary.rb +3 -2
  20. data/lib/kramdown/parser/kramdown/codespan.rb +12 -2
  21. data/lib/kramdown/parser/kramdown/emphasis.rb +1 -1
  22. data/lib/kramdown/parser/kramdown/extensions.rb +14 -8
  23. data/lib/kramdown/parser/kramdown/header.rb +3 -2
  24. data/lib/kramdown/parser/kramdown/html.rb +4 -10
  25. data/lib/kramdown/parser/kramdown/link.rb +3 -2
  26. data/lib/kramdown/parser/kramdown/list.rb +64 -33
  27. data/lib/kramdown/parser/kramdown/math.rb +1 -1
  28. data/lib/kramdown/parser/kramdown/paragraph.rb +3 -3
  29. data/lib/kramdown/parser/kramdown/table.rb +3 -3
  30. data/lib/kramdown/utils/html.rb +9 -0
  31. data/lib/kramdown/version.rb +1 -1
  32. data/man/man1/kramdown.1 +31 -1
  33. data/test/test_files.rb +18 -11
  34. data/test/test_location.rb +2 -2
  35. data/test/test_string_scanner_kramdown.rb +1 -1
  36. data/test/testcases/block/03_paragraph/standalone_image.html +8 -0
  37. data/test/testcases/block/03_paragraph/standalone_image.text +6 -0
  38. data/test/testcases/block/04_header/atx_header.html +6 -0
  39. data/test/testcases/block/04_header/atx_header.text +6 -0
  40. data/test/testcases/block/06_codeblock/guess_lang_css_class.html +15 -0
  41. data/test/testcases/block/06_codeblock/guess_lang_css_class.options +2 -0
  42. data/test/testcases/block/06_codeblock/guess_lang_css_class.text +13 -0
  43. data/test/testcases/block/09_html/processing_instruction.html +5 -6
  44. data/test/testcases/block/09_html/standalone_image_in_div.htmlinput +7 -0
  45. data/test/testcases/block/09_html/standalone_image_in_div.text +8 -0
  46. data/test/testcases/block/12_extension/options.html +4 -4
  47. data/test/testcases/block/12_extension/options.text +2 -0
  48. data/test/testcases/block/12_extension/options2.html +4 -4
  49. data/test/testcases/block/14_table/table_with_footnote.html +4 -4
  50. data/test/testcases/block/15_math/gh_128.html +1 -2
  51. data/test/testcases/block/15_math/normal.html +16 -15
  52. data/test/testcases/block/16_toc/toc_with_footnotes.html +4 -4
  53. data/test/testcases/cjk-line-break.html +4 -0
  54. data/test/testcases/cjk-line-break.options +1 -0
  55. data/test/testcases/cjk-line-break.text +12 -0
  56. data/test/testcases/span/02_emphasis/normal.html +4 -0
  57. data/test/testcases/span/02_emphasis/normal.text +4 -0
  58. data/test/testcases/span/03_codespan/normal-css-class.html +1 -0
  59. data/test/testcases/span/03_codespan/normal-css-class.options +2 -0
  60. data/test/testcases/span/03_codespan/normal-css-class.text +1 -0
  61. data/test/testcases/span/04_footnote/backlink_inline.html +21 -21
  62. data/test/testcases/span/04_footnote/backlink_text.html +4 -4
  63. data/test/testcases/span/04_footnote/footnote_nr.html +6 -6
  64. data/test/testcases/span/04_footnote/footnote_prefix.html +12 -0
  65. data/test/testcases/span/04_footnote/footnote_prefix.options +1 -0
  66. data/test/testcases/span/04_footnote/footnote_prefix.text +4 -0
  67. data/test/testcases/span/04_footnote/inside_footnote.html +9 -9
  68. data/test/testcases/span/04_footnote/markers.html +16 -16
  69. data/test/testcases/span/04_footnote/placement.html +4 -4
  70. data/test/testcases/span/04_footnote/regexp_problem.html +4 -4
  71. data/test/testcases/span/04_footnote/without_backlink.html +3 -3
  72. data/test/testcases/span/05_html/normal.html +1 -1
  73. data/test/testcases/span/abbreviations/in_footnote.html +4 -4
  74. data/test/testcases/span/math/normal.html +4 -4
  75. metadata +35 -15
  76. data/test/testcases/block/15_math/mathjax_preview.html +0 -4
  77. data/test/testcases/block/15_math/mathjax_preview.options +0 -2
  78. data/test/testcases/block/15_math/mathjax_preview.text +0 -5
  79. data/test/testcases/block/15_math/mathjax_preview_as_code.html +0 -4
  80. data/test/testcases/block/15_math/mathjax_preview_as_code.options +0 -3
  81. data/test/testcases/block/15_math/mathjax_preview_as_code.text +0 -5
  82. data/test/testcases/block/15_math/mathjax_preview_simple.html +0 -4
  83. data/test/testcases/block/15_math/mathjax_preview_simple.options +0 -2
  84. data/test/testcases/block/15_math/mathjax_preview_simple.text +0 -5
@@ -42,7 +42,7 @@ module Kramdown
42
42
  #
43
43
  # == Special Implementation Details
44
44
  #
45
- # HTML converter:: If the syntax highlighter is used with a HTML converter, it should return
45
+ # HTML converter:: If the syntax highlighter is used with an HTML converter, it should return
46
46
  # :block type text correctly wrapped (i.e. normally inside a pre-tag, but may
47
47
  # also be a table-tag or just a div-tag) but :span type text *without* a
48
48
  # code-tag!
@@ -24,8 +24,11 @@ module Kramdown::Converter::SyntaxHighlighter
24
24
  def self.call(converter, text, lang, type, call_opts)
25
25
  opts = options(converter, type)
26
26
  call_opts[:default_lang] = opts[:default_lang]
27
+ return nil unless lang || opts[:default_lang] || opts[:guess_lang]
28
+
27
29
  lexer = ::Rouge::Lexer.find_fancy(lang || opts[:default_lang], text)
28
- return nil if opts[:disable] || !lexer || lexer.tag == "plaintext"
30
+ return nil if opts[:disable] || !lexer || (lexer.tag == "plaintext" && !opts[:guess_lang])
31
+
29
32
  opts[:css_class] ||= 'highlight' # For backward compatibility when using Rouge 2.0
30
33
  formatter = formatter_class(opts).new(opts)
31
34
  formatter.format(lexer.lex(text))
@@ -42,18 +45,26 @@ module Kramdown::Converter::SyntaxHighlighter
42
45
  cache = converter.data[:syntax_highlighter_rouge] = {}
43
46
 
44
47
  opts = converter.options[:syntax_highlighter_opts].dup
45
- span_opts = (opts.delete(:span) || {}).dup
46
- block_opts = (opts.delete(:block) || {}).dup
47
- [span_opts, block_opts].each do |hash|
48
- hash.keys.each do |k|
49
- hash[k.kind_of?(String) ? Kramdown::Options.str_to_sym(k) : k] = hash.delete(k)
50
- end
51
- end
52
48
 
53
- cache[:span] = opts.merge(span_opts).update(wrap: false)
49
+ span_opts = opts.delete(:span)&.dup || {}
50
+ block_opts = opts.delete(:block)&.dup || {}
51
+ normalize_keys(span_opts)
52
+ normalize_keys(block_opts)
53
+
54
+ cache[:span] = opts.merge(span_opts)
55
+ cache[:span][:wrap] = false
56
+
54
57
  cache[:block] = opts.merge(block_opts)
55
58
  end
56
59
 
60
+ def self.normalize_keys(hash)
61
+ return if hash.empty?
62
+
63
+ hash.keys.each do |k|
64
+ hash[k.kind_of?(String) ? Kramdown::Options.str_to_sym(k) : k] = hash.delete(k)
65
+ end
66
+ end
67
+
57
68
  def self.formatter_class(opts = {})
58
69
  case formatter = opts[:formatter]
59
70
  when Class
@@ -14,6 +14,14 @@ module Kramdown
14
14
  # kramdown only uses this one class for representing all available elements in an element tree
15
15
  # (paragraphs, headers, emphasis, ...). The type of element can be set via the #type accessor.
16
16
  #
17
+ # The root of a kramdown element tree has to be an element of type :root. It needs to have certain
18
+ # option keys set so that conversions work correctly. If only a part of a tree should be
19
+ # converted, duplicate the root node and assign the #children appropriately, e.g:
20
+ #
21
+ # root = doc.root
22
+ # new_root = root.dup
23
+ # new_root.children = [root.children[0]] # assign new array with elements to convert
24
+ #
17
25
  # Following is a description of all supported element types.
18
26
  #
19
27
  # Note that the option :location may contain the start line number of an element in the source
@@ -42,6 +50,7 @@ module Kramdown
42
50
  #
43
51
  # :options:: This key may be used to store options that were set during parsing of the document.
44
52
  #
53
+ # :footnote_count:: This key stores the number of actually referenced footnotes of the document.
45
54
  #
46
55
  # === :blank
47
56
  #
@@ -500,9 +509,11 @@ module Kramdown
500
509
  end
501
510
 
502
511
  def inspect #:nodoc:
503
- "<kd:#{@type}#{@value.nil? ? '' : ' ' + @value.inspect} " \
504
- "#{@attr.inspect}#{options.empty? ? '' : ' ' + @options.inspect} " \
505
- "#{@children.empty? ? '' : ' ' + @children.inspect}>"
512
+ "<kd:#{@type}" \
513
+ "#{value.nil? ? '' : ' value=' + value.inspect}" \
514
+ "#{attr.empty? ? '' : ' attr=' + attr.inspect}" \
515
+ "#{options.empty? ? '' : ' options=' + options.inspect}" \
516
+ "#{children.empty? ? '' : ' children=' + children.inspect}>"
506
517
  end
507
518
 
508
519
  CATEGORY = {} # :nodoc:
@@ -519,6 +530,22 @@ module Kramdown
519
530
  CATEGORY[el.type] || el.options[:category]
520
531
  end
521
532
 
533
+ # syntactic sugar to simplify calls such as +Kramdown::Element.category(el) == :block+ with
534
+ # +el.block?+.
535
+ #
536
+ # Returns boolean true or false.
537
+ def block?
538
+ (CATEGORY[type] || options[:category]) == :block
539
+ end
540
+
541
+ # syntactic sugar to simplify calls such as +Kramdown::Element.category(el) == :span+ with
542
+ # +el.span?+.
543
+ #
544
+ # Returns boolean true or false.
545
+ def span?
546
+ (CATEGORY[type] || options[:category]) == :span
547
+ end
548
+
522
549
  end
523
550
 
524
551
  end
@@ -328,7 +328,11 @@ module Kramdown
328
328
  Used by: HTML converter, kramdown converter
329
329
  EOF
330
330
 
331
- define(:toc_levels, Object, (1..6).to_a, <<~EOF) do |val|
331
+ TOC_LEVELS_RANGE = (1..6).freeze
332
+ TOC_LEVELS_ARRAY = TOC_LEVELS_RANGE.to_a.freeze
333
+ private_constant :TOC_LEVELS_RANGE, :TOC_LEVELS_ARRAY
334
+
335
+ define(:toc_levels, Object, TOC_LEVELS_ARRAY, <<~EOF) do |val|
332
336
  Defines the levels that are used for the table of contents
333
337
 
334
338
  The individual levels can be specified by separating them with commas
@@ -347,12 +351,20 @@ module Kramdown
347
351
  else
348
352
  raise Kramdown::Error, "Invalid syntax for option toc_levels"
349
353
  end
350
- when Array, Range
351
- val = val.map(&:to_i).uniq
354
+ when Array
355
+ unless val.eql?(TOC_LEVELS_ARRAY)
356
+ val = val.map(&:to_i).uniq
357
+ end
358
+ when Range
359
+ if val.eql?(TOC_LEVELS_RANGE)
360
+ val = TOC_LEVELS_ARRAY
361
+ else
362
+ val = val.map(&:to_i).uniq
363
+ end
352
364
  else
353
365
  raise Kramdown::Error, "Invalid type #{val.class} for option toc_levels"
354
366
  end
355
- if val.any? {|i| !(1..6).cover?(i) }
367
+ if val.any? {|i| !TOC_LEVELS_RANGE.cover?(i) }
356
368
  raise Kramdown::Error, "Level numbers for option toc_levels have to be integers from 1 to 6"
357
369
  end
358
370
  val
@@ -377,7 +389,11 @@ module Kramdown
377
389
  simple_array_validator(val, :latex_headers, 6)
378
390
  end
379
391
 
380
- define(:smart_quotes, Object, %w[lsquo rsquo ldquo rdquo], <<~EOF) do |val|
392
+ SMART_QUOTES_ENTITIES = %w[lsquo rsquo ldquo rdquo].freeze
393
+ SMART_QUOTES_STR = SMART_QUOTES_ENTITIES.join(',').freeze
394
+ private_constant :SMART_QUOTES_ENTITIES, :SMART_QUOTES_STR
395
+
396
+ define(:smart_quotes, Object, SMART_QUOTES_ENTITIES, <<~EOF) do |val|
381
397
  Defines the HTML entity names or code points for smart quote output
382
398
 
383
399
  The entities identified by entity name or code point that should be
@@ -388,9 +404,13 @@ module Kramdown
388
404
  Default: lsquo,rsquo,ldquo,rdquo
389
405
  Used by: HTML/Latex converter
390
406
  EOF
391
- val = simple_array_validator(val, :smart_quotes, 4)
392
- val.map! {|v| Integer(v) rescue v }
393
- val
407
+ if val == SMART_QUOTES_STR || val == SMART_QUOTES_ENTITIES
408
+ SMART_QUOTES_ENTITIES
409
+ else
410
+ val = simple_array_validator(val, :smart_quotes, 4)
411
+ val.map! {|v| Integer(v) rescue v }
412
+ val
413
+ end
394
414
  end
395
415
 
396
416
  define(:typographic_symbols, Object, {}, <<~EOF) do |val|
@@ -550,6 +570,35 @@ module Kramdown
550
570
  Used by: HTML converter
551
571
  EOF
552
572
 
573
+ define(:footnote_prefix, String, '', <<~EOF)
574
+ Prefix used for footnote IDs
575
+
576
+ This option can be used to set a prefix for footnote IDs. This is useful
577
+ when rendering multiple documents into the same output file to avoid
578
+ duplicate IDs. The prefix should only contain characters that are valid
579
+ in an ID!
580
+
581
+ Default: ''
582
+ Used by: HTML
583
+ EOF
584
+
585
+ define(:remove_line_breaks_for_cjk, Boolean, false, <<~EOF)
586
+ Specifies whether line breaks should be removed between CJK characters
587
+
588
+ Default: false
589
+ Used by: HTML converter
590
+ EOF
591
+
592
+ define(:forbidden_inline_options, Object, %w[template], <<~EOF) do |val|
593
+ Defines the options that may not be set using the {::options} extension
594
+
595
+ Default: template
596
+ Used by: HTML converter
597
+ EOF
598
+ val.map! {|item| item.kind_of?(String) ? str_to_sym(item) : item }
599
+ simple_array_validator(val, :forbidden_inline_options)
600
+ end
601
+
553
602
  end
554
603
 
555
604
  end
@@ -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").chomp + "\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
@@ -16,7 +16,7 @@ module Kramdown
16
16
 
17
17
  module Parser
18
18
 
19
- # Used for parsing a HTML document.
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
@@ -286,7 +286,7 @@ module Kramdown
286
286
  src = Kramdown::Utils::StringScanner.new(raw)
287
287
  result = []
288
288
  until src.eos?
289
- if (tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/))
289
+ if (tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/o))
290
290
  result << Element.new(:text, tmp)
291
291
  src.scan(HTML_ENTITY_RE)
292
292
  val = src[1] || (src[2]&.to_i) || src[3].hex
@@ -324,7 +324,7 @@ module Kramdown
324
324
  tmp = []
325
325
  last_is_p = false
326
326
  el.children.each do |c|
327
- if Element.category(c) != :block || c.type == :text
327
+ if !c.block? || c.type == :text
328
328
  unless last_is_p
329
329
  tmp << Element.new(:p, nil, nil, transparent: true)
330
330
  last_is_p = true
@@ -354,8 +354,8 @@ module Kramdown
354
354
  el.children = el.children.reject do |c|
355
355
  i += 1
356
356
  c.type == :text && c.value.strip.empty? &&
357
- (i == 0 || i == el.children.length - 1 || (Element.category(el.children[i - 1]) == :block &&
358
- Element.category(el.children[i + 1]) == :block))
357
+ (i == 0 || i == el.children.length - 1 || ((el.children[i - 1]).block? &&
358
+ (el.children[i + 1]).block?))
359
359
  end
360
360
  end
361
361
 
@@ -581,11 +581,11 @@ module Kramdown
581
581
  @src = Kramdown::Utils::StringScanner.new(adapt_source(source))
582
582
 
583
583
  while true
584
- if (result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/))
584
+ if (result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/o))
585
585
  @tree.children << Element.new(:xml_pi, result.strip, nil, category: :block)
586
- elsif (result = @src.scan(/\s*#{HTML_DOCTYPE_RE}/))
586
+ elsif (result = @src.scan(/\s*#{HTML_DOCTYPE_RE}/o))
587
587
  # ignore the doctype
588
- elsif (result = @src.scan(/\s*#{HTML_COMMENT_RE}/))
588
+ elsif (result = @src.scan(/\s*#{HTML_COMMENT_RE}/o))
589
589
  @tree.children << Element.new(:xml_comment, result.strip, nil, category: :block)
590
590
  else
591
591
  break
@@ -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
 
@@ -93,11 +95,13 @@ module Kramdown
93
95
  update_tree(data[:content])
94
96
  replace_abbreviations(data[:content])
95
97
  end
98
+ footnote_count = 0
96
99
  @footnotes.each do |name, data|
97
- next if data.key?(:marker)
100
+ (footnote_count += 1; next) if data.key?(:marker)
98
101
  line = data[:content].options[:location]
99
102
  warning("Footnote definition for '#{name}' on line #{line} is unreferenced - ignoring")
100
103
  end
104
+ @root.options[:footnote_count] = footnote_count
101
105
  end
102
106
 
103
107
  protected
@@ -193,6 +197,11 @@ module Kramdown
193
197
  end.flatten!
194
198
  end
195
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
+
196
205
  # Parse all span-level elements in the source string of @src into +el+.
197
206
  #
198
207
  # If the parameter +stop_re+ (a regexp) is used, parsing is immediately stopped if the regexp
@@ -211,7 +220,7 @@ module Kramdown
211
220
  span_start, span_start_re = span_parser_regexps(parsers) if parsers
212
221
  parsers ||= @span_parsers
213
222
 
214
- used_re = (stop_re.nil? ? span_start_re : /(?=#{Regexp.union(stop_re, span_start)})/)
223
+ used_re = (stop_re.nil? ? span_start_re : span_pattern_cache(stop_re, span_start))
215
224
  stop_re_found = false
216
225
  while !@src.eos? && !stop_re_found
217
226
  if (result = @src.scan_until(used_re))
@@ -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?):.+?|[-.#{ACHARS}]+@[-#{ACHARS}]+(?:\.[-#{ACHARS}]+)*\.[a-z]+)>"
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.
@@ -16,8 +16,8 @@ module Kramdown
16
16
  # Parse the blank line at the current postition.
17
17
  def parse_blank_line
18
18
  @src.pos += @src.matched_size
19
- if @tree.children.last && @tree.children.last.type == :blank
20
- @tree.children.last.value << @src.matched
19
+ if (last_child = @tree.children.last) && last_child.type == :blank
20
+ last_child.value << @src.matched
21
21
  else
22
22
  @tree.children << new_block_el(:blank, @src.matched)
23
23
  end
@@ -19,8 +19,9 @@ module Kramdown
19
19
 
20
20
  # Return +true+ if we are after a block boundary.
21
21
  def after_block_boundary?
22
- !@tree.children.last || @tree.children.last.type == :blank ||
23
- (@tree.children.last.type == :eob && @tree.children.last.value.nil?) || @block_ial
22
+ last_child = @tree.children.last
23
+ !last_child || last_child.type == :blank ||
24
+ (last_child.type == :eob && last_child.value.nil?) || @block_ial
24
25
  end
25
26
 
26
27
  # Return +true+ if we are before a block boundary.
@@ -25,8 +25,18 @@ module Kramdown
25
25
  return
26
26
  end
27
27
 
28
- if (text = @src.scan_until(/#{result}/))
29
- text.sub!(/#{result}\Z/, '')
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] == ' '
@@ -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:]-]\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
@@ -39,7 +39,7 @@ module Kramdown
39
39
 
40
40
  # Update the +ial+ with the information from the inline attribute list +opts+.
41
41
  def update_ial_with_ial(ial, opts)
42
- (ial[:refs] ||= []) << opts[:refs]
42
+ (ial[:refs] ||= []).concat(opts[:refs]) if opts.key?(:refs)
43
43
  opts.each do |k, v|
44
44
  if k == IAL_CLASS_ATTR
45
45
  ial[k] = "#{ial[k]} #{v}".lstrip
@@ -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
@@ -163,10 +169,10 @@ module Kramdown
163
169
  elsif @src.check(EXT_BLOCK_START)
164
170
  parse_extension_start_tag(:block)
165
171
  elsif @src.scan(IAL_BLOCK_START)
166
- if @tree.children.last && @tree.children.last.type != :blank &&
167
- (@tree.children.last.type != :eob ||
168
- [:link_def, :abbrev_def, :footnote_def].include?(@tree.children.last.value))
169
- parse_attribute_list(@src[1], @tree.children.last.options[:ial] ||= {})
172
+ if (last_child = @tree.children.last) && last_child.type != :blank &&
173
+ (last_child.type != :eob ||
174
+ [:link_def, :abbrev_def, :footnote_def].include?(last_child.value))
175
+ parse_attribute_list(@src[1], last_child.options[:ial] ||= {})
170
176
  @tree.children << new_block_el(:eob, :ial) unless @src.check(IAL_BLOCK_START)
171
177
  else
172
178
  parse_attribute_list(@src[1], @block_ial ||= {})
@@ -187,12 +193,12 @@ module Kramdown
187
193
  if @src.check(EXT_SPAN_START)
188
194
  parse_extension_start_tag(:span)
189
195
  elsif @src.check(IAL_SPAN_START)
190
- if @tree.children.last && @tree.children.last.type != :text
196
+ if (last_child = @tree.children.last) && last_child.type != :text
191
197
  @src.pos += @src.matched_size
192
198
  attr = {}
193
199
  parse_attribute_list(@src[1], attr)
194
- update_ial_with_ial(@tree.children.last.options[:ial] ||= {}, attr)
195
- update_attr_with_ial(@tree.children.last.attr, attr)
200
+ update_ial_with_ial(last_child.options[:ial] ||= {}, attr)
201
+ update_attr_with_ial(last_child.attr, attr)
196
202
  else
197
203
  warning("Found span IAL after text - ignoring it")
198
204
  add_text(@src.getch)
@@ -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!(/[\t ]#+\z/, '') && text.rstrip!
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>[A-Za-z][\w:-]*)}\z/
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