kramdown 0.11.0 → 0.12.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 (94) hide show
  1. data/CONTRIBUTERS +1 -1
  2. data/ChangeLog +532 -0
  3. data/README +22 -12
  4. data/Rakefile +9 -8
  5. data/VERSION +1 -1
  6. data/benchmark/benchmark.sh +61 -0
  7. data/benchmark/generate_data.rb +57 -55
  8. data/benchmark/testing.sh +1 -1
  9. data/benchmark/timing.sh +3 -3
  10. data/bin/kramdown +1 -2
  11. data/data/kramdown/document.html +2 -2
  12. data/data/kramdown/document.latex +2 -2
  13. data/doc/default.scss.css +6 -1
  14. data/doc/default.template +1 -1
  15. data/doc/documentation.page +1 -1
  16. data/doc/index.page +9 -7
  17. data/doc/installation.page +2 -3
  18. data/doc/links.markdown +1 -1
  19. data/doc/quickref.page +19 -19
  20. data/doc/syntax.page +117 -98
  21. data/doc/tests.page +8 -7
  22. data/lib/kramdown/compatibility.rb +2 -1
  23. data/lib/kramdown/converter.rb +5 -7
  24. data/lib/kramdown/converter/base.rb +87 -32
  25. data/lib/kramdown/converter/html.rb +134 -122
  26. data/lib/kramdown/converter/kramdown.rb +24 -25
  27. data/lib/kramdown/converter/latex.rb +65 -55
  28. data/lib/kramdown/document.rb +487 -42
  29. data/lib/kramdown/error.rb +3 -0
  30. data/lib/kramdown/options.rb +83 -28
  31. data/lib/kramdown/parser.rb +5 -5
  32. data/lib/kramdown/parser/base.rb +55 -13
  33. data/lib/kramdown/parser/html.rb +83 -71
  34. data/lib/kramdown/parser/kramdown.rb +73 -54
  35. data/lib/kramdown/parser/kramdown/abbreviation.rb +17 -12
  36. data/lib/kramdown/parser/kramdown/autolink.rb +2 -3
  37. data/lib/kramdown/parser/kramdown/blank_line.rb +1 -1
  38. data/lib/kramdown/parser/kramdown/block_boundary.rb +2 -2
  39. data/lib/kramdown/parser/kramdown/blockquote.rb +2 -2
  40. data/lib/kramdown/parser/kramdown/codeblock.rb +5 -2
  41. data/lib/kramdown/parser/kramdown/codespan.rb +1 -2
  42. data/lib/kramdown/parser/kramdown/emphasis.rb +1 -1
  43. data/lib/kramdown/parser/kramdown/escaped_chars.rb +1 -1
  44. data/lib/kramdown/parser/kramdown/extensions.rb +204 -0
  45. data/lib/kramdown/parser/kramdown/footnote.rb +7 -7
  46. data/lib/kramdown/parser/kramdown/header.rb +4 -2
  47. data/lib/kramdown/parser/kramdown/horizontal_rule.rb +1 -1
  48. data/lib/kramdown/parser/kramdown/html.rb +39 -45
  49. data/lib/kramdown/parser/kramdown/link.rb +19 -29
  50. data/lib/kramdown/parser/kramdown/list.rb +13 -13
  51. data/lib/kramdown/parser/kramdown/math.rb +1 -1
  52. data/lib/kramdown/parser/kramdown/paragraph.rb +5 -4
  53. data/lib/kramdown/parser/kramdown/smart_quotes.rb +1 -1
  54. data/lib/kramdown/parser/kramdown/table.rb +51 -12
  55. data/lib/kramdown/parser/markdown.rb +69 -0
  56. data/lib/kramdown/utils.rb +2 -2
  57. data/lib/kramdown/utils/entities.rb +10 -1
  58. data/lib/kramdown/utils/html.rb +22 -11
  59. data/lib/kramdown/utils/ordered_hash.rb +44 -40
  60. data/lib/kramdown/version.rb +1 -1
  61. data/man/man1/kramdown.1 +31 -4
  62. data/test/testcases/block/08_list/item_ial.html +1 -1
  63. data/test/testcases/block/11_ial/nested.html +11 -0
  64. data/test/testcases/block/11_ial/nested.text +15 -0
  65. data/test/testcases/block/13_definition_list/item_ial.html +1 -1
  66. data/test/testcases/block/14_table/escaping.html +52 -0
  67. data/test/testcases/block/14_table/escaping.text +19 -0
  68. data/test/testcases/block/14_table/simple.html.19 +139 -0
  69. data/test/testcases/block/14_table/simple.text +1 -1
  70. data/test/testcases/block/15_math/normal.html +13 -13
  71. data/test/testcases/block/16_toc/{no_toc_depth.html → no_toc.html} +0 -0
  72. data/test/testcases/block/16_toc/{no_toc_depth.options → no_toc.options} +0 -0
  73. data/test/testcases/block/16_toc/{no_toc_depth.text → no_toc.text} +0 -0
  74. data/test/testcases/block/16_toc/{toc_depth_2.html → toc_levels.html} +4 -4
  75. data/test/testcases/block/16_toc/toc_levels.options +1 -0
  76. data/test/testcases/block/16_toc/{toc_depth_2.text → toc_levels.text} +0 -0
  77. data/test/testcases/span/escaped_chars/normal.html +4 -0
  78. data/test/testcases/span/escaped_chars/normal.text +4 -0
  79. data/test/testcases/span/ial/simple.html +1 -1
  80. data/test/testcases/span/math/normal.html +2 -2
  81. metadata +20 -25
  82. data/benchmark/historic-jruby-1.4.0.dat +0 -7
  83. data/benchmark/historic-ruby-1.8.6.dat +0 -7
  84. data/benchmark/historic-ruby-1.8.7.dat +0 -7
  85. data/benchmark/historic-ruby-1.9.1p243.dat +0 -7
  86. data/benchmark/historic-ruby-1.9.2dev.dat +0 -7
  87. data/benchmark/static-jruby-1.4.0.dat +0 -7
  88. data/benchmark/static-ruby-1.8.6.dat +0 -7
  89. data/benchmark/static-ruby-1.8.7.dat +0 -7
  90. data/benchmark/static-ruby-1.9.1p243.dat +0 -7
  91. data/benchmark/static-ruby-1.9.2dev.dat +0 -7
  92. data/lib/kramdown/parser/kramdown/attribute_list.rb +0 -111
  93. data/lib/kramdown/parser/kramdown/extension.rb +0 -116
  94. data/test/testcases/block/16_toc/toc_depth_2.options +0 -1
@@ -32,7 +32,7 @@ module Kramdown
32
32
 
33
33
  # Used for parsing a document in kramdown format.
34
34
  #
35
- # If you want to extend the functionality of the parser, you need to the following:
35
+ # If you want to extend the functionality of the parser, you need to do the following:
36
36
  #
37
37
  # * Create a new subclass
38
38
  # * add the needed parser methods
@@ -40,15 +40,15 @@ module Kramdown
40
40
  # methods
41
41
  #
42
42
  # Here is a small example for an extended parser class that parses ERB style tags as raw text if
43
- # they are used as span level elements (an equivalent block level parser should probably also be
43
+ # they are used as span-level elements (an equivalent block-level parser should probably also be
44
44
  # made to handle the block case):
45
45
  #
46
46
  # require 'kramdown/parser/kramdown'
47
47
  #
48
48
  # class Kramdown::Parser::ERBKramdown < Kramdown::Parser::Kramdown
49
49
  #
50
- # def initialize(doc)
51
- # super(doc)
50
+ # def initialize(source, options)
51
+ # super
52
52
  # @span_parsers.unshift(:erb_tags)
53
53
  # end
54
54
  #
@@ -73,47 +73,35 @@ module Kramdown
73
73
 
74
74
  include ::Kramdown
75
75
 
76
- attr_reader :tree
77
- attr_reader :doc
78
- attr_reader :options
76
+ # Create a new Kramdown parser object with the given +options+.
77
+ def initialize(source, options)
78
+ super
79
79
 
80
- # Create a new Kramdown parser object for the Kramdown::Document +doc+.
81
- def initialize(doc)
82
- super(doc)
80
+ reset_env
83
81
 
84
- @src = nil
85
- @tree = nil
86
- @stack = []
87
- @text_type = :raw_text
88
- @block_ial = nil
89
-
90
- @doc.parse_infos[:ald] = {}
91
- @doc.parse_infos[:link_defs] = {}
92
- @doc.parse_infos[:abbrev_defs] = {}
93
- @doc.parse_infos[:footnotes] = {}
82
+ @root.options[:abbrev_defs] = {}
83
+ @alds = {}
84
+ @link_defs = {}
85
+ @footnotes = {}
94
86
 
95
87
  @block_parsers = [:blank_line, :codeblock, :codeblock_fenced, :blockquote, :table, :atx_header,
96
88
  :setext_header, :horizontal_rule, :list, :definition_list, :link_definition, :block_html,
97
- :footnote_definition, :abbrev_definition, :ald, :block_math,
98
- :block_extension, :block_ial, :eob_marker, :paragraph]
89
+ :footnote_definition, :abbrev_definition, :block_extensions, :block_math,
90
+ :eob_marker, :paragraph]
99
91
  @span_parsers = [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link, :smart_quotes, :inline_math,
100
- :span_extension, :span_ial, :html_entity, :typographic_syms, :line_break, :escaped_chars]
92
+ :span_extensions, :html_entity, :typographic_syms, :line_break, :escaped_chars]
101
93
 
102
94
  end
103
95
  private_class_method(:new, :allocate)
104
96
 
105
97
 
106
- # The source string provided on initialization is parsed and the created +tree+ is returned.
107
- def parse(source)
98
+ # The source string provided on initialization is parsed into the <tt>@root</tt> element.
99
+ def parse
108
100
  configure_parser
109
- tree = Element.new(:root)
110
- parse_blocks(tree, adapt_source(source))
111
- update_tree(tree)
112
- replace_abbreviations(tree)
113
- @doc.parse_infos[:footnotes].each do |name, data|
114
- update_tree(data[:content])
115
- end
116
- tree
101
+ parse_blocks(@root, adapt_source(source))
102
+ update_tree(@root)
103
+ replace_abbreviations(@root)
104
+ @footnotes.each {|name,data| update_tree(data[:marker].value) if data[:marker]}
117
105
  end
118
106
 
119
107
  #######
@@ -139,10 +127,10 @@ module Kramdown
139
127
  [span_start, /(?=#{span_start})/]
140
128
  end
141
129
 
142
- # Parse all block level elements in +text+ into the element +el+.
130
+ # Parse all block-level elements in +text+ into the element +el+.
143
131
  def parse_blocks(el, text = nil)
144
- @stack.push([@tree, @src])
145
- @tree, @src = el, (text.nil? ? @src : StringScanner.new(text))
132
+ @stack.push([@tree, @src, @block_ial])
133
+ @tree, @src, @block_ial = el, (text.nil? ? @src : StringScanner.new(text)), nil
146
134
 
147
135
  status = catch(:stop_block_parsing) do
148
136
  while !@src.eos?
@@ -161,26 +149,25 @@ module Kramdown
161
149
  end
162
150
  end
163
151
 
164
- @tree, @src = *@stack.pop
152
+ @tree, @src, @block_ial = *@stack.pop
165
153
  status
166
154
  end
167
155
 
168
- # Update the tree by parsing all <tt>:raw_text</tt> elements with the span level parser
169
- # (resets +@tree+, +@src+ and the +@stack+) and by updating the attributes from the IALs.
156
+ # Update the tree by parsing all <tt>:raw_text</tt> elements with the span-level parser
157
+ # (resets the environment) and by updating the attributes from the IALs.
170
158
  def update_tree(element)
171
159
  last_blank = nil
172
160
  element.children.map! do |child|
173
161
  if child.type == :raw_text
174
162
  last_blank = nil
175
- @stack, @tree, @text_type = [], nil, :text
176
- @src = StringScanner.new(child.value)
163
+ reset_env(:src => StringScanner.new(child.value), :text_type => :text)
177
164
  parse_spans(child)
178
165
  child.children
179
166
  elsif child.type == :eob
180
167
  []
181
168
  elsif child.type == :blank
182
169
  if last_blank
183
- last_blank.value += child.value
170
+ last_blank.value << child.value
184
171
  []
185
172
  else
186
173
  last_blank = child
@@ -195,7 +182,15 @@ module Kramdown
195
182
  end.flatten!
196
183
  end
197
184
 
198
- # Parse all span level elements in the source string.
185
+ # Parse all span-level elements in the source string of <tt>@src</tt> into +el+.
186
+ #
187
+ # If the parameter +stop_re+ (a regexp) is used, parsing is immediately stopped if the regexp
188
+ # matches and if no block is given or if a block is given and it returns +true+.
189
+ #
190
+ # The parameter +parsers+ can be used to specify the (span-level) parsing methods that should
191
+ # be used for parsing.
192
+ #
193
+ # The parameter +text_type+ specifies the type which should be used for created text nodes.
199
194
  def parse_spans(el, stop_re = nil, parsers = nil, text_type = @text_type)
200
195
  @stack.push([@tree, @text_type]) unless @tree.nil?
201
196
  @tree, @text_type = el, text_type
@@ -221,9 +216,9 @@ module Kramdown
221
216
  false
222
217
  end
223
218
  end unless stop_re_found
224
- add_text(@src.scan(/./)) if !processed && !stop_re_found
219
+ add_text(@src.getch) if !processed && !stop_re_found
225
220
  else
226
- add_text(@src.scan(/.*/m)) unless stop_re
221
+ (add_text(@src.rest); @src.terminate) unless stop_re
227
222
  break
228
223
  end
229
224
  end
@@ -233,31 +228,54 @@ module Kramdown
233
228
  stop_re_found
234
229
  end
235
230
 
236
- # Update the attributes with the information from the inline attribute list and all referenced ALDs.
231
+ # Reset the current parsing environment. The parameter +env+ can be used to set initial
232
+ # values for one or more environment variables.
233
+ def reset_env(opts = {})
234
+ opts = {:text_type => :raw_text, :stack => []}.merge(opts)
235
+ @src = opts[:src]
236
+ @tree = opts[:tree]
237
+ @block_ial = opts[:block_ial]
238
+ @stack = opts[:stack]
239
+ @text_type = opts[:text_type]
240
+ end
241
+
242
+ # Return the current parsing environment.
243
+ def save_env
244
+ [@src, @tree, @block_ial, @stack, @text_type]
245
+ end
246
+
247
+ # Restore the current parsing environment.
248
+ def restore_env(env)
249
+ @src, @tree, @block_ial, @stack, @text_type = *env
250
+ end
251
+
252
+ # Update the given attributes hash +attr+ with the information from the inline attribute list
253
+ # +ial+ and all referenced ALDs.
237
254
  def update_attr_with_ial(attr, ial)
238
255
  ial[:refs].each do |ref|
239
- update_attr_with_ial(attr, ref) if ref = @doc.parse_infos[:ald][ref]
256
+ update_attr_with_ial(attr, ref) if ref = @alds[ref]
240
257
  end if ial[:refs]
241
258
  ial.each do |k,v|
242
- if k == 'class'
243
- attr[k] = ((attr[k] || '') + " #{v}").lstrip
259
+ if k == IAL_CLASS_ATTR
260
+ attr[k] = (attr[k] || '') << " #{v}"
261
+ attr[k].lstrip!
244
262
  elsif k.kind_of?(String)
245
263
  attr[k] = v
246
264
  end
247
265
  end
248
266
  end
249
267
 
250
- # Create a new block level element, taking care of applying a preceding block IAL if it exists.
268
+ # Create a new block-level element, taking care of applying a preceding block IAL if it
269
+ # exists. This method should always be used for creating a block-level element!
251
270
  def new_block_el(*args)
252
271
  el = Element.new(*args)
253
- el.options[:category] ||= :block
254
272
  el.options[:ial] = @block_ial if @block_ial && el.type != :blank && el.type != :eob
255
273
  el
256
274
  end
257
275
 
258
276
  @@parsers = {}
259
277
 
260
- # Holds all the needed data for one block/span level parser.
278
+ # Struct class holding all the needed data for one block/span-level parser method.
261
279
  Data = Struct.new(:name, :start_re, :span_start, :method)
262
280
 
263
281
  # Add a parser method
@@ -284,7 +302,9 @@ module Kramdown
284
302
  @@parsers.has_key?(name)
285
303
  end
286
304
 
305
+ # Regexp for matching indentation (one tab or four spaces)
287
306
  INDENT = /^(?:\t| {4})/
307
+ # Regexp for matching the optional space (zero or up to three spaces)
288
308
  OPT_SPACE = / {0,3}/
289
309
 
290
310
  require 'kramdown/parser/kramdown/blank_line'
@@ -297,8 +317,7 @@ module Kramdown
297
317
  require 'kramdown/parser/kramdown/horizontal_rule'
298
318
  require 'kramdown/parser/kramdown/list'
299
319
  require 'kramdown/parser/kramdown/link'
300
- require 'kramdown/parser/kramdown/attribute_list'
301
- require 'kramdown/parser/kramdown/extension'
320
+ require 'kramdown/parser/kramdown/extensions'
302
321
  require 'kramdown/parser/kramdown/footnote'
303
322
  require 'kramdown/parser/kramdown/html'
304
323
  require 'kramdown/parser/kramdown/escaped_chars'
@@ -29,9 +29,10 @@ module Kramdown
29
29
  # Parse the link definition at the current location.
30
30
  def parse_abbrev_definition
31
31
  @src.pos += @src.matched_size
32
- abbrev_id, abbrev_text = @src[1], @src[2].strip
33
- warning("Duplicate abbreviation ID '#{abbrev_id}' - overwriting") if @doc.parse_infos[:abbrev_defs][abbrev_id]
34
- @doc.parse_infos[:abbrev_defs][abbrev_id] = abbrev_text
32
+ abbrev_id, abbrev_text = @src[1], @src[2]
33
+ abbrev_text.strip!
34
+ warning("Duplicate abbreviation ID '#{abbrev_id}' - overwriting") if @root.options[:abbrev_defs][abbrev_id]
35
+ @root.options[:abbrev_defs][abbrev_id] = abbrev_text
35
36
  @tree.children << Element.new(:eob, :abbrev_def)
36
37
  true
37
38
  end
@@ -39,21 +40,25 @@ module Kramdown
39
40
 
40
41
  # Replace the abbreviation text with elements.
41
42
  def replace_abbreviations(el, regexps = nil)
42
- return if @doc.parse_infos[:abbrev_defs].empty?
43
+ return if @root.options[:abbrev_defs].empty?
43
44
  if !regexps
44
- regexps = [Regexp.union(*@doc.parse_infos[:abbrev_defs].keys.map {|k| /#{Regexp.escape(k)}/})]
45
+ regexps = [Regexp.union(*@root.options[:abbrev_defs].keys.map {|k| /#{Regexp.escape(k)}/})]
45
46
  regexps << /(?=(?:\W|^)#{regexps.first}(?!\w))/ # regexp should only match on word boundaries
46
47
  end
47
48
  el.children.map! do |child|
48
49
  if child.type == :text
49
- result = []
50
- strscan = StringScanner.new(child.value)
51
- while temp = strscan.scan_until(regexps.last)
52
- temp += strscan.scan(/\W|^/)
53
- abbr = strscan.scan(regexps.first)
54
- result += [Element.new(:text, temp), Element.new(:abbreviation, abbr)]
50
+ if child.value =~ regexps.first
51
+ result = []
52
+ strscan = StringScanner.new(child.value)
53
+ while temp = strscan.scan_until(regexps.last)
54
+ temp << strscan.scan(/\W|^/)
55
+ abbr = strscan.scan(regexps.first)
56
+ result << Element.new(:text, temp) << Element.new(:abbreviation, abbr)
57
+ end
58
+ result << Element.new(:text, strscan.rest)
59
+ else
60
+ child
55
61
  end
56
- result + [Element.new(:text, extract_string(strscan.pos..-1, strscan))]
57
62
  else
58
63
  replace_abbreviations(child, regexps)
59
64
  child
@@ -31,7 +31,7 @@ module Kramdown
31
31
  else
32
32
  ACHARS = '[[:alnum:]]'
33
33
  end
34
- AUTOLINK_START_STR = "<((mailto|https?|ftps?):.+?|[-.#{ACHARS}]+@[-#{ACHARS}]+(\.[-#{ACHARS}]+)*\.[a-z]+)>"
34
+ AUTOLINK_START_STR = "<((mailto|https?|ftps?):.+?|[-.#{ACHARS}]+@[-#{ACHARS}]+(?:\.[-#{ACHARS}]+)*\.[a-z]+)>"
35
35
  if RUBY_VERSION < '1.9.0'
36
36
  AUTOLINK_START = /#{AUTOLINK_START_STR}/u
37
37
  else
@@ -41,8 +41,7 @@ module Kramdown
41
41
  # Parse the autolink at the current location.
42
42
  def parse_autolink
43
43
  @src.pos += @src.matched_size
44
- href = @src[1]
45
- href= "mailto:#{href}" if @src[2].nil?
44
+ href = (@src[2].nil? ? "mailto:#{@src[1]}" : @src[1])
46
45
  el = Element.new(:a, nil, {'href' => href})
47
46
  add_text(@src[1].sub(/^mailto:/, ''), el)
48
47
  @tree.children << el
@@ -30,7 +30,7 @@ module Kramdown
30
30
  def parse_blank_line
31
31
  @src.pos += @src.matched_size
32
32
  if @tree.children.last && @tree.children.last.type == :blank
33
- @tree.children.last.value += @src.matched
33
+ @tree.children.last.value << @src.matched
34
34
  else
35
35
  @tree.children << new_block_el(:blank, @src.matched)
36
36
  end
@@ -20,7 +20,7 @@
20
20
  #++
21
21
  #
22
22
 
23
- require 'kramdown/parser/kramdown/attribute_list'
23
+ require 'kramdown/parser/kramdown/extensions'
24
24
  require 'kramdown/parser/kramdown/blank_line'
25
25
  require 'kramdown/parser/kramdown/eob'
26
26
 
@@ -38,7 +38,7 @@ module Kramdown
38
38
 
39
39
  # Return +true+ if we are before a block boundary.
40
40
  def before_block_boundary?
41
- @src.check(BLOCK_BOUNDARY)
41
+ @src.check(self.class::BLOCK_BOUNDARY)
42
42
  end
43
43
 
44
44
  end
@@ -21,7 +21,7 @@
21
21
  #
22
22
 
23
23
  require 'kramdown/parser/kramdown/blank_line'
24
- require 'kramdown/parser/kramdown/attribute_list'
24
+ require 'kramdown/parser/kramdown/extensions'
25
25
  require 'kramdown/parser/kramdown/eob'
26
26
 
27
27
  module Kramdown
@@ -35,7 +35,7 @@ module Kramdown
35
35
  def parse_blockquote
36
36
  el = new_block_el(:blockquote)
37
37
  @tree.children << el
38
- parse_blocks(el, @src.scan(BLOCKQUOTE_MATCH).gsub!(BLOCKQUOTE_START, ''))
38
+ parse_blocks(el, @src.scan(self.class::BLOCKQUOTE_MATCH).gsub!(BLOCKQUOTE_START, ''))
39
39
  true
40
40
  end
41
41
  define_parser(:blockquote, BLOCKQUOTE_START)
@@ -21,7 +21,7 @@
21
21
  #
22
22
 
23
23
  require 'kramdown/parser/kramdown/blank_line'
24
- require 'kramdown/parser/kramdown/attribute_list'
24
+ require 'kramdown/parser/kramdown/extensions'
25
25
  require 'kramdown/parser/kramdown/eob'
26
26
  require 'kramdown/parser/kramdown/paragraph'
27
27
 
@@ -34,7 +34,10 @@ module Kramdown
34
34
 
35
35
  # Parse the indented codeblock at the current location.
36
36
  def parse_codeblock
37
- @tree.children << new_block_el(:codeblock, @src.scan(CODEBLOCK_MATCH).gsub(/\n( {0,3}\S)/, ' \\1').gsub!(INDENT, ''))
37
+ data = @src.scan(self.class::CODEBLOCK_MATCH)
38
+ data.gsub!(/\n( {0,3}\S)/, ' \\1')
39
+ data.gsub!(INDENT, '')
40
+ @tree.children << new_block_el(:codeblock, data)
38
41
  true
39
42
  end
40
43
  define_parser(:codeblock, CODEBLOCK_START)
@@ -37,8 +37,7 @@ module Kramdown
37
37
  return
38
38
  end
39
39
 
40
- text = @src.scan_until(/#{result}/)
41
- if text
40
+ if text = @src.scan_until(/#{result}/)
42
41
  text.sub!(/#{result}\Z/, '')
43
42
  if !simple
44
43
  text = text[1..-1] if text[0..0] == ' '
@@ -30,7 +30,7 @@ module Kramdown
30
30
  def parse_emphasis
31
31
  result = @src.scan(EMPHASIS_START)
32
32
  element = (result.length == 2 ? :strong : :em)
33
- type = (result =~ /_/ ? '_' : '*')
33
+ type = result[0..0]
34
34
  reset_pos = @src.pos
35
35
 
36
36
  if (type == '_' && @src.pre_match =~ /[[:alpha:]]\z/ && @src.check(/[[:alpha:]]/)) || @src.check(/\s/) ||
@@ -24,7 +24,7 @@ module Kramdown
24
24
  module Parser
25
25
  class Kramdown
26
26
 
27
- ESCAPED_CHARS = /\\([\\.*_+`()\[\]{}#!:|"'\$=-])/
27
+ ESCAPED_CHARS = /\\([\\.*_+`<>()\[\]{}#!:|"'\$=-])/
28
28
 
29
29
  # Parse the backslash-escaped character at the current location.
30
30
  def parse_escaped_chars
@@ -0,0 +1,204 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ #--
4
+ # Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
5
+ #
6
+ # This file is part of kramdown.
7
+ #
8
+ # kramdown is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ #++
21
+ #
22
+
23
+ module Kramdown
24
+ module Parser
25
+ class Kramdown
26
+
27
+ IAL_CLASS_ATTR = 'class'
28
+
29
+ # Parse the string +str+ and extract all attributes and add all found attributes to the hash
30
+ # +opts+.
31
+ def parse_attribute_list(str, opts)
32
+ str.scan(ALD_TYPE_ANY).each do |key, sep, val, id_attr, class_attr, ref|
33
+ if ref
34
+ (opts[:refs] ||= []) << ref
35
+ elsif class_attr
36
+ opts[IAL_CLASS_ATTR] = (opts[IAL_CLASS_ATTR] || '') << " #{class_attr}"
37
+ opts[IAL_CLASS_ATTR].lstrip!
38
+ elsif id_attr
39
+ opts['id'] = id_attr
40
+ else
41
+ val.gsub!(/\\(\}|#{sep})/, "\\1")
42
+ opts[key] = val
43
+ end
44
+ end
45
+ end
46
+
47
+ # Update the +ial+ with the information from the inline attribute list +opts+.
48
+ def update_ial_with_ial(ial, opts)
49
+ (ial[:refs] ||= []) << opts[:refs]
50
+ opts.each do |k,v|
51
+ if k == IAL_CLASS_ATTR
52
+ ial[k] = (ial[k] || '') << " #{v}"
53
+ ial[k].lstrip!
54
+ elsif k.kind_of?(String)
55
+ ial[k] = v
56
+ end
57
+ end
58
+ end
59
+
60
+ # Parse the generic extension at the current point. The parameter +type+ can either be
61
+ # <tt>:block</tt> or <tt>:span</tt> depending whether we parse a block or span extension tag.
62
+ def parse_extension_start_tag(type)
63
+ orig_pos = @src.pos
64
+ @src.pos += @src.matched_size
65
+
66
+ error_block = lambda do |msg|
67
+ warning(msg)
68
+ @src.pos = orig_pos
69
+ add_text(@src.getch) if type == :span
70
+ false
71
+ end
72
+
73
+ if @src[4] || @src.matched == '{:/}'
74
+ name = (@src[4] ? "for '#{@src[4]}' " : '')
75
+ return error_block.call("Invalid extension stop tag #{name}found - ignoring it")
76
+ end
77
+
78
+ ext = @src[1]
79
+ opts = {}
80
+ body = nil
81
+ parse_attribute_list(@src[2] || '', opts)
82
+
83
+ if !@src[3]
84
+ stop_re = (type == :block ? /#{EXT_BLOCK_STOP_STR % ext}/ : /#{EXT_STOP_STR % ext}/)
85
+ if result = @src.scan_until(stop_re)
86
+ body = result.sub!(stop_re, '')
87
+ body.chomp! if type == :block
88
+ else
89
+ return error_block.call("No stop tag for extension '#{ext}' found - ignoring it")
90
+ end
91
+ end
92
+
93
+ if !handle_extension(ext, opts, body, type)
94
+ error_block.call("Invalid extension with name '#{ext}' specified - ignoring it")
95
+ else
96
+ true
97
+ end
98
+ end
99
+
100
+ def handle_extension(name, opts, body, type)
101
+ case name
102
+ when 'comment'
103
+ @tree.children << Element.new(:comment, body, nil, :category => type) if body.kind_of?(String)
104
+ true
105
+ when 'nomarkdown'
106
+ @tree.children << Element.new(:raw, body, nil, :category => type, :type => opts['type'].to_s.split(/\s+/)) if body.kind_of?(String)
107
+ true
108
+ when 'options'
109
+ opts.select do |k,v|
110
+ k = k.to_sym
111
+ if Kramdown::Options.defined?(k)
112
+ begin
113
+ val = Kramdown::Options.parse(k, v)
114
+ @options[k] = val
115
+ (@root.options[:options] ||= {})[k] = val
116
+ rescue
117
+ end
118
+ false
119
+ else
120
+ true
121
+ end
122
+ end.each do |k,v|
123
+ warning("Unknown kramdown option '#{k}'")
124
+ end
125
+ @tree.children << Element.new(:eob, :extension) if type == :block
126
+ true
127
+ else
128
+ false
129
+ end
130
+ end
131
+
132
+
133
+ ALD_ID_CHARS = /[\w-]/
134
+ ALD_ANY_CHARS = /\\\}|[^\}]/
135
+ ALD_ID_NAME = /\w#{ALD_ID_CHARS}*/
136
+ ALD_TYPE_KEY_VALUE_PAIR = /(#{ALD_ID_NAME})=("|')((?:\\\}|\\\2|[^\}\2])*?)\2/
137
+ ALD_TYPE_CLASS_NAME = /\.(#{ALD_ID_NAME})/
138
+ ALD_TYPE_ID_NAME = /#(\w[\w:-]*)/
139
+ ALD_TYPE_REF = /(#{ALD_ID_NAME})/
140
+ ALD_TYPE_ANY = /(?:\A|\s)(?:#{ALD_TYPE_KEY_VALUE_PAIR}|#{ALD_TYPE_ID_NAME}|#{ALD_TYPE_CLASS_NAME}|#{ALD_TYPE_REF})(?=\s|\Z)/
141
+ ALD_START = /^#{OPT_SPACE}\{:(#{ALD_ID_NAME}):(#{ALD_ANY_CHARS}+)\}\s*?\n/
142
+
143
+ EXT_STOP_STR = "\\{:/(%s)?\\}"
144
+ EXT_START_STR = "\\{::(\\w+)(?:\\s(#{ALD_ANY_CHARS}*?)|)(\\/)?\\}"
145
+ EXT_BLOCK_START = /^#{OPT_SPACE}(?:#{EXT_START_STR}|#{EXT_STOP_STR % ALD_ID_NAME})\s*?\n/
146
+ EXT_BLOCK_STOP_STR = "^#{OPT_SPACE}#{EXT_STOP_STR}\s*?\n"
147
+
148
+ IAL_BLOCK = /\{:(?!:|\/)(#{ALD_ANY_CHARS}+)\}\s*?\n/
149
+ IAL_BLOCK_START = /^#{OPT_SPACE}#{IAL_BLOCK}/
150
+
151
+ BLOCK_EXTENSIONS_START = /^#{OPT_SPACE}\{:/
152
+
153
+ # Parse one of the block extensions (ALD, block IAL or generic extension) at the current
154
+ # location.
155
+ def parse_block_extensions
156
+ if @src.scan(ALD_START)
157
+ parse_attribute_list(@src[2], @alds[@src[1]] ||= Utils::OrderedHash.new)
158
+ @tree.children << Element.new(:eob, :ald)
159
+ true
160
+ elsif @src.check(EXT_BLOCK_START)
161
+ parse_extension_start_tag(:block)
162
+ elsif @src.scan(IAL_BLOCK_START)
163
+ if @tree.children.last && @tree.children.last.type != :blank && @tree.children.last.type != :eob
164
+ parse_attribute_list(@src[1], @tree.children.last.options[:ial] ||= Utils::OrderedHash.new)
165
+ @tree.children << Element.new(:eob, :ial) unless @src.check(IAL_BLOCK_START)
166
+ else
167
+ parse_attribute_list(@src[1], @block_ial = Utils::OrderedHash.new)
168
+ end
169
+ true
170
+ else
171
+ false
172
+ end
173
+ end
174
+ define_parser(:block_extensions, BLOCK_EXTENSIONS_START)
175
+
176
+
177
+ EXT_SPAN_START = /#{EXT_START_STR}|#{EXT_STOP_STR % ALD_ID_NAME}/
178
+ IAL_SPAN_START = /\{:(#{ALD_ANY_CHARS}+)\}/
179
+ SPAN_EXTENSIONS_START = /\{:/
180
+
181
+ # Parse the extension span at the current location.
182
+ def parse_span_extensions
183
+ if @src.check(EXT_SPAN_START)
184
+ parse_extension_start_tag(:span)
185
+ elsif @src.check(IAL_SPAN_START)
186
+ if @tree.children.last && @tree.children.last.type != :text
187
+ @src.pos += @src.matched_size
188
+ attr = Utils::OrderedHash.new
189
+ parse_attribute_list(@src[1], attr)
190
+ update_ial_with_ial(@tree.children.last.options[:ial] ||= Utils::OrderedHash.new, attr)
191
+ update_attr_with_ial(@tree.children.last.attr, attr)
192
+ else
193
+ warning("Found span IAL after text - ignoring it")
194
+ add_text(@src.getch)
195
+ end
196
+ else
197
+ add_text(@src.getch)
198
+ end
199
+ end
200
+ define_parser(:span_extensions, SPAN_EXTENSIONS_START, '\{:')
201
+
202
+ end
203
+ end
204
+ end