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.
- data/CONTRIBUTERS +1 -1
- data/ChangeLog +532 -0
- data/README +22 -12
- data/Rakefile +9 -8
- data/VERSION +1 -1
- data/benchmark/benchmark.sh +61 -0
- data/benchmark/generate_data.rb +57 -55
- data/benchmark/testing.sh +1 -1
- data/benchmark/timing.sh +3 -3
- data/bin/kramdown +1 -2
- data/data/kramdown/document.html +2 -2
- data/data/kramdown/document.latex +2 -2
- data/doc/default.scss.css +6 -1
- data/doc/default.template +1 -1
- data/doc/documentation.page +1 -1
- data/doc/index.page +9 -7
- data/doc/installation.page +2 -3
- data/doc/links.markdown +1 -1
- data/doc/quickref.page +19 -19
- data/doc/syntax.page +117 -98
- data/doc/tests.page +8 -7
- data/lib/kramdown/compatibility.rb +2 -1
- data/lib/kramdown/converter.rb +5 -7
- data/lib/kramdown/converter/base.rb +87 -32
- data/lib/kramdown/converter/html.rb +134 -122
- data/lib/kramdown/converter/kramdown.rb +24 -25
- data/lib/kramdown/converter/latex.rb +65 -55
- data/lib/kramdown/document.rb +487 -42
- data/lib/kramdown/error.rb +3 -0
- data/lib/kramdown/options.rb +83 -28
- data/lib/kramdown/parser.rb +5 -5
- data/lib/kramdown/parser/base.rb +55 -13
- data/lib/kramdown/parser/html.rb +83 -71
- data/lib/kramdown/parser/kramdown.rb +73 -54
- data/lib/kramdown/parser/kramdown/abbreviation.rb +17 -12
- data/lib/kramdown/parser/kramdown/autolink.rb +2 -3
- data/lib/kramdown/parser/kramdown/blank_line.rb +1 -1
- data/lib/kramdown/parser/kramdown/block_boundary.rb +2 -2
- data/lib/kramdown/parser/kramdown/blockquote.rb +2 -2
- data/lib/kramdown/parser/kramdown/codeblock.rb +5 -2
- data/lib/kramdown/parser/kramdown/codespan.rb +1 -2
- data/lib/kramdown/parser/kramdown/emphasis.rb +1 -1
- data/lib/kramdown/parser/kramdown/escaped_chars.rb +1 -1
- data/lib/kramdown/parser/kramdown/extensions.rb +204 -0
- data/lib/kramdown/parser/kramdown/footnote.rb +7 -7
- data/lib/kramdown/parser/kramdown/header.rb +4 -2
- data/lib/kramdown/parser/kramdown/horizontal_rule.rb +1 -1
- data/lib/kramdown/parser/kramdown/html.rb +39 -45
- data/lib/kramdown/parser/kramdown/link.rb +19 -29
- data/lib/kramdown/parser/kramdown/list.rb +13 -13
- data/lib/kramdown/parser/kramdown/math.rb +1 -1
- data/lib/kramdown/parser/kramdown/paragraph.rb +5 -4
- data/lib/kramdown/parser/kramdown/smart_quotes.rb +1 -1
- data/lib/kramdown/parser/kramdown/table.rb +51 -12
- data/lib/kramdown/parser/markdown.rb +69 -0
- data/lib/kramdown/utils.rb +2 -2
- data/lib/kramdown/utils/entities.rb +10 -1
- data/lib/kramdown/utils/html.rb +22 -11
- data/lib/kramdown/utils/ordered_hash.rb +44 -40
- data/lib/kramdown/version.rb +1 -1
- data/man/man1/kramdown.1 +31 -4
- data/test/testcases/block/08_list/item_ial.html +1 -1
- data/test/testcases/block/11_ial/nested.html +11 -0
- data/test/testcases/block/11_ial/nested.text +15 -0
- data/test/testcases/block/13_definition_list/item_ial.html +1 -1
- data/test/testcases/block/14_table/escaping.html +52 -0
- data/test/testcases/block/14_table/escaping.text +19 -0
- data/test/testcases/block/14_table/simple.html.19 +139 -0
- data/test/testcases/block/14_table/simple.text +1 -1
- data/test/testcases/block/15_math/normal.html +13 -13
- data/test/testcases/block/16_toc/{no_toc_depth.html → no_toc.html} +0 -0
- data/test/testcases/block/16_toc/{no_toc_depth.options → no_toc.options} +0 -0
- data/test/testcases/block/16_toc/{no_toc_depth.text → no_toc.text} +0 -0
- data/test/testcases/block/16_toc/{toc_depth_2.html → toc_levels.html} +4 -4
- data/test/testcases/block/16_toc/toc_levels.options +1 -0
- data/test/testcases/block/16_toc/{toc_depth_2.text → toc_levels.text} +0 -0
- data/test/testcases/span/escaped_chars/normal.html +4 -0
- data/test/testcases/span/escaped_chars/normal.text +4 -0
- data/test/testcases/span/ial/simple.html +1 -1
- data/test/testcases/span/math/normal.html +2 -2
- metadata +20 -25
- data/benchmark/historic-jruby-1.4.0.dat +0 -7
- data/benchmark/historic-ruby-1.8.6.dat +0 -7
- data/benchmark/historic-ruby-1.8.7.dat +0 -7
- data/benchmark/historic-ruby-1.9.1p243.dat +0 -7
- data/benchmark/historic-ruby-1.9.2dev.dat +0 -7
- data/benchmark/static-jruby-1.4.0.dat +0 -7
- data/benchmark/static-ruby-1.8.6.dat +0 -7
- data/benchmark/static-ruby-1.8.7.dat +0 -7
- data/benchmark/static-ruby-1.9.1p243.dat +0 -7
- data/benchmark/static-ruby-1.9.2dev.dat +0 -7
- data/lib/kramdown/parser/kramdown/attribute_list.rb +0 -111
- data/lib/kramdown/parser/kramdown/extension.rb +0 -116
- 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
|
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(
|
51
|
-
# super
|
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
|
-
|
77
|
-
|
78
|
-
|
76
|
+
# Create a new Kramdown parser object with the given +options+.
|
77
|
+
def initialize(source, options)
|
78
|
+
super
|
79
79
|
|
80
|
-
|
81
|
-
def initialize(doc)
|
82
|
-
super(doc)
|
80
|
+
reset_env
|
83
81
|
|
84
|
-
@
|
85
|
-
@
|
86
|
-
@
|
87
|
-
@
|
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, :
|
98
|
-
:
|
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
|
-
:
|
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
|
107
|
-
def parse
|
98
|
+
# The source string provided on initialization is parsed into the <tt>@root</tt> element.
|
99
|
+
def parse
|
108
100
|
configure_parser
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
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
|
169
|
-
# (resets
|
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
|
-
|
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
|
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
|
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.
|
219
|
+
add_text(@src.getch) if !processed && !stop_re_found
|
225
220
|
else
|
226
|
-
add_text(@src.
|
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
|
-
#
|
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 = @
|
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 ==
|
243
|
-
attr[k] = (
|
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
|
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
|
-
#
|
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/
|
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]
|
33
|
-
|
34
|
-
@
|
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 @
|
43
|
+
return if @root.options[:abbrev_defs].empty?
|
43
44
|
if !regexps
|
44
|
-
regexps = [Regexp.union(*@
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
temp
|
53
|
-
|
54
|
-
|
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}]+(
|
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
|
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/
|
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/
|
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/
|
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
|
-
|
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)
|
@@ -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 =
|
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/) ||
|
@@ -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
|