kramdown 0.3.0 → 0.4.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/ChangeLog +243 -0
- data/Rakefile +0 -17
- data/VERSION +1 -1
- data/benchmark/historic-jruby-1.4.0.dat +7 -7
- data/benchmark/historic-ruby-1.8.6.dat +7 -7
- data/benchmark/historic-ruby-1.8.7.dat +7 -7
- data/benchmark/historic-ruby-1.9.1p243.dat +7 -7
- data/benchmark/historic-ruby-1.9.2dev.dat +7 -7
- data/benchmark/testing.sh +1 -1
- data/doc/index.page +4 -3
- data/doc/quickref.page +31 -1
- data/doc/syntax.page +166 -12
- data/lib/kramdown/converter.rb +107 -56
- data/lib/kramdown/document.rb +16 -1
- data/lib/kramdown/extension.rb +19 -0
- data/lib/kramdown/parser/kramdown.rb +2 -1
- data/lib/kramdown/parser/kramdown/attribute_list.rb +2 -2
- data/lib/kramdown/parser/kramdown/codeblock.rb +5 -9
- data/lib/kramdown/parser/kramdown/escaped_chars.rb +1 -1
- data/lib/kramdown/parser/kramdown/footnote.rb +2 -1
- data/lib/kramdown/parser/kramdown/table.rb +125 -0
- data/lib/kramdown/version.rb +1 -1
- data/test/testcases/block/06_codeblock/with_blank_line.text +1 -0
- data/test/testcases/block/09_html/parse_as_span.html +3 -0
- data/test/testcases/block/09_html/parse_as_span.text +2 -0
- data/test/testcases/block/11_ial/simple.html +3 -0
- data/test/testcases/block/11_ial/simple.text +4 -2
- data/test/testcases/block/12_extension/options.html +1 -1
- data/test/testcases/block/12_extension/options2.html +1 -1
- data/test/testcases/block/12_extension/options3.html +7 -0
- data/test/testcases/block/12_extension/options3.text +7 -0
- data/test/testcases/block/13_definition_list/no_def_list.html +2 -0
- data/test/testcases/block/13_definition_list/no_def_list.text +2 -0
- data/test/testcases/block/14_table/errors.html +3 -0
- data/test/testcases/block/14_table/errors.text +3 -0
- data/test/testcases/block/14_table/footer.html +65 -0
- data/test/testcases/block/14_table/footer.text +25 -0
- data/test/testcases/block/14_table/header.html +103 -0
- data/test/testcases/block/14_table/header.text +32 -0
- data/test/testcases/block/14_table/no_table.html +3 -0
- data/test/testcases/block/14_table/no_table.text +3 -0
- data/test/testcases/block/14_table/simple.html +61 -0
- data/test/testcases/block/14_table/simple.text +16 -0
- data/test/testcases/span/04_footnote/footnote_nr.html +1 -1
- data/test/testcases/span/04_footnote/markers.html +1 -1
- data/test/testcases/span/escaped_chars/normal.html +4 -0
- data/test/testcases/span/escaped_chars/normal.text +4 -0
- metadata +17 -2
data/lib/kramdown/converter.rb
CHANGED
@@ -31,6 +31,15 @@ module Kramdown
|
|
31
31
|
# Converts a Kramdown::Document to HTML.
|
32
32
|
class Html
|
33
33
|
|
34
|
+
INDENTATION = 2
|
35
|
+
|
36
|
+
begin
|
37
|
+
require 'coderay'
|
38
|
+
HIGHLIGHTING_AVAILABLE = true
|
39
|
+
rescue LoadError => e
|
40
|
+
HIGHLIGHTING_AVAILABLE = false
|
41
|
+
end
|
42
|
+
|
34
43
|
# Initialize the HTML converter with the given Kramdown document +doc+.
|
35
44
|
def initialize(doc)
|
36
45
|
@doc = doc
|
@@ -45,94 +54,107 @@ module Kramdown
|
|
45
54
|
end
|
46
55
|
|
47
56
|
# Convert the element tree +el+, setting the indentation level to +indent+.
|
48
|
-
def convert(el, indent = -
|
57
|
+
def convert(el, indent = -INDENTATION, opts = {})
|
58
|
+
send("convert_#{el.type}", el, indent, opts)
|
59
|
+
end
|
60
|
+
|
61
|
+
def inner(el, indent, opts)
|
49
62
|
result = ''
|
63
|
+
indent += INDENTATION
|
50
64
|
el.children.each do |inner_el|
|
51
|
-
result <<
|
65
|
+
result << send("convert_#{inner_el.type}", inner_el, indent, opts)
|
52
66
|
end
|
53
|
-
|
67
|
+
result
|
54
68
|
end
|
55
69
|
|
56
|
-
def convert_blank(el,
|
70
|
+
def convert_blank(el, indent, opts)
|
57
71
|
"\n"
|
58
72
|
end
|
59
73
|
|
60
|
-
def convert_text(el,
|
74
|
+
def convert_text(el, indent, opts)
|
61
75
|
escape_html(el.value, false)
|
62
76
|
end
|
63
77
|
|
64
|
-
def convert_eob(el,
|
78
|
+
def convert_eob(el, indent, opts)
|
65
79
|
''
|
66
80
|
end
|
67
81
|
|
68
|
-
def convert_p(el,
|
69
|
-
"#{' '*indent}<p#{options_for_element(el)}>#{inner}</p>\n"
|
82
|
+
def convert_p(el, indent, opts)
|
83
|
+
"#{' '*indent}<p#{options_for_element(el)}>#{inner(el, indent, opts)}</p>\n"
|
70
84
|
end
|
71
85
|
|
72
|
-
def convert_codeblock(el,
|
73
|
-
|
74
|
-
|
75
|
-
result.
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
86
|
+
def convert_codeblock(el, indent, opts)
|
87
|
+
if el.options[:attr] && el.options[:attr]['lang'] && HIGHLIGHTING_AVAILABLE && @doc.options[:coderay]
|
88
|
+
el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
|
89
|
+
result = CodeRay.scan(el.value, el.options[:attr].delete('lang').to_sym).html(@doc.options[:coderay]).chomp + "\n"
|
90
|
+
"#{' '*indent}<div#{options_for_element(el)}>#{result}#{' '*indent}</div>\n"
|
91
|
+
else
|
92
|
+
result = escape_html(el.value)
|
93
|
+
if el.options[:attr] && el.options[:attr].has_key?('class') && el.options[:attr]['class'] =~ /\bshow-whitespaces\b/
|
94
|
+
result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m|
|
95
|
+
suffix = ($1 ? '-l' : ($2 ? '-r' : ''))
|
96
|
+
m.scan(/./).map do |c|
|
97
|
+
case c
|
98
|
+
when "\t" then "<span class=\"ws-tab#{suffix}\">\t</span>"
|
99
|
+
when " " then "<span class=\"ws-space#{suffix}\">⋅</span>"
|
100
|
+
end
|
101
|
+
end.join('')
|
102
|
+
end
|
83
103
|
end
|
104
|
+
"#{' '*indent}<pre#{options_for_element(el)}><code>#{result}#{result =~ /\n\Z/ ? '' : "\n"}</code></pre>\n"
|
84
105
|
end
|
85
|
-
"#{' '*indent}<pre#{options_for_element(el)}><code>#{result}#{result =~ /\n\Z/ ? '' : "\n"}</code></pre>\n"
|
86
106
|
end
|
87
107
|
|
88
|
-
def convert_blockquote(el,
|
89
|
-
"#{' '*indent}<blockquote#{options_for_element(el)}>\n#{inner}#{' '*indent}</blockquote>\n"
|
108
|
+
def convert_blockquote(el, indent, opts)
|
109
|
+
"#{' '*indent}<blockquote#{options_for_element(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</blockquote>\n"
|
90
110
|
end
|
91
111
|
|
92
|
-
def convert_header(el,
|
93
|
-
"#{' '*indent}<h#{el.options[:level]}#{options_for_element(el)}>#{inner}</h#{el.options[:level]}>\n"
|
112
|
+
def convert_header(el, indent, opts)
|
113
|
+
"#{' '*indent}<h#{el.options[:level]}#{options_for_element(el)}>#{inner(el, indent, opts)}</h#{el.options[:level]}>\n"
|
94
114
|
end
|
95
115
|
|
96
|
-
def convert_hr(el,
|
116
|
+
def convert_hr(el, indent, opts)
|
97
117
|
"#{' '*indent}<hr />\n"
|
98
118
|
end
|
99
119
|
|
100
|
-
def convert_ul(el,
|
101
|
-
"#{' '*indent}<#{el.type}#{options_for_element(el)}>\n#{inner}#{' '*indent}</#{el.type}>\n"
|
120
|
+
def convert_ul(el, indent, opts)
|
121
|
+
"#{' '*indent}<#{el.type}#{options_for_element(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</#{el.type}>\n"
|
102
122
|
end
|
103
123
|
alias :convert_ol :convert_ul
|
104
124
|
alias :convert_dl :convert_ul
|
105
125
|
|
106
|
-
def convert_li(el,
|
126
|
+
def convert_li(el, indent, opts)
|
107
127
|
output = ' '*indent << "<#{el.type}" << options_for_element(el) << ">"
|
128
|
+
res = inner(el, indent, opts)
|
108
129
|
if el.options[:first_is_block]
|
109
|
-
output << "\n" <<
|
130
|
+
output << "\n" << res << ' '*indent
|
110
131
|
else
|
111
|
-
output <<
|
132
|
+
output << res << (res =~ /\n\Z/ ? ' '*indent : '')
|
112
133
|
end
|
113
134
|
output << "</#{el.type}>\n"
|
114
135
|
end
|
115
136
|
alias :convert_dd :convert_li
|
116
137
|
|
117
|
-
def convert_dt(el,
|
118
|
-
"#{' '*indent}<dt#{options_for_element(el)}>#{inner}</dt>\n"
|
138
|
+
def convert_dt(el, indent, opts)
|
139
|
+
"#{' '*indent}<dt#{options_for_element(el)}>#{inner(el, indent, opts)}</dt>\n"
|
119
140
|
end
|
120
141
|
|
121
142
|
HTML_TAGS_WITH_BODY=['div', 'script']
|
122
143
|
|
123
|
-
def convert_html_element(el,
|
144
|
+
def convert_html_element(el, indent, opts)
|
145
|
+
res = inner(el, indent, opts)
|
124
146
|
if @doc.options[:filter_html].include?(el.value)
|
125
|
-
|
147
|
+
res.chomp + (el.options[:type] == :block ? "\n" : '')
|
126
148
|
elsif el.options[:type] == :span
|
127
|
-
"<#{el.value}#{options_for_element(el)}" << (!
|
149
|
+
"<#{el.value}#{options_for_element(el)}" << (!res.empty? ? ">#{res}</#{el.value}>" : " />")
|
128
150
|
else
|
129
151
|
output = ''
|
130
152
|
output << ' '*indent if el.options[:parse_type] != :raw && !el.options[:parent_is_raw]
|
131
153
|
output << "<#{el.value}#{options_for_element(el)}"
|
132
|
-
if !
|
133
|
-
output << ">#{
|
134
|
-
elsif !
|
135
|
-
output << ">\n#{
|
154
|
+
if !res.empty? && el.options[:parse_type] != :block
|
155
|
+
output << ">#{res}</#{el.value}>"
|
156
|
+
elsif !res.empty?
|
157
|
+
output << ">\n#{res}" << ' '*indent << "</#{el.value}>"
|
136
158
|
elsif HTML_TAGS_WITH_BODY.include?(el.value)
|
137
159
|
output << "></#{el.value}>"
|
138
160
|
else
|
@@ -143,55 +165,84 @@ module Kramdown
|
|
143
165
|
end
|
144
166
|
end
|
145
167
|
|
146
|
-
def convert_html_text(el,
|
168
|
+
def convert_html_text(el, indent, opts)
|
147
169
|
escape_html(el.value, false)
|
148
170
|
end
|
149
171
|
|
150
|
-
def convert_xml_comment(el,
|
172
|
+
def convert_xml_comment(el, indent, opts)
|
151
173
|
el.value + (el.options[:type] == :block ? "\n" : '')
|
152
174
|
end
|
153
175
|
alias :convert_xml_pi :convert_xml_comment
|
154
176
|
|
155
|
-
def
|
177
|
+
def convert_table(el, indent, opts)
|
178
|
+
if el.options[:alignment].all? {|a| a == :default}
|
179
|
+
alignment = ''
|
180
|
+
else
|
181
|
+
alignment = el.options[:alignment].map do |a|
|
182
|
+
"#{' '*(indent + INDENTATION)}" + (a == :default ? "<col />" : "<col align=\"#{a}\" />") + "\n"
|
183
|
+
end.join('')
|
184
|
+
end
|
185
|
+
"#{' '*indent}<table#{options_for_element(el)}>\n#{alignment}#{inner(el, indent, opts)}#{' '*indent}</table>\n"
|
186
|
+
end
|
187
|
+
|
188
|
+
def convert_thead(el, indent, opts)
|
189
|
+
opts[:cell_type] = case el.type
|
190
|
+
when :thead then 'th'
|
191
|
+
when :tbody, :tfoot then 'td'
|
192
|
+
else opts[:cell_type]
|
193
|
+
end
|
194
|
+
"#{' '*indent}<#{el.type}#{options_for_element(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</#{el.type}>\n"
|
195
|
+
end
|
196
|
+
alias :convert_tbody :convert_thead
|
197
|
+
alias :convert_tfoot :convert_thead
|
198
|
+
alias :convert_tr :convert_thead
|
199
|
+
|
200
|
+
def convert_td(el, indent, opts)
|
201
|
+
res = inner(el, indent, opts)
|
202
|
+
"#{' '*indent}<#{opts[:cell_type]}#{options_for_element(el)}>#{res.empty? ? " " : res}</#{opts[:cell_type]}>\n"
|
203
|
+
end
|
204
|
+
|
205
|
+
def convert_br(el, indent, opts)
|
156
206
|
"<br />"
|
157
207
|
end
|
158
208
|
|
159
|
-
def convert_a(el,
|
209
|
+
def convert_a(el, indent, opts)
|
160
210
|
if el.options[:attr]['href'] =~ /^mailto:/
|
161
211
|
el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
|
162
212
|
href = obfuscate(el.options[:attr]['href'].sub(/^mailto:/, ''))
|
163
213
|
mailto = obfuscate('mailto')
|
164
214
|
el.options[:attr]['href'] = "#{mailto}:#{href}"
|
165
215
|
end
|
166
|
-
|
167
|
-
|
216
|
+
res = inner(el, indent, opts)
|
217
|
+
res = obfuscate(res) if el.options[:obfuscate_text]
|
218
|
+
"<a#{options_for_element(el)}>#{res}</a>"
|
168
219
|
end
|
169
220
|
|
170
|
-
def convert_img(el,
|
221
|
+
def convert_img(el, indent, opts)
|
171
222
|
"<img#{options_for_element(el)} />"
|
172
223
|
end
|
173
224
|
|
174
|
-
def convert_codespan(el,
|
225
|
+
def convert_codespan(el, indent, opts)
|
175
226
|
"<code#{options_for_element(el)}>#{escape_html(el.value)}</code>"
|
176
227
|
end
|
177
228
|
|
178
|
-
def convert_footnote(el,
|
229
|
+
def convert_footnote(el, indent, opts)
|
179
230
|
number = @footnote_counter
|
180
231
|
@footnote_counter += 1
|
181
232
|
@footnotes << [el.options[:name], @doc.parse_infos[:footnotes][el.options[:name]]]
|
182
233
|
"<sup id=\"fnref:#{el.options[:name]}\"><a href=\"#fn:#{el.options[:name]}\" rel=\"footnote\">#{number}</a></sup>"
|
183
234
|
end
|
184
235
|
|
185
|
-
def convert_raw(el,
|
236
|
+
def convert_raw(el, indent, opts)
|
186
237
|
el.value
|
187
238
|
end
|
188
239
|
|
189
|
-
def convert_em(el,
|
190
|
-
"<#{el.type}#{options_for_element(el)}>#{inner}</#{el.type}>"
|
240
|
+
def convert_em(el, indent, opts)
|
241
|
+
"<#{el.type}#{options_for_element(el)}>#{inner(el, indent, opts)}</#{el.type}>"
|
191
242
|
end
|
192
243
|
alias :convert_strong :convert_em
|
193
244
|
|
194
|
-
def convert_entity(el,
|
245
|
+
def convert_entity(el, indent, opts)
|
195
246
|
el.value
|
196
247
|
end
|
197
248
|
|
@@ -200,12 +251,12 @@ module Kramdown
|
|
200
251
|
:laquo_space => '« ', :raquo_space => ' »',
|
201
252
|
:laquo => '«', :raquo => '»'
|
202
253
|
}
|
203
|
-
def convert_typographic_sym(el,
|
254
|
+
def convert_typographic_sym(el, indent, opts)
|
204
255
|
TYPOGRAPHIC_SYMS[el.value]
|
205
256
|
end
|
206
257
|
|
207
|
-
def convert_root(el,
|
208
|
-
inner << footnote_content
|
258
|
+
def convert_root(el, indent, opts)
|
259
|
+
inner(el, indent, opts) << footnote_content
|
209
260
|
end
|
210
261
|
|
211
262
|
# Helper method for obfuscating the +text+ by using HTML entities.
|
@@ -234,7 +285,7 @@ module Kramdown
|
|
234
285
|
end
|
235
286
|
para.children << ref
|
236
287
|
end
|
237
|
-
(ol.children.empty? ? '' : "<div class=\"
|
288
|
+
(ol.children.empty? ? '' : "<div class=\"footnotes\">\n#{convert(ol, 2)}</div>\n")
|
238
289
|
end
|
239
290
|
|
240
291
|
# Return the string with the attributes of the element +el+.
|
data/lib/kramdown/document.rb
CHANGED
@@ -50,17 +50,30 @@ module Kramdown
|
|
50
50
|
#
|
51
51
|
# [:auto_ids (used by the parser)]
|
52
52
|
# A boolean value deciding whether automatic header ID generation is used. Default: +false+.
|
53
|
+
#
|
54
|
+
# [:coderay (used by the HTML converter)]
|
55
|
+
# A hash containing options for the CodeRay syntax highlighter. If this is set to +nil+,
|
56
|
+
# syntax highlighting is disabled. When using the +options+ extension, any CodeRay option can
|
57
|
+
# be set by prefixing it with +coderay_+.
|
58
|
+
#
|
59
|
+
# Default:
|
60
|
+
# {:wrap => :div, :line_numbers => :inline, :line_number_start => 1,
|
61
|
+
# :tab_width => 8, :bold_every => 10, :css => :style}
|
62
|
+
#
|
53
63
|
# [:filter_html (used by the HTML converter)]
|
54
64
|
# An array of HTML tag names that defines which tags should be filtered from the output. For
|
55
65
|
# example, if the value contains +iframe+, then all HTML +iframe+ tags are filtered out and
|
56
66
|
# only the body is displayed. Default: empty array. When using the +options+ extension, the
|
57
67
|
# string value needs to hold the HTML tag names separated by one or more spaces.
|
68
|
+
#
|
58
69
|
# [:footnote_nr (used by the HTML converter)]
|
59
70
|
# The initial number used for creating the link to the first footnote. Default: +1+. When
|
60
71
|
# using the +options+ extension, the string value needs to be a valid number.
|
72
|
+
#
|
61
73
|
# [:parse_block_html (used by the parser)]
|
62
74
|
# A boolean value deciding whether kramdown syntax is processed in block HTML tags. Default:
|
63
75
|
# +false+.
|
76
|
+
#
|
64
77
|
# [:parse_span_html (used by the parser)]
|
65
78
|
# A boolean value deciding whether kramdown syntax is processed in span HTML tags. Default:
|
66
79
|
# +true+.
|
@@ -73,7 +86,9 @@ module Kramdown
|
|
73
86
|
:filter_html => [],
|
74
87
|
:auto_ids => true,
|
75
88
|
:parse_block_html => false,
|
76
|
-
:parse_span_html => true
|
89
|
+
:parse_span_html => true,
|
90
|
+
:coderay => {:wrap => :div, :line_numbers => :inline,
|
91
|
+
:line_number_start => 1, :tab_width => 8, :bold_every => 10, :css => :style}
|
77
92
|
}
|
78
93
|
|
79
94
|
|
data/lib/kramdown/extension.rb
CHANGED
@@ -65,6 +65,25 @@ module Kramdown
|
|
65
65
|
if val = opts.delete('parse_span_html')
|
66
66
|
parser.doc.options[:parse_span_html] = boolean_value(val)
|
67
67
|
end
|
68
|
+
if val = opts.delete('coderay_wrap')
|
69
|
+
(parser.doc.options[:coderay] ||= {})[:wrap] = (val.empty? ? nil : val.to_sym)
|
70
|
+
end
|
71
|
+
if val = opts.delete('coderay_css')
|
72
|
+
(parser.doc.options[:coderay] ||= {})[:css] = val.to_sym
|
73
|
+
end
|
74
|
+
if val = opts.delete('coderay_tab_width')
|
75
|
+
(parser.doc.options[:coderay] ||= {})[:tab_width] = val.to_i
|
76
|
+
end
|
77
|
+
if val = opts.delete('coderay_line_numbers')
|
78
|
+
(parser.doc.options[:coderay] ||= {})[:line_numbers] = (val.empty? ? nil : val.to_sym)
|
79
|
+
end
|
80
|
+
if val = opts.delete('coderay_line_number_start')
|
81
|
+
(parser.doc.options[:coderay] ||= {})[:line_number_start] = val.to_i
|
82
|
+
end
|
83
|
+
if val = opts.delete('coderay_bold_every')
|
84
|
+
(parser.doc.options[:coderay] ||= {})[:bold_every] = val.to_i
|
85
|
+
end
|
86
|
+
|
68
87
|
opts.each {|k,v| parser.warning("Unknown kramdown options '#{k}'")}
|
69
88
|
end
|
70
89
|
|
@@ -81,7 +81,7 @@ module Kramdown
|
|
81
81
|
private
|
82
82
|
#######
|
83
83
|
|
84
|
-
BLOCK_PARSERS = [:blank_line, :codeblock, :codeblock_fenced, :blockquote, :atx_header,
|
84
|
+
BLOCK_PARSERS = [:blank_line, :codeblock, :codeblock_fenced, :blockquote, :table, :atx_header,
|
85
85
|
:setext_header, :horizontal_rule, :list, :definition_list, :link_definition, :block_html,
|
86
86
|
:footnote_definition, :ald, :block_ial, :extension_block, :eob_marker, :paragraph]
|
87
87
|
SPAN_PARSERS = [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link,
|
@@ -249,6 +249,7 @@ module Kramdown
|
|
249
249
|
require 'kramdown/parser/kramdown/paragraph'
|
250
250
|
require 'kramdown/parser/kramdown/header'
|
251
251
|
require 'kramdown/parser/kramdown/blockquote'
|
252
|
+
require 'kramdown/parser/kramdown/table'
|
252
253
|
require 'kramdown/parser/kramdown/codeblock'
|
253
254
|
require 'kramdown/parser/kramdown/horizontal_rule'
|
254
255
|
require 'kramdown/parser/kramdown/list'
|
@@ -51,7 +51,7 @@ module Kramdown
|
|
51
51
|
ALD_ID_CHARS = /[\w\d-]/
|
52
52
|
ALD_ANY_CHARS = /\\\}|[^\}]/
|
53
53
|
ALD_ID_NAME = /(?:\w|\d)#{ALD_ID_CHARS}*/
|
54
|
-
ALD_TYPE_KEY_VALUE_PAIR = /(#{ALD_ID_NAME})=("|')((?:\\\}|\\\2|[^\}\2])
|
54
|
+
ALD_TYPE_KEY_VALUE_PAIR = /(#{ALD_ID_NAME})=("|')((?:\\\}|\\\2|[^\}\2])*?)\2/
|
55
55
|
ALD_TYPE_CLASS_NAME = /\.(#{ALD_ID_NAME})/
|
56
56
|
ALD_TYPE_ID_NAME = /#(#{ALD_ID_NAME})/
|
57
57
|
ALD_TYPE_REF = /(#{ALD_ID_NAME})/
|
@@ -72,7 +72,7 @@ module Kramdown
|
|
72
72
|
# Parse the inline attribute list at the current location.
|
73
73
|
def parse_block_ial
|
74
74
|
@src.pos += @src.matched_size
|
75
|
-
if @tree.children.last && @tree.children.last.type != :blank
|
75
|
+
if @tree.children.last && @tree.children.last.type != :blank && @tree.children.last.type != :eob
|
76
76
|
parse_attribute_list(@src[1], @tree.children.last.options[:ial] ||= {})
|
77
77
|
end
|
78
78
|
true
|
@@ -20,23 +20,19 @@
|
|
20
20
|
#++
|
21
21
|
#
|
22
22
|
|
23
|
+
require 'kramdown/parser/kramdown/blank_line'
|
24
|
+
|
23
25
|
module Kramdown
|
24
26
|
module Parser
|
25
27
|
class Kramdown
|
26
28
|
|
27
29
|
CODEBLOCK_START = INDENT
|
28
|
-
|
30
|
+
CODEBLOCK_LINE = /(?:#{INDENT}.*?\S.*?\n)+/
|
31
|
+
CODEBLOCK_MATCH = /(?:#{BLANK_LINE}?#{CODEBLOCK_LINE})*/
|
29
32
|
|
30
33
|
# Parse the indented codeblock at the current location.
|
31
34
|
def parse_codeblock
|
32
|
-
|
33
|
-
children = @tree.children
|
34
|
-
if children.length >= 2 && children[-1].type == :blank && children[-2].type == :codeblock
|
35
|
-
children[-2].value << children[-1].value.gsub(INDENT, '') << result
|
36
|
-
children.pop
|
37
|
-
else
|
38
|
-
@tree.children << Element.new(:codeblock, result)
|
39
|
-
end
|
35
|
+
@tree.children << Element.new(:codeblock, @src.scan(CODEBLOCK_MATCH).gsub!(INDENT, ''))
|
40
36
|
true
|
41
37
|
end
|
42
38
|
define_parser(:codeblock, CODEBLOCK_START)
|
@@ -28,7 +28,7 @@ module Kramdown
|
|
28
28
|
module Parser
|
29
29
|
class Kramdown
|
30
30
|
|
31
|
-
FOOTNOTE_DEFINITION_START = /^#{OPT_SPACE}\[\^(#{ALD_ID_NAME})\]:\s*?(.*?\n(?:#{BLANK_LINE}?#{
|
31
|
+
FOOTNOTE_DEFINITION_START = /^#{OPT_SPACE}\[\^(#{ALD_ID_NAME})\]:\s*?(.*?\n(?:#{BLANK_LINE}?#{CODEBLOCK_LINE})*)/
|
32
32
|
|
33
33
|
# Parse the foot note definition at the current location.
|
34
34
|
def parse_footnote_definition
|
@@ -38,6 +38,7 @@ module Kramdown
|
|
38
38
|
parse_blocks(el, @src[2].gsub(INDENT, ''))
|
39
39
|
warning("Duplicate footnote name '#{@src[1]}' - overwriting") if @doc.parse_infos[:footnotes][@src[1]]
|
40
40
|
(@doc.parse_infos[:footnotes][@src[1]] = {})[:content] = el
|
41
|
+
true
|
41
42
|
end
|
42
43
|
define_parser(:footnote_definition, FOOTNOTE_DEFINITION_START)
|
43
44
|
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# Copyright (C) 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
|
+
require 'kramdown/parser/kramdown/blank_line'
|
24
|
+
require 'kramdown/parser/kramdown/eob'
|
25
|
+
require 'kramdown/parser/kramdown/horizontal_rule'
|
26
|
+
|
27
|
+
module Kramdown
|
28
|
+
module Parser
|
29
|
+
class Kramdown
|
30
|
+
|
31
|
+
TABLE_SEP_LINE = /^#{OPT_SPACE}(?:\||\+)([ ]?:?-[+|: -]*)[ \t]*\n/
|
32
|
+
TABLE_HSEP_ALIGN = /[ ]?(:?)-+(:?)[ ]?/
|
33
|
+
TABLE_FSEP_LINE = /^#{OPT_SPACE}(\||\+)[ ]?:?=[+|: =]*[ \t]*\n/
|
34
|
+
TABLE_ROW_LINE = /^#{OPT_SPACE}\|(.*?)[ \t]*\n/
|
35
|
+
TABLE_START = /^#{OPT_SPACE}\|(?:-|(?!=))/
|
36
|
+
|
37
|
+
# Parse the table at the current location.
|
38
|
+
def parse_table
|
39
|
+
orig_pos = @src.pos
|
40
|
+
table = Element.new(:table, nil, :alignment => [])
|
41
|
+
|
42
|
+
@src.scan(TABLE_SEP_LINE)
|
43
|
+
|
44
|
+
rows = []
|
45
|
+
has_footer = false
|
46
|
+
columns = 0
|
47
|
+
|
48
|
+
add_container = lambda do |type, force|
|
49
|
+
if force || type != :tbody || !has_footer
|
50
|
+
cont = Element.new(type)
|
51
|
+
cont.children, rows = rows, []
|
52
|
+
table.children << cont
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
while !@src.eos?
|
57
|
+
if @src.scan(TABLE_SEP_LINE) && !rows.empty?
|
58
|
+
if table.options[:alignment].empty? && !has_footer
|
59
|
+
add_container.call(:thead, false)
|
60
|
+
table.options[:alignment] = @src[1].scan(TABLE_HSEP_ALIGN).map do |left, right|
|
61
|
+
(left.empty? && right.empty? && :default) || (right.empty? && :left) || (left.empty? && :right) || :center
|
62
|
+
end
|
63
|
+
else # treat as normal separator line
|
64
|
+
add_container.call(:tbody, false)
|
65
|
+
end
|
66
|
+
elsif @src.scan(TABLE_FSEP_LINE)
|
67
|
+
add_container.call(:tbody, true) if !rows.empty?
|
68
|
+
has_footer = true
|
69
|
+
elsif @src.scan(TABLE_ROW_LINE)
|
70
|
+
trow = Element.new(:tr)
|
71
|
+
cells = (@src[1] + ' ').split(/\|/)
|
72
|
+
i = 0
|
73
|
+
while i < cells.length - 1
|
74
|
+
backslashes = cells[i].scan(/\\+$/).first
|
75
|
+
if backslashes && backslashes.length % 2 == 1
|
76
|
+
cells[i] = cells[i].chop + '|' + cells[i+1]
|
77
|
+
cells.delete_at(i+1)
|
78
|
+
else
|
79
|
+
i += 1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
cells.pop if cells.last.strip.empty?
|
83
|
+
cells.each do |cell_text|
|
84
|
+
tcell = Element.new(:td)
|
85
|
+
tcell.children << Element.new(:text, cell_text.strip)
|
86
|
+
trow.children << tcell
|
87
|
+
end
|
88
|
+
columns = [columns, cells.length].max
|
89
|
+
rows << trow
|
90
|
+
else
|
91
|
+
break
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
add_container.call(has_footer ? :tfoot : :tbody, false) if !rows.empty?
|
96
|
+
|
97
|
+
if !table.children.any? {|c| c.type == :tbody}
|
98
|
+
warning("Found table without body - ignoring it")
|
99
|
+
@src.pos = orig_pos
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
|
103
|
+
# adjust all table rows to have equal number of columns, same for alignment defs
|
104
|
+
table.children.each do |kind|
|
105
|
+
kind.children.each do |row|
|
106
|
+
(columns - row.children.length).times do
|
107
|
+
row.children << Element.new(:td)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
if table.options[:alignment].length > columns
|
112
|
+
table.options[:alignment] = table.options[:alignment][0...columns]
|
113
|
+
else
|
114
|
+
table.options[:alignment] += [:default] * (columns - table.options[:alignment].length)
|
115
|
+
end
|
116
|
+
|
117
|
+
@tree.children << table
|
118
|
+
|
119
|
+
true
|
120
|
+
end
|
121
|
+
define_parser(:table, TABLE_START)
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|