Converts a Kramdown::Document to HTML.
You can customize the HTML converter by sub-classing it and overriding the
convert_NAME
methods. Each such method takes the following
parameters:
el
The element of type NAME
to be converted.
indent
A number representing the current amount of spaces for indent (only used for block-level elements).
The return value of such a method has to be a string containing the element
el
formatted as HTML element.
The mapping of element type to conversion method.
The amount of indentation used when nesting HTML tags.
Initialize the HTML converter with the given Kramdown document doc
.
# File lib/kramdown/converter/html.rb, line 38 def initialize(root, options) super @footnote_counter = @footnote_start = @options[:footnote_nr] @footnotes = [] @footnotes_by_name = {} @footnote_location = nil @toc = [] @toc_code = nil @indent = 2 @stack = [] end
Add the syntax highlighter name to the ‘class’ attribute of the given
attribute hash. And overwrites or add a “language-LANG” part using the
lang
parameter if lang
is not nil.
# File lib/kramdown/converter/html.rb, line 372 def add_syntax_highlighter_to_class_attr(attr, lang = nil) (attr['class'] = (attr['class'] || '') + " highlighter-#{@options[:syntax_highlighter]}").lstrip! attr['class'].sub!(/\blanguage-\S+|(^)/) { "language-#{lang}#{$1 ? ' ' : ''}" } if lang end
Dispatch the conversion of the element el
to a
convert_TYPE
method using the type
of the
element.
# File lib/kramdown/converter/html.rb, line 55 def convert(el, indent = -@indent) send(DISPATCHER[el.type], el, indent) end
# File lib/kramdown/converter/html.rb, line 244 def convert_a(el, indent) format_as_span_html(el.type, el.attr, inner(el, indent)) end
# File lib/kramdown/converter/html.rb, line 328 def convert_abbreviation(el, indent) title = @root.options[:abbrev_defs][el.value] attr = @root.options[:abbrev_attr][el.value].dup attr['title'] = title unless title.empty? format_as_span_html("abbr", attr, el.value) end
# File lib/kramdown/converter/html.rb, line 75 def convert_blank(el, indent) "\n" end
# File lib/kramdown/converter/html.rb, line 120 def convert_blockquote(el, indent) format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent) end
# File lib/kramdown/converter/html.rb, line 240 def convert_br(el, indent) "<br />" end
# File lib/kramdown/converter/html.rb, line 91 def convert_codeblock(el, indent) attr = el.attr.dup lang = extract_code_language!(attr) hl_opts = {} highlighted_code = highlight_code(el.value, el.options[:lang] || lang, :block, hl_opts) if highlighted_code add_syntax_highlighter_to_class_attr(attr, lang || hl_opts[:default_lang]) "#{' '*indent}<div#{html_attributes(attr)}>#{highlighted_code}#{' '*indent}</div>\n" else result = escape_html(el.value) result.chomp! if el.attr['class'].to_s =~ /\bshow-whitespaces\b/ result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m| suffix = ($1 ? '-l' : ($2 ? '-r' : '')) m.scan(/./).map do |c| case c when "\t" then "<span class=\"ws-tab#{suffix}\">\t</span>" when " " then "<span class=\"ws-space#{suffix}\">⋅</span>" end end.join('') end end code_attr = {} code_attr['class'] = "language-#{lang}" if lang "#{' '*indent}<pre#{html_attributes(attr)}><code#{html_attributes(code_attr)}>#{result}\n</code></pre>\n" end end
# File lib/kramdown/converter/html.rb, line 252 def convert_codespan(el, indent) attr = el.attr.dup lang = extract_code_language(attr) hl_opts = {} result = highlight_code(el.value, lang, :span, hl_opts) if result add_syntax_highlighter_to_class_attr(attr, hl_opts[:default_lang]) else result = escape_html(el.value) end format_as_span_html('code', attr, result) end
# File lib/kramdown/converter/html.rb, line 232 def convert_comment(el, indent) if el.options[:category] == :block "#{' '*indent}<!-- #{el.value} -->\n" else "<!-- #{el.value} -->" end end
# File lib/kramdown/converter/html.rb, line 150 def convert_dl(el, indent) format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent) end
# File lib/kramdown/converter/html.rb, line 166 def convert_dt(el, indent) attr = el.attr.dup @stack.last.options[:ial][:refs].each do |ref| if ref =~ /\Aauto_ids(?:-([\w-]+))?/ attr['id'] = ($1 ? $1 : '') << basic_generate_id(el.options[:raw_text]) break end end if !attr['id'] && @stack.last.options[:ial] && @stack.last.options[:ial][:refs] format_as_block_html(el.type, attr, inner(el, indent), indent) end
# File lib/kramdown/converter/html.rb, line 288 def convert_em(el, indent) format_as_span_html(el.type, el.attr, inner(el, indent)) end
# File lib/kramdown/converter/html.rb, line 293 def convert_entity(el, indent) entity_to_str(el.value, el.options[:original]) end
# File lib/kramdown/converter/html.rb, line 266 def convert_footnote(el, indent) repeat = '' if (footnote = @footnotes_by_name[el.options[:name]]) number = footnote[2] repeat = ":#{footnote[3] += 1}" else number = @footnote_counter @footnote_counter += 1 @footnotes << [el.options[:name], el.value, number, 0] @footnotes_by_name[el.options[:name]] = @footnotes.last end "<sup id=\"fnref:#{el.options[:name]}#{repeat}\"><a href=\"#fn:#{el.options[:name]}\" class=\"footnote\">#{number}</a></sup>" end
# File lib/kramdown/converter/html.rb, line 124 def convert_header(el, indent) attr = el.attr.dup if @options[:auto_ids] && !attr['id'] attr['id'] = generate_id(el.options[:raw_text]) end @toc << [el.options[:level], attr['id'], el.children] if attr['id'] && in_toc?(el) level = output_header_level(el.options[:level]) format_as_block_html("h#{level}", attr, inner(el, indent), indent) end
# File lib/kramdown/converter/html.rb, line 134 def convert_hr(el, indent) "#{' '*indent}<hr#{html_attributes(el.attr)} />\n" end
# File lib/kramdown/converter/html.rb, line 177 def convert_html_element(el, indent) res = inner(el, indent) if el.options[:category] == :span "<#{el.value}#{html_attributes(el.attr)}" << (res.empty? && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) ? " />" : ">#{res}</#{el.value}>") else output = '' output << ' '*indent if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw output << "<#{el.value}#{html_attributes(el.attr)}" if el.options[:is_closed] && el.options[:content_model] == :raw output << " />" elsif !res.empty? && el.options[:content_model] != :block output << ">#{res}</#{el.value}>" elsif !res.empty? output << ">\n#{res.chomp}\n" << ' '*indent << "</#{el.value}>" elsif HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) output << " />" else output << "></#{el.value}>" end output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw output end end
# File lib/kramdown/converter/html.rb, line 248 def convert_img(el, indent) "<img#{html_attributes(el.attr)} />" end
# File lib/kramdown/converter/html.rb, line 154 def convert_li(el, indent) output = ' '*indent << "<#{el.type}" << html_attributes(el.attr) << ">" res = inner(el, indent) if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent]) output << res << (res =~ /\n\Z/ ? ' '*indent : '') else output << "\n" << res << ' '*indent end output << "</#{el.type}>\n" end
# File lib/kramdown/converter/html.rb, line 314 def convert_math(el, indent) if (result = format_math(el, :indent => indent)) result else attr = el.attr.dup (attr['class'] = (attr['class'] || '') << " kdmath").lstrip! if el.options[:category] == :block format_as_block_html('div', attr, "$$\n#{el.value}\n$$", indent) else format_as_span_html('span', attr, "$#{el.value}$") end end end
# File lib/kramdown/converter/html.rb, line 83 def convert_p(el, indent) if el.options[:transparent] inner(el, indent) else format_as_block_html(el.type, el.attr, inner(el, indent), indent) end end
# File lib/kramdown/converter/html.rb, line 280 def convert_raw(el, indent) if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html') el.value + (el.options[:category] == :block ? "\n" : '') else '' end end
# File lib/kramdown/converter/html.rb, line 335 def convert_root(el, indent) result = inner(el, indent) if @footnote_location result.sub!(/#{@footnote_location}/, footnote_content.gsub(/\/, "\\\\\\\\")) else result << footnote_content end if @toc_code toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {}) text = if toc_tree.children.size > 0 convert(toc_tree, 0) else '' end result.sub!(/#{@toc_code.last}/, text.gsub(/\/, "\\\\\\\\")) end result end
# File lib/kramdown/converter/html.rb, line 310 def convert_smart_quote(el, indent) entity_to_str(smart_quote_entity(el)) end
# File lib/kramdown/converter/html.rb, line 210 def convert_table(el, indent) format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent) end
# File lib/kramdown/converter/html.rb, line 220 def convert_td(el, indent) res = inner(el, indent) type = (@stack[-2].type == :thead ? :th : :td) attr = el.attr alignment = @stack[-3].options[:alignment][@stack.last.children.index(el)] if alignment != :default attr = el.attr.dup attr['style'] = (attr.has_key?('style') ? "#{attr['style']}; ": '') << "text-align: #{alignment}" end format_as_block_html(type, attr, res.empty? ? entity_to_str(ENTITY_NBSP) : res, indent) end
# File lib/kramdown/converter/html.rb, line 79 def convert_text(el, indent) escape_html(el.value, :text) end
# File lib/kramdown/converter/html.rb, line 306 def convert_typographic_sym(el, indent) TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e)}.join('') end
# File lib/kramdown/converter/html.rb, line 138 def convert_ul(el, indent) if !@toc_code && (el.options[:ial][:refs].include?('toc') rescue nil) @toc_code = [el.type, el.attr, (0..128).to_a.map{|a| rand(36).to_s(36)}.join] @toc_code.last elsif !@footnote_location && el.options[:ial] && (el.options[:ial][:refs] || []).include?('footnotes') @footnote_location = (0..128).to_a.map{|a| rand(36).to_s(36)}.join else format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent) end end
# File lib/kramdown/converter/html.rb, line 201 def convert_xml_comment(el, indent) if el.options[:category] == :block && (@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw) ' '*indent << el.value << "\n" else el.value end end
Return a HTML ordered list with the footnote content for the used footnotes.
# File lib/kramdown/converter/html.rb, line 436 def footnote_content ol = Element.new(:ol) ol.attr['start'] = @footnote_start if @footnote_start != 1 i = 0 backlink_text = escape_html(@options[:footnote_backlink], :text) while i < @footnotes.length name, data, _, repeat = *@footnotes[i] li = Element.new(:li, nil, {'id' => "fn:#{name}"}) li.children = Marshal.load(Marshal.dump(data.children)) if li.children.last.type == :p para = li.children.last insert_space = true else li.children << (para = Element.new(:p)) insert_space = false end unless @options[:footnote_backlink].empty? para.children << Element.new(:raw, FOOTNOTE_BACKLINK_FMT % [insert_space ? ' ' : '', name, backlink_text]) (1..repeat).each do |index| para.children << Element.new(:raw, FOOTNOTE_BACKLINK_FMT % [" ", "#{name}:#{index}", "#{backlink_text}<sup>#{index+1}</sup>"]) end end ol.children << Element.new(:raw, convert(li, 4)) i += 1 end (ol.children.empty? ? '' : format_as_indented_block_html('div', {:class => "footnotes"}, convert(ol, 2), 0)) end
Format the given element as block HTML.
# File lib/kramdown/converter/html.rb, line 360 def format_as_block_html(name, attr, body, indent) "#{' '*indent}<#{name}#{html_attributes(attr)}>#{body}</#{name}>\n" end
Format the given element as block HTML with a newline after the start tag and indentation before the end tag.
# File lib/kramdown/converter/html.rb, line 366 def format_as_indented_block_html(name, attr, body, indent) "#{' '*indent}<#{name}#{html_attributes(attr)}>\n#{body}#{' '*indent}</#{name}>\n" end
Format the given element as span HTML.
# File lib/kramdown/converter/html.rb, line 355 def format_as_span_html(name, attr, body) "<#{name}#{html_attributes(attr)}>#{body}</#{name}>" end
Generate and return an element tree for the table of contents.
# File lib/kramdown/converter/html.rb, line 378 def generate_toc_tree(toc, type, attr) sections = Element.new(type, nil, attr) sections.attr['id'] ||= 'markdown-toc' stack = [] toc.each do |level, id, children| li = Element.new(:li, nil, nil, {:level => level}) li.children << Element.new(:p, nil, nil, {:transparent => true}) a = Element.new(:a, nil) a.attr['href'] = "##{id}" a.attr['id'] = "#{sections.attr['id']}-#{id}" a.children.concat(remove_footnotes(Marshal.load(Marshal.dump(children)))) li.children.last.children << a li.children << Element.new(type) success = false while !success if stack.empty? sections.children << li stack << li success = true elsif stack.last.options[:level] < li.options[:level] stack.last.children.last.children << li stack << li success = true else item = stack.pop item.children.pop unless item.children.last.children.size > 0 end end end while !stack.empty? item = stack.pop item.children.pop unless item.children.last.children.size > 0 end sections end
Return the converted content of the children of el
as a
string. The parameter indent
has to be the amount of
indentation used for the element el
.
Pushes el
onto the @stack before converting the child elements
and pops it from the stack afterwards.
# File lib/kramdown/converter/html.rb, line 64 def inner(el, indent) result = '' indent += @indent @stack.push(el) el.children.each do |inner_el| result << send(DISPATCHER[inner_el.type], inner_el, indent) end @stack.pop result end
Obfuscate the text
by using HTML entities.
# File lib/kramdown/converter/html.rb, line 424 def obfuscate(text) result = "" text.each_byte do |b| result << (b > 128 ? b.chr : "&#%03d;" % b) end result.force_encoding(text.encoding) if result.respond_to?(:force_encoding) result end
Remove all footnotes from the given elements.
# File lib/kramdown/converter/html.rb, line 416 def remove_footnotes(elements) elements.delete_if do |c| remove_footnotes(c.children) c.type == :footnote end end