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
@@ -30,8 +30,8 @@ kramdown comes with a small benchmark to test how fast it is in regard to four o
30
30
  implementations: Maruku, BlueFeather, BlueCloth and RDiscount. The first two are written using only
31
31
  Ruby, the latter two use the C discount library for the actual hard work (which makes them really
32
32
  fast but they do not provide additional syntax elements). As one can see below, kramdown is
33
- currently (June 2010) ~4x faster than Maruku, ~9x faster than BlueFeather but ~30x slower than
34
- BlueCloth and rdiscount:
33
+ currently (November 2010) ~3-4x faster than Maruku, ~4-5x faster than BlueFeather but ~30x slower
34
+ than BlueCloth and rdiscount:
35
35
 
36
36
  <pre><code>
37
37
  {execute_cmd: {command: "ruby -Ilib -rubygems benchmark/benchmark.rb", process_output: false, escape_html: true}}
@@ -41,11 +41,12 @@ BlueCloth and rdiscount:
41
41
  And here are some graphs which show the execution times of the various kramdown releases on
42
42
  different Ruby interpreters:
43
43
 
44
- ![ruby 1.8.6]({relocatable: img/graph-ruby-1.8.6.png})
45
- ![ruby 1.8.7]({relocatable: img/graph-ruby-1.8.7.png})
46
- ![ruby 1.9.1p243]({relocatable: img/graph-ruby-1.9.1p243.png})
47
- ![ruby 1.9.2dev]({relocatable: img/graph-ruby-1.9.2dev.png})
48
- ![jruby 1.4.0]({relocatable: img/graph-jruby-1.4.0.png})
44
+ ![ruby 1.8.5p231]({relocatable: img/graph-ruby-1.8.5-231.png})
45
+ ![ruby 1.8.6p399]({relocatable: img/graph-ruby-1.8.6-399.png})
46
+ ![ruby 1.8.7p249]({relocatable: img/graph-ruby-1.8.7-249.png})
47
+ ![ruby 1.8.7p302]({relocatable: img/graph-ruby-1.8.7-302.png})
48
+ ![ruby 1.9.2p0]({relocatable: img/graph-ruby-1.9.2p0-0.png})
49
+ ![jruby 1.5.3]({relocatable: img/graph-jruby-1.5.3-249.png})
49
50
 
50
51
  [Markdown Test Suite]: http://daringfireball.net/projects/downloads/MarkdownTest_1.0.zip
51
52
  [MDTest]: http://www.michelf.com/docs/projets/mdtest-1.0.zip
@@ -19,8 +19,9 @@
19
19
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
20
  #++
21
21
  #
22
-
23
22
  # All the code in this file is backported from Ruby 1.8.7 sothat kramdown works under 1.8.5
23
+ #
24
+ # :stopdoc:
24
25
 
25
26
  if RUBY_VERSION == '1.8.5'
26
27
  require 'rexml/parsers/baseparser'
@@ -22,14 +22,12 @@
22
22
 
23
23
  module Kramdown
24
24
 
25
- # == Converter Module
25
+ # This module contains all available converters, i.e. classes that take a root Element and convert
26
+ # it to a specific output format. The result is normally a string. For example, the
27
+ # Converter::Html module converts an element tree into valid HTML.
26
28
  #
27
- # This module contains all available converters, i.e. classes that take a document and convert the
28
- # document tree to a specific output format, normally a string. For example, the Html module
29
- # converts the document tree into HTML.
30
- #
31
- # Converters use the Base class for common functionality (like applying a template to the output)-
32
- # see its API documentation for how to create a converter class.
29
+ # Converters use the Base class for common functionality (like applying a template to the output)
30
+ # \- see its API documentation for how to create a custom converter class.
33
31
  module Converter
34
32
 
35
33
  autoload :Base, 'kramdown/converter/base'
@@ -26,57 +26,91 @@ module Kramdown
26
26
 
27
27
  module Converter
28
28
 
29
- # == Base class for converters
29
+ # == \Base class for converters
30
30
  #
31
31
  # This class serves as base class for all converters. It provides methods that can/should be
32
32
  # used by all converters (like #generate_id) as well as common functionality that is
33
33
  # automatically applied to the result (for example, embedding the output into a template).
34
34
  #
35
- # == Implementing a converter
35
+ # A converter object is used as a throw-away object, i.e. it is only used for storing the needed
36
+ # state information during conversion. Therefore one can't instantiate a converter object
37
+ # directly but only use the Base::convert method.
36
38
  #
37
- # Implementing a new converter is rather easy: just create a new sub class from this class and
38
- # put it in the Kramdown::Converter module (the latter is only needed if auto-detection should
39
- # work properly). Then you need to implement the #convert(tree) method which takes a document
40
- # tree and should return the converted output.
39
+ # == Implementing a converter
41
40
  #
42
- # The document instance is automatically set as @doc in Base#initialize. Furthermore, the
43
- # document instance provides a hash called `conversion_infos` that is also automatically cleared
44
- # and can be used to store information about the conversion process.
41
+ # Implementing a new converter is rather easy: just derive a new class from this class and put
42
+ # it in the Kramdown::Converter module (the latter is only needed if auto-detection should work
43
+ # properly). Then you need to implement the <tt>convert(el)</tt> method which has to contain
44
+ # the conversion code for converting an element and has to return the conversion result.
45
45
  #
46
46
  # The actual transformation of the document tree can be done in any way. However, writing one
47
- # method per tree element type is a straight forward way to do it - this is how the Html and
48
- # Latex converters do the transformation.
47
+ # method per element type is a straight forward way to do it - this is how the Html and Latex
48
+ # converters do the transformation.
49
+ #
50
+ # Have a look at the Base::convert method for additional information!
49
51
  class Base
50
52
 
51
- # Initialize the converter with the given Kramdown document +doc+.
52
- def initialize(doc)
53
- @doc = doc
54
- @doc.conversion_infos.clear
53
+ # Can be used by a converter for storing arbitrary information during the conversion process.
54
+ attr_reader :data
55
+
56
+ # The hash with the conversion options.
57
+ attr_reader :options
58
+
59
+ # The root element that is converted.
60
+ attr_reader :root
61
+
62
+ # The warnings array.
63
+ attr_reader :warnings
64
+
65
+ # Initialize the converter with the given +root+ element and +options+ hash.
66
+ def initialize(root, options)
67
+ @options = options
68
+ @root = root
69
+ @data = {}
70
+ @warnings = []
55
71
  end
56
72
  private_class_method(:new, :allocate)
57
73
 
58
- # Convert the Kramdown document +doc+ to the output format implemented by a subclass.
74
+ # Convert the element tree +tree+ and return the resulting conversion object (normally a
75
+ # string) and an array with warning messages. The parameter +options+ specifies the conversion
76
+ # options that should be used.
77
+ #
78
+ # Initializes a new instance of the calling class and then calls the #convert method with
79
+ # +tree+ as parameter. If the +template+ option is specified and non-empty, the result is
80
+ # rendered into the specified template. The template resolution is done in the following way:
81
+ #
82
+ # 1. Look in the current working directory for the template.
83
+ #
84
+ # 2. Append <tt>.convertername</tt> (e.g. +.html+) to the template name and look for the
85
+ # resulting file in the current working directory.
59
86
  #
60
- # Initializes a new instance of the calling class and then calls the #convert method that must
61
- # be implemented by each subclass. If the +template+ option is specified and non-empty, the
62
- # result is rendered into the specified template.
63
- def self.convert(doc)
64
- result = new(doc).convert(doc.tree)
65
- result = apply_template(doc, result) if !doc.options[:template].empty?
66
- result
87
+ # 3. Append <tt>.convertername</tt> to the template name and look for it in the kramdown data
88
+ # directory.
89
+ def self.convert(tree, options = {})
90
+ converter = new(tree, ::Kramdown::Options.merge(options.merge(tree.options[:options] || {})))
91
+ result = converter.convert(tree)
92
+ result = apply_template(converter, result) if !converter.options[:template].empty?
93
+ [result, converter.warnings]
67
94
  end
68
95
 
69
- # Apply the template specified in the +doc+ options, using +body+ as the body string.
70
- def self.apply_template(doc, body)
71
- erb = ERB.new(get_template(doc.options[:template]))
96
+ # Convert the element +el+ and return the resulting object.
97
+ #
98
+ # This is the only method that has to be implemented by sub-classes!
99
+ def convert(el)
100
+ raise NotImplementedError
101
+ end
102
+
103
+ # Apply the +template+ using +body+ as the body string.
104
+ def self.apply_template(converter, body) # :nodoc:
105
+ erb = ERB.new(get_template(converter.options[:template]))
72
106
  obj = Object.new
73
- obj.instance_variable_set(:@doc, doc)
107
+ obj.instance_variable_set(:@converter, converter)
74
108
  obj.instance_variable_set(:@body, body)
75
109
  erb.result(obj.instance_eval{binding})
76
110
  end
77
111
 
78
112
  # Return the template specified by +template+.
79
- def self.get_template(template)
113
+ def self.get_template(template) # :nodoc:
80
114
  format_ext = '.' + self.name.split(/::/).last.downcase
81
115
  shipped = File.join(::Kramdown.data_dir, template + format_ext)
82
116
  if File.exist?(template)
@@ -90,18 +124,39 @@ module Kramdown
90
124
  end
91
125
  end
92
126
 
127
+ # Add the given warning +text+ to the warning array.
128
+ def warning(text)
129
+ @warnings << text
130
+ end
93
131
 
94
- # Generate an unique alpha-numeric ID from the the string +str+ for use as header ID.
132
+ # Return +true+ if the header element +el+ should be used for the table of contents (as
133
+ # specified by the +toc_levels+ option).
134
+ def in_toc?(el)
135
+ if @options[:toc_depth] >= 0
136
+ warn('Option toc_depth is deprecated, use the new option toc_levels instead')
137
+ @options[:toc_depth] == 0 || el.options[:level] <= @options[:toc_depth]
138
+ else
139
+ @options[:toc_levels].include?(el.options[:level])
140
+ end
141
+ end
142
+
143
+ # Generate an unique alpha-numeric ID from the the string +str+ for use as a header ID.
144
+ #
145
+ # Uses the option +auto_id_prefix+: the value of this option is prepended to every generated
146
+ # ID.
95
147
  def generate_id(str)
96
- gen_id = str.gsub(/[^a-zA-Z0-9 -]/, '').gsub(/^[^a-zA-Z]*/, '').gsub(' ', '-').downcase
148
+ gen_id = str.gsub(/^[^a-zA-Z]+/, '')
149
+ gen_id.tr!('^a-zA-Z0-9 -', '')
150
+ gen_id.tr!(' ', '-')
151
+ gen_id.downcase!
97
152
  gen_id = 'section' if gen_id.length == 0
98
153
  @used_ids ||= {}
99
154
  if @used_ids.has_key?(gen_id)
100
- gen_id += '-' + (@used_ids[gen_id] += 1).to_s
155
+ gen_id += '-' << (@used_ids[gen_id] += 1).to_s
101
156
  else
102
157
  @used_ids[gen_id] = 0
103
158
  end
104
- @doc.options[:auto_id_prefix] + gen_id
159
+ @options[:auto_id_prefix] + gen_id
105
160
  end
106
161
 
107
162
  end
@@ -27,74 +27,97 @@ module Kramdown
27
27
  module Converter
28
28
 
29
29
  # Converts a Kramdown::Document to HTML.
30
+ #
31
+ # You can customize the HTML converter by sub-classing it and overriding the
32
+ # <tt>convert_NAME</tt> methods. Each such method takes the following parameters:
33
+ #
34
+ # [+el+] The element of type +NAME+ to be converted.
35
+ #
36
+ # [+indent+] A number representing the current amount of spaces for indent (only used for
37
+ # block-level elements).
38
+ #
39
+ # The return value of such a method has to be a string containing the element +el+ formatted as
40
+ # HTML element.
30
41
  class Html < Base
31
42
 
32
- include ::Kramdown::Utils::HTML
33
-
34
- # :stopdoc:
35
-
36
- # Defines the amount of indentation used when nesting HTML tags.
37
- INDENTATION = 2
38
-
39
43
  begin
40
44
  require 'coderay'
41
45
 
42
46
  # Highlighting via coderay is available if this constant is +true+.
43
47
  HIGHLIGHTING_AVAILABLE = true
44
48
  rescue LoadError => e
45
- HIGHLIGHTING_AVAILABLE = false
49
+ HIGHLIGHTING_AVAILABLE = false # :nodoc:
46
50
  end
47
51
 
52
+ include ::Kramdown::Utils::Html
53
+
54
+
55
+ # The amount of indentation used when nesting HTML tags.
56
+ attr_accessor :indent
57
+
48
58
  # Initialize the HTML converter with the given Kramdown document +doc+.
49
- def initialize(doc)
59
+ def initialize(root, options)
50
60
  super
51
- @footnote_counter = @footnote_start = @doc.options[:footnote_nr]
61
+ @footnote_counter = @footnote_start = @options[:footnote_nr]
52
62
  @footnotes = []
53
63
  @toc = []
54
64
  @toc_code = nil
65
+ @indent = 2
66
+ @stack = []
55
67
  end
56
68
 
57
- def convert(el, indent = -INDENTATION, opts = {})
58
- send("convert_#{el.type}", el, indent, opts)
69
+ # The mapping of element type to conversion method.
70
+ DISPATCHER = Hash.new {|h,k| h[k] = "convert_#{k}"}
71
+
72
+ # Dispatch the conversion of the element +el+ to a <tt>convert_TYPE</tt> method using the
73
+ # +type+ of the element.
74
+ def convert(el, indent = -@indent)
75
+ send(DISPATCHER[el.type], el, indent)
59
76
  end
60
77
 
61
- def inner(el, indent, opts)
78
+ # Return the converted content of the children of +el+ as a string. The parameter +indent+ has
79
+ # to be the amount of indentation used for the element +el+.
80
+ #
81
+ # Pushes +el+ onto the <tt>@stack</tt> before converting the child elements and pops it from
82
+ # the stack afterwards.
83
+ def inner(el, indent)
62
84
  result = ''
63
- indent += INDENTATION
85
+ indent += @indent
86
+ @stack.push(el)
64
87
  el.children.each do |inner_el|
65
- opts[:parent] = el
66
- result << send("convert_#{inner_el.type}", inner_el, indent, opts)
88
+ result << send(DISPATCHER[inner_el.type], inner_el, indent)
67
89
  end
90
+ @stack.pop
68
91
  result
69
92
  end
70
93
 
71
- def convert_blank(el, indent, opts)
94
+ def convert_blank(el, indent)
72
95
  "\n"
73
96
  end
74
97
 
75
- def convert_text(el, indent, opts)
98
+ def convert_text(el, indent)
76
99
  escape_html(el.value, :text)
77
100
  end
78
101
 
79
- def convert_p(el, indent, opts)
102
+ def convert_p(el, indent)
80
103
  if el.options[:transparent]
81
- "#{inner(el, indent, opts)}"
104
+ inner(el, indent)
82
105
  else
83
- "#{' '*indent}<p#{html_attributes(el)}>#{inner(el, indent, opts)}</p>\n"
106
+ "#{' '*indent}<p#{html_attributes(el.attr)}>#{inner(el, indent)}</p>\n"
84
107
  end
85
108
  end
86
109
 
87
- def convert_codeblock(el, indent, opts)
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
91
- opts = {:wrap => @doc.options[:coderay_wrap], :line_numbers => @doc.options[:coderay_line_numbers],
92
- :line_number_start => @doc.options[:coderay_line_number_start], :tab_width => @doc.options[:coderay_tab_width],
93
- :bold_every => @doc.options[:coderay_bold_every], :css => @doc.options[:coderay_css]}
94
- result = CodeRay.scan(el.value, lang.to_sym).html(opts).chomp + "\n"
95
- "#{' '*indent}<div#{html_attributes(el)}>#{result}#{' '*indent}</div>\n"
110
+ def convert_codeblock(el, indent)
111
+ if el.attr['lang'] && HIGHLIGHTING_AVAILABLE
112
+ attr = el.attr.dup
113
+ opts = {:wrap => @options[:coderay_wrap], :line_numbers => @options[:coderay_line_numbers],
114
+ :line_number_start => @options[:coderay_line_number_start], :tab_width => @options[:coderay_tab_width],
115
+ :bold_every => @options[:coderay_bold_every], :css => @options[:coderay_css]}
116
+ result = CodeRay.scan(el.value, attr.delete('lang').to_sym).html(opts).chomp << "\n"
117
+ "#{' '*indent}<div#{html_attributes(attr)}>#{result}#{' '*indent}</div>\n"
96
118
  else
97
119
  result = escape_html(el.value)
120
+ result.chomp!
98
121
  if el.attr['class'].to_s =~ /\bshow-whitespaces\b/
99
122
  result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m|
100
123
  suffix = ($1 ? '-l' : ($2 ? '-r' : ''))
@@ -106,45 +129,41 @@ module Kramdown
106
129
  end.join('')
107
130
  end
108
131
  end
109
- "#{' '*indent}<pre#{html_attributes(el)}><code>#{result}#{result =~ /\n\Z/ ? '' : "\n"}</code></pre>\n"
132
+ "#{' '*indent}<pre#{html_attributes(el.attr)}><code>#{result}\n</code></pre>\n"
110
133
  end
111
134
  end
112
135
 
113
- def convert_blockquote(el, indent, opts)
114
- "#{' '*indent}<blockquote#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</blockquote>\n"
136
+ def convert_blockquote(el, indent)
137
+ "#{' '*indent}<blockquote#{html_attributes(el.attr)}>\n#{inner(el, indent)}#{' '*indent}</blockquote>\n"
115
138
  end
116
139
 
117
- def convert_header(el, indent, opts)
118
- el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
119
- if @doc.options[:auto_ids] && !el.attr['id']
120
- el.attr['id'] = generate_id(el.options[:raw_text])
140
+ def convert_header(el, indent)
141
+ attr = el.attr.dup
142
+ if @options[:auto_ids] && !attr['id']
143
+ attr['id'] = generate_id(el.options[:raw_text])
121
144
  end
122
- @toc << [el.options[:level], el.attr['id'], el.children] if el.attr['id'] && within_toc_depth?(el)
123
- "#{' '*indent}<h#{el.options[:level]}#{html_attributes(el)}>#{inner(el, indent, opts)}</h#{el.options[:level]}>\n"
145
+ @toc << [el.options[:level], attr['id'], el.children] if attr['id'] && in_toc?(el)
146
+ "#{' '*indent}<h#{el.options[:level]}#{html_attributes(attr)}>#{inner(el, indent)}</h#{el.options[:level]}>\n"
124
147
  end
125
148
 
126
- def within_toc_depth?(el)
127
- @doc.options[:toc_depth] <= 0 || el.options[:level] <= @doc.options[:toc_depth]
128
- end
129
-
130
- def convert_hr(el, indent, opts)
149
+ def convert_hr(el, indent)
131
150
  "#{' '*indent}<hr />\n"
132
151
  end
133
152
 
134
- def convert_ul(el, indent, opts)
153
+ def convert_ul(el, indent)
135
154
  if !@toc_code && (el.options[:ial][:refs].include?('toc') rescue nil) && (el.type == :ul || el.type == :ol)
136
155
  @toc_code = [el.type, el.attr, (0..128).to_a.map{|a| rand(36).to_s(36)}.join]
137
156
  @toc_code.last
138
157
  else
139
- "#{' '*indent}<#{el.type}#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</#{el.type}>\n"
158
+ "#{' '*indent}<#{el.type}#{html_attributes(el.attr)}>\n#{inner(el, indent)}#{' '*indent}</#{el.type}>\n"
140
159
  end
141
160
  end
142
161
  alias :convert_ol :convert_ul
143
162
  alias :convert_dl :convert_ul
144
163
 
145
- def convert_li(el, indent, opts)
146
- output = ' '*indent << "<#{el.type}" << html_attributes(el) << ">"
147
- res = inner(el, indent, opts)
164
+ def convert_li(el, indent)
165
+ output = ' '*indent << "<#{el.type}" << html_attributes(el.attr) << ">"
166
+ res = inner(el, indent)
148
167
  if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent])
149
168
  output << res << (res =~ /\n\Z/ ? ' '*indent : '')
150
169
  else
@@ -154,22 +173,22 @@ module Kramdown
154
173
  end
155
174
  alias :convert_dd :convert_li
156
175
 
157
- def convert_dt(el, indent, opts)
158
- "#{' '*indent}<dt#{html_attributes(el)}>#{inner(el, indent, opts)}</dt>\n"
176
+ def convert_dt(el, indent)
177
+ "#{' '*indent}<dt#{html_attributes(el.attr)}>#{inner(el, indent)}</dt>\n"
159
178
  end
160
179
 
161
- HTML_TAGS_WITH_BODY=['div', 'script', 'iframe', 'textarea']
180
+ # A list of all HTML tags that need to have a body (even if the body is empty).
181
+ HTML_TAGS_WITH_BODY=['div', 'script', 'iframe', 'textarea'] # :nodoc:
162
182
 
163
- def convert_html_element(el, indent, opts)
164
- parent = opts[:parent]
165
- res = inner(el, indent, opts)
183
+ def convert_html_element(el, indent)
184
+ res = inner(el, indent)
166
185
  if el.options[:category] == :span
167
- "<#{el.value}#{html_attributes(el)}" << (!res.empty? || HTML_TAGS_WITH_BODY.include?(el.value) ? ">#{res}</#{el.value}>" : " />")
186
+ "<#{el.value}#{html_attributes(el.attr)}" << (!res.empty? || HTML_TAGS_WITH_BODY.include?(el.value) ? ">#{res}</#{el.value}>" : " />")
168
187
  else
169
188
  output = ''
170
- output << ' '*indent if parent.type != :html_element || parent.options[:parse_type] != :raw
171
- output << "<#{el.value}#{html_attributes(el)}"
172
- if !res.empty? && el.options[:parse_type] != :block
189
+ output << ' '*indent if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
190
+ output << "<#{el.value}#{html_attributes(el.attr)}"
191
+ if !res.empty? && el.options[:content_model] != :block
173
192
  output << ">#{res}</#{el.value}>"
174
193
  elsif !res.empty?
175
194
  output << ">\n#{res.chomp}\n" << ' '*indent << "</#{el.value}>"
@@ -178,46 +197,47 @@ module Kramdown
178
197
  else
179
198
  output << " />"
180
199
  end
181
- output << "\n" if parent.type != :html_element || parent.options[:parse_type] != :raw
200
+ output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
182
201
  output
183
202
  end
184
203
  end
185
204
 
186
- def convert_xml_comment(el, indent, opts)
187
- if el.options[:category] == :block && (opts[:parent].type != :html_element || opts[:parent].options[:parse_type] != :raw)
188
- ' '*indent + el.value + "\n"
205
+ def convert_xml_comment(el, indent)
206
+ if el.options[:category] == :block && (@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw)
207
+ ' '*indent << el.value << "\n"
189
208
  else
190
209
  el.value
191
210
  end
192
211
  end
193
212
  alias :convert_xml_pi :convert_xml_comment
194
- alias :convert_html_doctype :convert_xml_comment
195
213
 
196
- def convert_table(el, indent, opts)
214
+ def convert_table(el, indent)
197
215
  if el.options[:alignment].all? {|a| a == :default}
198
216
  alignment = ''
199
217
  else
200
218
  alignment = el.options[:alignment].map do |a|
201
- "#{' '*(indent + INDENTATION)}" + (a == :default ? "<col />" : "<col align=\"#{a}\" />") + "\n"
219
+ "#{' '*(indent + @indent)}" << (a == :default ? "<col />" : "<col align=\"#{a}\" />") << "\n"
202
220
  end.join('')
203
221
  end
204
- "#{' '*indent}<table#{html_attributes(el)}>\n#{alignment}#{inner(el, indent, opts)}#{' '*indent}</table>\n"
222
+ "#{' '*indent}<table#{html_attributes(el.attr)}>\n#{alignment}#{inner(el, indent)}#{' '*indent}</table>\n"
205
223
  end
206
224
 
207
- def convert_thead(el, indent, opts)
208
- "#{' '*indent}<#{el.type}#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</#{el.type}>\n"
225
+ def convert_thead(el, indent)
226
+ "#{' '*indent}<#{el.type}#{html_attributes(el.attr)}>\n#{inner(el, indent)}#{' '*indent}</#{el.type}>\n"
209
227
  end
210
228
  alias :convert_tbody :convert_thead
211
229
  alias :convert_tfoot :convert_thead
212
230
  alias :convert_tr :convert_thead
213
231
 
214
- def convert_td(el, indent, opts)
215
- res = inner(el, indent, opts)
216
- "#{' '*indent}<#{el.type}#{html_attributes(el)}>#{res.empty? ? "&nbsp;" : res}</#{el.type}>\n"
232
+ ENTITY_NBSP = ::Kramdown::Utils::Entities.entity('nbsp') # :nodoc:
233
+
234
+ def convert_td(el, indent)
235
+ res = inner(el, indent)
236
+ type = (@stack[-2].type == :thead ? :th : :td)
237
+ "#{' '*indent}<#{type}#{html_attributes(el.attr)}>#{res.empty? ? entity_to_str(ENTITY_NBSP) : res}</#{type}>\n"
217
238
  end
218
- alias :convert_th :convert_td
219
239
 
220
- def convert_comment(el, indent, opts)
240
+ def convert_comment(el, indent)
221
241
  if el.options[:category] == :block
222
242
  "#{' '*indent}<!-- #{el.value} -->\n"
223
243
  else
@@ -225,46 +245,42 @@ module Kramdown
225
245
  end
226
246
  end
227
247
 
228
- def convert_br(el, indent, opts)
248
+ def convert_br(el, indent)
229
249
  "<br />"
230
250
  end
231
251
 
232
- def convert_a(el, indent, opts)
233
- do_obfuscation = el.attr['href'] =~ /^mailto:/
234
- if do_obfuscation
235
- el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
236
- href = obfuscate(el.attr['href'].sub(/^mailto:/, ''))
237
- mailto = obfuscate('mailto')
238
- el.attr['href'] = "#{mailto}:#{href}"
252
+ def convert_a(el, indent)
253
+ res = inner(el, indent)
254
+ attr = el.attr.dup
255
+ if attr['href'] =~ /^mailto:/
256
+ attr['href'] = obfuscate('mailto') << ":" << obfuscate(attr['href'].sub(/^mailto:/, ''))
257
+ res = obfuscate(res)
239
258
  end
240
- res = inner(el, indent, opts)
241
- res = obfuscate(res) if do_obfuscation
242
- "<a#{html_attributes(el)}>#{res}</a>"
259
+ "<a#{html_attributes(attr)}>#{res}</a>"
243
260
  end
244
261
 
245
- def convert_img(el, indent, opts)
246
- "<img#{html_attributes(el)} />"
262
+ def convert_img(el, indent)
263
+ "<img#{html_attributes(el.attr)} />"
247
264
  end
248
265
 
249
- def convert_codespan(el, indent, opts)
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>"
266
+ def convert_codespan(el, indent)
267
+ if el.attr['lang'] && HIGHLIGHTING_AVAILABLE
268
+ attr = el.attr.dup
269
+ result = CodeRay.scan(el.value, attr.delete('lang').to_sym).html(:wrap => :span, :css => @options[:coderay_css]).chomp
270
+ "<code#{html_attributes(attr)}>#{result}</code>"
255
271
  else
256
- "<code#{html_attributes(el)}>#{escape_html(el.value)}</code>"
272
+ "<code#{html_attributes(el.attr)}>#{escape_html(el.value)}</code>"
257
273
  end
258
274
  end
259
275
 
260
- def convert_footnote(el, indent, opts)
276
+ def convert_footnote(el, indent)
261
277
  number = @footnote_counter
262
278
  @footnote_counter += 1
263
- @footnotes << [el.options[:name], @doc.parse_infos[:footnotes][el.options[:name]]]
279
+ @footnotes << [el.options[:name], el.value]
264
280
  "<sup id=\"fnref:#{el.options[:name]}\"><a href=\"#fn:#{el.options[:name]}\" rel=\"footnote\">#{number}</a></sup>"
265
281
  end
266
282
 
267
- def convert_raw(el, indent, opts)
283
+ def convert_raw(el, indent)
268
284
  if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html')
269
285
  el.value + (el.options[:category] == :block ? "\n" : '')
270
286
  else
@@ -272,12 +288,12 @@ module Kramdown
272
288
  end
273
289
  end
274
290
 
275
- def convert_em(el, indent, opts)
276
- "<#{el.type}#{html_attributes(el)}>#{inner(el, indent, opts)}</#{el.type}>"
291
+ def convert_em(el, indent)
292
+ "<#{el.type}#{html_attributes(el.attr)}>#{inner(el, indent)}</#{el.type}>"
277
293
  end
278
294
  alias :convert_strong :convert_em
279
295
 
280
- def convert_entity(el, indent, opts)
296
+ def convert_entity(el, indent)
281
297
  entity_to_str(el.value, el.options[:original])
282
298
  end
283
299
 
@@ -289,32 +305,27 @@ module Kramdown
289
305
  :raquo_space => [::Kramdown::Utils::Entities.entity('nbsp'), ::Kramdown::Utils::Entities.entity('raquo')],
290
306
  :laquo => [::Kramdown::Utils::Entities.entity('laquo')],
291
307
  :raquo => [::Kramdown::Utils::Entities.entity('raquo')]
292
- }
293
- def convert_typographic_sym(el, indent, opts)
308
+ } # :nodoc:
309
+ def convert_typographic_sym(el, indent)
294
310
  TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e)}.join('')
295
311
  end
296
312
 
297
- def convert_smart_quote(el, indent, opts)
313
+ def convert_smart_quote(el, indent)
298
314
  entity_to_str(::Kramdown::Utils::Entities.entity(el.value.to_s))
299
315
  end
300
316
 
301
- def convert_math(el, indent, opts)
302
- el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
303
- el.attr['class'] ||= ''
304
- el.attr['class'] += (el.attr['class'].empty? ? '' : ' ') + 'math'
305
- type = 'span'
306
- type = 'div' if el.options[:category] == :block
307
- "<#{type}#{html_attributes(el)}>#{escape_html(el.value)}</#{type}>#{type == 'div' ? "\n" : ''}"
317
+ def convert_math(el, indent)
318
+ block = (el.options[:category] == :block)
319
+ "<script type=\"math/tex#{block ? '; mode=display' : ''}\">#{el.value}</script>#{block ? "\n" : ''}"
308
320
  end
309
321
 
310
- def convert_abbreviation(el, indent, opts)
311
- title = @doc.parse_infos[:abbrev_defs][el.value]
312
- title = nil if title.empty?
313
- "<abbr#{title ? " title=\"#{title}\"" : ''}>#{el.value}</abbr>"
322
+ def convert_abbreviation(el, indent)
323
+ title = @root.options[:abbrev_defs][el.value]
324
+ "<abbr#{!title.empty? ? " title=\"#{title}\"" : ''}>#{el.value}</abbr>"
314
325
  end
315
326
 
316
- def convert_root(el, indent, opts)
317
- result = inner(el, indent, opts)
327
+ def convert_root(el, indent)
328
+ result = inner(el, indent)
318
329
  result << footnote_content
319
330
  if @toc_code
320
331
  toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {})
@@ -328,6 +339,7 @@ module Kramdown
328
339
  result
329
340
  end
330
341
 
342
+ # Generate and return an element tree for the table of contents.
331
343
  def generate_toc_tree(toc, type, attr)
332
344
  sections = Element.new(type, nil, attr)
333
345
  sections.attr['id'] ||= 'markdown-toc'
@@ -336,7 +348,7 @@ module Kramdown
336
348
  li = Element.new(:li, nil, nil, {:level => level})
337
349
  li.children << Element.new(:p, nil, nil, {:transparent => true})
338
350
  a = Element.new(:a, nil, {'href' => "##{id}"})
339
- a.children += children
351
+ a.children.concat(children)
340
352
  li.children.last.children << a
341
353
  li.children << Element.new(type)
342
354
 
@@ -363,23 +375,23 @@ module Kramdown
363
375
  sections
364
376
  end
365
377
 
366
- # Helper method for obfuscating the +text+ by using HTML entities.
378
+ # Obfuscate the +text+ by using HTML entities.
367
379
  def obfuscate(text)
368
380
  result = ""
369
381
  text.each_byte do |b|
370
- result += (b > 128 ? b.chr : "&#%03d;" % b)
382
+ result << (b > 128 ? b.chr : "&#%03d;" % b)
371
383
  end
372
384
  result.force_encoding(text.encoding) if RUBY_VERSION >= '1.9'
373
385
  result
374
386
  end
375
387
 
376
- # Return a HTML list with the footnote content for the used footnotes.
388
+ # Return a HTML ordered list with the footnote content for the used footnotes.
377
389
  def footnote_content
378
390
  ol = Element.new(:ol)
379
391
  ol.attr['start'] = @footnote_start if @footnote_start != 1
380
392
  @footnotes.each do |name, data|
381
- li = Element.new(:li, nil, {'id' => "fn:#{name}"}, {:first_is_block => true})
382
- li.children = Marshal.load(Marshal.dump(data[:content].children))
393
+ li = Element.new(:li, nil, {'id' => "fn:#{name}"})
394
+ li.children = Marshal.load(Marshal.dump(data.children))
383
395
  ol.children << li
384
396
 
385
397
  ref = Element.new(:raw, "<a href=\"#fnref:#{name}\" rev=\"footnote\">&#8617;</a>")