PageRenderTime 55ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/radiant/vendor/extensions/markdown_filter/vendor/kramdown/lib/kramdown/converter/html.rb

https://github.com/roguedev/Jeanine-Ann-Lenertz
Ruby | 399 lines | 321 code | 50 blank | 28 comment | 64 complexity | aaf10eccee96405510926d7b0d724e56 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. #
  3. #--
  4. # Copyright (C) 2009-2010 Thomas Leitner <t_leitner@gmx.at>
  5. #
  6. # This file is part of kramdown.
  7. #
  8. # kramdown is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation, either version 3 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #++
  21. #
  22. require 'rexml/parsers/baseparser'
  23. module Kramdown
  24. module Converter
  25. # Converts a Kramdown::Document to HTML.
  26. class Html < Base
  27. include ::Kramdown::Utils::HTML
  28. # :stopdoc:
  29. # Defines the amount of indentation used when nesting HTML tags.
  30. INDENTATION = 2
  31. begin
  32. require 'coderay'
  33. # Highlighting via coderay is available if this constant is +true+.
  34. HIGHLIGHTING_AVAILABLE = true
  35. rescue LoadError => e
  36. HIGHLIGHTING_AVAILABLE = false
  37. end
  38. # Initialize the HTML converter with the given Kramdown document +doc+.
  39. def initialize(doc)
  40. super
  41. @footnote_counter = @footnote_start = @doc.options[:footnote_nr]
  42. @footnotes = []
  43. @toc = []
  44. @toc_code = nil
  45. end
  46. def convert(el, indent = -INDENTATION, opts = {})
  47. send("convert_#{el.type}", el, indent, opts)
  48. end
  49. def inner(el, indent, opts)
  50. result = ''
  51. indent += INDENTATION
  52. el.children.each do |inner_el|
  53. opts[:parent] = el
  54. result << send("convert_#{inner_el.type}", inner_el, indent, opts)
  55. end
  56. result
  57. end
  58. def convert_blank(el, indent, opts)
  59. "\n"
  60. end
  61. def convert_text(el, indent, opts)
  62. escape_html(el.value, :text)
  63. end
  64. def convert_p(el, indent, opts)
  65. if el.options[:transparent]
  66. "#{inner(el, indent, opts)}"
  67. else
  68. "#{' '*indent}<p#{html_attributes(el)}>#{inner(el, indent, opts)}</p>\n"
  69. end
  70. end
  71. def convert_codeblock(el, indent, opts)
  72. el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
  73. lang = el.attr.delete('lang')
  74. if lang && HIGHLIGHTING_AVAILABLE
  75. opts = {:wrap => @doc.options[:coderay_wrap], :line_numbers => @doc.options[:coderay_line_numbers],
  76. :line_number_start => @doc.options[:coderay_line_number_start], :tab_width => @doc.options[:coderay_tab_width],
  77. :bold_every => @doc.options[:coderay_bold_every], :css => @doc.options[:coderay_css]}
  78. result = CodeRay.scan(el.value, lang.to_sym).html(opts).chomp + "\n"
  79. "#{' '*indent}<div#{html_attributes(el)}>#{result}#{' '*indent}</div>\n"
  80. else
  81. result = escape_html(el.value)
  82. if el.attr['class'].to_s =~ /\bshow-whitespaces\b/
  83. result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m|
  84. suffix = ($1 ? '-l' : ($2 ? '-r' : ''))
  85. m.scan(/./).map do |c|
  86. case c
  87. when "\t" then "<span class=\"ws-tab#{suffix}\">\t</span>"
  88. when " " then "<span class=\"ws-space#{suffix}\">&#8901;</span>"
  89. end
  90. end.join('')
  91. end
  92. end
  93. "#{' '*indent}<pre#{html_attributes(el)}><code>#{result}#{result =~ /\n\Z/ ? '' : "\n"}</code></pre>\n"
  94. end
  95. end
  96. def convert_blockquote(el, indent, opts)
  97. "#{' '*indent}<blockquote#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</blockquote>\n"
  98. end
  99. def convert_header(el, indent, opts)
  100. el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
  101. if @doc.options[:auto_ids] && !el.attr['id']
  102. el.attr['id'] = generate_id(el.options[:raw_text])
  103. end
  104. @toc << [el.options[:level], el.attr['id'], el.children] if el.attr['id'] && within_toc_depth?(el)
  105. "#{' '*indent}<h#{el.options[:level]}#{html_attributes(el)}>#{inner(el, indent, opts)}</h#{el.options[:level]}>\n"
  106. end
  107. def within_toc_depth?(el)
  108. @doc.options[:toc_depth] <= 0 || el.options[:level] <= @doc.options[:toc_depth]
  109. end
  110. def convert_hr(el, indent, opts)
  111. "#{' '*indent}<hr />\n"
  112. end
  113. def convert_ul(el, indent, opts)
  114. if !@toc_code && (el.options[:ial][:refs].include?('toc') rescue nil) && (el.type == :ul || el.type == :ol)
  115. @toc_code = [el.type, el.attr, (0..128).to_a.map{|a| rand(36).to_s(36)}.join]
  116. @toc_code.last
  117. else
  118. "#{' '*indent}<#{el.type}#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</#{el.type}>\n"
  119. end
  120. end
  121. alias :convert_ol :convert_ul
  122. alias :convert_dl :convert_ul
  123. def convert_li(el, indent, opts)
  124. output = ' '*indent << "<#{el.type}" << html_attributes(el) << ">"
  125. res = inner(el, indent, opts)
  126. if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent])
  127. output << res << (res =~ /\n\Z/ ? ' '*indent : '')
  128. else
  129. output << "\n" << res << ' '*indent
  130. end
  131. output << "</#{el.type}>\n"
  132. end
  133. alias :convert_dd :convert_li
  134. def convert_dt(el, indent, opts)
  135. "#{' '*indent}<dt#{html_attributes(el)}>#{inner(el, indent, opts)}</dt>\n"
  136. end
  137. HTML_TAGS_WITH_BODY=['div', 'script', 'iframe', 'textarea']
  138. def convert_html_element(el, indent, opts)
  139. parent = opts[:parent]
  140. res = inner(el, indent, opts)
  141. if el.options[:category] == :span
  142. "<#{el.value}#{html_attributes(el)}" << (!res.empty? || HTML_TAGS_WITH_BODY.include?(el.value) ? ">#{res}</#{el.value}>" : " />")
  143. else
  144. output = ''
  145. output << ' '*indent if parent.type != :html_element || parent.options[:parse_type] != :raw
  146. output << "<#{el.value}#{html_attributes(el)}"
  147. if !res.empty? && el.options[:parse_type] != :block
  148. output << ">#{res}</#{el.value}>"
  149. elsif !res.empty?
  150. output << ">\n#{res.chomp}\n" << ' '*indent << "</#{el.value}>"
  151. elsif HTML_TAGS_WITH_BODY.include?(el.value)
  152. output << "></#{el.value}>"
  153. else
  154. output << " />"
  155. end
  156. output << "\n" if parent.type != :html_element || parent.options[:parse_type] != :raw
  157. output
  158. end
  159. end
  160. def convert_xml_comment(el, indent, opts)
  161. if el.options[:category] == :block && (opts[:parent].type != :html_element || opts[:parent].options[:parse_type] != :raw)
  162. ' '*indent + el.value + "\n"
  163. else
  164. el.value
  165. end
  166. end
  167. alias :convert_xml_pi :convert_xml_comment
  168. alias :convert_html_doctype :convert_xml_comment
  169. def convert_table(el, indent, opts)
  170. if el.options[:alignment].all? {|a| a == :default}
  171. alignment = ''
  172. else
  173. alignment = el.options[:alignment].map do |a|
  174. "#{' '*(indent + INDENTATION)}" + (a == :default ? "<col />" : "<col align=\"#{a}\" />") + "\n"
  175. end.join('')
  176. end
  177. "#{' '*indent}<table#{html_attributes(el)}>\n#{alignment}#{inner(el, indent, opts)}#{' '*indent}</table>\n"
  178. end
  179. def convert_thead(el, indent, opts)
  180. "#{' '*indent}<#{el.type}#{html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</#{el.type}>\n"
  181. end
  182. alias :convert_tbody :convert_thead
  183. alias :convert_tfoot :convert_thead
  184. alias :convert_tr :convert_thead
  185. def convert_td(el, indent, opts)
  186. res = inner(el, indent, opts)
  187. "#{' '*indent}<#{el.type}#{html_attributes(el)}>#{res.empty? ? "&nbsp;" : res}</#{el.type}>\n"
  188. end
  189. alias :convert_th :convert_td
  190. def convert_comment(el, indent, opts)
  191. if el.options[:category] == :block
  192. "#{' '*indent}<!-- #{el.value} -->\n"
  193. else
  194. "<!-- #{el.value} -->"
  195. end
  196. end
  197. def convert_br(el, indent, opts)
  198. "<br />"
  199. end
  200. def convert_a(el, indent, opts)
  201. do_obfuscation = el.attr['href'] =~ /^mailto:/
  202. if do_obfuscation
  203. el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
  204. href = obfuscate(el.attr['href'].sub(/^mailto:/, ''))
  205. mailto = obfuscate('mailto')
  206. el.attr['href'] = "#{mailto}:#{href}"
  207. end
  208. res = inner(el, indent, opts)
  209. res = obfuscate(res) if do_obfuscation
  210. "<a#{html_attributes(el)}>#{res}</a>"
  211. end
  212. def convert_img(el, indent, opts)
  213. "<img#{html_attributes(el)} />"
  214. end
  215. def convert_codespan(el, indent, opts)
  216. el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
  217. lang = el.attr.delete('lang')
  218. if lang && HIGHLIGHTING_AVAILABLE
  219. result = CodeRay.scan(el.value, lang.to_sym).html(:wrap => :span, :css => @doc.options[:coderay_css]).chomp
  220. "<code#{html_attributes(el)}>#{result}</code>"
  221. else
  222. "<code#{html_attributes(el)}>#{escape_html(el.value)}</code>"
  223. end
  224. end
  225. def convert_footnote(el, indent, opts)
  226. number = @footnote_counter
  227. @footnote_counter += 1
  228. @footnotes << [el.options[:name], @doc.parse_infos[:footnotes][el.options[:name]]]
  229. "<sup id=\"fnref:#{el.options[:name]}\"><a href=\"#fn:#{el.options[:name]}\" rel=\"footnote\">#{number}</a></sup>"
  230. end
  231. def convert_raw(el, indent, opts)
  232. if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html')
  233. el.value + (el.options[:category] == :block ? "\n" : '')
  234. else
  235. ''
  236. end
  237. end
  238. def convert_em(el, indent, opts)
  239. "<#{el.type}#{html_attributes(el)}>#{inner(el, indent, opts)}</#{el.type}>"
  240. end
  241. alias :convert_strong :convert_em
  242. def convert_entity(el, indent, opts)
  243. entity_to_str(el.value, el.options[:original])
  244. end
  245. TYPOGRAPHIC_SYMS = {
  246. :mdash => [::Kramdown::Utils::Entities.entity('mdash')],
  247. :ndash => [::Kramdown::Utils::Entities.entity('ndash')],
  248. :hellip => [::Kramdown::Utils::Entities.entity('hellip')],
  249. :laquo_space => [::Kramdown::Utils::Entities.entity('laquo'), ::Kramdown::Utils::Entities.entity('nbsp')],
  250. :raquo_space => [::Kramdown::Utils::Entities.entity('nbsp'), ::Kramdown::Utils::Entities.entity('raquo')],
  251. :laquo => [::Kramdown::Utils::Entities.entity('laquo')],
  252. :raquo => [::Kramdown::Utils::Entities.entity('raquo')]
  253. }
  254. def convert_typographic_sym(el, indent, opts)
  255. TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e)}.join('')
  256. end
  257. def convert_smart_quote(el, indent, opts)
  258. entity_to_str(::Kramdown::Utils::Entities.entity(el.value.to_s))
  259. end
  260. def convert_math(el, indent, opts)
  261. el = Marshal.load(Marshal.dump(el)) # so that the original is not changed
  262. el.attr['class'] ||= ''
  263. el.attr['class'] += (el.attr['class'].empty? ? '' : ' ') + 'math'
  264. type = 'span'
  265. type = 'div' if el.options[:category] == :block
  266. "<#{type}#{html_attributes(el)}>#{escape_html(el.value)}</#{type}>#{type == 'div' ? "\n" : ''}"
  267. end
  268. def convert_abbreviation(el, indent, opts)
  269. title = @doc.parse_infos[:abbrev_defs][el.value]
  270. title = nil if title.empty?
  271. "<abbr#{title ? " title=\"#{title}\"" : ''}>#{el.value}</abbr>"
  272. end
  273. def convert_root(el, indent, opts)
  274. result = inner(el, indent, opts)
  275. result << footnote_content
  276. if @toc_code
  277. toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {})
  278. text = if toc_tree.children.size > 0
  279. convert(toc_tree, 0)
  280. else
  281. ''
  282. end
  283. result.sub!(/#{@toc_code.last}/, text)
  284. end
  285. result
  286. end
  287. def generate_toc_tree(toc, type, attr)
  288. sections = Element.new(type, nil, attr)
  289. sections.attr['id'] ||= 'markdown-toc'
  290. stack = []
  291. toc.each do |level, id, children|
  292. li = Element.new(:li, nil, nil, {:level => level})
  293. li.children << Element.new(:p, nil, nil, {:transparent => true})
  294. a = Element.new(:a, nil, {'href' => "##{id}"})
  295. a.children += children
  296. li.children.last.children << a
  297. li.children << Element.new(type)
  298. success = false
  299. while !success
  300. if stack.empty?
  301. sections.children << li
  302. stack << li
  303. success = true
  304. elsif stack.last.options[:level] < li.options[:level]
  305. stack.last.children.last.children << li
  306. stack << li
  307. success = true
  308. else
  309. item = stack.pop
  310. item.children.pop unless item.children.last.children.size > 0
  311. end
  312. end
  313. end
  314. while !stack.empty?
  315. item = stack.pop
  316. item.children.pop unless item.children.last.children.size > 0
  317. end
  318. sections
  319. end
  320. # Helper method for obfuscating the +text+ by using HTML entities.
  321. def obfuscate(text)
  322. result = ""
  323. text.each_byte do |b|
  324. result += (b > 128 ? b.chr : "&#%03d;" % b)
  325. end
  326. result.force_encoding(text.encoding) if RUBY_VERSION >= '1.9'
  327. result
  328. end
  329. # Return a HTML list with the footnote content for the used footnotes.
  330. def footnote_content
  331. ol = Element.new(:ol)
  332. ol.attr['start'] = @footnote_start if @footnote_start != 1
  333. @footnotes.each do |name, data|
  334. li = Element.new(:li, nil, {'id' => "fn:#{name}"}, {:first_is_block => true})
  335. li.children = Marshal.load(Marshal.dump(data[:content].children))
  336. ol.children << li
  337. ref = Element.new(:raw, "<a href=\"#fnref:#{name}\" rev=\"footnote\">&#8617;</a>")
  338. if li.children.last.type == :p
  339. para = li.children.last
  340. else
  341. li.children << (para = Element.new(:p))
  342. end
  343. para.children << ref
  344. end
  345. (ol.children.empty? ? '' : "<div class=\"footnotes\">\n#{convert(ol, 2)}</div>\n")
  346. end
  347. end
  348. end
  349. end