kramdown 1.2.0 → 1.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTERS +5 -1
  3. data/README.md +5 -1
  4. data/Rakefile +5 -3
  5. data/VERSION +1 -1
  6. data/benchmark/generate_data.rb +1 -1
  7. data/doc/default.template +2 -2
  8. data/doc/index.page +3 -4
  9. data/doc/news.feed +1 -1
  10. data/doc/quickref.page +4 -4
  11. data/doc/sidebar.template +7 -8
  12. data/doc/syntax.page +10 -3
  13. data/doc/tests.page +1 -1
  14. data/lib/kramdown/converter.rb +1 -0
  15. data/lib/kramdown/converter/base.rb +57 -9
  16. data/lib/kramdown/converter/kramdown.rb +4 -2
  17. data/lib/kramdown/converter/pdf.rb +638 -0
  18. data/lib/kramdown/document.rb +1 -1
  19. data/lib/kramdown/element.rb +4 -0
  20. data/lib/kramdown/options.rb +44 -8
  21. data/lib/kramdown/parser/base.rb +4 -2
  22. data/lib/kramdown/parser/gfm.rb +25 -18
  23. data/lib/kramdown/parser/html.rb +2 -2
  24. data/lib/kramdown/parser/kramdown.rb +24 -2
  25. data/lib/kramdown/parser/kramdown/abbreviation.rb +3 -2
  26. data/lib/kramdown/parser/kramdown/autolink.rb +2 -1
  27. data/lib/kramdown/parser/kramdown/blockquote.rb +2 -1
  28. data/lib/kramdown/parser/kramdown/codeblock.rb +4 -2
  29. data/lib/kramdown/parser/kramdown/codespan.rb +2 -1
  30. data/lib/kramdown/parser/kramdown/emphasis.rb +2 -1
  31. data/lib/kramdown/parser/kramdown/extensions.rb +5 -4
  32. data/lib/kramdown/parser/kramdown/footnote.rb +6 -4
  33. data/lib/kramdown/parser/kramdown/header.rb +4 -2
  34. data/lib/kramdown/parser/kramdown/horizontal_rule.rb +2 -1
  35. data/lib/kramdown/parser/kramdown/html_entity.rb +4 -2
  36. data/lib/kramdown/parser/kramdown/link.rb +3 -2
  37. data/lib/kramdown/parser/kramdown/list.rb +9 -5
  38. data/lib/kramdown/parser/kramdown/math.rb +5 -3
  39. data/lib/kramdown/parser/kramdown/paragraph.rb +2 -1
  40. data/lib/kramdown/parser/kramdown/table.rb +5 -3
  41. data/lib/kramdown/parser/kramdown/typographic_symbol.rb +10 -5
  42. data/lib/kramdown/utils.rb +1 -0
  43. data/lib/kramdown/utils/string_scanner.rb +52 -0
  44. data/lib/kramdown/version.rb +1 -1
  45. data/man/man1/kramdown.1 +41 -6
  46. data/test/test_files.rb +2 -2
  47. data/test/test_location.rb +158 -0
  48. data/test/test_string_scanner_kramdown.rb +22 -0
  49. data/test/testcases/block/04_header/with_auto_id_stripping.html +1 -0
  50. data/test/testcases/block/04_header/with_auto_id_stripping.options +1 -0
  51. data/test/testcases/block/04_header/with_auto_id_stripping.text +1 -0
  52. data/test/testcases/span/math/normal.html +2 -1
  53. data/test/testcases/span/math/normal.text +2 -1
  54. data/test/testcases_gfm/hard_line_breaks_off.html +2 -0
  55. data/test/testcases_gfm/hard_line_breaks_off.options +1 -0
  56. data/test/testcases_gfm/hard_line_breaks_off.text +2 -0
  57. metadata +27 -3
@@ -19,7 +19,7 @@
19
19
  # * output formats: HTML, kramdown, LaTeX (and therefore PDF)
20
20
  #
21
21
  # All the documentation on the available input and output formats is available at
22
- # http://kramdown.rubyforge.org.
22
+ # http://kramdown.gettalong.org.
23
23
  #
24
24
  # == Usage
25
25
  #
@@ -16,6 +16,9 @@ module Kramdown
16
16
  #
17
17
  # Following is a description of all supported element types.
18
18
  #
19
+ # Note that the option :location may contain the start line number of an element in the source
20
+ # document.
21
+ #
19
22
  # == Structural Elements
20
23
  #
21
24
  # === :root
@@ -459,6 +462,7 @@ module Kramdown
459
462
  #
460
463
  # The option :type can be set to an array of strings to define for which converters the raw string
461
464
  # is valid.
465
+ #
462
466
  class Element
463
467
 
464
468
  # A symbol representing the element type. For example, :p or :blockquote.
@@ -79,8 +79,7 @@ module Kramdown
79
79
  temp = defaults
80
80
  hash.each do |k,v|
81
81
  k = k.to_sym
82
- next unless @options.has_key?(k)
83
- temp[k] = parse(k, v)
82
+ @options.has_key?(k) ? temp[k] = parse(k, v) : temp[k] = v
84
83
  end
85
84
  temp
86
85
  end
@@ -147,6 +146,7 @@ module Kramdown
147
146
 
148
147
  define(:template, String, '', <<EOF)
149
148
  The name of an ERB template file that should be used to wrap the output
149
+ or the ERB template itself.
150
150
 
151
151
  This is used to wrap the output in an environment so that the output can
152
152
  be used as a stand-alone document. For example, an HTML template would
@@ -155,10 +155,13 @@ valid HTML file. If no template is specified, the output will be just
155
155
  the converted text.
156
156
 
157
157
  When resolving the template file, the given template name is used first.
158
- If such a file is not found, the converter extension is appended. If the
159
- file still cannot be found, the templates name is interpreted as a
160
- template name that is provided by kramdown (without the converter
161
- extension).
158
+ If such a file is not found, the converter extension (the same as the
159
+ converter name) is appended. If the file still cannot be found, the
160
+ templates name is interpreted as a template name that is provided by
161
+ kramdown (without the converter extension). If the file is still not
162
+ found, the template name is checked if it starts with 'string://' and if
163
+ it does, this prefix is removed and the rest is used as template
164
+ content.
162
165
 
163
166
  kramdown provides a default template named 'document' for each converter.
164
167
 
@@ -174,10 +177,24 @@ generated if no ID is explicitly specified.
174
177
 
175
178
  Default: true
176
179
  Used by: HTML/Latex converter
180
+ EOF
181
+
182
+ define(:auto_id_stripping, Boolean, false, <<EOF)
183
+ Strip all formatting from header text for automatic ID generation
184
+
185
+ If this option is `true`, only the text elements of a header are used
186
+ for generating the ID later (in contrast to just using the raw header
187
+ text line).
188
+
189
+ This option will be removed in version 2.0 because this will be the
190
+ default then.
191
+
192
+ Default: false
193
+ Used by: kramdown parser
177
194
  EOF
178
195
 
179
196
  define(:auto_id_prefix, String, '', <<EOF)
180
- Prefix used for automatically generated heaer IDs
197
+ Prefix used for automatically generated header IDs
181
198
 
182
199
  This option can be used to set a prefix for the automatically generated
183
200
  header IDs so that there is no conflict when rendering multiple kramdown
@@ -321,12 +338,21 @@ The tab width used in highlighted code
321
338
  Used by: HTML converter
322
339
  EOF
323
340
 
324
- define(:coderay_bold_every, Integer, 10, <<EOF)
341
+ define(:coderay_bold_every, Object, 10, <<EOF) do |val|
325
342
  Defines how often a line number should be made bold
326
343
 
344
+ Can either be an integer or false (to turn off bold line numbers
345
+ completely).
346
+
327
347
  Default: 10
328
348
  Used by: HTML converter
329
349
  EOF
350
+ if val == false || val.to_s == 'false'
351
+ false
352
+ else
353
+ Integer(val.to_s) rescue raise Kramdown::Error, "Invalid value for option 'coderay_bold_every'"
354
+ end
355
+ end
330
356
 
331
357
  define(:coderay_css, Symbol, :style, <<EOF)
332
358
  Defines how the highlighted code gets styled
@@ -456,6 +482,16 @@ level 1 will be used. If c+n is greater than 6, level 6 will be used.
456
482
 
457
483
  Default: 0
458
484
  Used by: HTML converter, Kramdown converter, Latex converter
485
+ EOF
486
+
487
+ define(:hard_wrap, Boolean, true, <<EOF)
488
+ Interprets line breaks literally
489
+
490
+ Insert HTML `<br />` tags inside paragraphs where the original Markdown
491
+ document had newlines (by default, Markdown ignores these newlines).
492
+
493
+ Default: true
494
+ Used by: GFM parser
459
495
  EOF
460
496
 
461
497
  end
@@ -7,6 +7,8 @@
7
7
  #++
8
8
  #
9
9
 
10
+ require 'kramdown/utils/string_scanner'
11
+
10
12
  module Kramdown
11
13
 
12
14
  module Parser
@@ -14,7 +16,7 @@ module Kramdown
14
16
  # == \Base class for parsers
15
17
  #
16
18
  # This class serves as base class for parsers. It provides common methods that can/should be
17
- # used by all parsers, especially by those using StringScanner for parsing.
19
+ # used by all parsers, especially by those using StringScanner(Kramdown) for parsing.
18
20
  #
19
21
  # A parser object is used as a throw-away object, i.e. it is only used for storing the needed
20
22
  # state information during parsing. Therefore one can't instantiate a parser object directly but
@@ -49,7 +51,7 @@ module Kramdown
49
51
  def initialize(source, options)
50
52
  @source = source
51
53
  @options = Kramdown::Options.merge(options)
52
- @root = Element.new(:root, nil, nil, :encoding => (source.encoding rescue nil))
54
+ @root = Element.new(:root, nil, nil, :encoding => (source.encoding rescue nil), :location => 1)
53
55
  @warnings = []
54
56
  @text_type = :text
55
57
  end
@@ -8,33 +8,40 @@ module Kramdown
8
8
 
9
9
  def initialize(source, options)
10
10
  super
11
+ @span_parsers.delete(:line_break)
11
12
  i = @block_parsers.index(:codeblock_fenced)
12
13
  @block_parsers.delete(:codeblock_fenced)
13
14
  @block_parsers.insert(i, :codeblock_fenced_gfm)
14
15
  end
15
16
 
17
+ def parse
18
+ super
19
+ add_hard_line_breaks(@root) if @options[:hard_wrap]
20
+ end
21
+
22
+ def add_hard_line_breaks(element)
23
+ element.children.map! do |child|
24
+ if child.type == :text && child.value =~ /\n/
25
+ children = []
26
+ lines = child.value.split(/\n(?=.)/)
27
+ lines.each_with_index do |line, index|
28
+ children << Element.new(:text, (index > 0 ? "\n#{line}" : line))
29
+ children << Element.new(:br) if index < lines.size - 1
30
+ end
31
+ children
32
+ elsif child.type == :html_element
33
+ child
34
+ else
35
+ add_hard_line_breaks(child)
36
+ child
37
+ end
38
+ end.flatten!
39
+ end
40
+
16
41
  FENCED_CODEBLOCK_MATCH = /^(([~`]){3,})\s*?(\w+)?\s*?\n(.*?)^\1\2*\s*?\n/m
17
42
 
18
43
  define_parser(:codeblock_fenced_gfm, /^[~`]{3,}/, nil, 'parse_codeblock_fenced')
19
44
 
20
- def parse_paragraph
21
- result = @src.scan(PARAGRAPH_MATCH)
22
- while !@src.match?(self.class::PARAGRAPH_END)
23
- result << @src.scan(PARAGRAPH_MATCH)
24
- end
25
- result.chomp!
26
- unless @tree.children.last && @tree.children.last.type == :p
27
- @tree.children << new_block_el(:p)
28
- end
29
- lines = result.lstrip.split(/\n/)
30
- lines.each_with_index do |line, index|
31
- @tree.children.last.children << Element.new(@text_type, line) << Element.new(:br) << Element.new(@text_type, "\n")
32
- end
33
- @tree.children.last.children.pop # added one \n too many
34
- @tree.children.last.children.pop # added one :br too many
35
- true
36
- end
37
-
38
45
  end
39
46
  end
40
47
  end
@@ -243,7 +243,7 @@ module Kramdown
243
243
  # entities in entity elements.
244
244
  def process_text(raw, preserve = false)
245
245
  raw.gsub!(/\s+/, ' ') unless preserve
246
- src = StringScanner.new(raw)
246
+ src = Kramdown::Utils::StringScanner.new(raw)
247
247
  result = []
248
248
  while !src.eos?
249
249
  if tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/)
@@ -533,7 +533,7 @@ module Kramdown
533
533
  # Parse the source string provided on initialization as HTML document.
534
534
  def parse
535
535
  @stack, @tree = [], @root
536
- @src = StringScanner.new(adapt_source(source))
536
+ @src = Kramdown::Utils::StringScanner.new(adapt_source(source))
537
537
 
538
538
  while true
539
539
  if result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/)
@@ -118,7 +118,8 @@ module Kramdown
118
118
  # Parse all block-level elements in +text+ into the element +el+.
119
119
  def parse_blocks(el, text = nil)
120
120
  @stack.push([@tree, @src, @block_ial])
121
- @tree, @src, @block_ial = el, (text.nil? ? @src : StringScanner.new(text)), nil
121
+ @tree, @block_ial = el, nil
122
+ @src = (text.nil? ? @src : ::Kramdown::Utils::StringScanner.new(text, el.options[:location]))
122
123
 
123
124
  status = catch(:stop_block_parsing) do
124
125
  while !@src.eos?
@@ -148,7 +149,8 @@ module Kramdown
148
149
  element.children.map! do |child|
149
150
  if child.type == :raw_text
150
151
  last_blank = nil
151
- reset_env(:src => StringScanner.new(child.value), :text_type => :text)
152
+ reset_env(:src => ::Kramdown::Utils::StringScanner.new(child.value, element.options[:location]),
153
+ :text_type => :text)
152
154
  parse_spans(child)
153
155
  child.children
154
156
  elsif child.type == :eob
@@ -165,6 +167,7 @@ module Kramdown
165
167
  last_blank = nil
166
168
  update_tree(child)
167
169
  update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
170
+ update_raw_header_text(child) if child.type == :header
168
171
  child
169
172
  end
170
173
  end.flatten!
@@ -253,6 +256,25 @@ module Kramdown
253
256
  end
254
257
  end
255
258
 
259
+ # Update the raw header text for automatic ID generation.
260
+ def update_raw_header_text(header)
261
+ # DEPRECATED: option auto_id_stripping will be removed in 2.0 because then this will be the
262
+ # default behaviour
263
+ return unless @options[:auto_id_stripping]
264
+ raw_text = ''
265
+
266
+ append_text = lambda do |child|
267
+ if child.type == :text
268
+ raw_text << child.value
269
+ else
270
+ child.children.each {|c| append_text.call(c)}
271
+ end
272
+ end
273
+
274
+ append_text.call(header)
275
+ header.options[:raw_text] = raw_text
276
+ end
277
+
256
278
  # Create a new block-level element, taking care of applying a preceding block IAL if it
257
279
  # exists. This method should always be used for creating a block-level element!
258
280
  def new_block_el(*args)
@@ -15,10 +15,11 @@ module Kramdown
15
15
 
16
16
  # Parse the link definition at the current location.
17
17
  def parse_abbrev_definition
18
+ start_line_number = @src.current_line_number
18
19
  @src.pos += @src.matched_size
19
20
  abbrev_id, abbrev_text = @src[1], @src[2]
20
21
  abbrev_text.strip!
21
- warning("Duplicate abbreviation ID '#{abbrev_id}' - overwriting") if @root.options[:abbrev_defs][abbrev_id]
22
+ warning("Duplicate abbreviation ID '#{abbrev_id}' on line #{start_line_number} - overwriting") if @root.options[:abbrev_defs][abbrev_id]
22
23
  @root.options[:abbrev_defs][abbrev_id] = abbrev_text
23
24
  @tree.children << Element.new(:eob, :abbrev_def)
24
25
  true
@@ -37,7 +38,7 @@ module Kramdown
37
38
  if child.type == :text
38
39
  if child.value =~ regexps.first
39
40
  result = []
40
- strscan = StringScanner.new(child.value)
41
+ strscan = Kramdown::Utils::StringScanner.new(child.value)
41
42
  while temp = strscan.scan_until(regexps.last)
42
43
  abbr = strscan.scan(regexps.first) # begin of line case of abbr with \W char as first one
43
44
  if abbr.nil?
@@ -23,9 +23,10 @@ module Kramdown
23
23
 
24
24
  # Parse the autolink at the current location.
25
25
  def parse_autolink
26
+ start_line_number = @src.current_line_number
26
27
  @src.pos += @src.matched_size
27
28
  href = (@src[2].nil? ? "mailto:#{@src[1]}" : @src[1])
28
- el = Element.new(:a, nil, {'href' => href})
29
+ el = Element.new(:a, nil, {'href' => href}, :location => start_line_number)
29
30
  add_text(@src[1].sub(/^mailto:/, ''), el)
30
31
  @tree.children << el
31
32
  end
@@ -19,13 +19,14 @@ module Kramdown
19
19
 
20
20
  # Parse the blockquote at the current location.
21
21
  def parse_blockquote
22
+ start_line_number = @src.current_line_number
22
23
  result = @src.scan(PARAGRAPH_MATCH)
23
24
  while !@src.match?(self.class::LAZY_END)
24
25
  result << @src.scan(PARAGRAPH_MATCH)
25
26
  end
26
27
  result.gsub!(BLOCKQUOTE_START, '')
27
28
 
28
- el = new_block_el(:blockquote)
29
+ el = new_block_el(:blockquote, nil, nil, :location => start_line_number)
29
30
  @tree.children << el
30
31
  parse_blocks(el, result)
31
32
  true
@@ -21,10 +21,11 @@ module Kramdown
21
21
 
22
22
  # Parse the indented codeblock at the current location.
23
23
  def parse_codeblock
24
+ start_line_number = @src.current_line_number
24
25
  data = @src.scan(self.class::CODEBLOCK_MATCH)
25
26
  data.gsub!(/\n( {0,3}\S)/, ' \\1')
26
27
  data.gsub!(INDENT, '')
27
- @tree.children << new_block_el(:codeblock, data)
28
+ @tree.children << new_block_el(:codeblock, data, nil, :location => start_line_number)
28
29
  true
29
30
  end
30
31
  define_parser(:codeblock, CODEBLOCK_START)
@@ -36,8 +37,9 @@ module Kramdown
36
37
  # Parse the fenced codeblock at the current location.
37
38
  def parse_codeblock_fenced
38
39
  if @src.check(self.class::FENCED_CODEBLOCK_MATCH)
40
+ start_line_number = @src.current_line_number
39
41
  @src.pos += @src.matched_size
40
- el = new_block_el(:codeblock, @src[4])
42
+ el = new_block_el(:codeblock, @src[4], nil, :location => start_line_number)
41
43
  lang = @src[3].to_s.strip
42
44
  el.attr['class'] = "language-#{lang}" unless lang.empty?
43
45
  @tree.children << el
@@ -15,6 +15,7 @@ module Kramdown
15
15
 
16
16
  # Parse the codespan at the current scanner location.
17
17
  def parse_codespan
18
+ start_line_number = @src.current_line_number
18
19
  result = @src.scan(CODESPAN_DELIMITER)
19
20
  simple = (result.length == 1)
20
21
  reset_pos = @src.pos
@@ -30,7 +31,7 @@ module Kramdown
30
31
  text = text[1..-1] if text[0..0] == ' '
31
32
  text = text[0..-2] if text[-1..-1] == ' '
32
33
  end
33
- @tree.children << Element.new(:codespan, text)
34
+ @tree.children << Element.new(:codespan, text, nil, :location => start_line_number)
34
35
  else
35
36
  @src.pos = reset_pos
36
37
  add_text(result)
@@ -15,6 +15,7 @@ module Kramdown
15
15
 
16
16
  # Parse the emphasis at the current location.
17
17
  def parse_emphasis
18
+ start_line_number = @src.current_line_number
18
19
  result = @src.scan(EMPHASIS_START)
19
20
  element = (result.length == 2 ? :strong : :em)
20
21
  type = result[0..0]
@@ -27,7 +28,7 @@ module Kramdown
27
28
  end
28
29
 
29
30
  sub_parse = lambda do |delim, elem|
30
- el = Element.new(elem)
31
+ el = Element.new(elem, nil, nil, :location => start_line_number)
31
32
  stop_re = /#{Regexp.escape(delim)}/
32
33
  found = parse_spans(el, stop_re) do
33
34
  (@src.pre_match[-1, 1] !~ /\s/) &&
@@ -16,7 +16,7 @@ module Kramdown
16
16
  # Parse the string +str+ and extract all attributes and add all found attributes to the hash
17
17
  # +opts+.
18
18
  def parse_attribute_list(str, opts)
19
- return if str.strip.empty?
19
+ return if str.strip.empty? || str.strip == ':'
20
20
  attrs = str.scan(ALD_TYPE_ANY)
21
21
  attrs.each do |key, sep, val, ref, id_and_or_class, _, _|
22
22
  if ref
@@ -55,6 +55,7 @@ module Kramdown
55
55
  # or :span depending whether we parse a block or span extension tag.
56
56
  def parse_extension_start_tag(type)
57
57
  orig_pos = @src.pos
58
+ start_line_number = @src.current_line_number
58
59
  @src.pos += @src.matched_size
59
60
 
60
61
  error_block = lambda do |msg|
@@ -66,7 +67,7 @@ module Kramdown
66
67
 
67
68
  if @src[4] || @src.matched == '{:/}'
68
69
  name = (@src[4] ? "for '#{@src[4]}' " : '')
69
- return error_block.call("Invalid extension stop tag #{name}found - ignoring it")
70
+ return error_block.call("Invalid extension stop tag #{name} found on line #{start_line_number} - ignoring it")
70
71
  end
71
72
 
72
73
  ext = @src[1]
@@ -80,12 +81,12 @@ module Kramdown
80
81
  body = result.sub!(stop_re, '')
81
82
  body.chomp! if type == :block
82
83
  else
83
- return error_block.call("No stop tag for extension '#{ext}' found - ignoring it")
84
+ return error_block.call("No stop tag for extension '#{ext}' found on line #{start_line_number} - ignoring it")
84
85
  end
85
86
  end
86
87
 
87
88
  if !handle_extension(ext, opts, body, type)
88
- error_block.call("Invalid extension with name '#{ext}' specified - ignoring it")
89
+ error_block.call("Invalid extension with name '#{ext}' specified on line #{start_line_number} - ignoring it")
89
90
  else
90
91
  true
91
92
  end
@@ -19,13 +19,14 @@ module Kramdown
19
19
 
20
20
  # Parse the foot note definition at the current location.
21
21
  def parse_footnote_definition
22
+ start_line_number = @src.current_line_number
22
23
  @src.pos += @src.matched_size
23
24
 
24
- el = Element.new(:footnote_def)
25
+ el = Element.new(:footnote_def, nil, nil, :location => start_line_number)
25
26
  parse_blocks(el, @src[2].gsub(INDENT, ''))
26
27
  warning("Duplicate footnote name '#{@src[1]}' - overwriting") if @footnotes[@src[1]]
27
28
  (@footnotes[@src[1]] = {})[:content] = el
28
- @tree.children << Element.new(:eob, :footnote_def)
29
+ @tree.children << Element.new(:eob, :footnote_def, nil, :location => start_line_number)
29
30
  true
30
31
  end
31
32
  define_parser(:footnote_definition, FOOTNOTE_DEFINITION_START)
@@ -35,15 +36,16 @@ module Kramdown
35
36
 
36
37
  # Parse the footnote marker at the current location.
37
38
  def parse_footnote_marker
39
+ start_line_number = @src.current_line_number
38
40
  @src.pos += @src.matched_size
39
41
  fn_def = @footnotes[@src[1]]
40
42
  if fn_def
41
43
  fn_def[:marker] ||= []
42
- fn_def[:marker].push(Element.new(:footnote, fn_def[:content], nil, :name => @src[1]))
44
+ fn_def[:marker].push(Element.new(:footnote, fn_def[:content], nil, :name => @src[1], :location => start_line_number))
43
45
  fn_def[:stack] = [@stack.map {|s| s.first}, @tree, fn_def[:marker]].flatten.compact
44
46
  @tree.children << fn_def[:marker].last
45
47
  else
46
- warning("Footnote definition for '#{@src[1]}' not found")
48
+ warning("Footnote definition for '#{@src[1]}' not found on line #{start_line_number}")
47
49
  add_text(@src.matched)
48
50
  end
49
51
  end