Class: Kramdown::Converter::Html

Inherits:
Base
  • Object
show all
Includes:
Utils::Html
Defined in:
lib/kramdown/converter/html.rb

Overview

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.

Constant Summary collapse

DISPATCHER =

The mapping of element type to conversion method.

Hash.new {|h,k| h[k] = "convert_#{k}"}
HTML_TAGS_WITH_BODY =

A list of all HTML tags that need to have a body (even if the body is empty).

['div', 'span', 'script', 'iframe', 'textarea', 'a']
ENTITY_NBSP =

:nodoc:

::Kramdown::Utils::Entities.entity('nbsp')
TYPOGRAPHIC_SYMS =
{
  :mdash => [::Kramdown::Utils::Entities.entity('mdash')],
  :ndash => [::Kramdown::Utils::Entities.entity('ndash')],
  :hellip => [::Kramdown::Utils::Entities.entity('hellip')],
  :laquo_space => [::Kramdown::Utils::Entities.entity('laquo'), ::Kramdown::Utils::Entities.entity('nbsp')],
  :raquo_space => [::Kramdown::Utils::Entities.entity('nbsp'), ::Kramdown::Utils::Entities.entity('raquo')],
  :laquo => [::Kramdown::Utils::Entities.entity('laquo')],
  :raquo => [::Kramdown::Utils::Entities.entity('raquo')]
}

Constants included from Utils::Html

Utils::Html::ESCAPE_ALL_RE, Utils::Html::ESCAPE_ATTRIBUTE_RE, Utils::Html::ESCAPE_MAP, Utils::Html::ESCAPE_RE_FROM_TYPE, Utils::Html::ESCAPE_TEXT_RE

Constants inherited from Base

Base::SMART_QUOTE_INDICES

Instance Attribute Summary collapse

Attributes inherited from Base

#data, #options, #root, #warnings

Instance Method Summary collapse

Methods included from Utils::Html

#entity_to_str, #escape_html, #html_attributes

Methods inherited from Base

apply_template, convert, #generate_id, get_template, #in_toc?, #smart_quote_entity, #warning

Constructor Details

#initialize(root, options) ⇒ Html

Initialize the HTML converter with the given Kramdown document doc.



59
60
61
62
63
64
65
66
67
# File 'lib/kramdown/converter/html.rb', line 59

def initialize(root, options)
  super
  @footnote_counter = @footnote_start = @options[:footnote_nr]
  @footnotes = []
  @toc = []
  @toc_code = nil
  @indent = 2
  @stack = []
end

Instance Attribute Details

#indentObject

The amount of indentation used when nesting HTML tags.



56
57
58
# File 'lib/kramdown/converter/html.rb', line 56

def indent
  @indent
end

Instance Method Details

#convert(el, indent = -@indent)) ⇒ Object

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



74
75
76
# File 'lib/kramdown/converter/html.rb', line 74

def convert(el, indent = -@indent)
  send(DISPATCHER[el.type], el, indent)
end

#convert_a(el, indent) ⇒ Object



251
252
253
254
255
256
257
258
259
# File 'lib/kramdown/converter/html.rb', line 251

def convert_a(el, indent)
  res = inner(el, indent)
  attr = el.attr.dup
  if attr['href'] =~ /^mailto:/
    attr['href'] = obfuscate('mailto') << ":" << obfuscate(attr['href'].sub(/^mailto:/, ''))
    res = obfuscate(res)
  end
  "<a#{html_attributes(attr)}>#{res}</a>"
end

#convert_abbreviation(el, indent) ⇒ Object



321
322
323
324
# File 'lib/kramdown/converter/html.rb', line 321

def convert_abbreviation(el, indent)
  title = @root.options[:abbrev_defs][el.value]
  "<abbr#{!title.empty? ? " title=\"#{title}\"" : ''}>#{el.value}</abbr>"
end

#convert_blank(el, indent) ⇒ Object



94
95
96
# File 'lib/kramdown/converter/html.rb', line 94

def convert_blank(el, indent)
  "\n"
end

#convert_blockquote(el, indent) ⇒ Object



136
137
138
# File 'lib/kramdown/converter/html.rb', line 136

def convert_blockquote(el, indent)
  "#{' '*indent}<blockquote#{html_attributes(el.attr)}>\n#{inner(el, indent)}#{' '*indent}</blockquote>\n"
end

#convert_br(el, indent) ⇒ Object



247
248
249
# File 'lib/kramdown/converter/html.rb', line 247

def convert_br(el, indent)
  "<br />"
end

#convert_codeblock(el, indent) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/kramdown/converter/html.rb', line 110

def convert_codeblock(el, indent)
  if el.attr['lang'] && HIGHLIGHTING_AVAILABLE
    attr = el.attr.dup
    opts = {:wrap => @options[:coderay_wrap], :line_numbers => @options[:coderay_line_numbers],
      :line_number_start => @options[:coderay_line_number_start], :tab_width => @options[:coderay_tab_width],
      :bold_every => @options[:coderay_bold_every], :css => @options[:coderay_css]}
    result = CodeRay.scan(el.value, attr.delete('lang').to_sym).html(opts).chomp << "\n"
    "#{' '*indent}<div#{html_attributes(attr)}>#{result}#{' '*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}\">&#8901;</span>"
          end
        end.join('')
      end
    end
    "#{' '*indent}<pre#{html_attributes(el.attr)}><code>#{result}\n</code></pre>\n"
  end
end

#convert_codespan(el, indent) ⇒ Object



265
266
267
268
269
270
271
272
273
# File 'lib/kramdown/converter/html.rb', line 265

def convert_codespan(el, indent)
  if el.attr['lang'] && HIGHLIGHTING_AVAILABLE
    attr = el.attr.dup
    result = CodeRay.scan(el.value, attr.delete('lang').to_sym).html(:wrap => :span, :css => @options[:coderay_css]).chomp
    "<code#{html_attributes(attr)}>#{result}</code>"
  else
    "<code#{html_attributes(el.attr)}>#{escape_html(el.value)}</code>"
  end
end

#convert_comment(el, indent) ⇒ Object



239
240
241
242
243
244
245
# File 'lib/kramdown/converter/html.rb', line 239

def convert_comment(el, indent)
  if el.options[:category] == :block
    "#{' '*indent}<!-- #{el.value} -->\n"
  else
    "<!-- #{el.value} -->"
  end
end

#convert_dt(el, indent) ⇒ Object



176
177
178
# File 'lib/kramdown/converter/html.rb', line 176

def convert_dt(el, indent)
  "#{' '*indent}<dt#{html_attributes(el.attr)}>#{inner(el, indent)}</dt>\n"
end

#convert_em(el, indent) ⇒ Object Also known as: convert_strong



290
291
292
# File 'lib/kramdown/converter/html.rb', line 290

def convert_em(el, indent)
  "<#{el.type}#{html_attributes(el.attr)}>#{inner(el, indent)}</#{el.type}>"
end

#convert_entity(el, indent) ⇒ Object



295
296
297
# File 'lib/kramdown/converter/html.rb', line 295

def convert_entity(el, indent)
  entity_to_str(el.value, el.options[:original])
end

#convert_footnote(el, indent) ⇒ Object



275
276
277
278
279
280
# File 'lib/kramdown/converter/html.rb', line 275

def convert_footnote(el, indent)
  number = @footnote_counter
  @footnote_counter += 1
  @footnotes << [el.options[:name], el.value]
  "<sup id=\"fnref:#{el.options[:name]}\"><a href=\"#fn:#{el.options[:name]}\" rel=\"footnote\">#{number}</a></sup>"
end

#convert_header(el, indent) ⇒ Object



140
141
142
143
144
145
146
147
# File 'lib/kramdown/converter/html.rb', line 140

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)
  "#{' '*indent}<h#{el.options[:level]}#{html_attributes(attr)}>#{inner(el, indent)}</h#{el.options[:level]}>\n"
end

#convert_hr(el, indent) ⇒ Object



149
150
151
# File 'lib/kramdown/converter/html.rb', line 149

def convert_hr(el, indent)
  "#{' '*indent}<hr />\n"
end

#convert_html_element(el, indent) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/kramdown/converter/html.rb', line 183

def convert_html_element(el, indent)
  res = inner(el, indent)
  if el.options[:category] == :span
    "<#{el.value}#{html_attributes(el.attr)}" << (!res.empty? || HTML_TAGS_WITH_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 !res.empty? && el.options[:content_model] != :block
      output << ">#{res}</#{el.value}>"
    elsif !res.empty?
      output << ">\n#{res.chomp}\n"  << ' '*indent << "</#{el.value}>"
    elsif HTML_TAGS_WITH_BODY.include?(el.value)
      output << "></#{el.value}>"
    else
      output << " />"
    end
    output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
    output
  end
end

#convert_img(el, indent) ⇒ Object



261
262
263
# File 'lib/kramdown/converter/html.rb', line 261

def convert_img(el, indent)
  "<img#{html_attributes(el.attr)} />"
end

#convert_li(el, indent) ⇒ Object Also known as: convert_dd



164
165
166
167
168
169
170
171
172
173
# File 'lib/kramdown/converter/html.rb', line 164

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

#convert_math(el, indent) ⇒ Object



316
317
318
319
# File 'lib/kramdown/converter/html.rb', line 316

def convert_math(el, indent)
  block = (el.options[:category] == :block)
  "<script type=\"math/tex#{block ? '; mode=display' : ''}\">#{el.value}</script>#{block ? "\n" : ''}"
end

#convert_p(el, indent) ⇒ Object



102
103
104
105
106
107
108
# File 'lib/kramdown/converter/html.rb', line 102

def convert_p(el, indent)
  if el.options[:transparent]
    inner(el, indent)
  else
    "#{' '*indent}<p#{html_attributes(el.attr)}>#{inner(el, indent)}</p>\n"
  end
end

#convert_raw(el, indent) ⇒ Object



282
283
284
285
286
287
288
# File 'lib/kramdown/converter/html.rb', line 282

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

#convert_root(el, indent) ⇒ Object



326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/kramdown/converter/html.rb', line 326

def convert_root(el, indent)
  result = inner(el, indent)
  result << footnote_content
  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)
  end
  result
end

#convert_smart_quote(el, indent) ⇒ Object



312
313
314
# File 'lib/kramdown/converter/html.rb', line 312

def convert_smart_quote(el, indent)
  entity_to_str(smart_quote_entity(el))
end

#convert_table(el, indent) ⇒ Object



214
215
216
# File 'lib/kramdown/converter/html.rb', line 214

def convert_table(el, indent)
  "#{' '*indent}<table#{html_attributes(el.attr)}>\n#{inner(el, indent)}#{' '*indent}</table>\n"
end

#convert_td(el, indent) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/kramdown/converter/html.rb', line 227

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
  "#{' '*indent}<#{type}#{html_attributes(attr)}>#{res.empty? ? entity_to_str(ENTITY_NBSP) : res}</#{type}>\n"
end

#convert_text(el, indent) ⇒ Object



98
99
100
# File 'lib/kramdown/converter/html.rb', line 98

def convert_text(el, indent)
  escape_html(el.value, :text)
end

#convert_thead(el, indent) ⇒ Object Also known as: convert_tbody, convert_tfoot, convert_tr



218
219
220
# File 'lib/kramdown/converter/html.rb', line 218

def convert_thead(el, indent)
  "#{' '*indent}<#{el.type}#{html_attributes(el.attr)}>\n#{inner(el, indent)}#{' '*indent}</#{el.type}>\n"
end

#convert_typographic_sym(el, indent) ⇒ Object

:nodoc:



308
309
310
# File 'lib/kramdown/converter/html.rb', line 308

def convert_typographic_sym(el, indent)
  TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e)}.join('')
end

#convert_ul(el, indent) ⇒ Object Also known as: convert_ol, convert_dl



153
154
155
156
157
158
159
160
# File 'lib/kramdown/converter/html.rb', line 153

def convert_ul(el, indent)
  if !@toc_code && (el.options[:ial][:refs].include?('toc') rescue nil) && (el.type == :ul || el.type == :ol)
    @toc_code = [el.type, el.attr, (0..128).to_a.map{|a| rand(36).to_s(36)}.join]
    @toc_code.last
  else
    "#{' '*indent}<#{el.type}#{html_attributes(el.attr)}>\n#{inner(el, indent)}#{' '*indent}</#{el.type}>\n"
  end
end

#convert_xml_comment(el, indent) ⇒ Object Also known as: convert_xml_pi



205
206
207
208
209
210
211
# File 'lib/kramdown/converter/html.rb', line 205

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

#footnote_contentObject

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



388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/kramdown/converter/html.rb', line 388

def footnote_content
  ol = Element.new(:ol)
  ol.attr['start'] = @footnote_start if @footnote_start != 1
  @footnotes.each do |name, data|
    li = Element.new(:li, nil, {'id' => "fn:#{name}"})
    li.children = Marshal.load(Marshal.dump(data.children))
    ol.children << li

    ref = Element.new(:raw, "<a href=\"#fnref:#{name}\" rel=\"reference\">&#8617;</a>")
    if li.children.last.type == :p
      para = li.children.last
    else
      li.children << (para = Element.new(:p))
    end
    para.children << ref
  end
  (ol.children.empty? ? '' : "<div class=\"footnotes\">\n#{convert(ol, 2)}</div>\n")
end

#generate_toc_tree(toc, type, attr) ⇒ Object

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



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/kramdown/converter/html.rb', line 342

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, {'href' => "##{id}"})
    a.children.concat(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

#inner(el, indent) ⇒ Object

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.



83
84
85
86
87
88
89
90
91
92
# File 'lib/kramdown/converter/html.rb', line 83

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(text) ⇒ Object

Obfuscate the text by using HTML entities.



378
379
380
381
382
383
384
385
# File 'lib/kramdown/converter/html.rb', line 378

def obfuscate(text)
  result = ""
  text.each_byte do |b|
    result << (b > 128 ? b.chr : "&#%03d;" % b)
  end
  result.force_encoding(text.encoding) if RUBY_VERSION >= '1.9'
  result
end