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
408 def add_syntax_highlighter_to_class_attr(attr, lang = nil)
409   (attr['class'] = (attr['class'] || '') + @highlighter_class).lstrip!
410   attr['class'].sub!(/\blanguage-\S+|(^)/) { "language-#{lang}#{$1 ? ' ' : ''}" } if lang
411 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
271 def convert_a(el, indent)
272   format_as_span_html("a", el.attr, inner(el, indent))
273 end
convert_abbreviation(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
364 def convert_abbreviation(el, _indent)
365   title = @root.options[:abbrev_defs][el.value]
366   attr = @root.options[:abbrev_attr][el.value].dup
367   attr['title'] = title unless title.empty?
368   format_as_span_html("abbr", attr, el.value)
369 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
140 def convert_blockquote(el, indent)
141   format_as_indented_block_html("blockquote", el.attr, inner(el, indent), indent)
142 end
convert_br(_el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
267 def convert_br(_el, _indent)
268   "<br />"
269 end
convert_codeblock(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
110 def convert_codeblock(el, indent)
111   attr = el.attr.dup
112   lang = extract_code_language!(attr)
113   hl_opts = {}
114   highlighted_code = highlight_code(el.value, el.options[:lang] || lang, :block, hl_opts)
115 
116   if highlighted_code
117     add_syntax_highlighter_to_class_attr(attr, lang || hl_opts[:default_lang])
118     "#{' ' * indent}<div#{html_attributes(attr)}>#{highlighted_code}#{' ' * indent}</div>\n"
119   else
120     result = escape_html(el.value)
121     result.chomp!
122     if el.attr['class'].to_s =~ /\bshow-whitespaces\b/
123       result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m|
124         suffix = ($1 ? '-l' : ($2 ? '-r' : ''))
125         m.scan(/./).map do |c|
126           case c
127           when "\t" then "<span class=\"ws-tab#{suffix}\">\t</span>"
128           when " " then "<span class=\"ws-space#{suffix}\">&#8901;</span>"
129           end
130         end.join('')
131       end
132     end
133     code_attr = {}
134     code_attr['class'] = "language-#{lang}" if lang
135     "#{' ' * indent}<pre#{html_attributes(attr)}>" \
136       "<code#{html_attributes(code_attr)}>#{result}\n</code></pre>\n"
137   end
138 end
convert_codespan(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
279 def convert_codespan(el, _indent)
280   attr = el.attr.dup
281   lang = extract_code_language(attr)
282   hl_opts = {}
283   result = highlight_code(el.value, lang, :span, hl_opts)
284   if result
285     add_syntax_highlighter_to_class_attr(attr, lang || hl_opts[:default_lang])
286   else
287     result = escape_html(el.value)
288   end
289 
290   format_as_span_html('code', attr, result)
291 end
convert_comment(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
259 def convert_comment(el, indent)
260   if el.options[:category] == :block
261     "#{' ' * indent}<!-- #{el.value} -->\n"
262   else
263     "<!-- #{el.value} -->"
264   end
265 end
convert_dd(el, indent)
Alias for: convert_li
convert_dl(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
173 def convert_dl(el, indent)
174   format_as_indented_block_html("dl", el.attr, inner(el, indent), indent)
175 end
convert_dt(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
189 def convert_dt(el, indent)
190   attr = el.attr.dup
191   @stack.last.options[:ial][:refs].each do |ref|
192     if ref =~ /\Aauto_ids(?:-([\w-]+))?/
193       attr['id'] = "#{$1}#{basic_generate_id(el.options[:raw_text])}".lstrip
194       break
195     end
196   end if !attr['id'] && @stack.last.options[:ial] && @stack.last.options[:ial][:refs]
197   format_as_block_html("dt", attr, inner(el, indent), indent)
198 end
convert_em(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
318 def convert_em(el, indent)
319   format_as_span_html(el.type, el.attr, inner(el, indent))
320 end
Also aliased as: convert_strong
convert_entity(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
323 def convert_entity(el, _indent)
324   entity_to_str(el.value, el.options[:original])
325 end
convert_footnote(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
293 def convert_footnote(el, _indent)
294   repeat = ''
295   name = @options[:footnote_prefix] + el.options[:name]
296   if (footnote = @footnotes_by_name[name])
297     number = footnote[2]
298     repeat = ":#{footnote[3] += 1}"
299   else
300     number = @footnote_counter
301     @footnote_counter += 1
302     @footnotes << [name, el.value, number, 0]
303     @footnotes_by_name[name] = @footnotes.last
304   end
305   "<sup id=\"fnref:#{name}#{repeat}\" role=\"doc-noteref\">" \
306     "<a href=\"#fn:#{name}\" class=\"footnote\" rel=\"footnote\">" \
307     "#{number}</a></sup>"
308 end
convert_header(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
144 def convert_header(el, indent)
145   attr = el.attr.dup
146   if @options[:auto_ids] && !attr['id']
147     attr['id'] = generate_id(el.options[:raw_text])
148   end
149   @toc << [el.options[:level], attr['id'], el.children] if attr['id'] && in_toc?(el)
150   level = output_header_level(el.options[:level])
151   format_as_block_html("h#{level}", attr, inner(el, indent), indent)
152 end
convert_hr(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
154 def convert_hr(el, indent)
155   "#{' ' * indent}<hr#{html_attributes(el.attr)} />\n"
156 end
convert_html_element(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
200 def convert_html_element(el, indent)
201   res = inner(el, indent)
202   if el.options[:category] == :span
203     "<#{el.value}#{html_attributes(el.attr)}" + \
204       (res.empty? && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) ? " />" : ">#{res}</#{el.value}>")
205   else
206     output = +''
207     if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
208       output << ' ' * indent
209     end
210     output << "<#{el.value}#{html_attributes(el.attr)}"
211     if el.options[:is_closed] && el.options[:content_model] == :raw
212       output << " />"
213     elsif !res.empty? && el.options[:content_model] != :block
214       output << ">#{res}</#{el.value}>"
215     elsif !res.empty?
216       output << ">\n#{res.chomp}\n" << ' ' * indent << "</#{el.value}>"
217     elsif HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
218       output << " />"
219     else
220       output << "></#{el.value}>"
221     end
222     output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
223     output
224   end
225 end
convert_img(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
275 def convert_img(el, _indent)
276   "<img#{html_attributes(el.attr)} />"
277 end
convert_li(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
177 def convert_li(el, indent)
178   output = ' ' * indent << "<#{el.type}" << html_attributes(el.attr) << ">"
179   res = inner(el, indent)
180   if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent])
181     output << res << (res =~ /\n\Z/ ? ' ' * indent : '')
182   else
183     output << "\n" << res << ' ' * indent
184   end
185   output << "</#{el.type}>\n"
186 end
Also aliased as: convert_dd
convert_math(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
350 def convert_math(el, indent)
351   if (result = format_math(el, indent: indent))
352     result
353   else
354     attr = el.attr.dup
355     attr['class'] = "#{attr['class']} kdmath".lstrip
356     if el.options[:category] == :block
357       format_as_block_html('div', attr, "$$\n#{el.value}\n$$", indent)
358     else
359       format_as_span_html('span', attr, "$#{el.value}$")
360     end
361   end
362 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
310 def convert_raw(el, _indent)
311   if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html')
312     el.value + (el.options[:category] == :block ? "\n" : '')
313   else
314     ''
315   end
316 end
convert_root(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
371 def convert_root(el, indent)
372   result = inner(el, indent)
373   if @footnote_location
374     result.sub!(/#{@footnote_location}/, footnote_content.gsub(/\\/, "\\\\\\\\"))
375   else
376     result << footnote_content
377   end
378   if @toc_code
379     toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {})
380     text = if !toc_tree.children.empty?
381              convert(toc_tree, 0)
382            else
383              ''
384            end
385     result.sub!(/#{@toc_code.last}/, text.gsub(/\\/, "\\\\\\\\"))
386   end
387   result
388 end
convert_smart_quote(el, _indent) click to toggle source
    # File lib/kramdown/converter/html.rb
346 def convert_smart_quote(el, _indent)
347   entity_to_str(smart_quote_entity(el))
348 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   figure_attr['class'] = image_attr.delete('class') if image_attr.key?('class') and not figure_attr.key?('class')
103   figure_attr['id'] = image_attr.delete('id') if image_attr.key?('id') and not figure_attr.key?('id')
104 
105   body = "#{' ' * (indent + @indent)}<img#{html_attributes(image_attr)} />\n" \
106     "#{' ' * (indent + @indent)}<figcaption>#{image_attr['alt']}</figcaption>\n"
107   format_as_indented_block_html("figure", figure_attr, body, indent)
108 end
convert_strong(el, indent)
Alias for: convert_em
convert_table(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
237 def convert_table(el, indent)
238   format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
239 end
convert_tbody(el, indent)
Alias for: convert_table
convert_td(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
247 def convert_td(el, indent)
248   res = inner(el, indent)
249   type = (@stack[-2].type == :thead ? :th : :td)
250   attr = el.attr
251   alignment = @stack[-3].options[:alignment][@stack.last.children.index(el)]
252   if alignment != :default
253     attr = el.attr.dup
254     attr['style'] = (attr.key?('style') ? "#{attr['style']}; " : '') + "text-align: #{alignment}"
255   end
256   format_as_block_html(type, attr, res.empty? ? entity_to_str(ENTITY_NBSP) : res, indent)
257 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
338 def convert_typographic_sym(el, _indent)
339   if (result = @options[:typographic_symbols][el.value])
340     escape_html(result, :text)
341   else
342     TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e) }.join('')
343   end
344 end
convert_ul(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
161 def convert_ul(el, indent)
162   if !@toc_code && el.options.dig(:ial, :refs)&.include?('toc')
163     @toc_code = [el.type, el.attr, ZERO_TO_ONETWENTYEIGHT.map { rand(36).to_s(36) }.join]
164     @toc_code.last
165   elsif !@footnote_location && el.options.dig(:ial, :refs)&.include?('footnotes')
166     @footnote_location = ZERO_TO_ONETWENTYEIGHT.map { rand(36).to_s(36) }.join
167   else
168     format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
169   end
170 end
Also aliased as: convert_ol
convert_xml_comment(el, indent) click to toggle source
    # File lib/kramdown/converter/html.rb
227 def convert_xml_comment(el, indent)
228   if el.options[:category] == :block &&
229       (@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw)
230     ' ' * indent << el.value << "\n"
231   else
232     el.value
233   end
234 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
452 def fix_for_toc_entry(elements)
453   remove_footnotes(elements)
454   unwrap_links(elements)
455   elements
456 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
487 def footnote_content
488   ol = Element.new(:ol)
489   ol.attr['start'] = @footnote_start if @footnote_start != 1
490   i = 0
491   backlink_text = escape_html(@options[:footnote_backlink], :text)
492   while i < @footnotes.length
493     name, data, _, repeat = *@footnotes[i]
494     li = Element.new(:li, nil, 'id' => "fn:#{name}", 'role' => 'doc-endnote')
495     li.children = Marshal.load(Marshal.dump(data.children))
496 
497     para = nil
498     if li.children.last.type == :p || @options[:footnote_backlink_inline]
499       parent = li
500       while !parent.children.empty? && ![:p, :header].include?(parent.children.last.type)
501         parent = parent.children.last
502       end
503       para = parent.children.last
504       insert_space = true
505     end
506 
507     unless para
508       li.children << (para = Element.new(:p))
509       insert_space = false
510     end
511 
512     unless @options[:footnote_backlink].empty?
513       nbsp = entity_to_str(ENTITY_NBSP)
514       value = sprintf(FOOTNOTE_BACKLINK_FMT, (insert_space ? nbsp : ''), name, backlink_text)
515       para.children << Element.new(:raw, value)
516       (1..repeat).each do |index|
517         value = sprintf(FOOTNOTE_BACKLINK_FMT, nbsp, "#{name}:#{index}",
518                         "#{backlink_text}<sup>#{index + 1}</sup>")
519         para.children << Element.new(:raw, value)
520       end
521     end
522 
523     ol.children << Element.new(:raw, convert(li, 4))
524     i += 1
525   end
526   if ol.children.empty?
527     ''
528   else
529     format_as_indented_block_html('div', {class: "footnotes", role: "doc-endnotes"}, convert(ol, 2), 0)
530   end
531 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
396 def format_as_block_html(name, attr, body, indent)
397   "#{' ' * indent}<#{name}#{html_attributes(attr)}>#{body}</#{name}>\n"
398 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
402 def format_as_indented_block_html(name, attr, body, indent)
403   "#{' ' * indent}<#{name}#{html_attributes(attr)}>\n#{body}#{' ' * indent}</#{name}>\n"
404 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
391 def format_as_span_html(name, attr, body)
392   "<#{name}#{html_attributes(attr)}>#{body}</#{name}>"
393 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
414 def generate_toc_tree(toc, type, attr)
415   sections = Element.new(type, nil, attr.dup)
416   sections.attr['id'] ||= 'markdown-toc'
417   stack = []
418   toc.each do |level, id, children|
419     li = Element.new(:li, nil, nil, level: level)
420     li.children << Element.new(:p, nil, nil, transparent: true)
421     a = Element.new(:a, nil)
422     a.attr['href'] = "##{id}"
423     a.attr['id'] = "#{sections.attr['id']}-#{id}"
424     a.children.concat(fix_for_toc_entry(Marshal.load(Marshal.dump(children))))
425     li.children.last.children << a
426     li.children << Element.new(type)
427 
428     success = false
429     until success
430       if stack.empty?
431         sections.children << li
432         stack << li
433         success = true
434       elsif stack.last.options[:level] < li.options[:level]
435         stack.last.children.last.children << li
436         stack << li
437         success = true
438       else
439         item = stack.pop
440         item.children.pop if item.children.last.children.empty?
441       end
442     end
443   end
444   until stack.empty?
445     item = stack.pop
446     item.children.pop if item.children.last.children.empty?
447   end
448   sections
449 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
475 def obfuscate(text)
476   result = +''
477   text.each_byte do |b|
478     result << (b > 128 ? b.chr : sprintf("&#%03d;", b))
479   end
480   result.force_encoding(text.encoding)
481   result
482 end
remove_footnotes(elements) click to toggle source

Remove all footnotes from the given elements.

    # File lib/kramdown/converter/html.rb
467 def remove_footnotes(elements)
468   elements.delete_if do |c|
469     remove_footnotes(c.children)
470     c.type == :footnote
471   end
472 end