kramdown 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of kramdown might be problematic. Click here for more details.

Files changed (94) hide show
  1. data/CONTRIBUTERS +1 -1
  2. data/ChangeLog +532 -0
  3. data/README +22 -12
  4. data/Rakefile +9 -8
  5. data/VERSION +1 -1
  6. data/benchmark/benchmark.sh +61 -0
  7. data/benchmark/generate_data.rb +57 -55
  8. data/benchmark/testing.sh +1 -1
  9. data/benchmark/timing.sh +3 -3
  10. data/bin/kramdown +1 -2
  11. data/data/kramdown/document.html +2 -2
  12. data/data/kramdown/document.latex +2 -2
  13. data/doc/default.scss.css +6 -1
  14. data/doc/default.template +1 -1
  15. data/doc/documentation.page +1 -1
  16. data/doc/index.page +9 -7
  17. data/doc/installation.page +2 -3
  18. data/doc/links.markdown +1 -1
  19. data/doc/quickref.page +19 -19
  20. data/doc/syntax.page +117 -98
  21. data/doc/tests.page +8 -7
  22. data/lib/kramdown/compatibility.rb +2 -1
  23. data/lib/kramdown/converter.rb +5 -7
  24. data/lib/kramdown/converter/base.rb +87 -32
  25. data/lib/kramdown/converter/html.rb +134 -122
  26. data/lib/kramdown/converter/kramdown.rb +24 -25
  27. data/lib/kramdown/converter/latex.rb +65 -55
  28. data/lib/kramdown/document.rb +487 -42
  29. data/lib/kramdown/error.rb +3 -0
  30. data/lib/kramdown/options.rb +83 -28
  31. data/lib/kramdown/parser.rb +5 -5
  32. data/lib/kramdown/parser/base.rb +55 -13
  33. data/lib/kramdown/parser/html.rb +83 -71
  34. data/lib/kramdown/parser/kramdown.rb +73 -54
  35. data/lib/kramdown/parser/kramdown/abbreviation.rb +17 -12
  36. data/lib/kramdown/parser/kramdown/autolink.rb +2 -3
  37. data/lib/kramdown/parser/kramdown/blank_line.rb +1 -1
  38. data/lib/kramdown/parser/kramdown/block_boundary.rb +2 -2
  39. data/lib/kramdown/parser/kramdown/blockquote.rb +2 -2
  40. data/lib/kramdown/parser/kramdown/codeblock.rb +5 -2
  41. data/lib/kramdown/parser/kramdown/codespan.rb +1 -2
  42. data/lib/kramdown/parser/kramdown/emphasis.rb +1 -1
  43. data/lib/kramdown/parser/kramdown/escaped_chars.rb +1 -1
  44. data/lib/kramdown/parser/kramdown/extensions.rb +204 -0
  45. data/lib/kramdown/parser/kramdown/footnote.rb +7 -7
  46. data/lib/kramdown/parser/kramdown/header.rb +4 -2
  47. data/lib/kramdown/parser/kramdown/horizontal_rule.rb +1 -1
  48. data/lib/kramdown/parser/kramdown/html.rb +39 -45
  49. data/lib/kramdown/parser/kramdown/link.rb +19 -29
  50. data/lib/kramdown/parser/kramdown/list.rb +13 -13
  51. data/lib/kramdown/parser/kramdown/math.rb +1 -1
  52. data/lib/kramdown/parser/kramdown/paragraph.rb +5 -4
  53. data/lib/kramdown/parser/kramdown/smart_quotes.rb +1 -1
  54. data/lib/kramdown/parser/kramdown/table.rb +51 -12
  55. data/lib/kramdown/parser/markdown.rb +69 -0
  56. data/lib/kramdown/utils.rb +2 -2
  57. data/lib/kramdown/utils/entities.rb +10 -1
  58. data/lib/kramdown/utils/html.rb +22 -11
  59. data/lib/kramdown/utils/ordered_hash.rb +44 -40
  60. data/lib/kramdown/version.rb +1 -1
  61. data/man/man1/kramdown.1 +31 -4
  62. data/test/testcases/block/08_list/item_ial.html +1 -1
  63. data/test/testcases/block/11_ial/nested.html +11 -0
  64. data/test/testcases/block/11_ial/nested.text +15 -0
  65. data/test/testcases/block/13_definition_list/item_ial.html +1 -1
  66. data/test/testcases/block/14_table/escaping.html +52 -0
  67. data/test/testcases/block/14_table/escaping.text +19 -0
  68. data/test/testcases/block/14_table/simple.html.19 +139 -0
  69. data/test/testcases/block/14_table/simple.text +1 -1
  70. data/test/testcases/block/15_math/normal.html +13 -13
  71. data/test/testcases/block/16_toc/{no_toc_depth.html → no_toc.html} +0 -0
  72. data/test/testcases/block/16_toc/{no_toc_depth.options → no_toc.options} +0 -0
  73. data/test/testcases/block/16_toc/{no_toc_depth.text → no_toc.text} +0 -0
  74. data/test/testcases/block/16_toc/{toc_depth_2.html → toc_levels.html} +4 -4
  75. data/test/testcases/block/16_toc/toc_levels.options +1 -0
  76. data/test/testcases/block/16_toc/{toc_depth_2.text → toc_levels.text} +0 -0
  77. data/test/testcases/span/escaped_chars/normal.html +4 -0
  78. data/test/testcases/span/escaped_chars/normal.text +4 -0
  79. data/test/testcases/span/ial/simple.html +1 -1
  80. data/test/testcases/span/math/normal.html +2 -2
  81. metadata +20 -25
  82. data/benchmark/historic-jruby-1.4.0.dat +0 -7
  83. data/benchmark/historic-ruby-1.8.6.dat +0 -7
  84. data/benchmark/historic-ruby-1.8.7.dat +0 -7
  85. data/benchmark/historic-ruby-1.9.1p243.dat +0 -7
  86. data/benchmark/historic-ruby-1.9.2dev.dat +0 -7
  87. data/benchmark/static-jruby-1.4.0.dat +0 -7
  88. data/benchmark/static-ruby-1.8.6.dat +0 -7
  89. data/benchmark/static-ruby-1.8.7.dat +0 -7
  90. data/benchmark/static-ruby-1.9.1p243.dat +0 -7
  91. data/benchmark/static-ruby-1.9.2dev.dat +0 -7
  92. data/lib/kramdown/parser/kramdown/attribute_list.rb +0 -111
  93. data/lib/kramdown/parser/kramdown/extension.rb +0 -116
  94. data/test/testcases/block/16_toc/toc_depth_2.options +0 -1
@@ -26,14 +26,14 @@ module Kramdown
26
26
 
27
27
  module Converter
28
28
 
29
- # Converts a Kramdown::Document to the kramdown format.
29
+ # Converts an element tree to the kramdown format.
30
30
  class Kramdown < Base
31
31
 
32
32
  # :stopdoc:
33
33
 
34
- include ::Kramdown::Utils::HTML
34
+ include ::Kramdown::Utils::Html
35
35
 
36
- def initialize(doc)
36
+ def initialize(root, options)
37
37
  super
38
38
  @linkrefs = []
39
39
  @footnotes = []
@@ -45,12 +45,12 @@ module Kramdown
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
- res << "\n\n" if el.options[:category] == :block
48
+ res << "\n\n" if Element.category(el) == :block
49
49
  elsif [:ul, :dl, :ol, :codeblock].include?(el.type) && opts[:next] &&
50
50
  ([el.type, :codeblock].include?(opts[:next].type) ||
51
51
  (opts[:next].type == :blank && opts[:nnext] && [el.type, :codeblock].include?(opts[:nnext].type)))
52
52
  res << "^\n\n"
53
- elsif el.options[:category] == :block && ![:li, :dd, :dt, :td, :th, :tr, :thead, :tbody, :tfoot, :blank].include?(el.type) &&
53
+ elsif Element.category(el) == :block && ![:li, :dd, :dt, :td, :th, :tr, :thead, :tbody, :tfoot, :blank].include?(el.type) &&
54
54
  (el.type != :p || !el.options[:transparent])
55
55
  res << "\n"
56
56
  end
@@ -77,7 +77,7 @@ module Kramdown
77
77
  ""
78
78
  end
79
79
 
80
- ESCAPED_CHAR_RE = /(\$\$|[\\*_`\[\]\{"'])|^[ ]{0,3}(:)/
80
+ ESCAPED_CHAR_RE = /(\$\$|[\\*_`\[\]\{"'|])|^[ ]{0,3}(:)/
81
81
 
82
82
  def convert_text(el, opts)
83
83
  if opts[:raw_text]
@@ -90,10 +90,9 @@ module Kramdown
90
90
  end
91
91
 
92
92
  def convert_p(el, opts)
93
- w = @doc.options[:line_width] - opts[:indent].to_s.to_i
93
+ w = @options[:line_width] - opts[:indent].to_s.to_i
94
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!(/\|/, '\\|')
95
+ first.gsub!(/^(?:(#|>)|(\d+)\.|([+-]\s))/) { $1 || $3 ? "\\#{$1 || $3}" : "#{$2}\\."} if first
97
96
  second.gsub!(/^([=-]+\s*?)$/, "\\\1") if second
98
97
  [first, second, *rest].compact.join("\n") + "\n"
99
98
  end
@@ -132,7 +131,7 @@ module Kramdown
132
131
  ["#{opts[:index] + 1}.".ljust(4), 4]
133
132
  end
134
133
  if ial = ial_for_element(el)
135
- sym += ial + " "
134
+ sym << ial << " "
136
135
  end
137
136
 
138
137
  opts[:indent] += width
@@ -156,7 +155,7 @@ module Kramdown
156
155
  def convert_dd(el, opts)
157
156
  sym, width = ": ", (el.children.first.type == :codeblock ? 4 : 2)
158
157
  if ial = ial_for_element(el)
159
- sym += ial + " "
158
+ sym << ial << " "
160
159
  end
161
160
 
162
161
  opts[:indent] += width
@@ -166,7 +165,7 @@ module Kramdown
166
165
  last = last.map {|l| " "*width + l}.join("\n")
167
166
  text = first + (last.empty? ? "" : "\n") + last + newlines
168
167
  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
168
+ text << "\n" if text !~ /\n\n\Z/ && opts[:next] && opts[:next].type == :dt
170
169
  if el.children.first.type == :p && !el.children.first.options[:transparent]
171
170
  "\n#{sym}#{text}"
172
171
  elsif el.children.first.type == :codeblock
@@ -184,19 +183,19 @@ module Kramdown
184
183
 
185
184
  def convert_html_element(el, opts)
186
185
  markdown_attr = el.options[:category] == :block && el.children.any? do |c|
187
- c.type != :html_element && (c.type != :p || !c.options[:transparent]) && c.options[:category] == :block
186
+ c.type != :html_element && (c.type != :p || !c.options[:transparent]) && Element.category(c) == :block
188
187
  end
189
188
  opts[:force_raw_text] = true if %w{script pre code}.include?(el.value)
190
189
  opts[:raw_text] = opts[:force_raw_text] || opts[:block_raw_text] || (el.options[:category] != :span && !markdown_attr)
191
190
  opts[:block_raw_text] = true if el.options[:category] == :block && opts[:raw_text]
192
191
  res = inner(el, opts)
193
192
  if el.options[:category] == :span
194
- "<#{el.value}#{html_attributes(el)}" << (!res.empty? || HTML_TAGS_WITH_BODY.include?(el.value) ? ">#{res}</#{el.value}>" : " />")
193
+ "<#{el.value}#{html_attributes(el.attr)}" << (!res.empty? || HTML_TAGS_WITH_BODY.include?(el.value) ? ">#{res}</#{el.value}>" : " />")
195
194
  else
196
195
  output = ''
197
- output << "<#{el.value}#{html_attributes(el)}"
196
+ output << "<#{el.value}#{html_attributes(el.attr)}"
198
197
  output << " markdown=\"1\"" if markdown_attr
199
- if !res.empty? && el.options[:parse_type] != :block
198
+ if !res.empty? && el.options[:content_model] != :block
200
199
  output << ">#{res}</#{el.value}>"
201
200
  elsif !res.empty?
202
201
  output << ">\n#{res}" << "</#{el.value}>"
@@ -205,20 +204,19 @@ module Kramdown
205
204
  else
206
205
  output << " />"
207
206
  end
208
- output << "\n" if @stack.last.type != :html_element || @stack.last.options[:parse_type] != :raw
207
+ output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
209
208
  output
210
209
  end
211
210
  end
212
211
 
213
212
  def convert_xml_comment(el, opts)
214
- if el.options[:category] == :block && (@stack.last.type != :html_element || @stack.last.options[:parse_type] != :raw)
213
+ if el.options[:category] == :block && (@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw)
215
214
  el.value + "\n"
216
215
  else
217
216
  el.value
218
217
  end
219
218
  end
220
219
  alias :convert_xml_pi :convert_xml_comment
221
- alias :convert_html_doctype :convert_xml_comment
222
220
 
223
221
  def convert_table(el, opts)
224
222
  opts[:alignment] = el.options[:alignment]
@@ -257,9 +255,8 @@ module Kramdown
257
255
  end
258
256
 
259
257
  def convert_td(el, opts)
260
- inner(el, opts).gsub(/\|/, '\\|')
258
+ inner(el, opts)
261
259
  end
262
- alias :convert_th :convert_td
263
260
 
264
261
  def convert_comment(el, opts)
265
262
  if el.options[:category] == :block
@@ -310,7 +307,7 @@ module Kramdown
310
307
  end
311
308
 
312
309
  def convert_footnote(el, opts)
313
- @footnotes << [el.options[:name], @doc.parse_infos[:footnotes][el.options[:name]]]
310
+ @footnotes << [el.options[:name], el.value]
314
311
  "[^#{el.options[:name]}]"
315
312
  end
316
313
 
@@ -381,15 +378,15 @@ module Kramdown
381
378
  res = ''
382
379
  @footnotes.each do |name, data|
383
380
  res << "[^#{name}]:\n"
384
- res << inner(data[:content]).chomp.split(/\n/).map {|l| " #{l}"}.join("\n") + "\n\n"
381
+ res << inner(data).chomp.split(/\n/).map {|l| " #{l}"}.join("\n") + "\n\n"
385
382
  end
386
383
  res
387
384
  end
388
385
 
389
386
  def create_abbrev_defs
390
- return '' unless @doc.parse_infos[:abbrev_defs]
387
+ return '' unless @root.options[:abbrev_defs]
391
388
  res = ''
392
- @doc.parse_infos[:abbrev_defs].each do |name, text|
389
+ @root.options[:abbrev_defs].each do |name, text|
393
390
  res << "*[#{name}]: #{text}\n"
394
391
  end
395
392
  res
@@ -415,6 +412,8 @@ module Kramdown
415
412
  res.strip.empty? ? nil : "{:#{res}}"
416
413
  end
417
414
 
415
+ # :startdoc:
416
+
418
417
  end
419
418
 
420
419
  end
@@ -26,24 +26,37 @@ module Kramdown
26
26
 
27
27
  module Converter
28
28
 
29
- # Converts a Kramdown::Document to LaTeX. This converter uses ideas from other Markdown-to-LaTeX
30
- # converters like Pandoc and Maruku.
29
+ # Converts an element tree to LaTeX.
30
+ #
31
+ # This converter uses ideas from other Markdown-to-LaTeX converters like Pandoc and Maruku.
32
+ #
33
+ # You can customize this converter by sub-classing it and overriding the <tt>convert_NAME</tt>
34
+ # methods. Each such method takes the following parameters:
35
+ #
36
+ # [+el+] The element of type +NAME+ to be converted.
37
+ #
38
+ # [+opts+] A hash containing processing options that are passed down from parent elements. The
39
+ # key <tt>:parent</tt> is always set and contains the parent element as value.
40
+ #
41
+ # The return value of such a method has to be a string containing the element +el+ formatted
42
+ # correctly as LaTeX markup.
31
43
  class Latex < Base
32
44
 
33
- # :stopdoc:
34
-
35
- # Initialize the LaTeX converter with the given Kramdown document +doc+.
36
- def initialize(doc)
45
+ # Initialize the LaTeX converter with the +root+ element and the conversion +options+.
46
+ def initialize(root, options)
37
47
  super
38
48
  #TODO: set the footnote counter at the beginning of the document
39
- @doc.options[:footnote_nr]
40
- @doc.conversion_infos[:packages] = Set.new
49
+ @options[:footnote_nr]
50
+ @data[:packages] = Set.new
41
51
  end
42
52
 
53
+ # Dispatch the conversion of the element +el+ to a <tt>convert_TYPE</tt> method using the
54
+ # +type+ of the element.
43
55
  def convert(el, opts = {})
44
56
  send("convert_#{el.type}", el, opts)
45
57
  end
46
58
 
59
+ # Return the converted content of the children of +el+ as a string.
47
60
  def inner(el, opts)
48
61
  result = ''
49
62
  options = opts.dup.merge(:parent => el)
@@ -75,6 +88,8 @@ module Kramdown
75
88
  end
76
89
  end
77
90
 
91
+ # Helper method used by +convert_p+ to convert a paragraph that only contains a single
92
+ # <tt>:img</tt> element.
78
93
  def convert_standalone_image(el, opts, img)
79
94
  attrs = attribute_list(el)
80
95
  "\\begin{figure}#{attrs}\n\\begin{center}\n#{img}\n\\end{center}\n\\caption{#{escape(el.children.first.attr['alt'])}}\n\\end{figure}#{attrs}\n"
@@ -85,37 +100,23 @@ module Kramdown
85
100
  lang = el.attr['lang']
86
101
  if show_whitespace || lang
87
102
  result = "\\lstset{showspaces=%s,showtabs=%s}\n" % (show_whitespace ? ['true', 'true'] : ['false', 'false'])
88
- result += "\\lstset{language=#{lang}}\n" if lang
89
- result += "\\lstset{basicstyle=\\ttfamily\\footnotesize}\\lstset{columns=fixed,frame=tlbr}\n"
103
+ result << "\\lstset{language=#{lang}}\n" if lang
104
+ result << "\\lstset{basicstyle=\\ttfamily\\footnotesize}\\lstset{columns=fixed,frame=tlbr}\n"
90
105
  attrs = attribute_list(el)
91
- "#{result}\\begin{lstlisting}#{attrs}\n#{el.value}\n\\end{lstlisting}#{attrs}\n"
106
+ result << "\\begin{lstlisting}#{attrs}\n#{el.value}\n\\end{lstlisting}#{attrs}\n"
92
107
  else
93
108
  "\\begin{verbatim}#{el.value}\\end{verbatim}\n"
94
109
  end
95
110
  end
96
111
 
97
- def latex_environment(type, el, text)
98
- attrs = attribute_list(el)
99
- "\\begin{#{type}}#{attrs}\n#{text.rstrip}\n\\end{#{type}}#{attrs}\n"
100
- end
101
-
102
112
  def convert_blockquote(el, opts)
103
113
  latex_environment(el.children.size > 1 ? 'quotation' : 'quote', el, inner(el, opts))
104
114
  end
105
115
 
106
- HEADER_TYPES = {
107
- 1 => 'section',
108
- 2 => 'subsection',
109
- 3 => 'subsubsection',
110
- 4 => 'paragraph',
111
- 5 => 'subparagraph',
112
- 6 => 'subparagraph'
113
- }
114
116
  def convert_header(el, opts)
115
- type = HEADER_TYPES[el.options[:level]]
117
+ type = @options[:latex_headers][el.options[:level] - 1]
116
118
  if ((id = el.attr['id']) ||
117
- (@doc.options[:auto_ids] && (id = generate_id(el.options[:raw_text])))) &&
118
- (@doc.options[:toc_depth] <= 0 || el.options[:level] <= @doc.options[:toc_depth])
119
+ (@options[:auto_ids] && (id = generate_id(el.options[:raw_text])))) && in_toc?(el)
119
120
  "\\hypertarget{#{id}}{}\\#{type}{#{inner(el, opts)}}\\label{#{id}}\n\n"
120
121
  else
121
122
  "\\#{type}*{#{inner(el, opts)}}\n\n"
@@ -128,8 +129,8 @@ module Kramdown
128
129
  end
129
130
 
130
131
  def convert_ul(el, opts)
131
- if !@doc.conversion_infos[:has_toc] && (el.options[:ial][:refs].include?('toc') rescue nil)
132
- @doc.conversion_infos[:has_toc] = true
132
+ if !@data[:has_toc] && (el.options[:ial][:refs].include?('toc') rescue nil)
133
+ @data[:has_toc] = true
133
134
  '\tableofcontents'
134
135
  else
135
136
  latex_environment(el.type == :ul ? 'itemize' : 'enumerate', el, inner(el, opts))
@@ -159,7 +160,7 @@ module Kramdown
159
160
  elsif el.value == 'b'
160
161
  "\\emph{#{inner(el, opts)}}"
161
162
  else
162
- @doc.warnings << "Can't convert HTML element"
163
+ warning("Can't convert HTML element")
163
164
  ''
164
165
  end
165
166
  end
@@ -169,12 +170,11 @@ module Kramdown
169
170
  end
170
171
 
171
172
  def convert_xml_pi(el, opts)
172
- @doc.warnings << "Can't convert XML PI/HTML document type"
173
+ warning("Can't convert XML PI")
173
174
  ''
174
175
  end
175
- alias :convert_html_doctype :convert_xml_pi
176
176
 
177
- TABLE_ALIGNMENT_CHAR = {:default => 'l', :left => 'l', :center => 'c', :right => 'r'}
177
+ TABLE_ALIGNMENT_CHAR = {:default => 'l', :left => 'l', :center => 'c', :right => 'r'} # :nodoc:
178
178
 
179
179
  def convert_table(el, opts)
180
180
  align = el.options[:alignment].map {|a| TABLE_ALIGNMENT_CHAR[a]}.join('|')
@@ -201,7 +201,6 @@ module Kramdown
201
201
  def convert_td(el, opts)
202
202
  inner(el, opts)
203
203
  end
204
- alias :convert_th :convert_td
205
204
 
206
205
  def convert_comment(el, opts)
207
206
  el.value.split(/\n/).map {|l| "% #{l}"}.join("\n") + "\n"
@@ -209,7 +208,7 @@ module Kramdown
209
208
 
210
209
  def convert_br(el, opts)
211
210
  res = "\\newline"
212
- res += "\n" if (c = opts[:parent].children[opts[:index]+1]) && (c.type != :text || c.value !~ /^\s*\n/)
211
+ res << "\n" if (c = opts[:parent].children[opts[:index]+1]) && (c.type != :text || c.value !~ /^\s*\n/)
213
212
  res
214
213
  end
215
214
 
@@ -224,13 +223,13 @@ module Kramdown
224
223
 
225
224
  def convert_img(el, opts)
226
225
  if el.attr['src'] =~ /^(https?|ftps?):\/\//
227
- @doc.warnings << "Cannot include non-local image"
226
+ warning("Cannot include non-local image")
228
227
  ''
229
228
  elsif !el.options.attr['src'].empty?
230
- @doc.conversion_infos[:packages] << 'graphicx'
229
+ @data[:packages] << 'graphicx'
231
230
  "\\includegraphics{#{el.attr['src']}}"
232
231
  else
233
- @doc.warnings << "Cannot include image with empty path"
232
+ warning("Cannot include image with empty path")
234
233
  ''
235
234
  end
236
235
  end
@@ -240,8 +239,8 @@ module Kramdown
240
239
  end
241
240
 
242
241
  def convert_footnote(el, opts)
243
- @doc.conversion_infos[:packages] << 'fancyvrb'
244
- "\\footnote{#{inner(@doc.parse_infos[:footnotes][el.options[:name]][:content], opts).rstrip}}"
242
+ @data[:packages] << 'fancyvrb'
243
+ "\\footnote{#{inner(el.value, opts).rstrip}}"
245
244
  end
246
245
 
247
246
  def convert_raw(el, opts)
@@ -504,16 +503,16 @@ module Kramdown
504
503
  253 => ['\\\'y'],
505
504
  254 => ['\thorn', 'wasysym'],
506
505
  255 => ['\"y'],
507
- }
506
+ } # :nodoc:
508
507
  ENTITY_CONV_TABLE.each {|k,v| ENTITY_CONV_TABLE[k] = v.unshift(v.shift + '{}')}
509
508
 
510
509
  def convert_entity(el, opts)
511
510
  text, package = ENTITY_CONV_TABLE[el.value.code_point]
512
511
  if text
513
- @doc.conversion_infos[:packages] << package if package
512
+ @data[:packages] << package if package
514
513
  text
515
514
  else
516
- @doc.warnings << "Couldn't find entity in substitution table!"
515
+ warning("Couldn't find entity in substitution table!")
517
516
  ''
518
517
  end
519
518
  end
@@ -522,20 +521,20 @@ module Kramdown
522
521
  :mdash => '---', :ndash => '--', :hellip => '\ldots{}',
523
522
  :laquo_space => '\guillemotleft{}~', :raquo_space => '~\guillemotright{}',
524
523
  :laquo => '\guillemotleft{}', :raquo => '\guillemotright{}'
525
- }
524
+ } # :nodoc:
526
525
  def convert_typographic_sym(el, opts)
527
526
  TYPOGRAPHIC_SYMS[el.value]
528
527
  end
529
528
 
530
- SMART_QUOTE_SYMS = {:lsquo => '`', :rsquo => '\'', :ldquo => '``', :rdquo => '\'\''}
529
+ SMART_QUOTE_SYMS = {:lsquo => '`', :rsquo => '\'', :ldquo => '``', :rdquo => '\'\''} # :nodoc:
531
530
  def convert_smart_quote(el, opts)
532
531
  res = SMART_QUOTE_SYMS[el.value]
533
- res += "{}" if (nel = opts[:parent].children[opts[:index]+1]) && nel.type == :smart_quote
532
+ res << "{}" if (nel = opts[:parent].children[opts[:index]+1]) && nel.type == :smart_quote
534
533
  res
535
534
  end
536
535
 
537
536
  def convert_math(el, opts)
538
- @doc.conversion_infos[:packages] += %w[amssymb amsmath amsthm amsfonts]
537
+ @data[:packages] += %w[amssymb amsmath amsthm amsfonts]
539
538
  if el.options[:category] == :block
540
539
  if el.value =~ /\A\s*\\begin\{/
541
540
  el.value
@@ -551,6 +550,22 @@ module Kramdown
551
550
  el.value
552
551
  end
553
552
 
553
+ # Wrap the +text+ inside a LaTeX environment of type +type+. The element +el+ is passed on to
554
+ # the method #attribute_list -- the resulting string is appended to both the <tt>\\begin</tt>
555
+ # and the <tt>\\end</tt> lines of the LaTeX environment for easier post-processing of LaTeX
556
+ # environments.
557
+ def latex_environment(type, el, text)
558
+ attrs = attribute_list(el)
559
+ "\\begin{#{type}}#{attrs}\n#{text.rstrip}\n\\end{#{type}}#{attrs}\n"
560
+ end
561
+
562
+ # Return a LaTeX comment containing all attributes as <tt>key="value"</tt> pairs.
563
+ def attribute_list(el)
564
+ attrs = el.attr.map {|k,v| v.nil? ? '' : " #{k}=\"#{v.to_s}\""}.compact.sort.join('')
565
+ attrs = " % #{attrs}" if !attrs.empty?
566
+ attrs
567
+ end
568
+
554
569
  ESCAPE_MAP = {
555
570
  "^" => "\\^{}",
556
571
  "\\" => "\\textbackslash{}",
@@ -558,19 +573,14 @@ module Kramdown
558
573
  "|" => "\\textbar{}",
559
574
  "<" => "\\textless{}",
560
575
  ">" => "\\textgreater{}"
561
- }.merge(Hash[*("{}$%&_#".scan(/./).map {|c| [c, "\\#{c}"]}.flatten)])
562
- ESCAPE_RE = Regexp.union(*ESCAPE_MAP.collect {|k,v| k})
576
+ }.merge(Hash[*("{}$%&_#".scan(/./).map {|c| [c, "\\#{c}"]}.flatten)]) # :nodoc:
577
+ ESCAPE_RE = Regexp.union(*ESCAPE_MAP.collect {|k,v| k}) # :nodoc:
563
578
 
579
+ # Escape the special LaTeX characters in the string +str+.
564
580
  def escape(str)
565
581
  str.gsub(ESCAPE_RE) {|m| ESCAPE_MAP[m]}
566
582
  end
567
583
 
568
- def attribute_list(el)
569
- attrs = el.attr.map {|k,v| v.nil? ? '' : " #{k}=\"#{v.to_s}\""}.compact.sort.join('')
570
- attrs = " % #{attrs}" if !attrs.empty?
571
- attrs
572
- end
573
-
574
584
  end
575
585
 
576
586
  end
@@ -52,32 +52,24 @@ module Kramdown
52
52
  # doc = Kramdown::Document.new('This *is* some kramdown text')
53
53
  # puts doc.to_html
54
54
  #
55
- # The #to_html method is a shortcut for using the Converter::Html class.
55
+ # The #to_html method is a shortcut for using the Converter::Html class. See #method_missing for
56
+ # more information.
56
57
  #
57
- # The second argument to the #new method is an options hash for customizing the behaviour of the
58
- # used parser and the converter. See Document#new for more information!
58
+ # The second argument to the ::new method is an options hash for customizing the behaviour of the
59
+ # used parser and the converter. See ::new for more information!
59
60
  class Document
60
61
 
61
- # The element tree of the document. It is immediately available after the #new method has been
62
- # called.
63
- attr_accessor :tree
62
+ # The root Element of the element tree. It is immediately available after the ::new method has
63
+ # been called.
64
+ attr_accessor :root
64
65
 
65
- # The options hash which holds the options for parsing/converting the Kramdown document. It is
66
- # possible that these values get changed during the parsing phase.
66
+ # The options hash which holds the options for parsing/converting the Kramdown document.
67
67
  attr_reader :options
68
68
 
69
69
  # An array of warning messages. It is filled with warnings during the parsing phase (i.e. in
70
- # #new) and the conversion phase.
70
+ # ::new) and the conversion phase.
71
71
  attr_reader :warnings
72
72
 
73
- # Holds needed parse information which is dependent on the used parser, like ALDs, link
74
- # definitions and so on. This information may be used by converters afterwards.
75
- attr_reader :parse_infos
76
-
77
- # Holds conversion information which is dependent on the used converter. A converter clears this
78
- # variable before doing the conversion.
79
- attr_reader :conversion_infos
80
-
81
73
 
82
74
  # Create a new Kramdown document from the string +source+ and use the provided +options+. The
83
75
  # options that can be used are defined in the Options module.
@@ -87,18 +79,14 @@ module Kramdown
87
79
  # select the kramdown parser, one would set the <tt>:input</tt> key to +Kramdown+. If this key
88
80
  # is not set, it defaults to +Kramdown+.
89
81
  #
90
- # The +source+ is immediately parsed by the selected parser so that the document tree is
82
+ # The +source+ is immediately parsed by the selected parser so that the root element is
91
83
  # immediately available and the output can be generated.
92
84
  def initialize(source, options = {})
93
- @options = Options.merge(options)
94
- @warnings = []
95
- @parse_infos = {}
96
- @parse_infos[:encoding] = source.encoding if RUBY_VERSION >= '1.9'
97
- @conversion_infos = {}
85
+ @options = Options.merge(options).freeze
98
86
  parser = (options[:input] || 'kramdown').to_s
99
87
  parser = parser[0..0].upcase + parser[1..-1]
100
88
  if Parser.const_defined?(parser)
101
- @tree = Parser.const_get(parser).parse(source, self)
89
+ @root, @warnings = Parser.const_get(parser).parse(source, @options)
102
90
  else
103
91
  raise Kramdown::Error.new("kramdown has no parser to handle the specified input format: #{options[:input]}")
104
92
  end
@@ -109,24 +97,468 @@ module Kramdown
109
97
  #
110
98
  # For example, +to_html+ would instantiate the Kramdown::Converter::Html class.
111
99
  def method_missing(id, *attr, &block)
112
- if id.to_s =~ /^to_(\w+)$/
113
- Converter.const_get($1[0..0].upcase + $1[1..-1]).convert(self)
100
+ if id.to_s =~ /^to_(\w+)$/ && (name = $1[0..0].upcase + $1[1..-1]) && Converter.const_defined?(name)
101
+ output, warnings = Converter.const_get(name).convert(@root, @options)
102
+ @warnings.concat(warnings)
103
+ output
114
104
  else
115
105
  super
116
106
  end
117
107
  end
118
108
 
119
109
  def inspect #:nodoc:
120
- "<KD:Document: options=#{@options.inspect} tree=#{@tree.inspect} warnings=#{@warnings.inspect}>"
110
+ "<KD:Document: options=#{@options.inspect} root=#{@root.inspect} warnings=#{@warnings.inspect}>"
121
111
  end
122
112
 
123
113
  end
124
114
 
125
115
 
126
- # Represents all elements in the parse tree.
116
+ # Represents all elements in the element tree.
127
117
  #
128
- # kramdown only uses this one class for representing all available elements in a parse tree
118
+ # kramdown only uses this one class for representing all available elements in an element tree
129
119
  # (paragraphs, headers, emphasis, ...). The type of element can be set via the #type accessor.
120
+ #
121
+ # Following is a description of all supported element types.
122
+ #
123
+ # == Structural Elements
124
+ #
125
+ # === :root
126
+ #
127
+ # [Category] None
128
+ # [Usage context] As the root element of a document
129
+ # [Content model] Block-level elements
130
+ #
131
+ # Represents the root of a kramdown document.
132
+ #
133
+ # The root element contains the following option keys:
134
+ #
135
+ # <tt>:encoding</tt>:: When running on Ruby 1.9 this key has to be set to the encoding used for
136
+ # the text parts of the kramdown document.
137
+ #
138
+ # <tt>:abbrev_defs</tt>:: This key may be used to store the mapping of abbreviation to
139
+ # abbreviation definition.
140
+ #
141
+ #
142
+ # === :blank
143
+ #
144
+ # [Category] Block-level element
145
+ # [Usage context] Where block-level elements are expected
146
+ # [Content model] Empty
147
+ #
148
+ # Represents one or more blank lines. It is not allowed to have two or more consecutive blank
149
+ # elements.
150
+ #
151
+ # The +value+ field may contain the original content of the blank lines.
152
+ #
153
+ #
154
+ # === :p
155
+ #
156
+ # [Category] Block-level element
157
+ # [Usage context] Where block-level elements are expected
158
+ # [Content model] Span-level elements
159
+ #
160
+ # Represents a paragraph.
161
+ #
162
+ # If the option <tt>:transparent</tt> is +true+, this element just represents a block of text.
163
+ # I.e. this element just functions as a container for span-level elements.
164
+ #
165
+ #
166
+ # === :header
167
+ #
168
+ # [Category] Block-level element
169
+ # [Usage context] Where block-level elements are expected
170
+ # [Content model] Span-level elements
171
+ #
172
+ # Represents a header.
173
+ #
174
+ # The option <tt>:level</tt> specifies the header level and has to contain a number between 1 and
175
+ # \6. The option <tt>:raw_text</tt> has to contain the raw header text.
176
+ #
177
+ #
178
+ # === :blockquote
179
+ #
180
+ # [Category] Block-level element
181
+ # [Usage context] Where block-level elements are expected
182
+ # [Content model] Block-level elements
183
+ #
184
+ # Represents a blockquote.
185
+ #
186
+ #
187
+ # === :codeblock
188
+ #
189
+ # [Category] Block-level element
190
+ # [Usage context] Where block-level elements are expected
191
+ # [Content model] Empty
192
+ #
193
+ # Represents a code block, i.e. a block of text that should be used as-is.
194
+ #
195
+ # The +value+ field has to contain the content of the code block.
196
+ #
197
+ #
198
+ # === :ul
199
+ #
200
+ # [Category] Block-level element
201
+ # [Usage context] Where block-level elements are expected
202
+ # [Content model] One or more :li elements
203
+ #
204
+ # Represents an unordered list.
205
+ #
206
+ #
207
+ # === :ol
208
+ #
209
+ # [Category] Block-level element
210
+ # [Usage context] Where block-level elements are expected
211
+ # [Content model] One or more :li elements
212
+ #
213
+ # Represents an ordered list.
214
+ #
215
+ #
216
+ # === :li
217
+ #
218
+ # [Category] None
219
+ # [Usage context] Inside :ol and :ul elements
220
+ # [Content model] Block-level elements
221
+ #
222
+ # Represents a list item of an ordered or unordered list.
223
+ #
224
+ #
225
+ # === :dl
226
+ #
227
+ # [Category] Block-level element
228
+ # [Usage context] Where block-level elements are expected
229
+ # [Content model] One or more groups each consisting of one or more :dt elements followed by one
230
+ # or more :dd elements.
231
+ #
232
+ # Represents a definition list which contains groups consisting of terms and definitions for them.
233
+ #
234
+ #
235
+ # === :dt
236
+ #
237
+ # [Category] None
238
+ # [Usage context] Before :dt or :dd elements inside a :dl elment
239
+ # [Content model] Span-level elements
240
+ #
241
+ # Represents the term part of a term-definition group in a definition list.
242
+ #
243
+ #
244
+ # === :dd
245
+ #
246
+ # [Category] None
247
+ # [Usage context] After :dt or :dd elements inside a :dl elment
248
+ # [Content model] Block-level elements
249
+ #
250
+ # Represents the definition part of a term-definition group in a definition list.
251
+ #
252
+ #
253
+ # === :hr
254
+ #
255
+ # [Category] Block-level element
256
+ # [Usage context] Where block-level elements are expected
257
+ # [Content model] None
258
+ #
259
+ # Represents a horizontal line.
260
+ #
261
+ #
262
+ # === :table
263
+ #
264
+ # [Category] Block-level element
265
+ # [Usage context] Where block-level elements are expected
266
+ # [Content model] Zero or one :thead elements, one or more :tbody elements, zero or one :tfoot
267
+ # elements
268
+ #
269
+ # Represents a table. Each table row (i.e. :tr element) of the table has to contain the same
270
+ # number of :td elements.
271
+ #
272
+ # The option <tt>:alignment</tt> has to be an array containing the alignment values, exactly one
273
+ # for each column of the table. The possible alignment values are <tt>:left</tt>,
274
+ # <tt>:center</tt>, <tt>:right</tt> and <tt>:default</tt>.
275
+ #
276
+ #
277
+ # === :thead
278
+ #
279
+ # [Category] None
280
+ # [Usage context] As first element inside a :table element
281
+ # [Content model] One or more :tr elements
282
+ #
283
+ # Represents the table header.
284
+ #
285
+ #
286
+ # === :tbody
287
+ #
288
+ # [Category] None
289
+ # [Usage context] After a :thead element but before a :tfoot element inside a :table element
290
+ # [Content model] One or more :tr elements
291
+ #
292
+ # Represents a table body.
293
+ #
294
+ #
295
+ # === :tfoot
296
+ #
297
+ # [Category] None
298
+ # [Usage context] As last element inside a :table element
299
+ # [Content model] One or more :tr elements
300
+ #
301
+ # Represents the table footer.
302
+ #
303
+ #
304
+ # === :tr
305
+ #
306
+ # [Category] None
307
+ # [Usage context] Inside :thead, :tbody and :tfoot elements
308
+ # [Content model] One or more :td elements
309
+ #
310
+ # Represents a table row.
311
+ #
312
+ #
313
+ # === :td
314
+ #
315
+ # [Category] None
316
+ # [Usage context] Inside :tr elements
317
+ # [Content model] As child of :thead/:tr span-level elements, as child of :tbody/:tr and
318
+ # :tfoot/:tr block-level elements
319
+ #
320
+ # Represents a table cell.
321
+ #
322
+ #
323
+ # === :math
324
+ #
325
+ # [Category] Block/span-level element
326
+ # [Usage context] Where block/span-level elements are expected
327
+ # [Content model] None
328
+ #
329
+ # Represents mathematical text that is written in LaTeX.
330
+ #
331
+ # The +value+ field has to contain the actual mathematical text.
332
+ #
333
+ # The option <tt>:category</tt> has to be set to either <tt>:span</tt> or <tt>:block</tt>
334
+ # depending on the context where the element is used.
335
+ #
336
+ #
337
+ # == Text Markup Elements
338
+ #
339
+ # === :text
340
+ #
341
+ # [Category] Span-level element
342
+ # [Usage context] Where span-level elements are expected
343
+ # [Content model] None
344
+ #
345
+ # Represents text.
346
+ #
347
+ # The +value+ field has to contain the text itself.
348
+ #
349
+ #
350
+ # === :br
351
+ #
352
+ # [Category] Span-level element
353
+ # [Usage context] Where span-level elements are expected
354
+ # [Content model] None
355
+ #
356
+ # Represents a hard line break.
357
+ #
358
+ #
359
+ # === :a
360
+ #
361
+ # [Category] Span-level element
362
+ # [Usage context] Where span-level elements are expected
363
+ # [Content model] Span-level elements
364
+ #
365
+ # Represents a link to an URL.
366
+ #
367
+ # The attribute +href+ has to be set to the URL to which the link points. The attribute +title+
368
+ # optionally contains the title of the link.
369
+ #
370
+ #
371
+ # === :img
372
+ #
373
+ # [Category] Span-level element
374
+ # [Usage context] Where span-level elements are expected
375
+ # [Content model] None
376
+ #
377
+ # Represents an image.
378
+ #
379
+ # The attribute +src+ has to be set to the URL of the image. The attribute +alt+ has to contain a
380
+ # text description of the image. The attribute +title+ optionally contains the title of the image.
381
+ #
382
+ #
383
+ # === :codespan
384
+ #
385
+ # [Category] Span-level element
386
+ # [Usage context] Where span-level elements are expected
387
+ # [Content model] None
388
+ #
389
+ # Represents verbatim text.
390
+ #
391
+ # The +value+ field has to contain the content of the code span.
392
+ #
393
+ #
394
+ # === :footnote
395
+ #
396
+ # [Category] Span-level element
397
+ # [Usage context] Where span-level elements are expected
398
+ # [Content model] None
399
+ #
400
+ # Represents a footnote marker.
401
+ #
402
+ # The +value+ field has to contain an element whose children are the content of the footnote. The
403
+ # option <tt>:name</tt> has to contain a valid and unique footnote name. A valid footnote name
404
+ # consists of a word character or a digit and then optionally followed by other word characters,
405
+ # digits or dashes.
406
+ #
407
+ #
408
+ # === :em
409
+ #
410
+ # [Category] Span-level element
411
+ # [Usage context] Where span-level elements are expected
412
+ # [Content model] Span-level elements
413
+ #
414
+ # Represents emphasis of its contents.
415
+ #
416
+ #
417
+ # === :strong
418
+ #
419
+ # [Category] Span-level element
420
+ # [Usage context] Where span-level elements are expected
421
+ # [Content model] Span-level elements
422
+ #
423
+ # Represents strong importance for its contents.
424
+ #
425
+ #
426
+ # === :entity
427
+ #
428
+ # [Category] Span-level element
429
+ # [Usage context] Where span-level elements are expected
430
+ # [Content model] None
431
+ #
432
+ # Represents an HTML entity.
433
+ #
434
+ # The +value+ field has to contain an instance of Kramdown::Utils::Entities::Entity. The option
435
+ # <tt>:original</tt> can be used to store the original representation of the entity.
436
+ #
437
+ #
438
+ # === :typographic_sym
439
+ #
440
+ # [Category] Span-level element
441
+ # [Usage context] Where span-level elements are expected
442
+ # [Content model] None
443
+ #
444
+ # Represents a typographic symbol.
445
+ #
446
+ # The +value+ field needs to contain a Symbol representing the specific typographic symbol from
447
+ # the following list:
448
+ #
449
+ # <tt>:mdash</tt>:: An mdash character (---)
450
+ # <tt>:ndash</tt>:: An ndash character (--)
451
+ # <tt>:hellip</tt>:: An ellipsis (...)
452
+ # <tt>:laquo</tt>:: A left guillemet (<<)
453
+ # <tt>:raquo</tt>:: A right guillemet (>>)
454
+ # <tt>:laquo_space</tt>:: A left guillemet with a space (<< )
455
+ # <tt>:raquo_space</tt>:: A right guillemet with a space ( >>)
456
+ #
457
+ #
458
+ # === :smart_quote
459
+ #
460
+ # [Category] Span-level element
461
+ # [Usage context] Where span-level elements are expected
462
+ # [Content model] None
463
+ #
464
+ # Represents a quotation character.
465
+ #
466
+ # The +value+ field needs to contain a Symbol representing the specific quotation character:
467
+ #
468
+ # <tt>:lsquo</tt>:: Left single quote
469
+ # <tt>:rsquo</tt>:: Right single quote
470
+ # <tt>:ldquo</tt>:: Left double quote
471
+ # <tt>:rdquo</tt>:: Right double quote
472
+ #
473
+ #
474
+ # === :abbreviation
475
+ #
476
+ # [Category] Span-level element
477
+ # [Usage context] Where span-level elements are expected
478
+ # [Content model] None
479
+ #
480
+ # Represents a text part that is an abbreviation.
481
+ #
482
+ # The +value+ field has to contain the text part that is the abbreviation. The definition of the
483
+ # abbreviation is stored in the <tt>:root</tt> element of the document.
484
+ #
485
+ #
486
+ # == Other Elements
487
+ #
488
+ # === :html_element
489
+ #
490
+ # [Category] Block/span-level element
491
+ # [Usage context] Where block/span-level elements or raw HTML elements are expected
492
+ # [Content model] Depends on the element
493
+ #
494
+ # Represents an HTML element.
495
+ #
496
+ # The +value+ field has to contain the name of the HTML element the element is representing.
497
+ #
498
+ # The option <tt>:category</tt> has to be set to either <tt>:span</tt> or <tt>:block</tt>
499
+ # depending on the whether the element is a block-level or a span-level element. The option
500
+ # <tt>:content_model</tt> has to be set to the content model for the element (either
501
+ # <tt>:block</tt> if it contains block-level elements, <tt>:span</tt> if it contains span-level
502
+ # elements or <tt>:raw</tt> if it contains raw content).
503
+ #
504
+ #
505
+ # === :xml_comment
506
+ #
507
+ # [Category] Block/span-level element
508
+ # [Usage context] Where block/span-level elements are expected or in raw HTML elements
509
+ # [Content model] None
510
+ #
511
+ # Represents an XML/HTML comment.
512
+ #
513
+ # The +value+ field has to contain the whole XML/HTML comment including the delimiters.
514
+ #
515
+ # The option <tt>:category</tt> has to be set to either <tt>:span</tt> or <tt>:block</tt>
516
+ # depending on the context where the element is used.
517
+ #
518
+ #
519
+ # === :xml_pi
520
+ #
521
+ # [Category] Block/span-level element
522
+ # [Usage context] Where block/span-level elements are expected or in raw HTML elements
523
+ # [Content model] None
524
+ #
525
+ # Represents an XML/HTML processing instruction.
526
+ #
527
+ # The +value+ field has to contain the whole XML/HTML processing instruction including the
528
+ # delimiters.
529
+ #
530
+ # The option <tt>:category</tt> has to be set to either <tt>:span</tt> or <tt>:block</tt>
531
+ # depending on the context where the element is used.
532
+ #
533
+ #
534
+ # === :comment
535
+ #
536
+ # [Category] Block/span-level element
537
+ # [Usage context] Where block/span-level elements are expected
538
+ # [Content model] None
539
+ #
540
+ # Represents a comment.
541
+ #
542
+ # The +value+ field has to contain the comment.
543
+ #
544
+ # The option <tt>:category</tt> has to be set to either <tt>:span</tt> or <tt>:block</tt>
545
+ # depending on the context where the element is used.
546
+ #
547
+ #
548
+ # === :raw
549
+ #
550
+ # [Category] Block/span-level element
551
+ # [Usage context] Where block/span-level elements are expected
552
+ # [Content model] None
553
+ #
554
+ # Represents a raw string that should not be modified. For example, the element could contain some
555
+ # HTML code that should be output as-is without modification and escaping.
556
+ #
557
+ # The +value+ field has to contain the actual raw text.
558
+ #
559
+ # The option <tt>:category</tt> has to be set to either <tt>:span</tt> or <tt>:block</tt>
560
+ # depending on the context where the element is used. The option <tt>:type</tt> can be set to an
561
+ # array of strings to define for which converters the raw string is valid.
130
562
  class Element
131
563
 
132
564
  # A symbol representing the element type. For example, <tt>:p</tt> or <tt>:blockquote</tt>.
@@ -136,32 +568,45 @@ module Kramdown
136
568
  # Many elements don't use this field.
137
569
  attr_accessor :value
138
570
 
139
- # The attributes of the element. Uses an Utils::OrderedHash to retain the insertion order.
140
- attr_reader :attr
141
-
142
- # The options hash for the element. It is used for storing arbitray options as well as the
143
- # following special contents:
144
- #
145
- # - Category of the element, either <tt>:block</tt> or <tt>:span</tt>, under the
146
- # <tt>:category</tt> key. If this key is absent, it can be assumed that the element is in the
147
- # <tt>:span</tt> category.
148
- attr_accessor :options
149
-
150
571
  # The child elements of this element.
151
572
  attr_accessor :children
152
573
 
153
574
 
154
575
  # Create a new Element object of type +type+. The optional parameters +value+, +attr+ and
155
576
  # +options+ can also be set in this constructor for convenience.
156
- def initialize(type, value = nil, attr = nil, options = {})
157
- @type, @value, @attr, @options = type, value, Utils::OrderedHash.new(attr), options
577
+ def initialize(type, value = nil, attr = nil, options = nil)
578
+ @type, @value, @attr, @options = type, value, (Utils::OrderedHash.new.merge!(attr) if attr), options
158
579
  @children = []
159
580
  end
160
581
 
582
+ # The attributes of the element. Uses an Utils::OrderedHash to retain the insertion order.
583
+ def attr
584
+ @attr ||= Utils::OrderedHash.new
585
+ end
586
+
587
+ # The options hash for the element. It is used for storing arbitray options.
588
+ def options
589
+ @options ||= {}
590
+ end
591
+
161
592
  def inspect #:nodoc:
162
593
  "<kd:#{@type}#{@value.nil? ? '' : ' ' + @value.inspect} #{@attr.inspect}#{options.empty? ? '' : ' ' + @options.inspect}#{@children.empty? ? '' : ' ' + @children.inspect}>"
163
594
  end
164
595
 
596
+ CATEGORY = {} # :nodoc:
597
+ [:blank, :p, :header, :blockquote, :codeblock, :ul, :ol, :dl, :table, :hr].each {|b| CATEGORY[b] = :block}
598
+ [:text, :a, :br, :img, :codespan, :footnote, :em, :strong, :entity, :typographic_sym,
599
+ :smart_quote, :abbreviation].each {|b| CATEGORY[b] = :span}
600
+
601
+ # Return the category of +el+ which can be <tt>:block</tt>, <tt>:span</tt> or +nil+.
602
+ #
603
+ # Most elements have a fixed category, however, some elements can either appear in a block-level
604
+ # or a span-level context. These elements need to have the option <tt>:category</tt> correctly
605
+ # set.
606
+ def self.category(el)
607
+ CATEGORY[el.type] || el.options[:category]
608
+ end
609
+
165
610
  end
166
611
 
167
612
  end