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.
- data/CONTRIBUTERS +1 -1
- data/ChangeLog +532 -0
- data/README +22 -12
- data/Rakefile +9 -8
- data/VERSION +1 -1
- data/benchmark/benchmark.sh +61 -0
- data/benchmark/generate_data.rb +57 -55
- data/benchmark/testing.sh +1 -1
- data/benchmark/timing.sh +3 -3
- data/bin/kramdown +1 -2
- data/data/kramdown/document.html +2 -2
- data/data/kramdown/document.latex +2 -2
- data/doc/default.scss.css +6 -1
- data/doc/default.template +1 -1
- data/doc/documentation.page +1 -1
- data/doc/index.page +9 -7
- data/doc/installation.page +2 -3
- data/doc/links.markdown +1 -1
- data/doc/quickref.page +19 -19
- data/doc/syntax.page +117 -98
- data/doc/tests.page +8 -7
- data/lib/kramdown/compatibility.rb +2 -1
- data/lib/kramdown/converter.rb +5 -7
- data/lib/kramdown/converter/base.rb +87 -32
- data/lib/kramdown/converter/html.rb +134 -122
- data/lib/kramdown/converter/kramdown.rb +24 -25
- data/lib/kramdown/converter/latex.rb +65 -55
- data/lib/kramdown/document.rb +487 -42
- data/lib/kramdown/error.rb +3 -0
- data/lib/kramdown/options.rb +83 -28
- data/lib/kramdown/parser.rb +5 -5
- data/lib/kramdown/parser/base.rb +55 -13
- data/lib/kramdown/parser/html.rb +83 -71
- data/lib/kramdown/parser/kramdown.rb +73 -54
- data/lib/kramdown/parser/kramdown/abbreviation.rb +17 -12
- data/lib/kramdown/parser/kramdown/autolink.rb +2 -3
- data/lib/kramdown/parser/kramdown/blank_line.rb +1 -1
- data/lib/kramdown/parser/kramdown/block_boundary.rb +2 -2
- data/lib/kramdown/parser/kramdown/blockquote.rb +2 -2
- data/lib/kramdown/parser/kramdown/codeblock.rb +5 -2
- data/lib/kramdown/parser/kramdown/codespan.rb +1 -2
- data/lib/kramdown/parser/kramdown/emphasis.rb +1 -1
- data/lib/kramdown/parser/kramdown/escaped_chars.rb +1 -1
- data/lib/kramdown/parser/kramdown/extensions.rb +204 -0
- data/lib/kramdown/parser/kramdown/footnote.rb +7 -7
- data/lib/kramdown/parser/kramdown/header.rb +4 -2
- data/lib/kramdown/parser/kramdown/horizontal_rule.rb +1 -1
- data/lib/kramdown/parser/kramdown/html.rb +39 -45
- data/lib/kramdown/parser/kramdown/link.rb +19 -29
- data/lib/kramdown/parser/kramdown/list.rb +13 -13
- data/lib/kramdown/parser/kramdown/math.rb +1 -1
- data/lib/kramdown/parser/kramdown/paragraph.rb +5 -4
- data/lib/kramdown/parser/kramdown/smart_quotes.rb +1 -1
- data/lib/kramdown/parser/kramdown/table.rb +51 -12
- data/lib/kramdown/parser/markdown.rb +69 -0
- data/lib/kramdown/utils.rb +2 -2
- data/lib/kramdown/utils/entities.rb +10 -1
- data/lib/kramdown/utils/html.rb +22 -11
- data/lib/kramdown/utils/ordered_hash.rb +44 -40
- data/lib/kramdown/version.rb +1 -1
- data/man/man1/kramdown.1 +31 -4
- data/test/testcases/block/08_list/item_ial.html +1 -1
- data/test/testcases/block/11_ial/nested.html +11 -0
- data/test/testcases/block/11_ial/nested.text +15 -0
- data/test/testcases/block/13_definition_list/item_ial.html +1 -1
- data/test/testcases/block/14_table/escaping.html +52 -0
- data/test/testcases/block/14_table/escaping.text +19 -0
- data/test/testcases/block/14_table/simple.html.19 +139 -0
- data/test/testcases/block/14_table/simple.text +1 -1
- data/test/testcases/block/15_math/normal.html +13 -13
- data/test/testcases/block/16_toc/{no_toc_depth.html → no_toc.html} +0 -0
- data/test/testcases/block/16_toc/{no_toc_depth.options → no_toc.options} +0 -0
- data/test/testcases/block/16_toc/{no_toc_depth.text → no_toc.text} +0 -0
- data/test/testcases/block/16_toc/{toc_depth_2.html → toc_levels.html} +4 -4
- data/test/testcases/block/16_toc/toc_levels.options +1 -0
- data/test/testcases/block/16_toc/{toc_depth_2.text → toc_levels.text} +0 -0
- data/test/testcases/span/escaped_chars/normal.html +4 -0
- data/test/testcases/span/escaped_chars/normal.text +4 -0
- data/test/testcases/span/ial/simple.html +1 -1
- data/test/testcases/span/math/normal.html +2 -2
- metadata +20 -25
- data/benchmark/historic-jruby-1.4.0.dat +0 -7
- data/benchmark/historic-ruby-1.8.6.dat +0 -7
- data/benchmark/historic-ruby-1.8.7.dat +0 -7
- data/benchmark/historic-ruby-1.9.1p243.dat +0 -7
- data/benchmark/historic-ruby-1.9.2dev.dat +0 -7
- data/benchmark/static-jruby-1.4.0.dat +0 -7
- data/benchmark/static-ruby-1.8.6.dat +0 -7
- data/benchmark/static-ruby-1.8.7.dat +0 -7
- data/benchmark/static-ruby-1.9.1p243.dat +0 -7
- data/benchmark/static-ruby-1.9.2dev.dat +0 -7
- data/lib/kramdown/parser/kramdown/attribute_list.rb +0 -111
- data/lib/kramdown/parser/kramdown/extension.rb +0 -116
- data/test/testcases/block/16_toc/toc_depth_2.options +0 -1
data/doc/tests.page
CHANGED
@@ -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 (
|
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.
|
45
|
-
![ruby 1.8.
|
46
|
-
![ruby 1.
|
47
|
-
![ruby 1.
|
48
|
-
![
|
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'
|
data/lib/kramdown/converter.rb
CHANGED
@@ -22,14 +22,12 @@
|
|
22
22
|
|
23
23
|
module Kramdown
|
24
24
|
|
25
|
-
#
|
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
|
-
#
|
28
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
43
|
-
#
|
44
|
-
#
|
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
|
48
|
-
#
|
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
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
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
|
-
#
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
result =
|
65
|
-
result = apply_template(
|
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
|
-
#
|
70
|
-
|
71
|
-
|
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(:@
|
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
|
-
#
|
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(
|
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 += '-'
|
155
|
+
gen_id += '-' << (@used_ids[gen_id] += 1).to_s
|
101
156
|
else
|
102
157
|
@used_ids[gen_id] = 0
|
103
158
|
end
|
104
|
-
@
|
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(
|
59
|
+
def initialize(root, options)
|
50
60
|
super
|
51
|
-
@footnote_counter = @footnote_start = @
|
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
|
-
|
58
|
-
|
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
|
-
|
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 +=
|
85
|
+
indent += @indent
|
86
|
+
@stack.push(el)
|
64
87
|
el.children.each do |inner_el|
|
65
|
-
|
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
|
94
|
+
def convert_blank(el, indent)
|
72
95
|
"\n"
|
73
96
|
end
|
74
97
|
|
75
|
-
def convert_text(el, indent
|
98
|
+
def convert_text(el, indent)
|
76
99
|
escape_html(el.value, :text)
|
77
100
|
end
|
78
101
|
|
79
|
-
def convert_p(el, indent
|
102
|
+
def convert_p(el, indent)
|
80
103
|
if el.options[:transparent]
|
81
|
-
|
104
|
+
inner(el, indent)
|
82
105
|
else
|
83
|
-
"#{' '*indent}<p#{html_attributes(el)}>#{inner(el, indent
|
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
|
88
|
-
el
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
:
|
93
|
-
|
94
|
-
|
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}
|
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
|
114
|
-
"#{' '*indent}<blockquote#{html_attributes(el)}>\n#{inner(el, indent
|
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
|
118
|
-
|
119
|
-
if @
|
120
|
-
|
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],
|
123
|
-
"#{' '*indent}<h#{el.options[:level]}#{html_attributes(
|
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
|
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
|
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
|
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
|
146
|
-
output = ' '*indent << "<#{el.type}" << html_attributes(el) << ">"
|
147
|
-
res = inner(el, indent
|
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
|
158
|
-
"#{' '*indent}<dt#{html_attributes(el)}>#{inner(el, indent
|
176
|
+
def convert_dt(el, indent)
|
177
|
+
"#{' '*indent}<dt#{html_attributes(el.attr)}>#{inner(el, indent)}</dt>\n"
|
159
178
|
end
|
160
179
|
|
161
|
-
|
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
|
164
|
-
|
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
|
171
|
-
output << "<#{el.value}#{html_attributes(el)}"
|
172
|
-
if !res.empty? && el.options[:
|
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
|
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
|
187
|
-
if el.options[:category] == :block && (
|
188
|
-
' '*indent
|
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
|
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 +
|
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
|
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
|
208
|
-
"#{' '*indent}<#{el.type}#{html_attributes(el)}>\n#{inner(el, indent
|
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
|
-
|
215
|
-
|
216
|
-
|
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
|
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
|
248
|
+
def convert_br(el, indent)
|
229
249
|
"<br />"
|
230
250
|
end
|
231
251
|
|
232
|
-
def convert_a(el, indent
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
href = obfuscate(
|
237
|
-
|
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
|
-
|
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
|
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
|
250
|
-
el
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
276
|
+
def convert_footnote(el, indent)
|
261
277
|
number = @footnote_counter
|
262
278
|
@footnote_counter += 1
|
263
|
-
@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
|
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
|
276
|
-
"<#{el.type}#{html_attributes(el)}>#{inner(el, indent
|
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
|
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
|
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
|
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
|
302
|
-
|
303
|
-
|
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
|
311
|
-
title = @
|
312
|
-
title
|
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
|
317
|
-
result = inner(el, indent
|
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
|
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
|
-
#
|
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
|
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}"}
|
382
|
-
li.children = Marshal.load(Marshal.dump(data
|
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\">↩</a>")
|