class Kramdown::Converter::Html
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.
Constants
- DISPATCHER
The mapping of element type to conversion method.
- FOOTNOTE_BACKLINK_FMT
Attributes
The amount of indentation used when nesting HTML tags.
Public Class Methods
Initialize the HTML converter with the given Kramdown
document doc
.
Kramdown::Converter::Base::new
# File lib/kramdown/converter/html.rb 38 def initialize(root, options) 39 super 40 @footnote_counter = @footnote_start = @options[:footnote_nr] 41 @footnotes = [] 42 @footnotes_by_name = {} 43 @footnote_location = nil 44 @toc = [] 45 @toc_code = nil 46 @indent = 2 47 @stack = [] 48 end
Public Instance Methods
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 376 def add_syntax_highlighter_to_class_attr(attr, lang = nil) 377 (attr['class'] = (attr['class'] || '') + " highlighter-#{@options[:syntax_highlighter]}").lstrip! 378 attr['class'].sub!(/\blanguage-\S+|(^)/) { "language-#{lang}#{$1 ? ' ' : ''}" } if lang 379 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 55 def convert(el, indent = -@indent) 56 send(DISPATCHER[el.type], el, indent) 57 end
# File lib/kramdown/converter/html.rb 244 def convert_a(el, indent) 245 format_as_span_html(el.type, el.attr, inner(el, indent)) 246 end
# File lib/kramdown/converter/html.rb 332 def convert_abbreviation(el, indent) 333 title = @root.options[:abbrev_defs][el.value] 334 attr = @root.options[:abbrev_attr][el.value].dup 335 attr['title'] = title unless title.empty? 336 format_as_span_html("abbr", attr, el.value) 337 end
# File lib/kramdown/converter/html.rb 75 def convert_blank(el, indent) 76 "\n" 77 end
# File lib/kramdown/converter/html.rb 120 def convert_blockquote(el, indent) 121 format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent) 122 end
# File lib/kramdown/converter/html.rb 240 def convert_br(el, indent) 241 "<br />" 242 end
# File lib/kramdown/converter/html.rb 91 def convert_codeblock(el, indent) 92 attr = el.attr.dup 93 lang = extract_code_language!(attr) 94 hl_opts = {} 95 highlighted_code = highlight_code(el.value, el.options[:lang] || lang, :block, hl_opts) 96 97 if highlighted_code 98 add_syntax_highlighter_to_class_attr(attr, lang || hl_opts[:default_lang]) 99 "#{' '*indent}<div#{html_attributes(attr)}>#{highlighted_code}#{' '*indent}</div>\n" 100 else 101 result = escape_html(el.value) 102 result.chomp! 103 if el.attr['class'].to_s =~ /\bshow-whitespaces\b/ 104 result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m| 105 suffix = ($1 ? '-l' : ($2 ? '-r' : '')) 106 m.scan(/./).map do |c| 107 case c 108 when "\t" then "<span class=\"ws-tab#{suffix}\">\t</span>" 109 when " " then "<span class=\"ws-space#{suffix}\">⋅</span>" 110 end 111 end.join('') 112 end 113 end 114 code_attr = {} 115 code_attr['class'] = "language-#{lang}" if lang 116 "#{' '*indent}<pre#{html_attributes(attr)}><code#{html_attributes(code_attr)}>#{result}\n</code></pre>\n" 117 end 118 end
# File lib/kramdown/converter/html.rb 252 def convert_codespan(el, indent) 253 attr = el.attr.dup 254 lang = extract_code_language(attr) 255 hl_opts = {} 256 result = highlight_code(el.value, lang, :span, hl_opts) 257 if result 258 add_syntax_highlighter_to_class_attr(attr, hl_opts[:default_lang]) 259 else 260 result = escape_html(el.value) 261 end 262 263 format_as_span_html('code', attr, result) 264 end
# File lib/kramdown/converter/html.rb 232 def convert_comment(el, indent) 233 if el.options[:category] == :block 234 "#{' '*indent}<!-- #{el.value} -->\n" 235 else 236 "<!-- #{el.value} -->" 237 end 238 end
# File lib/kramdown/converter/html.rb 150 def convert_dl(el, indent) 151 format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent) 152 end
# File lib/kramdown/converter/html.rb 166 def convert_dt(el, indent) 167 attr = el.attr.dup 168 @stack.last.options[:ial][:refs].each do |ref| 169 if ref =~ /\Aauto_ids(?:-([\w-]+))?/ 170 attr['id'] = ($1 ? $1 : '') << basic_generate_id(el.options[:raw_text]) 171 break 172 end 173 end if !attr['id'] && @stack.last.options[:ial] && @stack.last.options[:ial][:refs] 174 format_as_block_html(el.type, attr, inner(el, indent), indent) 175 end
# File lib/kramdown/converter/html.rb 288 def convert_em(el, indent) 289 format_as_span_html(el.type, el.attr, inner(el, indent)) 290 end
# File lib/kramdown/converter/html.rb 293 def convert_entity(el, indent) 294 entity_to_str(el.value, el.options[:original]) 295 end
# File lib/kramdown/converter/html.rb 266 def convert_footnote(el, indent) 267 repeat = '' 268 if (footnote = @footnotes_by_name[el.options[:name]]) 269 number = footnote[2] 270 repeat = ":#{footnote[3] += 1}" 271 else 272 number = @footnote_counter 273 @footnote_counter += 1 274 @footnotes << [el.options[:name], el.value, number, 0] 275 @footnotes_by_name[el.options[:name]] = @footnotes.last 276 end 277 "<sup id=\"fnref:#{el.options[:name]}#{repeat}\"><a href=\"#fn:#{el.options[:name]}\" class=\"footnote\">#{number}</a></sup>" 278 end
# File lib/kramdown/converter/html.rb 124 def convert_header(el, indent) 125 attr = el.attr.dup 126 if @options[:auto_ids] && !attr['id'] 127 attr['id'] = generate_id(el.options[:raw_text]) 128 end 129 @toc << [el.options[:level], attr['id'], el.children] if attr['id'] && in_toc?(el) 130 level = output_header_level(el.options[:level]) 131 format_as_block_html("h#{level}", attr, inner(el, indent), indent) 132 end
# File lib/kramdown/converter/html.rb 134 def convert_hr(el, indent) 135 "#{' '*indent}<hr#{html_attributes(el.attr)} />\n" 136 end
# File lib/kramdown/converter/html.rb 177 def convert_html_element(el, indent) 178 res = inner(el, indent) 179 if el.options[:category] == :span 180 "<#{el.value}#{html_attributes(el.attr)}" << (res.empty? && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) ? " />" : ">#{res}</#{el.value}>") 181 else 182 output = '' 183 output << ' '*indent if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw 184 output << "<#{el.value}#{html_attributes(el.attr)}" 185 if el.options[:is_closed] && el.options[:content_model] == :raw 186 output << " />" 187 elsif !res.empty? && el.options[:content_model] != :block 188 output << ">#{res}</#{el.value}>" 189 elsif !res.empty? 190 output << ">\n#{res.chomp}\n" << ' '*indent << "</#{el.value}>" 191 elsif HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) 192 output << " />" 193 else 194 output << "></#{el.value}>" 195 end 196 output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw 197 output 198 end 199 end
# File lib/kramdown/converter/html.rb 248 def convert_img(el, indent) 249 "<img#{html_attributes(el.attr)} />" 250 end
# File lib/kramdown/converter/html.rb 154 def convert_li(el, indent) 155 output = ' '*indent << "<#{el.type}" << html_attributes(el.attr) << ">" 156 res = inner(el, indent) 157 if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent]) 158 output << res << (res =~ /\n\Z/ ? ' '*indent : '') 159 else 160 output << "\n" << res << ' '*indent 161 end 162 output << "</#{el.type}>\n" 163 end
# File lib/kramdown/converter/html.rb 318 def convert_math(el, indent) 319 if (result = format_math(el, :indent => indent)) 320 result 321 else 322 attr = el.attr.dup 323 (attr['class'] = (attr['class'] || '') << " kdmath").lstrip! 324 if el.options[:category] == :block 325 format_as_block_html('div', attr, "$$\n#{el.value}\n$$", indent) 326 else 327 format_as_span_html('span', attr, "$#{el.value}$") 328 end 329 end 330 end
# File lib/kramdown/converter/html.rb 83 def convert_p(el, indent) 84 if el.options[:transparent] 85 inner(el, indent) 86 else 87 format_as_block_html(el.type, el.attr, inner(el, indent), indent) 88 end 89 end
# File lib/kramdown/converter/html.rb 280 def convert_raw(el, indent) 281 if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html') 282 el.value + (el.options[:category] == :block ? "\n" : '') 283 else 284 '' 285 end 286 end
# File lib/kramdown/converter/html.rb 339 def convert_root(el, indent) 340 result = inner(el, indent) 341 if @footnote_location 342 result.sub!(/#{@footnote_location}/, footnote_content.gsub(/\\/, "\\\\\\\\")) 343 else 344 result << footnote_content 345 end 346 if @toc_code 347 toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {}) 348 text = if toc_tree.children.size > 0 349 convert(toc_tree, 0) 350 else 351 '' 352 end 353 result.sub!(/#{@toc_code.last}/, text.gsub(/\\/, "\\\\\\\\")) 354 end 355 result 356 end
# File lib/kramdown/converter/html.rb 314 def convert_smart_quote(el, indent) 315 entity_to_str(smart_quote_entity(el)) 316 end
# File lib/kramdown/converter/html.rb 210 def convert_table(el, indent) 211 format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent) 212 end
# File lib/kramdown/converter/html.rb 220 def convert_td(el, indent) 221 res = inner(el, indent) 222 type = (@stack[-2].type == :thead ? :th : :td) 223 attr = el.attr 224 alignment = @stack[-3].options[:alignment][@stack.last.children.index(el)] 225 if alignment != :default 226 attr = el.attr.dup 227 attr['style'] = (attr.has_key?('style') ? "#{attr['style']}; ": '') << "text-align: #{alignment}" 228 end 229 format_as_block_html(type, attr, res.empty? ? entity_to_str(ENTITY_NBSP) : res, indent) 230 end
# File lib/kramdown/converter/html.rb 79 def convert_text(el, indent) 80 escape_html(el.value, :text) 81 end
# File lib/kramdown/converter/html.rb 306 def convert_typographic_sym(el, indent) 307 if (result = @options[:typographic_symbols][el.value]) 308 escape_html(result, :text) 309 else 310 TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e)}.join('') 311 end 312 end
# File lib/kramdown/converter/html.rb 138 def convert_ul(el, indent) 139 if !@toc_code && (el.options[:ial][:refs].include?('toc') rescue nil) 140 @toc_code = [el.type, el.attr, (0..128).to_a.map{|a| rand(36).to_s(36)}.join] 141 @toc_code.last 142 elsif !@footnote_location && el.options[:ial] && (el.options[:ial][:refs] || []).include?('footnotes') 143 @footnote_location = (0..128).to_a.map{|a| rand(36).to_s(36)}.join 144 else 145 format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent) 146 end 147 end
# File lib/kramdown/converter/html.rb 201 def convert_xml_comment(el, indent) 202 if el.options[:category] == :block && (@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw) 203 ' '*indent << el.value << "\n" 204 else 205 el.value 206 end 207 end
Fixes the elements for use in a TOC entry.
# File lib/kramdown/converter/html.rb 420 def fix_for_toc_entry(elements) 421 remove_footnotes(elements) 422 unwrap_links(elements) 423 elements 424 end
Return a HTML ordered list with the footnote content for the used footnotes.
# File lib/kramdown/converter/html.rb 455 def footnote_content 456 ol = Element.new(:ol) 457 ol.attr['start'] = @footnote_start if @footnote_start != 1 458 i = 0 459 backlink_text = escape_html(@options[:footnote_backlink], :text) 460 while i < @footnotes.length 461 name, data, _, repeat = *@footnotes[i] 462 li = Element.new(:li, nil, {'id' => "fn:#{name}"}) 463 li.children = Marshal.load(Marshal.dump(data.children)) 464 465 para = nil 466 if li.children.last.type == :p || @options[:footnote_backlink_inline] 467 parent = li 468 while !parent.children.empty? && ![:p, :header].include?(parent.children.last.type) 469 parent = parent.children.last 470 end 471 para = parent.children.last 472 insert_space = true 473 end 474 475 unless para 476 li.children << (para = Element.new(:p)) 477 insert_space = false 478 end 479 480 unless @options[:footnote_backlink].empty? 481 nbsp = entity_to_str(ENTITY_NBSP) 482 para.children << Element.new(:raw, FOOTNOTE_BACKLINK_FMT % [insert_space ? nbsp : '', name, backlink_text]) 483 (1..repeat).each do |index| 484 para.children << Element.new(:raw, FOOTNOTE_BACKLINK_FMT % [nbsp, "#{name}:#{index}", "#{backlink_text}<sup>#{index+1}</sup>"]) 485 end 486 end 487 488 ol.children << Element.new(:raw, convert(li, 4)) 489 i += 1 490 end 491 (ol.children.empty? ? '' : format_as_indented_block_html('div', {:class => "footnotes"}, convert(ol, 2), 0)) 492 end
Format the given element as block HTML.
# File lib/kramdown/converter/html.rb 364 def format_as_block_html(name, attr, body, indent) 365 "#{' '*indent}<#{name}#{html_attributes(attr)}>#{body}</#{name}>\n" 366 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 370 def format_as_indented_block_html(name, attr, body, indent) 371 "#{' '*indent}<#{name}#{html_attributes(attr)}>\n#{body}#{' '*indent}</#{name}>\n" 372 end
Format the given element as span HTML.
# File lib/kramdown/converter/html.rb 359 def format_as_span_html(name, attr, body) 360 "<#{name}#{html_attributes(attr)}>#{body}</#{name}>" 361 end
Generate and return an element tree for the table of contents.
# File lib/kramdown/converter/html.rb 382 def generate_toc_tree(toc, type, attr) 383 sections = Element.new(type, nil, attr) 384 sections.attr['id'] ||= 'markdown-toc' 385 stack = [] 386 toc.each do |level, id, children| 387 li = Element.new(:li, nil, nil, {:level => level}) 388 li.children << Element.new(:p, nil, nil, {:transparent => true}) 389 a = Element.new(:a, nil) 390 a.attr['href'] = "##{id}" 391 a.attr['id'] = "#{sections.attr['id']}-#{id}" 392 a.children.concat(fix_for_toc_entry(Marshal.load(Marshal.dump(children)))) 393 li.children.last.children << a 394 li.children << Element.new(type) 395 396 success = false 397 while !success 398 if stack.empty? 399 sections.children << li 400 stack << li 401 success = true 402 elsif stack.last.options[:level] < li.options[:level] 403 stack.last.children.last.children << li 404 stack << li 405 success = true 406 else 407 item = stack.pop 408 item.children.pop unless item.children.last.children.size > 0 409 end 410 end 411 end 412 while !stack.empty? 413 item = stack.pop 414 item.children.pop unless item.children.last.children.size > 0 415 end 416 sections 417 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 64 def inner(el, indent) 65 result = '' 66 indent += @indent 67 @stack.push(el) 68 el.children.each do |inner_el| 69 result << send(DISPATCHER[inner_el.type], inner_el, indent) 70 end 71 @stack.pop 72 result 73 end
Obfuscate the text
by using HTML entities.
# File lib/kramdown/converter/html.rb 443 def obfuscate(text) 444 result = "" 445 text.each_byte do |b| 446 result << (b > 128 ? b.chr : "&#%03d;" % b) 447 end 448 result.force_encoding(text.encoding) 449 result 450 end
Remove all footnotes from the given elements.
# File lib/kramdown/converter/html.rb 435 def remove_footnotes(elements) 436 elements.delete_if do |c| 437 remove_footnotes(c.children) 438 c.type == :footnote 439 end 440 end
Remove all link elements by unwrapping them.
# File lib/kramdown/converter/html.rb 427 def unwrap_links(elements) 428 elements.map! do |c| 429 unwrap_links(c.children) 430 c.type == :a ? c.children : c 431 end.flatten! 432 end