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.

Files changed (48) hide show
  1. data/ChangeLog +243 -0
  2. data/Rakefile +0 -17
  3. data/VERSION +1 -1
  4. data/benchmark/historic-jruby-1.4.0.dat +7 -7
  5. data/benchmark/historic-ruby-1.8.6.dat +7 -7
  6. data/benchmark/historic-ruby-1.8.7.dat +7 -7
  7. data/benchmark/historic-ruby-1.9.1p243.dat +7 -7
  8. data/benchmark/historic-ruby-1.9.2dev.dat +7 -7
  9. data/benchmark/testing.sh +1 -1
  10. data/doc/index.page +4 -3
  11. data/doc/quickref.page +31 -1
  12. data/doc/syntax.page +166 -12
  13. data/lib/kramdown/converter.rb +107 -56
  14. data/lib/kramdown/document.rb +16 -1
  15. data/lib/kramdown/extension.rb +19 -0
  16. data/lib/kramdown/parser/kramdown.rb +2 -1
  17. data/lib/kramdown/parser/kramdown/attribute_list.rb +2 -2
  18. data/lib/kramdown/parser/kramdown/codeblock.rb +5 -9
  19. data/lib/kramdown/parser/kramdown/escaped_chars.rb +1 -1
  20. data/lib/kramdown/parser/kramdown/footnote.rb +2 -1
  21. data/lib/kramdown/parser/kramdown/table.rb +125 -0
  22. data/lib/kramdown/version.rb +1 -1
  23. data/test/testcases/block/06_codeblock/with_blank_line.text +1 -0
  24. data/test/testcases/block/09_html/parse_as_span.html +3 -0
  25. data/test/testcases/block/09_html/parse_as_span.text +2 -0
  26. data/test/testcases/block/11_ial/simple.html +3 -0
  27. data/test/testcases/block/11_ial/simple.text +4 -2
  28. data/test/testcases/block/12_extension/options.html +1 -1
  29. data/test/testcases/block/12_extension/options2.html +1 -1
  30. data/test/testcases/block/12_extension/options3.html +7 -0
  31. data/test/testcases/block/12_extension/options3.text +7 -0
  32. data/test/testcases/block/13_definition_list/no_def_list.html +2 -0
  33. data/test/testcases/block/13_definition_list/no_def_list.text +2 -0
  34. data/test/testcases/block/14_table/errors.html +3 -0
  35. data/test/testcases/block/14_table/errors.text +3 -0
  36. data/test/testcases/block/14_table/footer.html +65 -0
  37. data/test/testcases/block/14_table/footer.text +25 -0
  38. data/test/testcases/block/14_table/header.html +103 -0
  39. data/test/testcases/block/14_table/header.text +32 -0
  40. data/test/testcases/block/14_table/no_table.html +3 -0
  41. data/test/testcases/block/14_table/no_table.text +3 -0
  42. data/test/testcases/block/14_table/simple.html +61 -0
  43. data/test/testcases/block/14_table/simple.text +16 -0
  44. data/test/testcases/span/04_footnote/footnote_nr.html +1 -1
  45. data/test/testcases/span/04_footnote/markers.html +1 -1
  46. data/test/testcases/span/escaped_chars/normal.html +4 -0
  47. data/test/testcases/span/escaped_chars/normal.text +4 -0
  48. metadata +17 -2
@@ -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 = -2)
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 << convert(inner_el, indent + 2)
65
+ result << send("convert_#{inner_el.type}", inner_el, indent, opts)
52
66
  end
53
- send("convert_#{el.type}", el, result, indent)
67
+ result
54
68
  end
55
69
 
56
- def convert_blank(el, inner, indent)
70
+ def convert_blank(el, indent, opts)
57
71
  "\n"
58
72
  end
59
73
 
60
- def convert_text(el, inner, indent)
74
+ def convert_text(el, indent, opts)
61
75
  escape_html(el.value, false)
62
76
  end
63
77
 
64
- def convert_eob(el, inner, indent)
78
+ def convert_eob(el, indent, opts)
65
79
  ''
66
80
  end
67
81
 
68
- def convert_p(el, inner, indent)
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, inner, indent)
73
- result = escape_html(el.value)
74
- if el.options[:attr] && el.options[:attr].has_key?('class') && el.options[:attr]['class'] =~ /\bshow-whitespaces\b/
75
- result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m|
76
- suffix = ($1 ? '-l' : ($2 ? '-r' : ''))
77
- m.scan(/./).map do |c|
78
- case c
79
- when "\t" then "<span class=\"ws-tab#{suffix}\">\t</span>"
80
- when " " then "<span class=\"ws-space#{suffix}\">&sdot;</span>"
81
- end
82
- end.join('')
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}\">&sdot;</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, inner, indent)
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, inner, indent)
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, inner, indent)
116
+ def convert_hr(el, indent, opts)
97
117
  "#{' '*indent}<hr />\n"
98
118
  end
99
119
 
100
- def convert_ul(el, inner, indent)
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, inner, indent)
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" << inner << ' '*indent
130
+ output << "\n" << res << ' '*indent
110
131
  else
111
- output << inner << (inner =~ /\n\Z/ ? ' '*indent : '')
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, inner, indent)
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, inner, indent)
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
- inner.chomp + (el.options[:type] == :block ? "\n" : '')
147
+ res.chomp + (el.options[:type] == :block ? "\n" : '')
126
148
  elsif el.options[:type] == :span
127
- "<#{el.value}#{options_for_element(el)}" << (!inner.empty? ? ">#{inner}</#{el.value}>" : " />")
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 !inner.empty? && el.options[:parse_type] != :block
133
- output << ">#{inner}</#{el.value}>"
134
- elsif !inner.empty?
135
- output << ">\n#{inner}" << ' '*indent << "</#{el.value}>"
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, inner, indent)
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, inner, indent)
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 convert_br(el, inner, indent)
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? ? "&nbsp;" : 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, inner, indent)
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
- inner = obfuscate(inner) if el.options[:obfuscate_text]
167
- "<a#{options_for_element(el)}>#{inner}</a>"
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, inner, indent)
221
+ def convert_img(el, indent, opts)
171
222
  "<img#{options_for_element(el)} />"
172
223
  end
173
224
 
174
- def convert_codespan(el, inner, indent)
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, inner, indent)
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, inner, indent)
236
+ def convert_raw(el, indent, opts)
186
237
  el.value
187
238
  end
188
239
 
189
- def convert_em(el, inner, indent)
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, inner, indent)
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 => '&laquo;&nbsp;', :raquo_space => '&nbsp;&raquo;',
201
252
  :laquo => '&laquo;', :raquo => '&raquo;'
202
253
  }
203
- def convert_typographic_sym(el, inner, indent)
254
+ def convert_typographic_sym(el, indent, opts)
204
255
  TYPOGRAPHIC_SYMS[el.value]
205
256
  end
206
257
 
207
- def convert_root(el, inner, indent)
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=\"kramdown-footnotes\">\n#{convert(ol, 2)}</div>\n")
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+.
@@ -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
 
@@ -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])+?)\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
- CODEBLOCK_MATCH = /(?:#{INDENT}.*?\S.*?\n)+/
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
- result = @src.scan(CODEBLOCK_MATCH).gsub(INDENT, '')
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)
@@ -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
@@ -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}?#{CODEBLOCK_MATCH})*)/
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