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

Remove all footnotes from the given elements.

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