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

ZERO_TO_ONETWENTYEIGHT

Attributes

indent[RW]

The amount of indentation used when nesting HTML tags.

Public Class Methods

new(root, options) click to toggle source

Initialize the HTML converter with the given Kramdown document doc.

Calls superclass method 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 
49   # stash string representation of symbol to avoid allocations from multiple interpolations.
50   @highlighter_class = " highlighter-#{options[:syntax_highlighter]}"
51   @dispatcher = Hash.new {|h, k| h[k] = :"convert_#{k}" }
52 end

Public Instance Methods

add_syntax_highlighter_to_class_attr(attr, lang = nil) click to toggle source

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
416 def add_syntax_highlighter_to_class_attr(attr, lang = nil)
417   (attr['class'] = (attr['class'] || '') + @highlighter_class).lstrip!
418   attr['class'].sub!(/\blanguage-\S+|(^)/) { "language-#{lang}#{$1 ? ' ' : ''}" } if lang
419 end
convert(el, indent = -@indent) click to toggle source

Dispatch the conversion of the element el to a convert_TYPE method using the type of the element.

   # File lib/kramdown/converter/html.rb
56 def convert(el, indent = -@indent)
57   send(@dispatcher[el.type], el, indent)
58 end
convert_a(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
282 def convert_a(el, indent)
283   format_as_span_html("a", el.attr, inner(el, indent))
284 end
convert_abbreviation(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
376 def convert_abbreviation(el, _indent)
377   title = @root.options[:abbrev_defs][el.value]
378   attr = @root.options[:abbrev_attr][el.value].dup
379   attr['title'] = title unless title.empty?
380   format_as_span_html("abbr", attr, el.value)
381 end
convert_blank(_el, _indent) click to toggle source
   # File lib/kramdown/converter/html.rb
76 def convert_blank(_el, _indent)
77   "\n"
78 end
convert_blockquote(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
144 def convert_blockquote(el, indent)
145   format_as_indented_block_html("blockquote", el.attr, inner(el, indent), indent)
146 end
convert_br(_el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
278 def convert_br(_el, _indent)
279   "<br />"
280 end
convert_codeblock(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
114 def convert_codeblock(el, indent)
115   attr = el.attr.dup
116   lang = extract_code_language!(attr)
117   hl_opts = {}
118   highlighted_code = highlight_code(el.value, el.options[:lang] || lang, :block, hl_opts)
119 
120   if highlighted_code
121     add_syntax_highlighter_to_class_attr(attr, lang || hl_opts[:default_lang])
122     "#{' ' * indent}<div#{html_attributes(attr)}>#{highlighted_code}#{' ' * indent}</div>\n"
123   else
124     result = escape_html(el.value)
125     result.chomp!
126     if el.attr['class'].to_s =~ /\bshow-whitespaces\b/
127       result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m|
128         suffix = ($1 ? '-l' : ($2 ? '-r' : ''))
129         m.scan(/./).map do |c|
130           case c
131           when "\t" then "<span class=\"ws-tab#{suffix}\">\t</span>"
132           when " " then "<span class=\"ws-space#{suffix}\">&#8901;</span>"
133           end
134         end.join
135       end
136     end
137     code_attr = {}
138     code_attr['class'] = "language-#{lang}" if lang
139     "#{' ' * indent}<pre#{html_attributes(attr)}>" \
140       "<code#{html_attributes(code_attr)}>#{result}\n</code></pre>\n"
141   end
142 end
convert_codespan(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
290 def convert_codespan(el, _indent)
291   attr = el.attr.dup
292   lang = extract_code_language(attr)
293   hl_opts = {}
294   result = highlight_code(el.value, lang, :span, hl_opts)
295   if result
296     add_syntax_highlighter_to_class_attr(attr, lang || hl_opts[:default_lang])
297   else
298     result = escape_html(el.value)
299   end
300 
301   format_as_span_html('code', attr, result)
302 end
convert_comment(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
270 def convert_comment(el, indent)
271   if el.options[:category] == :block
272     "#{' ' * indent}<!-- #{el.value} -->\n"
273   else
274     "<!-- #{el.value} -->"
275   end
276 end
convert_dd(el, indent)
Alias for: convert_li
convert_dl(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
184 def convert_dl(el, indent)
185   format_as_indented_block_html("dl", el.attr, inner(el, indent), indent)
186 end
convert_dt(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
200 def convert_dt(el, indent)
201   attr = el.attr.dup
202   @stack.last.options[:ial][:refs].each do |ref|
203     if ref =~ /\Aauto_ids(?:-([\w-]+))?/
204       attr['id'] = "#{$1}#{basic_generate_id(el.options[:raw_text])}".lstrip
205       break
206     end
207   end if !attr['id'] && @stack.last.options[:ial] && @stack.last.options[:ial][:refs]
208   format_as_block_html("dt", attr, inner(el, indent), indent)
209 end
convert_em(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
330 def convert_em(el, indent)
331   format_as_span_html(el.type, el.attr, inner(el, indent))
332 end
Also aliased as: convert_strong
convert_entity(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
335 def convert_entity(el, _indent)
336   entity_to_str(el.value, el.options[:original])
337 end
convert_footnote(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
304 def convert_footnote(el, _indent)
305   repeat = ''
306   name = @options[:footnote_prefix] + el.options[:name]
307   if (footnote = @footnotes_by_name[name])
308     number = footnote[2]
309     repeat = ":#{footnote[3] += 1}"
310   else
311     number = @footnote_counter
312     @footnote_counter += 1
313     @footnotes << [name, el.value, number, 0]
314     @footnotes_by_name[name] = @footnotes.last
315   end
316   formatted_link_text = sprintf(@options[:footnote_link_text], number)
317   "<sup id=\"fnref:#{name}#{repeat}\">" \
318     "<a href=\"#fn:#{name}\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">" \
319     "#{formatted_link_text}</a></sup>"
320 end
convert_header(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
148 def convert_header(el, indent)
149   attr = el.attr.dup
150   if @options[:auto_ids] && !attr['id']
151     attr['id'] = generate_id(el.options[:raw_text])
152   end
153 
154   if @options[:header_links] && attr['id'].to_s.length > 0
155     link = Element.new(:a, nil, nil)
156     link.attr['href'] = "##{attr['id']}"
157     el.children.unshift(link)
158   end
159 
160   @toc << [el.options[:level], attr['id'], el.children] if attr['id'] && in_toc?(el)
161   level = output_header_level(el.options[:level])
162   format_as_block_html("h#{level}", attr, inner(el, indent), indent)
163 end
convert_hr(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
165 def convert_hr(el, indent)
166   "#{' ' * indent}<hr#{html_attributes(el.attr)} />\n"
167 end
convert_html_element(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
211 def convert_html_element(el, indent)
212   res = inner(el, indent)
213   if el.options[:category] == :span
214     "<#{el.value}#{html_attributes(el.attr)}" + \
215       (res.empty? && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) ? " />" : ">#{res}</#{el.value}>")
216   else
217     output = +''
218     if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
219       output << ' ' * indent
220     end
221     output << "<#{el.value}#{html_attributes(el.attr)}"
222     if el.options[:is_closed] && el.options[:content_model] == :raw
223       output << " />"
224     elsif !res.empty? && el.options[:content_model] != :block
225       output << ">#{res}</#{el.value}>"
226     elsif !res.empty?
227       output << ">\n#{res.chomp}\n" << ' ' * indent << "</#{el.value}>"
228     elsif HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
229       output << " />"
230     else
231       output << "></#{el.value}>"
232     end
233     output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
234     output
235   end
236 end
convert_img(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
286 def convert_img(el, _indent)
287   "<img#{html_attributes(el.attr)} />"
288 end
convert_li(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
188 def convert_li(el, indent)
189   output = ' ' * indent << "<#{el.type}" << html_attributes(el.attr) << ">"
190   res = inner(el, indent)
191   if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent])
192     output << res << (res.match?(/\n\Z/) ? ' ' * indent : '')
193   else
194     output << "\n" << res << ' ' * indent
195   end
196   output << "</#{el.type}>\n"
197 end
Also aliased as: convert_dd
convert_math(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
362 def convert_math(el, indent)
363   if (result = format_math(el, indent: indent))
364     result
365   else
366     attr = el.attr.dup
367     attr['class'] = "#{attr['class']} kdmath".lstrip
368     if el.options[:category] == :block
369       format_as_block_html('div', attr, "$$\n#{el.value}\n$$", indent)
370     else
371       format_as_span_html('span', attr, "$#{el.value}$")
372     end
373   end
374 end
convert_ol(el, indent)
Alias for: convert_ul
convert_p(el, indent) click to toggle source
   # File lib/kramdown/converter/html.rb
85 def convert_p(el, indent)
86   if el.options[:transparent]
87     inner(el, indent)
88   elsif el.children.size == 1 && el.children.first.type == :img &&
89       el.children.first.options[:ial]&.[](:refs)&.include?('standalone')
90     convert_standalone_image(el, indent)
91   else
92     format_as_block_html("p", el.attr, inner(el, indent), indent)
93   end
94 end
convert_raw(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
322 def convert_raw(el, _indent)
323   if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html')
324     el.value + (el.options[:category] == :block ? "\n" : '')
325   else
326     ''
327   end
328 end
convert_root(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
383 def convert_root(el, indent)
384   result = inner(el, indent)
385   if @footnote_location
386     result.sub!(/#{@footnote_location}/, footnote_content.gsub(/\\/, "\\\\\\\\"))
387   else
388     result << footnote_content
389   end
390   if @toc_code
391     toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {})
392     text = toc_tree.children.empty? ? '' : convert(toc_tree, 0)
393     result.sub!(/#{@toc_code.last}/, text.gsub(/\\/, "\\\\\\\\"))
394   end
395   result
396 end
convert_smart_quote(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
358 def convert_smart_quote(el, _indent)
359   entity_to_str(smart_quote_entity(el))
360 end
convert_standalone_image(el, indent) click to toggle source

Helper method used by convert_p to convert a paragraph that only contains a single :img element.

    # File lib/kramdown/converter/html.rb
 98 def convert_standalone_image(el, indent)
 99   figure_attr = el.attr.dup
100   image_attr = el.children.first.attr.dup
101 
102   if image_attr.key?('class') && !figure_attr.key?('class')
103     figure_attr['class'] = image_attr.delete('class')
104   end
105   if image_attr.key?('id') && !figure_attr.key?('id')
106     figure_attr['id'] = image_attr.delete('id')
107   end
108 
109   body = "#{' ' * (indent + @indent)}<img#{html_attributes(image_attr)} />\n" \
110     "#{' ' * (indent + @indent)}<figcaption>#{image_attr['alt']}</figcaption>\n"
111   format_as_indented_block_html("figure", figure_attr, body, indent)
112 end
convert_strong(el, indent)
Alias for: convert_em
convert_table(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
248 def convert_table(el, indent)
249   format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
250 end
convert_tbody(el, indent)
Alias for: convert_table
convert_td(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
258 def convert_td(el, indent)
259   res = inner(el, indent)
260   type = (@stack[-2].type == :thead ? :th : :td)
261   attr = el.attr
262   alignment = @stack[-3].options[:alignment][@stack.last.children.index(el)]
263   if alignment != :default
264     attr = el.attr.dup
265     attr['style'] = (attr.key?('style') ? "#{attr['style']}; " : '') + "text-align: #{alignment}"
266   end
267   format_as_block_html(type, attr, res.empty? ? entity_to_str(ENTITY_NBSP) : res, indent)
268 end
convert_text(el, _indent) click to toggle source
   # File lib/kramdown/converter/html.rb
80 def convert_text(el, _indent)
81   escaped = escape_html(el.value, :text)
82   @options[:remove_line_breaks_for_cjk] ? fix_cjk_line_break(escaped) : escaped
83 end
convert_tfoot(el, indent)
Alias for: convert_table
convert_thead(el, indent)
Alias for: convert_table
convert_tr(el, indent)
Alias for: convert_table
convert_typographic_sym(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
350 def convert_typographic_sym(el, _indent)
351   if (result = @options[:typographic_symbols][el.value])
352     escape_html(result, :text)
353   else
354     TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e) }.join
355   end
356 end
convert_ul(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
172 def convert_ul(el, indent)
173   if !@toc_code && el.options.dig(:ial, :refs)&.include?('toc')
174     @toc_code = [el.type, el.attr, ZERO_TO_ONETWENTYEIGHT.map { rand(36).to_s(36) }.join]
175     @toc_code.last
176   elsif !@footnote_location && el.options.dig(:ial, :refs)&.include?('footnotes')
177     @footnote_location = ZERO_TO_ONETWENTYEIGHT.map { rand(36).to_s(36) }.join
178   else
179     format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
180   end
181 end
Also aliased as: convert_ol
convert_xml_comment(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
238 def convert_xml_comment(el, indent)
239   if el.options[:category] == :block &&
240       (@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw)
241     ' ' * indent << el.value << "\n"
242   else
243     el.value
244   end
245 end
Also aliased as: convert_xml_pi
convert_xml_pi(el, indent)
Alias for: convert_xml_comment
fix_for_toc_entry(elements) click to toggle source

Fixes the elements for use in a TOC entry.

    # File lib/kramdown/converter/html.rb
460 def fix_for_toc_entry(elements)
461   remove_footnotes(elements)
462   unwrap_links(elements)
463   elements
464 end
footnote_content() click to toggle source

Return an HTML ordered list with the footnote content for the used footnotes.

    # File lib/kramdown/converter/html.rb
495 def footnote_content
496   ol = Element.new(:ol)
497   ol.attr['start'] = @footnote_start if @footnote_start != 1
498   i = 0
499   backlink_text = escape_html(@options[:footnote_backlink], :text)
500   while i < @footnotes.length
501     name, data, _, repeat = *@footnotes[i]
502     li = Element.new(:li, nil, 'id' => "fn:#{name}")
503     li.children = Marshal.load(Marshal.dump(data.children))
504 
505     para = nil
506     if li.children.last.type == :p || @options[:footnote_backlink_inline]
507       parent = li
508       while !parent.children.empty? && ![:p, :header].include?(parent.children.last.type)
509         parent = parent.children.last
510       end
511       para = parent.children.last
512       insert_space = true
513     end
514 
515     unless para
516       li.children << (para = Element.new(:p))
517       insert_space = false
518     end
519 
520     unless @options[:footnote_backlink].empty?
521       nbsp = entity_to_str(ENTITY_NBSP)
522       value = sprintf(FOOTNOTE_BACKLINK_FMT, (insert_space ? nbsp : ''), name, backlink_text)
523       para.children << Element.new(:raw, value)
524       (1..repeat).each do |index|
525         value = sprintf(FOOTNOTE_BACKLINK_FMT, nbsp, "#{name}:#{index}",
526                         "#{backlink_text}<sup>#{index + 1}</sup>")
527         para.children << Element.new(:raw, value)
528       end
529     end
530 
531     ol.children << Element.new(:raw, convert(li, 4))
532     i += 1
533   end
534   if ol.children.empty?
535     ''
536   else
537     format_as_indented_block_html('div', {class: "footnotes", role: "doc-endnotes"}, convert(ol, 2), 0)
538   end
539 end
format_as_block_html(name, attr, body, indent) click to toggle source

Format the given element as block HTML.

    # File lib/kramdown/converter/html.rb
404 def format_as_block_html(name, attr, body, indent)
405   "#{' ' * indent}<#{name}#{html_attributes(attr)}>#{body}</#{name}>\n"
406 end
format_as_indented_block_html(name, attr, body, indent) click to toggle source

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
410 def format_as_indented_block_html(name, attr, body, indent)
411   "#{' ' * indent}<#{name}#{html_attributes(attr)}>\n#{body}#{' ' * indent}</#{name}>\n"
412 end
format_as_span_html(name, attr, body) click to toggle source

Format the given element as span HTML.

    # File lib/kramdown/converter/html.rb
399 def format_as_span_html(name, attr, body)
400   "<#{name}#{html_attributes(attr)}>#{body}</#{name}>"
401 end
generate_toc_tree(toc, type, attr) click to toggle source

Generate and return an element tree for the table of contents.

    # File lib/kramdown/converter/html.rb
422 def generate_toc_tree(toc, type, attr)
423   sections = Element.new(type, nil, attr.dup)
424   sections.attr['id'] ||= 'markdown-toc'
425   stack = []
426   toc.each do |level, id, children|
427     li = Element.new(:li, nil, nil, level: level)
428     li.children << Element.new(:p, nil, nil, transparent: true)
429     a = Element.new(:a, nil)
430     a.attr['href'] = "##{id}"
431     a.attr['id'] = "#{sections.attr['id']}-#{id}"
432     a.children.concat(fix_for_toc_entry(Marshal.load(Marshal.dump(children))))
433     li.children.last.children << a
434     li.children << Element.new(type)
435 
436     success = false
437     until success
438       if stack.empty?
439         sections.children << li
440         stack << li
441         success = true
442       elsif stack.last.options[:level] < li.options[:level]
443         stack.last.children.last.children << li
444         stack << li
445         success = true
446       else
447         item = stack.pop
448         item.children.pop if item.children.last.children.empty?
449       end
450     end
451   end
452   until stack.empty?
453     item = stack.pop
454     item.children.pop if item.children.last.children.empty?
455   end
456   sections
457 end
inner(el, indent) click to toggle source

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
65 def inner(el, indent)
66   result = +''
67   indent += @indent
68   @stack.push(el)
69   el.children.each do |inner_el|
70     result << send(@dispatcher[inner_el.type], inner_el, indent)
71   end
72   @stack.pop
73   result
74 end
obfuscate(text) click to toggle source

Obfuscate the text by using HTML entities.

    # File lib/kramdown/converter/html.rb
483 def obfuscate(text)
484   result = +''
485   text.each_byte do |b|
486     result << (b > 128 ? b.chr : sprintf("&#%03d;", b))
487   end
488   result.force_encoding(text.encoding)
489   result
490 end
remove_footnotes(elements) click to toggle source

Remove all footnotes from the given elements.

    # File lib/kramdown/converter/html.rb
475 def remove_footnotes(elements)
476   elements.delete_if do |c|
477     remove_footnotes(c.children)
478     c.type == :footnote
479   end
480 end