kramdown 0.10.0 → 0.11.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 (132) hide show
  1. data/CONTRIBUTERS +1 -1
  2. data/ChangeLog +594 -0
  3. data/Rakefile +2 -2
  4. data/VERSION +1 -1
  5. data/data/kramdown/document.html +11 -2
  6. data/doc/default.template +2 -2
  7. data/doc/index.page +1 -2
  8. data/doc/quickref.page +2 -2
  9. data/doc/syntax.page +270 -179
  10. data/lib/kramdown/converter/html.rb +43 -29
  11. data/lib/kramdown/converter/kramdown.rb +97 -73
  12. data/lib/kramdown/converter/latex.rb +18 -13
  13. data/lib/kramdown/document.rb +8 -6
  14. data/lib/kramdown/options.rb +7 -10
  15. data/lib/kramdown/parser/html.rb +29 -21
  16. data/lib/kramdown/parser/kramdown.rb +19 -3
  17. data/lib/kramdown/parser/kramdown/abbreviation.rb +1 -0
  18. data/lib/kramdown/parser/kramdown/attribute_list.rb +18 -12
  19. data/lib/kramdown/parser/kramdown/autolink.rb +1 -1
  20. data/lib/kramdown/parser/kramdown/block_boundary.rb +46 -0
  21. data/lib/kramdown/parser/kramdown/blockquote.rb +7 -3
  22. data/lib/kramdown/parser/kramdown/codeblock.rb +5 -3
  23. data/lib/kramdown/parser/kramdown/escaped_chars.rb +1 -1
  24. data/lib/kramdown/parser/kramdown/extension.rb +22 -8
  25. data/lib/kramdown/parser/kramdown/footnote.rb +3 -2
  26. data/lib/kramdown/parser/kramdown/header.rb +10 -10
  27. data/lib/kramdown/parser/kramdown/html.rb +16 -14
  28. data/lib/kramdown/parser/kramdown/html_entity.rb +1 -1
  29. data/lib/kramdown/parser/kramdown/link.rb +8 -8
  30. data/lib/kramdown/parser/kramdown/list.rb +36 -29
  31. data/lib/kramdown/parser/kramdown/math.rb +15 -4
  32. data/lib/kramdown/parser/kramdown/paragraph.rb +14 -3
  33. data/lib/kramdown/parser/kramdown/table.rb +17 -9
  34. data/lib/kramdown/utils.rb +1 -0
  35. data/lib/kramdown/utils/html.rb +9 -9
  36. data/lib/kramdown/utils/ordered_hash.rb +79 -0
  37. data/lib/kramdown/version.rb +1 -1
  38. data/man/man1/kramdown.1 +9 -12
  39. data/test/test_files.rb +6 -1
  40. data/test/testcases/block/02_eob/middle.html +0 -1
  41. data/test/testcases/block/04_header/atx_header.html +5 -2
  42. data/test/testcases/block/04_header/atx_header.text +3 -1
  43. data/test/testcases/block/04_header/setext_header.html +4 -5
  44. data/test/testcases/block/04_header/setext_header.html.19 +30 -0
  45. data/test/testcases/block/05_blockquote/lazy.html +34 -0
  46. data/test/testcases/block/05_blockquote/lazy.text +20 -0
  47. data/test/testcases/block/05_blockquote/nested.html +1 -0
  48. data/test/testcases/block/05_blockquote/nested.text +1 -0
  49. data/test/testcases/block/05_blockquote/with_code_blocks.html +2 -2
  50. data/test/testcases/block/06_codeblock/lazy.html +4 -0
  51. data/test/testcases/block/06_codeblock/lazy.text +5 -0
  52. data/test/testcases/block/06_codeblock/no_newline_at_end_1.html +2 -0
  53. data/test/testcases/block/06_codeblock/no_newline_at_end_1.text +2 -0
  54. data/test/testcases/block/06_codeblock/with_ial.html +6 -0
  55. data/test/testcases/block/06_codeblock/with_ial.text +5 -0
  56. data/test/testcases/block/07_horizontal_rule/normal.html +0 -2
  57. data/test/testcases/block/07_horizontal_rule/normal.text +0 -2
  58. data/test/testcases/block/08_list/item_ial.html +1 -3
  59. data/test/testcases/block/08_list/lazy.html +39 -0
  60. data/test/testcases/block/08_list/lazy.text +29 -0
  61. data/test/testcases/block/08_list/list_and_others.html +5 -3
  62. data/test/testcases/block/08_list/list_and_others.text +1 -0
  63. data/test/testcases/block/08_list/other_first_element.html +2 -2
  64. data/test/testcases/block/08_list/other_first_element.text +1 -1
  65. data/test/testcases/block/08_list/simple_ul.html +0 -13
  66. data/test/testcases/block/08_list/simple_ul.text +0 -7
  67. data/test/testcases/block/08_list/special_cases.html +8 -31
  68. data/test/testcases/block/08_list/special_cases.text +2 -15
  69. data/test/testcases/block/09_html/comment.html +2 -2
  70. data/test/testcases/block/09_html/html_to_native/emphasis.html +2 -0
  71. data/test/testcases/block/09_html/html_to_native/emphasis.text +2 -0
  72. data/test/testcases/block/09_html/html_to_native/table_normal.html +2 -1
  73. data/test/testcases/block/09_html/html_to_native/table_simple.html +4 -2
  74. data/test/testcases/block/09_html/invalid_html_1.html +2 -0
  75. data/test/testcases/block/09_html/parse_as_raw.html +2 -2
  76. data/test/testcases/block/09_html/parse_as_span.html +1 -1
  77. data/test/testcases/block/09_html/simple.html +2 -0
  78. data/test/testcases/block/09_html/simple.html.19 +2 -0
  79. data/test/testcases/block/09_html/simple.text +2 -0
  80. data/test/testcases/block/11_ial/auto_id_and_ial.html +1 -1
  81. data/test/testcases/block/11_ial/simple.html +2 -3
  82. data/test/testcases/block/12_extension/comment.html +3 -1
  83. data/test/testcases/block/12_extension/comment.text +2 -1
  84. data/test/testcases/block/12_extension/ignored.html +5 -1
  85. data/test/testcases/block/12_extension/ignored.text +1 -1
  86. data/test/testcases/block/12_extension/nomarkdown.html +5 -1
  87. data/test/testcases/block/12_extension/nomarkdown.kramdown +20 -0
  88. data/test/testcases/block/12_extension/nomarkdown.latex +13 -0
  89. data/test/testcases/block/12_extension/nomarkdown.text +11 -1
  90. data/test/testcases/block/13_definition_list/item_ial.html +1 -3
  91. data/test/testcases/block/13_definition_list/item_ial.text +1 -1
  92. data/test/testcases/block/13_definition_list/simple.html +2 -2
  93. data/test/testcases/block/14_table/errors.html +5 -0
  94. data/test/testcases/block/14_table/errors.text +6 -0
  95. data/test/testcases/block/14_table/header.text +1 -1
  96. data/test/testcases/block/14_table/no_table.text +1 -1
  97. data/test/testcases/block/14_table/simple.html +78 -0
  98. data/test/testcases/block/14_table/simple.text +22 -0
  99. data/test/testcases/block/15_math/normal.html +11 -4
  100. data/test/testcases/block/15_math/normal.text +10 -0
  101. data/test/testcases/encoding.html +1 -1
  102. data/test/testcases/span/01_link/image_in_a.html +3 -3
  103. data/test/testcases/span/01_link/imagelinks.html +7 -7
  104. data/test/testcases/span/01_link/inline.html +11 -5
  105. data/test/testcases/span/01_link/inline.html.19 +11 -5
  106. data/test/testcases/span/01_link/inline.text +11 -5
  107. data/test/testcases/span/01_link/link_defs.html +2 -1
  108. data/test/testcases/span/01_link/link_defs.text +4 -0
  109. data/test/testcases/span/01_link/reference.html +3 -0
  110. data/test/testcases/span/01_link/reference.html.19 +3 -0
  111. data/test/testcases/span/01_link/reference.text +5 -0
  112. data/test/testcases/span/03_codespan/highlighting.html +1 -0
  113. data/test/testcases/span/03_codespan/highlighting.text +1 -0
  114. data/test/testcases/span/04_footnote/definitions.html +3 -0
  115. data/test/testcases/span/04_footnote/definitions.latex +3 -4
  116. data/test/testcases/span/04_footnote/definitions.text +6 -0
  117. data/test/testcases/span/04_footnote/footnote_nr.latex +1 -5
  118. data/test/testcases/span/04_footnote/markers.latex +5 -14
  119. data/test/testcases/span/05_html/markdown_attr.html +1 -1
  120. data/test/testcases/span/05_html/markdown_attr.text +1 -1
  121. data/test/testcases/span/05_html/normal.html +5 -3
  122. data/test/testcases/span/05_html/normal.text +2 -0
  123. data/test/testcases/span/escaped_chars/normal.html +2 -0
  124. data/test/testcases/span/escaped_chars/normal.text +2 -0
  125. data/test/testcases/span/extension/comment.html +2 -2
  126. data/test/testcases/span/extension/ignored.html +1 -1
  127. data/test/testcases/span/text_substitutions/typography.html +1 -1
  128. data/test/testcases/span/text_substitutions/typography.html.19 +1 -1
  129. data/test/testcases/span/text_substitutions/typography.text +1 -1
  130. metadata +20 -5
  131. data/test/testcases/block/05_blockquote/only_first_quoted.html +0 -8
  132. data/test/testcases/block/05_blockquote/only_first_quoted.text +0 -4
@@ -62,6 +62,7 @@ module Kramdown
62
62
  result = ''
63
63
  indent += INDENTATION
64
64
  el.children.each do |inner_el|
65
+ opts[:parent] = el
65
66
  result << send("convert_#{inner_el.type}", inner_el, indent, opts)
66
67
  end
67
68
  result
@@ -84,16 +85,17 @@ module Kramdown
84
85
  end
85
86
 
86
87
  def convert_codeblock(el, indent, opts)
87
- if el.options[:attr] && el.options[:attr]['lang'] && HIGHLIGHTING_AVAILABLE
88
- el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
88
+ el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
89
+ lang = el.attr.delete('lang')
90
+ if lang && HIGHLIGHTING_AVAILABLE
89
91
  opts = {:wrap => @doc.options[:coderay_wrap], :line_numbers => @doc.options[:coderay_line_numbers],
90
92
  :line_number_start => @doc.options[:coderay_line_number_start], :tab_width => @doc.options[:coderay_tab_width],
91
93
  :bold_every => @doc.options[:coderay_bold_every], :css => @doc.options[:coderay_css]}
92
- result = CodeRay.scan(el.value, el.options[:attr].delete('lang').to_sym).html(opts).chomp + "\n"
94
+ result = CodeRay.scan(el.value, lang.to_sym).html(opts).chomp + "\n"
93
95
  "#{' '*indent}<div#{html_attributes(el)}>#{result}#{' '*indent}</div>\n"
94
96
  else
95
97
  result = escape_html(el.value)
96
- if el.options[:attr] && el.options[:attr].has_key?('class') && el.options[:attr]['class'] =~ /\bshow-whitespaces\b/
98
+ if el.attr['class'].to_s =~ /\bshow-whitespaces\b/
97
99
  result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m|
98
100
  suffix = ($1 ? '-l' : ($2 ? '-r' : ''))
99
101
  m.scan(/./).map do |c|
@@ -114,10 +116,10 @@ module Kramdown
114
116
 
115
117
  def convert_header(el, indent, opts)
116
118
  el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
117
- if @doc.options[:auto_ids] && !(el.options[:attr] && el.options[:attr]['id'])
118
- (el.options[:attr] ||= {})['id'] = generate_id(el.options[:raw_text])
119
+ if @doc.options[:auto_ids] && !el.attr['id']
120
+ el.attr['id'] = generate_id(el.options[:raw_text])
119
121
  end
120
- @toc << [el.options[:level], el.options[:attr]['id'], el.children] if el.options[:attr] && el.options[:attr]['id'] && within_toc_depth?(el)
122
+ @toc << [el.options[:level], el.attr['id'], el.children] if el.attr['id'] && within_toc_depth?(el)
121
123
  "#{' '*indent}<h#{el.options[:level]}#{html_attributes(el)}>#{inner(el, indent, opts)}</h#{el.options[:level]}>\n"
122
124
  end
123
125
 
@@ -131,7 +133,7 @@ module Kramdown
131
133
 
132
134
  def convert_ul(el, indent, opts)
133
135
  if !@toc_code && (el.options[:ial][:refs].include?('toc') rescue nil) && (el.type == :ul || el.type == :ol)
134
- @toc_code = [el.type, el.options[:attr], (0..128).to_a.map{|a| rand(36).to_s(36)}.join]
136
+ @toc_code = [el.type, el.attr, (0..128).to_a.map{|a| rand(36).to_s(36)}.join]
135
137
  @toc_code.last
136
138
  else
137
139
  "#{' '*indent}<#{el.type}#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</#{el.type}>\n"
@@ -156,32 +158,33 @@ module Kramdown
156
158
  "#{' '*indent}<dt#{html_attributes(el)}>#{inner(el, indent, opts)}</dt>\n"
157
159
  end
158
160
 
159
- HTML_TAGS_WITH_BODY=['div', 'script']
161
+ HTML_TAGS_WITH_BODY=['div', 'script', 'iframe', 'textarea']
160
162
 
161
163
  def convert_html_element(el, indent, opts)
164
+ parent = opts[:parent]
162
165
  res = inner(el, indent, opts)
163
166
  if el.options[:category] == :span
164
- "<#{el.value}#{html_attributes(el)}" << (!res.empty? ? ">#{res}</#{el.value}>" : " />")
167
+ "<#{el.value}#{html_attributes(el)}" << (!res.empty? || HTML_TAGS_WITH_BODY.include?(el.value) ? ">#{res}</#{el.value}>" : " />")
165
168
  else
166
169
  output = ''
167
- output << ' '*indent if !el.options[:parent_is_raw]
170
+ output << ' '*indent if parent.type != :html_element || parent.options[:parse_type] != :raw
168
171
  output << "<#{el.value}#{html_attributes(el)}"
169
172
  if !res.empty? && el.options[:parse_type] != :block
170
173
  output << ">#{res}</#{el.value}>"
171
174
  elsif !res.empty?
172
- output << ">\n#{res}" << ' '*indent << "</#{el.value}>"
175
+ output << ">\n#{res.chomp}\n" << ' '*indent << "</#{el.value}>"
173
176
  elsif HTML_TAGS_WITH_BODY.include?(el.value)
174
177
  output << "></#{el.value}>"
175
178
  else
176
179
  output << " />"
177
180
  end
178
- output << "\n" if el.options[:outer_element] || !el.options[:parent_is_raw]
181
+ output << "\n" if parent.type != :html_element || parent.options[:parse_type] != :raw
179
182
  output
180
183
  end
181
184
  end
182
185
 
183
186
  def convert_xml_comment(el, indent, opts)
184
- if el.options[:category] == :block && !el.options[:parent_is_raw]
187
+ if el.options[:category] == :block && (opts[:parent].type != :html_element || opts[:parent].options[:parse_type] != :raw)
185
188
  ' '*indent + el.value + "\n"
186
189
  else
187
190
  el.value
@@ -227,12 +230,12 @@ module Kramdown
227
230
  end
228
231
 
229
232
  def convert_a(el, indent, opts)
230
- do_obfuscation = el.options[:attr]['href'] =~ /^mailto:/
233
+ do_obfuscation = el.attr['href'] =~ /^mailto:/
231
234
  if do_obfuscation
232
235
  el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
233
- href = obfuscate(el.options[:attr]['href'].sub(/^mailto:/, ''))
236
+ href = obfuscate(el.attr['href'].sub(/^mailto:/, ''))
234
237
  mailto = obfuscate('mailto')
235
- el.options[:attr]['href'] = "#{mailto}:#{href}"
238
+ el.attr['href'] = "#{mailto}:#{href}"
236
239
  end
237
240
  res = inner(el, indent, opts)
238
241
  res = obfuscate(res) if do_obfuscation
@@ -244,7 +247,14 @@ module Kramdown
244
247
  end
245
248
 
246
249
  def convert_codespan(el, indent, opts)
247
- "<code#{html_attributes(el)}>#{escape_html(el.value)}</code>"
250
+ el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
251
+ lang = el.attr.delete('lang')
252
+ if lang && HIGHLIGHTING_AVAILABLE
253
+ result = CodeRay.scan(el.value, lang.to_sym).html(:wrap => :span, :css => @doc.options[:coderay_css]).chomp
254
+ "<code#{html_attributes(el)}>#{result}</code>"
255
+ else
256
+ "<code#{html_attributes(el)}>#{escape_html(el.value)}</code>"
257
+ end
248
258
  end
249
259
 
250
260
  def convert_footnote(el, indent, opts)
@@ -255,7 +265,11 @@ module Kramdown
255
265
  end
256
266
 
257
267
  def convert_raw(el, indent, opts)
258
- el.value + (el.options[:category] == :block ? "\n" : '')
268
+ if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html')
269
+ el.value + (el.options[:category] == :block ? "\n" : '')
270
+ else
271
+ ''
272
+ end
259
273
  end
260
274
 
261
275
  def convert_em(el, indent, opts)
@@ -286,9 +300,8 @@ module Kramdown
286
300
 
287
301
  def convert_math(el, indent, opts)
288
302
  el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
289
- el.options[:attr] ||= {}
290
- el.options[:attr]['class'] ||= ''
291
- el.options[:attr]['class'] += (el.options[:attr]['class'].empty? ? '' : ' ') + 'math'
303
+ el.attr['class'] ||= ''
304
+ el.attr['class'] += (el.attr['class'].empty? ? '' : ' ') + 'math'
292
305
  type = 'span'
293
306
  type = 'div' if el.options[:category] == :block
294
307
  "<#{type}#{html_attributes(el)}>#{escape_html(el.value)}</#{type}>#{type == 'div' ? "\n" : ''}"
@@ -316,12 +329,13 @@ module Kramdown
316
329
  end
317
330
 
318
331
  def generate_toc_tree(toc, type, attr)
319
- sections = Element.new(type, nil, {:attr => {'id' => 'markdown-toc'}.merge(attr)})
332
+ sections = Element.new(type, nil, attr)
333
+ sections.attr['id'] ||= 'markdown-toc'
320
334
  stack = []
321
335
  toc.each do |level, id, children|
322
- li = Element.new(:li, nil, {:level => level})
323
- li.children << Element.new(:p, nil, {:transparent => true})
324
- a = Element.new(:a, nil, {:attr => {:href => "##{id}"}})
336
+ li = Element.new(:li, nil, nil, {:level => level})
337
+ li.children << Element.new(:p, nil, nil, {:transparent => true})
338
+ a = Element.new(:a, nil, {'href' => "##{id}"})
325
339
  a.children += children
326
340
  li.children.last.children << a
327
341
  li.children << Element.new(type)
@@ -362,10 +376,10 @@ module Kramdown
362
376
  # Return a HTML list with the footnote content for the used footnotes.
363
377
  def footnote_content
364
378
  ol = Element.new(:ol)
365
- ol.options[:attr] = {'start' => @footnote_start} if @footnote_start != 1
379
+ ol.attr['start'] = @footnote_start if @footnote_start != 1
366
380
  @footnotes.each do |name, data|
367
- li = Element.new(:li, nil, {:attr => {:id => "fn:#{name}"}, :first_is_block => true})
368
- li.children = Marshal.load(Marshal.dump(data[:content].children)) #TODO: probably remove this!!!!
381
+ li = Element.new(:li, nil, {'id' => "fn:#{name}"}, {:first_is_block => true})
382
+ li.children = Marshal.load(Marshal.dump(data[:content].children))
369
383
  ol.children << li
370
384
 
371
385
  ref = Element.new(:raw, "<a href=\"#fnref:#{name}\" rev=\"footnote\">&#8617;</a>")
@@ -41,23 +41,32 @@ module Kramdown
41
41
  @stack = []
42
42
  end
43
43
 
44
- def convert(el, opts = {})
44
+ def convert(el, opts = {:indent => 0})
45
45
  res = send("convert_#{el.type}", el, opts)
46
46
  if el.type != :html_element && el.type != :li && el.type != :dd && (ial = ial_for_element(el))
47
47
  res << ial
48
48
  res << "\n\n" if el.options[:category] == :block
49
+ elsif [:ul, :dl, :ol, :codeblock].include?(el.type) && opts[:next] &&
50
+ ([el.type, :codeblock].include?(opts[:next].type) ||
51
+ (opts[:next].type == :blank && opts[:nnext] && [el.type, :codeblock].include?(opts[:nnext].type)))
52
+ res << "^\n\n"
53
+ elsif el.options[:category] == :block && ![:li, :dd, :dt, :td, :th, :tr, :thead, :tbody, :tfoot, :blank].include?(el.type) &&
54
+ (el.type != :p || !el.options[:transparent])
55
+ res << "\n"
49
56
  end
50
57
  res
51
58
  end
52
59
 
53
- def inner(el, opts = {})
54
- @stack.push([el, opts])
60
+ def inner(el, opts = {:indent => 0})
61
+ @stack.push(el)
55
62
  result = ''
56
63
  el.children.each_with_index do |inner_el, index|
57
64
  options = opts.dup
58
65
  options[:index] = index
59
66
  options[:prev] = (index == 0 ? nil : el.children[index-1])
67
+ options[:pprev] = (index <= 1 ? nil : el.children[index-2])
60
68
  options[:next] = (index == el.children.length - 1 ? nil : el.children[index+1])
69
+ options[:nnext] = (index >= el.children.length - 2 ? nil : el.children[index+2])
61
70
  result << convert(inner_el, options)
62
71
  end
63
72
  @stack.pop
@@ -65,54 +74,44 @@ module Kramdown
65
74
  end
66
75
 
67
76
  def convert_blank(el, opts)
68
- "\n"
77
+ ""
69
78
  end
70
79
 
71
- ESCAPED_CHAR_RE = /(\$\$|[\\*_`\[\]\{\}"'])|^[ ]{0,3}(:)/
80
+ ESCAPED_CHAR_RE = /(\$\$|[\\*_`\[\]\{"'])|^[ ]{0,3}(:)/
72
81
 
73
82
  def convert_text(el, opts)
74
83
  if opts[:raw_text]
75
84
  el.value
76
85
  else
77
- nl = (el.value =~ /\n$/)
78
- el.value.gsub(/\s+/, ' ').gsub(ESCAPED_CHAR_RE) { "\\#{$1 || $2}" } + (nl ? "\n" : '')
86
+ el.value.gsub(/\A\n/) do
87
+ opts[:prev] && opts[:prev].type == :br ? '' : "\n"
88
+ end.gsub(/\s+/, ' ').gsub(ESCAPED_CHAR_RE) { "\\#{$1 || $2}" }
79
89
  end
80
90
  end
81
91
 
82
92
  def convert_p(el, opts)
83
- res = inner(el, opts).strip.gsub(/\A(?:([#|])|(\d+)\.|([+-]\s))/) do
84
- $1 || $3 ? "\\#{$1 || $3}" : "#{$2}\\."
85
- end + "\n"
86
- if opts[:next] && opts[:next].type == :p && !ial_for_element(el)
87
- res += "\n"
88
- end
89
- res
93
+ w = @doc.options[:line_width] - opts[:indent].to_s.to_i
94
+ first, second, *rest = inner(el, opts).strip.gsub(/(.{1,#{w}})( +|$\n?)/, "\\1\n").split(/\n/)
95
+ first.gsub!(/^(?:(#)|(\d+)\.|([+-]\s))/) { $1 || $3 ? "\\#{$1 || $3}" : "#{$2}\\."}
96
+ first.gsub!(/\|/, '\\|')
97
+ second.gsub!(/^([=-]+\s*?)$/, "\\\1") if second
98
+ [first, second, *rest].compact.join("\n") + "\n"
90
99
  end
91
100
 
92
- CODEBLOCK_PREV_EL = [:ul, :ol, :dl, :codeblock]
93
101
 
94
102
  def convert_codeblock(el, opts)
95
- res = ''
96
- res << "^\n" if opts[:prev] && ((CODEBLOCK_PREV_EL.include?(opts[:prev].type) && !ial_for_element(opts[:prev])) ||
97
- (opts[:prev].type == :blank &&
98
- opts[:index]-2 >= 0 &&
99
- (tmp = @stack.last.first.children[opts[:index]-2]) &&
100
- CODEBLOCK_PREV_EL.include?(tmp.type) && !ial_for_element(tmp)))
101
- res << el.value.split(/\n/).map {|l| l.empty? ? " " : " #{l}"}.join("\n") + "\n"
103
+ el.value.split(/\n/).map {|l| l.empty? ? " " : " #{l}"}.join("\n") + "\n"
102
104
  end
103
105
 
104
106
  def convert_blockquote(el, opts)
105
- res = ''
106
- res << "\n" if opts[:prev] && opts[:prev].type == :blockquote
107
- res << inner(el, opts).chomp.split(/\n/).map {|l| "> #{l}"}.join("\n") << "\n"
107
+ opts[:indent] += 2
108
+ inner(el, opts).chomp.split(/\n/).map {|l| "> #{l}"}.join("\n") << "\n"
108
109
  end
109
110
 
110
111
  def convert_header(el, opts)
111
112
  res = ''
112
- res << "\n" if opts[:prev] && opts[:prev].type != :blank
113
113
  res << "#{'#' * el.options[:level]} #{inner(el, opts)}"
114
- res << " {##{el.options[:attr]['id']}}" if el.options[:attr] && el.options[:attr]['id']
115
- res << "\n" if opts[:next] && opts[:next].type != :blank
114
+ res << " {##{el.attr['id']}}" if el.attr['id']
116
115
  res << "\n"
117
116
  end
118
117
 
@@ -121,19 +120,13 @@ module Kramdown
121
120
  end
122
121
 
123
122
  def convert_ul(el, opts)
124
- res = ''
125
- res << "\n" if opts[:prev] && (opts[:prev].type == :p && !opts[:prev].options[:transparent])
126
- res << "^\n" if opts[:prev] && ((opts[:prev].type == el.type && !ial_for_element(opts[:prev])) ||
127
- (opts[:prev].type == :blank && opts[:index]-2 >= 0 &&
128
- (tmp = @stack.last.first.children[opts[:index]-2]) &&
129
- tmp.type == el.type && !ial_for_element(tmp)))
130
- res + inner(el, opts).sub(/\n+\Z/, "\n")
123
+ inner(el, opts).sub(/\n+\Z/, "\n")
131
124
  end
132
125
  alias :convert_ol :convert_ul
133
126
  alias :convert_dl :convert_ul
134
127
 
135
128
  def convert_li(el, opts)
136
- sym, width = if @stack.last.first.type == :ul
129
+ sym, width = if @stack.last.type == :ul
137
130
  ['* ', el.children.first.type == :codeblock ? 4 : 2]
138
131
  else
139
132
  ["#{opts[:index] + 1}.".ljust(4), 4]
@@ -142,18 +135,21 @@ module Kramdown
142
135
  sym += ial + " "
143
136
  end
144
137
 
145
- first, *last = inner(el, opts).chomp.split(/\n/)
138
+ opts[:indent] += width
139
+ text = inner(el, opts)
140
+ newlines = text.scan(/\n*\Z/).first
141
+ first, *last = text.split(/\n/)
146
142
  last = last.map {|l| " "*width + l}.join("\n")
147
- last = last.empty? ? "\n" : "\n#{last}\n"
143
+ text = first + (last.empty? ? "" : "\n") + last + newlines
148
144
  if el.children.first.type == :p && !el.children.first.options[:transparent]
149
- res = "#{sym}#{first}\n#{last}"
150
- res << "^\n" if el.children.size == 1 && @stack.last.first.children.last == el &&
151
- (@stack.last.first.children.any? {|c| c.children.first.type != :p} || @stack.last.first.children.size == 1)
145
+ res = "#{sym}#{text}"
146
+ res << "^\n" if el.children.size == 1 && @stack.last.children.last == el &&
147
+ (@stack.last.children.any? {|c| c.children.first.type != :p} || @stack.last.children.size == 1)
152
148
  res
153
149
  elsif el.children.first.type == :codeblock
154
- "#{sym}\n #{first}#{last}"
150
+ "#{sym}\n #{text}"
155
151
  else
156
- "#{sym}#{first}#{last}"
152
+ "#{sym}#{text}"
157
153
  end
158
154
  end
159
155
 
@@ -163,24 +159,28 @@ module Kramdown
163
159
  sym += ial + " "
164
160
  end
165
161
 
166
- first, *last = inner(el, opts).chomp.split(/\n/)
162
+ opts[:indent] += width
163
+ text = inner(el, opts)
164
+ newlines = text.scan(/\n*\Z/).first
165
+ first, *last = text.split(/\n/)
167
166
  last = last.map {|l| " "*width + l}.join("\n")
168
- text = first + (last.empty? ? '' : "\n" + last)
167
+ text = first + (last.empty? ? "" : "\n") + last + newlines
168
+ text.chomp! if text =~ /\n\n\Z/ && opts[:next] && opts[:next].type == :dd
169
+ text += "\n" if text !~ /\n\n\Z/ && opts[:next] && opts[:next].type == :dt
169
170
  if el.children.first.type == :p && !el.children.first.options[:transparent]
170
- "\n#{sym}#{text}\n"
171
+ "\n#{sym}#{text}"
171
172
  elsif el.children.first.type == :codeblock
172
- "#{sym}\n #{text}\n"
173
+ "#{sym}\n #{text}"
173
174
  else
174
- "#{sym}#{text}\n"
175
+ "#{sym}#{text}"
175
176
  end
176
177
  end
177
178
 
178
179
  def convert_dt(el, opts)
179
- res = ''
180
- res << inner(el, opts) << "\n"
180
+ inner(el, opts) << "\n"
181
181
  end
182
182
 
183
- HTML_TAGS_WITH_BODY=['div', 'script']
183
+ HTML_TAGS_WITH_BODY=['div', 'script', 'iframe', 'textarea']
184
184
 
185
185
  def convert_html_element(el, opts)
186
186
  markdown_attr = el.options[:category] == :block && el.children.any? do |c|
@@ -191,7 +191,7 @@ module Kramdown
191
191
  opts[:block_raw_text] = true if el.options[:category] == :block && opts[:raw_text]
192
192
  res = inner(el, opts)
193
193
  if el.options[:category] == :span
194
- "<#{el.value}#{html_attributes(el)}" << (!res.empty? ? ">#{res}</#{el.value}>" : " />")
194
+ "<#{el.value}#{html_attributes(el)}" << (!res.empty? || HTML_TAGS_WITH_BODY.include?(el.value) ? ">#{res}</#{el.value}>" : " />")
195
195
  else
196
196
  output = ''
197
197
  output << "<#{el.value}#{html_attributes(el)}"
@@ -205,13 +205,13 @@ module Kramdown
205
205
  else
206
206
  output << " />"
207
207
  end
208
- output << "\n" if el.options[:outer_element] || !el.options[:parent_is_raw]
208
+ output << "\n" if @stack.last.type != :html_element || @stack.last.options[:parse_type] != :raw
209
209
  output
210
210
  end
211
211
  end
212
212
 
213
213
  def convert_xml_comment(el, opts)
214
- if el.options[:category] == :block && !el.options[:parent_is_raw]
214
+ if el.options[:category] == :block && (@stack.last.type != :html_element || @stack.last.options[:parse_type] != :raw)
215
215
  el.value + "\n"
216
216
  else
217
217
  el.value
@@ -274,17 +274,34 @@ module Kramdown
274
274
  end
275
275
 
276
276
  def convert_a(el, opts)
277
- if el.options[:attr]['href'].empty?
277
+ if el.attr['href'].empty?
278
278
  "[#{inner(el, opts)}]()"
279
+ elsif el.attr['href'] =~ /^(?:http|ftp)/ || el.attr['href'].count("()") > 0
280
+ index = if link_el = @linkrefs.find {|c| c.attr['href'] == el.attr['href']}
281
+ @linkrefs.index(link_el) + 1
282
+ else
283
+ @linkrefs << el
284
+ @linkrefs.size
285
+ end
286
+ "[#{inner(el, opts)}][#{index}]"
279
287
  else
280
- @linkrefs << el
281
- "[#{inner(el, opts)}][#{@linkrefs.size}]"
288
+ title = el.attr['title'].to_s.empty? ? '' : ' "' + el.attr['title'].gsub(/"/, "&quot;") + '"'
289
+ "[#{inner(el, opts)}](#{el.attr['href']}#{title})"
282
290
  end
283
291
  end
284
292
 
285
293
  def convert_img(el, opts)
286
- title = (el.options[:attr]['title'] ? ' "' + el.options[:attr]['title'].gsub(/"/, "&quot;") + '"' : '')
287
- "![#{el.options[:attr]['alt']}](<#{el.options[:attr]['src']}>#{title})"
294
+ if el.attr['src'].empty?
295
+ "![#{el.attr['alt']}]()"
296
+ else
297
+ title = (el.attr['title'] ? ' "' + el.attr['title'].gsub(/"/, "&quot;") + '"' : '')
298
+ link = if el.attr['src'].count("()") > 0
299
+ "<#{el.attr['src']}>"
300
+ else
301
+ el.attr['src']
302
+ end
303
+ "![#{el.attr['alt']}](#{link}#{title})"
304
+ end
288
305
  end
289
306
 
290
307
  def convert_codespan(el, opts)
@@ -298,12 +315,14 @@ module Kramdown
298
315
  end
299
316
 
300
317
  def convert_raw(el, opts)
301
- if @stack.last.first.type == :html_element
318
+ attr = (el.options[:type] || []).join(' ')
319
+ attr = " type=\"#{attr}\"" if attr.length > 0
320
+ if @stack.last.type == :html_element
302
321
  el.value
303
322
  elsif el.options[:category] == :block
304
- "{::nomarkdown}\n#{el.value}\n{:/}\n"
323
+ "{::nomarkdown#{attr}}\n#{el.value}\n{:/}\n"
305
324
  else
306
- "{::nomarkdown}#{el.value}{:/}"
325
+ "{::nomarkdown#{attr}}#{el.value}{:/}"
307
326
  end
308
327
  end
309
328
 
@@ -333,7 +352,7 @@ module Kramdown
333
352
  end
334
353
 
335
354
  def convert_math(el, opts)
336
- (@stack.last.first.type == :p && opts[:prev].nil? ? "\\" : '') + "$$#{el.value}$$" + (el.options[:category] == :block ? "\n" : '')
355
+ (@stack.last.type == :p && opts[:prev].nil? ? "\\" : '') + "$$#{el.value}$$" + (el.options[:category] == :block ? "\n" : '')
337
356
  end
338
357
 
339
358
  def convert_abbreviation(el, opts)
@@ -352,20 +371,17 @@ module Kramdown
352
371
  res = ''
353
372
  res << "\n\n" if @linkrefs.size > 0
354
373
  @linkrefs.each_with_index do |el, i|
355
- link = (el.type == :a ? el.options[:attr]['href'] : el.options[:attr]['src'])
356
- link = "<#{link}>" if link =~ / /
357
- title = el.options[:attr]['title']
358
- res << "[#{i+1}]: #{link} #{title ? '"' + title.gsub(/"/, "&quot;") + '"' : ''}\n"
374
+ title = el.attr['title']
375
+ res << "[#{i+1}]: #{el.attr['href']} #{title ? '"' + title.gsub(/"/, "&quot;") + '"' : ''}\n"
359
376
  end
360
377
  res
361
378
  end
362
379
 
363
380
  def create_footnote_defs
364
381
  res = ''
365
- res = "\n" if @footnotes.size > 0
366
382
  @footnotes.each do |name, data|
367
- res << "\n[^#{name}]:\n"
368
- res << inner(data[:content]).chomp.split(/\n/).map {|l| " #{l}"}.join("\n")
383
+ res << "[^#{name}]:\n"
384
+ res << inner(data[:content]).chomp.split(/\n/).map {|l| " #{l}"}.join("\n") + "\n\n"
369
385
  end
370
386
  res
371
387
  end
@@ -381,11 +397,19 @@ module Kramdown
381
397
 
382
398
  # Return the IAL containing the attributes of the element +el+.
383
399
  def ial_for_element(el)
384
- res = (el.options[:attr] || {}).map do |k,v|
400
+ res = el.attr.map do |k,v|
385
401
  next if [:img, :a].include?(el.type) && ['href', 'src', 'alt', 'title'].include?(k)
386
402
  next if el.type == :header && k == 'id'
387
- v.nil? ? '' : " #{k}=\"#{v.to_s}\""
388
- end.compact.sort.join('')
403
+ if v.nil?
404
+ ''
405
+ elsif k == 'class'
406
+ " " + v.split(/\s+/).map {|w| ".#{w}"}.join(" ")
407
+ elsif k == 'id'
408
+ " ##{v}"
409
+ else
410
+ " #{k}=\"#{v.to_s}\""
411
+ end
412
+ end.compact.join('')
389
413
  res = "toc" + (res.strip.empty? ? '' : " #{res}") if (el.type == :ul || el.type == :ol) &&
390
414
  (el.options[:ial][:refs].include?('toc') rescue nil)
391
415
  res.strip.empty? ? nil : "{:#{res}}"