PageRenderTime 67ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/asciidoctor/converter/html5.rb

https://github.com/aslakknutsen/asciidoctor
Ruby | 1058 lines | 926 code | 92 blank | 40 comment | 149 complexity | f5f4ad03aadc8313e0311ebce95c110c MD5 | raw file
Possible License(s): MIT
  1. module Asciidoctor
  2. # A built-in {Converter} implementation that generates HTML 5 output
  3. # consistent with the html5 backend from AsciiDoc Python.
  4. class Converter::Html5Converter < Converter::BuiltIn
  5. QUOTE_TAGS = {
  6. :emphasis => ['<em>', '</em>', true],
  7. :strong => ['<strong>', '</strong>', true],
  8. :monospaced => ['<code>', '</code>', true],
  9. :superscript => ['<sup>', '</sup>', true],
  10. :subscript => ['<sub>', '</sub>', true],
  11. :double => ['&#8220;', '&#8221;', false],
  12. :single => ['&#8216;', '&#8217;', false],
  13. :asciimath => ['\\$', '\\$', false],
  14. :latexmath => ['\\(', '\\)', false]
  15. # Opal can't resolve these constants when referenced here
  16. #:asciimath => INLINE_MATH_DELIMITERS[:asciimath] + [false],
  17. #:latexmath => INLINE_MATH_DELIMITERS[:latexmath] + [false]
  18. }
  19. QUOTE_TAGS.default = [nil, nil, nil]
  20. def initialize backend, opts = {}
  21. @xml_mode = opts[:htmlsyntax] == 'xml'
  22. @void_element_slash = @xml_mode ? '/' : nil
  23. @stylesheets = Stylesheets.instance
  24. end
  25. def document node
  26. result = []
  27. slash = @void_element_slash
  28. br = %(<br#{slash}>)
  29. linkcss = node.safe >= SafeMode::SECURE || (node.attr? 'linkcss')
  30. result << '<!DOCTYPE html>'
  31. lang_attribute = (node.attr? 'nolang') ? nil : %( lang="#{node.attr 'lang', 'en'}")
  32. result << %(<html#{@xml_mode ? ' xmlns="http://www.w3.org/1999/xhtml"' : nil}#{lang_attribute}>)
  33. result << %(<head>
  34. <meta charset="#{node.attr 'encoding', 'UTF-8'}"#{slash}>
  35. <!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"#{slash}><![endif]-->
  36. <meta name="viewport" content="width=device-width, initial-scale=1.0"#{slash}>
  37. <meta name="generator" content="Asciidoctor #{node.attr 'asciidoctor-version'}"#{slash}>)
  38. result << %(<meta name="application-name" content="#{node.attr 'app-name'}"#{slash}>) if node.attr? 'app-name'
  39. result << %(<meta name="description" content="#{node.attr 'description'}"#{slash}>) if node.attr? 'description'
  40. result << %(<meta name="keywords" content="#{node.attr 'keywords'}"#{slash}>) if node.attr? 'keywords'
  41. result << %(<meta name="author" content="#{node.attr 'authors'}"#{slash}>) if node.attr? 'authors'
  42. result << %(<meta name="copyright" content="#{node.attr 'copyright'}"#{slash}>) if node.attr? 'copyright'
  43. result << %(<title>#{node.doctitle(:sanitize => true) || node.attr('untitled-label')}</title>)
  44. if DEFAULT_STYLESHEET_KEYS.include?(node.attr 'stylesheet')
  45. if linkcss
  46. result << %(<link rel="stylesheet" href="#{node.normalize_web_path DEFAULT_STYLESHEET_NAME, (node.attr 'stylesdir', '')}"#{slash}>)
  47. else
  48. result << @stylesheets.embed_primary_stylesheet
  49. end
  50. elsif node.attr? 'stylesheet'
  51. if linkcss
  52. result << %(<link rel="stylesheet" href="#{node.normalize_web_path((node.attr 'stylesheet'), (node.attr 'stylesdir', ''))}"#{slash}>)
  53. else
  54. result << %(<style>
  55. #{node.read_asset node.normalize_system_path((node.attr 'stylesheet'), (node.attr 'stylesdir', '')), true}
  56. </style>)
  57. end
  58. end
  59. if node.attr? 'icons', 'font'
  60. if node.attr? 'iconfont-remote'
  61. result << %(<link rel="stylesheet" href="#{node.attr 'iconfont-cdn', 'http://cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.min.css'}"#{slash}>)
  62. else
  63. iconfont_stylesheet = %(#{node.attr 'iconfont-name', 'font-awesome'}.css)
  64. result << %(<link rel="stylesheet" href="#{node.normalize_web_path iconfont_stylesheet, (node.attr 'stylesdir', '')}"#{slash}>)
  65. end
  66. end
  67. case node.attr 'source-highlighter'
  68. when 'coderay'
  69. if (node.attr 'coderay-css', 'class') == 'class'
  70. if linkcss
  71. result << %(<link rel="stylesheet" href="#{node.normalize_web_path @stylesheets.coderay_stylesheet_name, (node.attr 'stylesdir', '')}"#{slash}>)
  72. else
  73. result << @stylesheets.embed_coderay_stylesheet
  74. end
  75. end
  76. when 'pygments'
  77. if (node.attr 'pygments-css', 'class') == 'class'
  78. pygments_style = (node.attr 'pygments-style', 'pastie')
  79. if linkcss
  80. result << %(<link rel="stylesheet" href="#{node.normalize_web_path @stylesheets.pygments_stylesheet_name(pygments_style), (node.attr 'stylesdir', '')}"#{slash}>)
  81. else
  82. result << (@stylesheets.embed_pygments_stylesheet pygments_style)
  83. end
  84. end
  85. when 'highlightjs', 'highlight.js'
  86. result << %(<link rel="stylesheet" href="#{node.attr 'highlightjsdir', 'http://cdnjs.cloudflare.com/ajax/libs/highlight.js/7.4'}/styles/#{node.attr 'highlightjs-theme', 'googlecode'}.min.css"#{slash}>
  87. <script src="#{node.attr 'highlightjsdir', 'http://cdnjs.cloudflare.com/ajax/libs/highlight.js/7.4'}/highlight.min.js"></script>
  88. <script src="#{node.attr 'highlightjsdir', 'http://cdnjs.cloudflare.com/ajax/libs/highlight.js/7.4'}/lang/common.min.js"></script>
  89. <script>hljs.initHighlightingOnLoad()</script>)
  90. when 'prettify'
  91. result << %(<link rel="stylesheet" href="#{node.attr 'prettifydir', 'http://cdnjs.cloudflare.com/ajax/libs/prettify/r298'}/#{node.attr 'prettify-theme', 'prettify'}.min.css"#{slash}>
  92. <script src="#{node.attr 'prettifydir', 'http://cdnjs.cloudflare.com/ajax/libs/prettify/r298'}/prettify.min.js"></script>
  93. <script>document.addEventListener('DOMContentLoaded', prettyPrint)</script>)
  94. end
  95. if node.attr? 'math'
  96. result << %(<script type="text/x-mathjax-config">
  97. MathJax.Hub.Config({
  98. tex2jax: {
  99. inlineMath: [#{INLINE_MATH_DELIMITERS[:latexmath]}],
  100. displayMath: [#{BLOCK_MATH_DELIMITERS[:latexmath]}],
  101. ignoreClass: "nomath|nolatexmath"
  102. },
  103. asciimath2jax: {
  104. delimiters: [#{BLOCK_MATH_DELIMITERS[:asciimath]}],
  105. ignoreClass: "nomath|noasciimath"
  106. }
  107. });
  108. </script>
  109. <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>
  110. <script>document.addEventListener('DOMContentLoaded', MathJax.Hub.TypeSet)</script>)
  111. end
  112. unless (docinfo_content = node.docinfo).empty?
  113. result << docinfo_content
  114. end
  115. result << '</head>'
  116. body_attrs = []
  117. if node.id
  118. body_attrs << %(id="#{node.id}")
  119. end
  120. if (node.attr? 'toc-class') && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
  121. body_attrs << %(class="#{node.doctype} #{node.attr 'toc-class'} toc-#{node.attr 'toc-position', 'left'}")
  122. else
  123. body_attrs << %(class="#{node.doctype}")
  124. end
  125. if node.attr? 'max-width'
  126. body_attrs << %(style="max-width: #{node.attr 'max-width'};")
  127. end
  128. result << %(<body #{body_attrs * ' '}>)
  129. unless node.noheader
  130. result << '<div id="header">'
  131. if node.doctype == 'manpage'
  132. result << %(<h1>#{node.doctitle} Manual Page</h1>)
  133. if (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
  134. result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
  135. <div id="toctitle">#{node.attr 'toc-title'}</div>
  136. #{outline node}
  137. </div>)
  138. end
  139. result << %(<h2>#{node.attr 'manname-title'}</h2>
  140. <div class="sectionbody">
  141. <p>#{node.attr 'manname'} - #{node.attr 'manpurpose'}</p>
  142. </div>)
  143. else
  144. if node.has_header?
  145. result << %(<h1>#{node.header.title}</h1>) unless node.notitle
  146. if node.attr? 'author'
  147. result << %(<span id="author" class="author">#{node.attr 'author'}</span>#{br})
  148. if node.attr? 'email'
  149. result << %(<span id="email" class="email">#{node.sub_macros(node.attr 'email')}</span>#{br})
  150. end
  151. if (authorcount = (node.attr 'authorcount').to_i) > 1
  152. (2..authorcount).each do |idx|
  153. result << %(<span id="author#{idx}" class="author">#{node.attr "author_#{idx}"}</span>#{br})
  154. if node.attr? %(email_#{idx})
  155. result << %(<span id="email#{idx}" class="email">#{node.sub_macros(node.attr "email_#{idx}")}</span>#{br})
  156. end
  157. end
  158. end
  159. end
  160. if node.attr? 'revnumber'
  161. result << %(<span id="revnumber">#{((node.attr 'version-label') || '').downcase} #{node.attr 'revnumber'}#{(node.attr? 'revdate') ? ',' : ''}</span>)
  162. end
  163. if node.attr? 'revdate'
  164. result << %(<span id="revdate">#{node.attr 'revdate'}</span>)
  165. end
  166. if node.attr? 'revremark'
  167. result << %(#{br}<span id="revremark">#{node.attr 'revremark'}</span>)
  168. end
  169. end
  170. if (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
  171. result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
  172. <div id="toctitle">#{node.attr 'toc-title'}</div>
  173. #{outline node}
  174. </div>)
  175. end
  176. end
  177. result << '</div>'
  178. end
  179. result << %(<div id="content">
  180. #{node.content}
  181. </div>)
  182. if node.footnotes? && !(node.attr? 'nofootnotes')
  183. result << %(<div id="footnotes">
  184. <hr#{slash}>)
  185. node.footnotes.each do |footnote|
  186. result << %(<div class="footnote" id="_footnote_#{footnote.index}">
  187. <a href="#_footnoteref_#{footnote.index}">#{footnote.index}</a>. #{footnote.text}
  188. </div>)
  189. end
  190. result << '</div>'
  191. end
  192. unless node.nofooter
  193. result << '<div id="footer">'
  194. result << '<div id="footer-text">'
  195. if node.attr? 'revnumber'
  196. result << %(#{node.attr 'version-label'} #{node.attr 'revnumber'}#{br})
  197. end
  198. if node.attr? 'last-update-label'
  199. result << %(#{node.attr 'last-update-label'} #{node.attr 'docdatetime'})
  200. end
  201. result << '</div>'
  202. unless (docinfo_content = node.docinfo :footer).empty?
  203. result << docinfo_content
  204. end
  205. result << '</div>'
  206. end
  207. result << '</body>'
  208. result << '</html>'
  209. result * EOL
  210. end
  211. def embedded node
  212. result = []
  213. if !node.notitle && node.has_header?
  214. id_attr = node.id ? %( id="#{node.id}") : nil
  215. result << %(<h1#{id_attr}>#{node.header.title}</h1>)
  216. end
  217. result << node.content
  218. if node.footnotes? && !(node.attr? 'nofootnotes')
  219. result << %(<div id="footnotes">
  220. <hr#{@void_element_slash}>)
  221. node.footnotes.each do |footnote|
  222. result << %(<div class="footnote" id="_footnote_#{footnote.index}">
  223. <a href="#_footnoteref_#{footnote.index}">#{footnote.index}</a> #{footnote.text}
  224. </div>)
  225. end
  226. result << '</div>'
  227. end
  228. result * EOL
  229. end
  230. def outline node, opts = {}
  231. return if (sections = node.sections).empty?
  232. sectnumlevels = opts[:sectnumlevels] || (node.document.attr 'sectnumlevels', 3).to_i
  233. toclevels = opts[:toclevels] || (node.document.attr 'toclevels', 2).to_i
  234. result = []
  235. # FIXME the level for special sections should be set correctly in the model
  236. # slevel will only be 0 if we have a book doctype with parts
  237. slevel = (first_section = sections[0]).level
  238. slevel = 1 if slevel == 0 && first_section.special
  239. result << %(<ul class="sectlevel#{slevel}">)
  240. sections.each do |section|
  241. section_num = (section.numbered && !section.caption && section.level <= sectnumlevels) ? %(#{section.sectnum} ) : nil
  242. result << %(<li><a href="##{section.id}">#{section_num}#{section.captioned_title}</a></li>)
  243. if section.level < toclevels && (child_toc_level = outline section, :toclevels => toclevels, :secnumlevels => sectnumlevels)
  244. result << '<li>'
  245. result << child_toc_level
  246. result << '</li>'
  247. end
  248. end
  249. result << '</ul>'
  250. result * EOL
  251. end
  252. def section node
  253. slevel = node.level
  254. # QUESTION should the check for slevel be done in section?
  255. slevel = 1 if slevel == 0 && node.special
  256. htag = %(h#{slevel + 1})
  257. id_attr = anchor = link_start = link_end = nil
  258. if node.id
  259. id_attr = %( id="#{node.id}")
  260. if node.document.attr? 'sectanchors'
  261. anchor = %(<a class="anchor" href="##{node.id}"></a>)
  262. # possible idea - anchor icons GitHub-style
  263. #if node.document.attr? 'icons', 'font'
  264. # anchor = %(<a class="anchor" href="##{node.id}"><i class="icon-anchor"></i></a>)
  265. #else
  266. elsif node.document.attr? 'sectlinks'
  267. link_start = %(<a class="link" href="##{node.id}">)
  268. link_end = '</a>'
  269. end
  270. end
  271. if slevel == 0
  272. %(<h1#{id_attr} class="sect0">#{anchor}#{link_start}#{node.title}#{link_end}</h1>
  273. #{node.content})
  274. else
  275. class_attr = (role = node.role) ? %( class="sect#{slevel} #{role}") : %( class="sect#{slevel}")
  276. sectnum = if node.numbered && !node.caption && slevel <= (node.document.attr 'sectnumlevels', 3).to_i
  277. %(#{node.sectnum} )
  278. end
  279. %(<div#{class_attr}>
  280. <#{htag}#{id_attr}>#{anchor}#{link_start}#{sectnum}#{node.captioned_title}#{link_end}</#{htag}>
  281. #{slevel == 1 ? %[<div class="sectionbody">\n#{node.content}\n</div>] : node.content}
  282. </div>)
  283. end
  284. end
  285. def admonition node
  286. id_attr = node.id ? %( id="#{node.id}") : nil
  287. name = node.attr 'name'
  288. title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
  289. caption = if node.document.attr? 'icons'
  290. if node.document.attr? 'icons', 'font'
  291. %(<i class="icon-#{name}" title="#{node.caption}"></i>)
  292. else
  293. %(<img src="#{node.icon_uri name}" alt="#{node.caption}"#{@void_element_slash}>)
  294. end
  295. else
  296. %(<div class="title">#{node.caption}</div>)
  297. end
  298. %(<div#{id_attr} class="admonitionblock #{name}#{(role = node.role) && " #{role}"}">
  299. <table>
  300. <tr>
  301. <td class="icon">
  302. #{caption}
  303. </td>
  304. <td class="content">
  305. #{title_element}#{node.content}
  306. </td>
  307. </tr>
  308. </table>
  309. </div>)
  310. end
  311. def audio node
  312. xml = node.document.attr? 'htmlsyntax', 'xml'
  313. id_attribute = node.id ? %( id="#{node.id}") : nil
  314. classes = ['audioblock', node.style, node.role].compact
  315. class_attribute = %( class="#{classes * ' '}")
  316. title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : nil
  317. %(<div#{id_attribute}#{class_attribute}>
  318. #{title_element}<div class="content">
  319. <audio src="#{node.media_uri(node.attr 'target')}"#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : nil}#{(node.option? 'nocontrols') ? nil : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : nil}>
  320. Your browser does not support the audio tag.
  321. </audio>
  322. </div>
  323. </div>)
  324. end
  325. def colist node
  326. result = []
  327. id_attribute = node.id ? %( id="#{node.id}") : nil
  328. classes = ['colist', node.style, node.role].compact
  329. class_attribute = %( class="#{classes * ' '}")
  330. result << %(<div#{id_attribute}#{class_attribute}>)
  331. result << %(<div class="title">#{node.title}</div>) if node.title?
  332. if node.document.attr? 'icons'
  333. result << '<table>'
  334. font_icons = node.document.attr? 'icons', 'font'
  335. node.items.each_with_index do |item, i|
  336. num = i + 1
  337. num_element = if font_icons
  338. %(<i class="conum" data-value="#{num}"></i><b>#{num}</b>)
  339. else
  340. %(<img src="#{node.icon_uri "callouts/#{num}"}" alt="#{num}"#{@void_element_slash}>)
  341. end
  342. result << %(<tr>
  343. <td>#{num_element}</td>
  344. <td>#{item.text}</td>
  345. </tr>)
  346. end
  347. result << '</table>'
  348. else
  349. result << '<ol>'
  350. node.items.each do |item|
  351. result << %(<li>
  352. <p>#{item.text}</p>
  353. </li>)
  354. end
  355. result << '</ol>'
  356. end
  357. result << '</div>'
  358. result * EOL
  359. end
  360. def dlist node
  361. result = []
  362. id_attribute = node.id ? %( id="#{node.id}") : nil
  363. classes = case node.style
  364. when 'qanda'
  365. ['qlist', 'qanda', node.role]
  366. when 'horizontal'
  367. ['hdlist', node.role]
  368. else
  369. ['dlist', node.style, node.role]
  370. end.compact
  371. class_attribute = %( class="#{classes * ' '}")
  372. result << %(<div#{id_attribute}#{class_attribute}>)
  373. result << %(<div class="title">#{node.title}</div>) if node.title?
  374. case node.style
  375. when 'qanda'
  376. result << '<ol>'
  377. node.items.each do |terms, dd|
  378. result << '<li>'
  379. [*terms].each do |dt|
  380. result << %(<p><em>#{dt.text}</em></p>)
  381. end
  382. if dd
  383. result << %(<p>#{dd.text}</p>) if dd.text?
  384. result << dd.content if dd.blocks?
  385. end
  386. result << '</li>'
  387. end
  388. result << '</ol>'
  389. when 'horizontal'
  390. slash = @void_element_slash
  391. result << '<table>'
  392. if (node.attr? 'labelwidth') || (node.attr? 'itemwidth')
  393. result << '<colgroup>'
  394. col_style_attribute = (node.attr? 'labelwidth') ? %( style="width: #{(node.attr 'labelwidth').chomp '%'}%;") : nil
  395. result << %(<col#{col_style_attribute}#{slash}>)
  396. col_style_attribute = (node.attr? 'itemwidth') ? %( style="width: #{(node.attr 'itemwidth').chomp '%'}%;") : nil
  397. result << %(<col#{col_style_attribute}#{slash}>)
  398. result << '</colgroup>'
  399. end
  400. node.items.each do |terms, dd|
  401. result << '<tr>'
  402. result << %(<td class="hdlist1#{(node.option? 'strong') ? ' strong' : nil}">)
  403. terms_array = [*terms]
  404. last_term = terms_array[-1]
  405. terms_array.each do |dt|
  406. result << dt.text
  407. result << %(<br#{slash}>) if dt != last_term
  408. end
  409. result << '</td>'
  410. result << '<td class="hdlist2">'
  411. if dd
  412. result << %(<p>#{dd.text}</p>) if dd.text?
  413. result << dd.content if dd.blocks?
  414. end
  415. result << '</td>'
  416. result << '</tr>'
  417. end
  418. result << '</table>'
  419. else
  420. result << '<dl>'
  421. dt_style_attribute = node.style ? nil : ' class="hdlist1"'
  422. node.items.each do |terms, dd|
  423. [*terms].each do |dt|
  424. result << %(<dt#{dt_style_attribute}>#{dt.text}</dt>)
  425. end
  426. if dd
  427. result << '<dd>'
  428. result << %(<p>#{dd.text}</p>) if dd.text?
  429. result << dd.content if dd.blocks?
  430. result << '</dd>'
  431. end
  432. end
  433. result << '</dl>'
  434. end
  435. result << '</div>'
  436. result * EOL
  437. end
  438. def example node
  439. id_attribute = node.id ? %( id="#{node.id}") : nil
  440. title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : nil
  441. %(<div#{id_attribute} class="#{(role = node.role) ? ['exampleblock', role] * ' ' : 'exampleblock'}">
  442. #{title_element}<div class="content">
  443. #{node.content}
  444. </div>
  445. </div>)
  446. end
  447. def floating_title node
  448. tag_name = %(h#{node.level + 1})
  449. id_attribute = node.id ? %( id="#{node.id}") : nil
  450. classes = [node.style, node.role].compact
  451. %(<#{tag_name}#{id_attribute} class="#{classes * ' '}">#{node.title}</#{tag_name}>)
  452. end
  453. def image node
  454. align = (node.attr? 'align') ? (node.attr 'align') : nil
  455. float = (node.attr? 'float') ? (node.attr 'float') : nil
  456. style_attribute = if align || float
  457. styles = [align ? %(text-align: #{align}) : nil, float ? %(float: #{float}) : nil].compact
  458. %( style="#{styles * ';'}")
  459. end
  460. width_attribute = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : nil
  461. height_attribute = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : nil
  462. img_element = %(<img src="#{node.image_uri node.attr('target')}" alt="#{node.attr 'alt'}"#{width_attribute}#{height_attribute}#{@void_element_slash}>)
  463. if (link = node.attr 'link')
  464. img_element = %(<a class="image" href="#{link}">#{img_element}</a>)
  465. end
  466. id_attribute = node.id ? %( id="#{node.id}") : nil
  467. classes = ['imageblock', node.style, node.role].compact
  468. class_attribute = %( class="#{classes * ' '}")
  469. title_element = node.title? ? %(\n<div class="title">#{node.captioned_title}</div>) : nil
  470. %(<div#{id_attribute}#{class_attribute}#{style_attribute}>
  471. <div class="content">
  472. #{img_element}
  473. </div>#{title_element}
  474. </div>)
  475. end
  476. def listing node
  477. nowrap = !(node.document.attr? 'prewrap') || (node.option? 'nowrap')
  478. if node.style == 'source'
  479. language = node.attr 'language'
  480. language_classes = language ? %(#{language} language-#{language}) : nil
  481. case node.attr 'source-highlighter'
  482. when 'coderay'
  483. pre_class = nowrap ? ' class="CodeRay nowrap"' : ' class="CodeRay"'
  484. code_class = language ? %( class="#{language_classes}") : nil
  485. when 'pygments'
  486. pre_class = nowrap ? ' class="pygments highlight nowrap"' : ' class="pygments highlight"'
  487. code_class = language ? %( class="#{language_classes}") : nil
  488. when 'highlightjs', 'highlight.js'
  489. pre_class = nowrap ? ' class="highlight nowrap"' : ' class="highlight"'
  490. code_class = language ? %( class="#{language_classes}") : nil
  491. when 'prettify'
  492. pre_class = %( class="prettyprint#{nowrap ? ' nowrap' : nil}#{(node.attr? 'linenums') ? ' linenums' : nil}")
  493. code_class = language ? %( class="#{language_classes}") : nil
  494. when 'html-pipeline'
  495. pre_class = language ? %( lang="#{language}") : nil
  496. code_class = nil
  497. else
  498. pre_class = nowrap ? ' class="highlight nowrap"' : ' class="highlight"'
  499. code_class = language ? %( class="#{language_classes}") : nil
  500. end
  501. pre_start = %(<pre#{pre_class}><code#{code_class}>)
  502. pre_end = '</code></pre>'
  503. else
  504. pre_start = %(<pre#{nowrap ? ' class="nowrap"' : nil}>)
  505. pre_end = '</pre>'
  506. end
  507. id_attribute = node.id ? %( id="#{node.id}") : nil
  508. title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : nil
  509. %(<div#{id_attribute} class="listingblock#{(role = node.role) && " #{role}"}">
  510. #{title_element}<div class="content">
  511. #{pre_start}#{node.content}#{pre_end}
  512. </div>
  513. </div>)
  514. end
  515. def literal node
  516. id_attribute = node.id ? %( id="#{node.id}") : nil
  517. title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
  518. nowrap = !(node.document.attr? 'prewrap') || (node.option? 'nowrap')
  519. %(<div#{id_attribute} class="literalblock#{(role = node.role) && " #{role}"}">
  520. #{title_element}<div class="content">
  521. <pre#{nowrap ? ' class="nowrap"' : nil}>#{node.content}</pre>
  522. </div>
  523. </div>)
  524. end
  525. def math node
  526. id_attribute = node.id ? %( id="#{node.id}") : nil
  527. title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
  528. open, close = BLOCK_MATH_DELIMITERS[node.style.to_sym]
  529. # QUESTION should the content be stripped already?
  530. equation = node.content.strip
  531. if node.subs.nil_or_empty? && !(node.attr? 'subs')
  532. equation = node.sub_specialcharacters equation
  533. end
  534. unless (equation.start_with? open) && (equation.end_with? close)
  535. equation = %(#{open}#{equation}#{close})
  536. end
  537. %(<div#{id_attribute} class="#{(role = node.role) ? ['mathblock', role] * ' ' : 'mathblock'}">
  538. #{title_element}<div class="content">
  539. #{equation}
  540. </div>
  541. </div>)
  542. end
  543. def olist node
  544. result = []
  545. id_attribute = node.id ? %( id="#{node.id}") : nil
  546. classes = ['olist', node.style, node.role].compact
  547. class_attribute = %( class="#{classes * ' '}")
  548. result << %(<div#{id_attribute}#{class_attribute}>)
  549. result << %(<div class="title">#{node.title}</div>) if node.title?
  550. type_attribute = (keyword = node.list_marker_keyword) ? %( type="#{keyword}") : nil
  551. start_attribute = (node.attr? 'start') ? %( start="#{node.attr 'start'}") : nil
  552. result << %(<ol class="#{node.style}"#{type_attribute}#{start_attribute}>)
  553. node.items.each do |item|
  554. result << '<li>'
  555. result << %(<p>#{item.text}</p>)
  556. result << item.content if item.blocks?
  557. result << '</li>'
  558. end
  559. result << '</ol>'
  560. result << '</div>'
  561. result * EOL
  562. end
  563. def open node
  564. if (style = node.style) == 'abstract'
  565. if node.parent == node.document && node.document.doctype == 'book'
  566. warn 'asciidoctor: WARNING: abstract block cannot be used in a document without a title when doctype is book. Excluding block content.'
  567. ''
  568. else
  569. id_attr = node.id ? %( id="#{node.id}") : nil
  570. title_el = node.title? ? %(<div class="title">#{node.title}</div>) : nil
  571. %(<div#{id_attr} class="quoteblock abstract#{(role = node.role) && " #{role}"}">
  572. #{title_el}<blockquote>
  573. #{node.content}
  574. </blockquote>
  575. </div>)
  576. end
  577. elsif style == 'partintro' && (node.level != 0 || node.parent.context != :section || node.document.doctype != 'book')
  578. warn 'asciidoctor: ERROR: partintro block can only be used when doctype is book and it\'s a child of a book part. Excluding block content.'
  579. ''
  580. else
  581. id_attr = node.id ? %( id="#{node.id}") : nil
  582. title_el = node.title? ? %(<div class="title">#{node.title}</div>) : nil
  583. %(<div#{id_attr} class="openblock#{style && style != 'open' ? " #{style}" : ''}#{(role = node.role) && " #{role}"}">
  584. #{title_el}<div class="content">
  585. #{node.content}
  586. </div>
  587. </div>)
  588. end
  589. end
  590. def page_break node
  591. '<div style="page-break-after: always;"></div>'
  592. end
  593. def paragraph node
  594. attributes = if node.id
  595. if node.role
  596. %( id="#{node.id}" class="paragraph #{node.role}")
  597. else
  598. %( id="#{node.id}" class="paragraph")
  599. end
  600. elsif node.role
  601. %( class="paragraph #{node.role}")
  602. else
  603. ' class="paragraph"'
  604. end
  605. if node.title?
  606. %(<div#{attributes}>
  607. <div class="title">#{node.title}</div>
  608. <p>#{node.content}</p>
  609. </div>)
  610. else
  611. %(<div#{attributes}>
  612. <p>#{node.content}</p>
  613. </div>)
  614. end
  615. end
  616. def preamble node
  617. toc = if (node.attr? 'toc') && (node.attr? 'toc-placement', 'preamble')
  618. %(\n<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
  619. <div id="toctitle">#{node.attr 'toc-title'}</div>
  620. #{outline node.document}
  621. </div>)
  622. end
  623. %(<div id="preamble">
  624. <div class="sectionbody">
  625. #{node.content}
  626. </div>#{toc}
  627. </div>)
  628. end
  629. def quote node
  630. id_attribute = node.id ? %( id="#{node.id}") : nil
  631. classes = ['quoteblock', node.role].compact
  632. class_attribute = %( class="#{classes * ' '}")
  633. title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : nil
  634. attribution = (node.attr? 'attribution') ? (node.attr 'attribution') : nil
  635. citetitle = (node.attr? 'citetitle') ? (node.attr 'citetitle') : nil
  636. if attribution || citetitle
  637. cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : nil
  638. attribution_text = attribution ? %(#{citetitle ? "<br#{@void_element_slash}>\n" : nil}&#8212; #{attribution}) : nil
  639. attribution_element = %(\n<div class="attribution">\n#{cite_element}#{attribution_text}\n</div>)
  640. else
  641. attribution_element = nil
  642. end
  643. %(<div#{id_attribute}#{class_attribute}>#{title_element}
  644. <blockquote>
  645. #{node.content}
  646. </blockquote>#{attribution_element}
  647. </div>)
  648. end
  649. def thematic_break node
  650. %(<hr#{@void_element_slash}>)
  651. end
  652. def sidebar node
  653. id_attribute = node.id ? %( id="#{node.id}") : nil
  654. title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
  655. %(<div#{id_attribute} class="#{(role = node.role) ? ['sidebarblock', role] * ' ' : 'sidebarblock'}">
  656. <div class="content">
  657. #{title_element}#{node.content}
  658. </div>
  659. </div>)
  660. end
  661. def table node
  662. result = []
  663. id_attribute = node.id ? %( id="#{node.id}") : nil
  664. classes = ['tableblock', %(frame-#{node.attr 'frame', 'all'}), %(grid-#{node.attr 'grid', 'all'})]
  665. if (role_class = node.role)
  666. classes << role_class
  667. end
  668. class_attribute = %( class="#{classes * ' '}")
  669. styles = [(node.option? 'autowidth') ? nil : %(width: #{node.attr 'tablepcwidth'}%;), (node.attr? 'float') ? %(float: #{node.attr 'float'};) : nil].compact
  670. style_attribute = styles.size > 0 ? %( style="#{styles * ' '}") : nil
  671. result << %(<table#{id_attribute}#{class_attribute}#{style_attribute}>)
  672. result << %(<caption class="title">#{node.captioned_title}</caption>) if node.title?
  673. if (node.attr 'rowcount') > 0
  674. slash = @void_element_slash
  675. result << '<colgroup>'
  676. if node.option? 'autowidth'
  677. tag = %(<col#{slash}>)
  678. node.columns.size.times do
  679. result << tag
  680. end
  681. else
  682. node.columns.each do |col|
  683. result << %(<col style="width: #{col.attr 'colpcwidth'}%;"#{slash}>)
  684. end
  685. end
  686. result << '</colgroup>'
  687. [:head, :foot, :body].select {|tsec| !node.rows[tsec].empty? }.each do |tsec|
  688. result << %(<t#{tsec}>)
  689. node.rows[tsec].each do |row|
  690. result << '<tr>'
  691. row.each do |cell|
  692. if tsec == :head
  693. cell_content = cell.text
  694. else
  695. case cell.style
  696. when :asciidoc
  697. cell_content = %(<div>#{cell.content}</div>)
  698. when :verse
  699. cell_content = %(<div class="verse">#{cell.text}</div>)
  700. when :literal
  701. cell_content = %(<div class="literal"><pre>#{cell.text}</pre></div>)
  702. else
  703. cell_content = ''
  704. cell.content.each do |text|
  705. cell_content = %(#{cell_content}<p class="tableblock">#{text}</p>)
  706. end
  707. end
  708. end
  709. cell_tag_name = (tsec == :head || cell.style == :header ? 'th' : 'td')
  710. cell_class_attribute = %( class="tableblock halign-#{cell.attr 'halign'} valign-#{cell.attr 'valign'}")
  711. cell_colspan_attribute = cell.colspan ? %( colspan="#{cell.colspan}") : nil
  712. cell_rowspan_attribute = cell.rowspan ? %( rowspan="#{cell.rowspan}") : nil
  713. cell_style_attribute = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'};") : nil
  714. result << %(<#{cell_tag_name}#{cell_class_attribute}#{cell_colspan_attribute}#{cell_rowspan_attribute}#{cell_style_attribute}>#{cell_content}</#{cell_tag_name}>)
  715. end
  716. result << '</tr>'
  717. end
  718. result << %(</t#{tsec}>)
  719. end
  720. end
  721. result << '</table>'
  722. result * EOL
  723. end
  724. def toc node
  725. return '<!-- toc disabled -->' unless (doc = node.document).attr? 'toc'
  726. if node.id
  727. id_attr = %( id="#{node.id}")
  728. title_id_attr = ''
  729. elsif doc.embedded? || !(doc.attr? 'toc-placement')
  730. id_attr = ' id="toc"'
  731. title_id_attr = ' id="toctitle"'
  732. else
  733. id_attr = nil
  734. title_id_attr = nil
  735. end
  736. title = node.title? ? node.title : (doc.attr 'toc-title')
  737. levels = (node.attr? 'levels') ? (node.attr 'levels').to_i : nil
  738. role = node.role? ? node.role : (doc.attr 'toc-class', 'toc')
  739. %(<div#{id_attr} class="#{role}">
  740. <div#{title_id_attr} class="title">#{title}</div>
  741. #{outline doc, :toclevels => levels}
  742. </div>)
  743. end
  744. def ulist node
  745. result = []
  746. id_attribute = node.id ? %( id="#{node.id}") : nil
  747. div_classes = ['ulist', node.style, node.role].compact
  748. marker_checked = nil
  749. marker_unchecked = nil
  750. if (checklist = node.option? 'checklist')
  751. div_classes.insert 1, 'checklist'
  752. ul_class_attribute = ' class="checklist"'
  753. if node.option? 'interactive'
  754. if node.document.attr? 'htmlsyntax', 'xml'
  755. marker_checked = '<input type="checkbox" data-item-complete="1" checked="checked"/> '
  756. marker_unchecked = '<input type="checkbox" data-item-complete="0"/> '
  757. else
  758. marker_checked = '<input type="checkbox" data-item-complete="1" checked> '
  759. marker_unchecked = '<input type="checkbox" data-item-complete="0"> '
  760. end
  761. else
  762. if node.document.attr? 'icons', 'font'
  763. marker_checked = '<i class="icon-check"></i> '
  764. marker_unchecked = '<i class="icon-check-empty"></i> '
  765. else
  766. marker_checked = '&#10003; '
  767. marker_unchecked = '&#10063; '
  768. end
  769. end
  770. else
  771. ul_class_attribute = node.style ? %( class="#{node.style}") : nil
  772. end
  773. result << %(<div#{id_attribute} class="#{div_classes * ' '}">)
  774. result << %(<div class="title">#{node.title}</div>) if node.title?
  775. result << %(<ul#{ul_class_attribute}>)
  776. node.items.each do |item|
  777. result << '<li>'
  778. if checklist && (item.attr? 'checkbox')
  779. result << %(<p>#{(item.attr? 'checked') ? marker_checked : marker_unchecked}#{item.text}</p>)
  780. else
  781. result << %(<p>#{item.text}</p>)
  782. end
  783. result << item.content if item.blocks?
  784. result << '</li>'
  785. end
  786. result << '</ul>'
  787. result << '</div>'
  788. result * EOL
  789. end
  790. def verse node
  791. id_attribute = node.id ? %( id="#{node.id}") : nil
  792. classes = ['verseblock', node.role].compact
  793. class_attribute = %( class="#{classes * ' '}")
  794. title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : nil
  795. attribution = (node.attr? 'attribution') ? (node.attr 'attribution') : nil
  796. citetitle = (node.attr? 'citetitle') ? (node.attr 'citetitle') : nil
  797. if attribution || citetitle
  798. cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : nil
  799. attribution_text = attribution ? %(#{citetitle ? "<br#{@void_element_slash}>\n" : nil}&#8212; #{attribution}) : nil
  800. attribution_element = %(\n<div class="attribution">\n#{cite_element}#{attribution_text}\n</div>)
  801. else
  802. attribution_element = nil
  803. end
  804. %(<div#{id_attribute}#{class_attribute}>#{title_element}
  805. <pre class="content">#{node.content}</pre>#{attribution_element}
  806. </div>)
  807. end
  808. def video node
  809. xml = node.document.attr? 'htmlsyntax', 'xml'
  810. id_attribute = node.id ? %( id="#{node.id}") : nil
  811. classes = ['videoblock', node.style, node.role].compact
  812. class_attribute = %( class="#{classes * ' '}")
  813. title_element = node.title? ? %(\n<div class="title">#{node.captioned_title}</div>) : nil
  814. width_attribute = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : nil
  815. height_attribute = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : nil
  816. case node.attr 'poster'
  817. when 'vimeo'
  818. start_anchor = (node.attr? 'start') ? "#at=#{node.attr 'start'}" : nil
  819. delimiter = '?'
  820. autoplay_param = (node.option? 'autoplay') ? "#{delimiter}autoplay=1" : nil
  821. delimiter = '&amp;' if autoplay_param
  822. loop_param = (node.option? 'loop') ? "#{delimiter}loop=1" : nil
  823. %(<div#{id_attribute}#{class_attribute}>#{title_element}
  824. <div class="content">
  825. <iframe#{width_attribute}#{height_attribute} src="//player.vimeo.com/video/#{node.attr 'target'}#{start_anchor}#{autoplay_param}#{loop_param}" frameborder="0"#{append_boolean_attribute 'webkitAllowFullScreen', xml}#{append_boolean_attribute 'mozallowfullscreen', xml}#{append_boolean_attribute 'allowFullScreen', xml}></iframe>
  826. </div>
  827. </div>)
  828. when 'youtube'
  829. start_param = (node.attr? 'start') ? "&amp;start=#{node.attr 'start'}" : nil
  830. end_param = (node.attr? 'end') ? "&amp;end=#{node.attr 'end'}" : nil
  831. autoplay_param = (node.option? 'autoplay') ? '&amp;autoplay=1' : nil
  832. loop_param = (node.option? 'loop') ? '&amp;loop=1' : nil
  833. controls_param = (node.option? 'nocontrols') ? '&amp;controls=0' : nil
  834. %(<div#{id_attribute}#{class_attribute}>#{title_element}
  835. <div class="content">
  836. <iframe#{width_attribute}#{height_attribute} src="//www.youtube.com/embed/#{node.attr 'target'}?rel=0#{start_param}#{end_param}#{autoplay_param}#{loop_param}#{controls_param}" frameborder="0"#{(node.option? 'nofullscreen') ? nil : (append_boolean_attribute 'allowfullscreen', xml)}></iframe>
  837. </div>
  838. </div>)
  839. else
  840. poster_attribute = %(#{poster = node.attr 'poster'}).empty? ? nil : %( poster="#{node.media_uri poster}")
  841. time_anchor = ((node.attr? 'start') || (node.attr? 'end')) ? %(#t=#{node.attr 'start'}#{(node.attr? 'end') ? ',' : nil}#{node.attr 'end'}) : nil
  842. %(<div#{id_attribute}#{class_attribute}>#{title_element}
  843. <div class="content">
  844. <video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : nil}#{(node.option? 'nocontrols') ? nil : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : nil}>
  845. Your browser does not support the video tag.
  846. </video>
  847. </div>
  848. </div>)
  849. end
  850. end
  851. def inline_anchor node
  852. target = node.target
  853. case node.type
  854. when :xref
  855. refid = (node.attr 'refid') || target
  856. # FIXME seems like text should be prepared already
  857. text = node.text || (node.document.references[:ids][refid] || %([#{refid}]))
  858. %(<a href="#{target}">#{text}</a>)
  859. when :ref
  860. %(<a id="#{target}"></a>)
  861. when :link
  862. class_attr = (role = node.role) ? %( class="#{role}") : nil
  863. id_attr = (node.attr? 'id') ? %( id="#{node.attr 'id'}") : nil
  864. window_attr = (node.attr? 'window') ? %( target="#{node.attr 'window'}") : nil
  865. %(<a href="#{target}"#{id_attr}#{class_attr}#{window_attr}>#{node.text}</a>)
  866. when :bibref
  867. %(<a id="#{target}"></a>[#{target}])
  868. else
  869. warn %(asciidoctor: WARNING: unknown anchor type: #{node.type.inspect})
  870. end
  871. end
  872. def inline_break node
  873. %(#{node.text}<br#{@void_element_slash}>)
  874. end
  875. def inline_button node
  876. %(<b class="button">#{node.text}</b>)
  877. end
  878. def inline_callout node
  879. if node.document.attr? 'icons', 'font'
  880. %(<i class="conum" data-value="#{node.text}"></i><b>(#{node.text})</b>)
  881. elsif node.document.attr? 'icons'
  882. src = node.icon_uri("callouts/#{node.text}")
  883. %(<img src="#{src}" alt="#{node.text}"#{@void_element_slash}>)
  884. else
  885. %(<b class="conum">(#{node.text})</b>)
  886. end
  887. end
  888. def inline_footnote node
  889. if (index = node.attr 'index')
  890. if node.type == :xref
  891. %(<span class="footnoteref">[<a class="footnote" href="#_footnote_#{index}" title="View footnote.">#{index}</a>]</span>)
  892. else
  893. id_attr = node.id ? %( id="_footnote_#{node.id}") : nil
  894. %(<span class="footnote"#{id_attr}>[<a id="_footnoteref_#{index}" class="footnote" href="#_footnote_#{index}" title="View footnote.">#{index}</a>]</span>)
  895. end
  896. elsif node.type == :xref
  897. %(<span class="footnoteref red" title="Unresolved footnote reference.">[#{node.text}]</span>)
  898. end
  899. end
  900. def inline_image node
  901. if (type = node.type) == 'icon' && (node.document.attr? 'icons', 'font')
  902. style_class = "icon-#{node.target}"
  903. if node.attr? 'size'
  904. style_class = %(#{style_class} icon-#{node.attr 'size'})
  905. end
  906. if node.attr? 'rotate'
  907. style_class = %(#{style_class} icon-rotate-#{node.attr 'rotate'})
  908. end
  909. if node.attr? 'flip'
  910. style_class = %(#{style_class} icon-flip-#{node.attr 'flip'})
  911. end
  912. title_attribute = (node.attr? 'title') ? %( title="#{node.attr 'title'}") : nil
  913. img = %(<i class="#{style_class}"#{title_attribute}></i>)
  914. elsif type == 'icon' && !(node.document.attr? 'icons')
  915. img = %([#{node.attr 'alt'}])
  916. else
  917. resolved_target = (type == 'icon') ? (node.icon_uri node.target) : (node.image_uri node.target)
  918. attrs = ['alt', 'width', 'height', 'title'].map {|name|
  919. (node.attr? name) ? %( #{name}="#{node.attr name}") : nil
  920. }.join
  921. img = %(<img src="#{resolved_target}"#{attrs}#{@void_element_slash}>)
  922. end
  923. if node.attr? 'link'
  924. window_attr = (node.attr? 'window') ? %( target="#{node.attr 'window'}") : nil
  925. img = %(<a class="image" href="#{node.attr 'link'}"#{window_attr}>#{img}</a>)
  926. end
  927. style_classes = (role = node.role) ? %(#{type} #{role}) : type
  928. style_attr = (node.attr? 'float') ? %( style="float: #{node.attr 'float'}") : nil
  929. %(<span class="#{style_classes}"#{style_attr}>#{img}</span>)
  930. end
  931. def inline_indexterm node
  932. node.type == :visible ? node.text : ''
  933. end
  934. def inline_kbd node
  935. if (keys = node.attr 'keys').size == 1
  936. %(<kbd>#{keys[0]}</kbd>)
  937. else
  938. key_combo = keys.map {|key| %(<kbd>#{key}</kbd>+) }.join.chop
  939. %(<span class="keyseq">#{key_combo}</span>)
  940. end
  941. end
  942. def inline_menu node
  943. menu = node.attr 'menu'
  944. if !(submenus = node.attr 'submenus').empty?
  945. submenu_path = submenus.map {|submenu| %(<span class="submenu">#{submenu}</span>&#160;&#9656; ) }.join.chop
  946. %(<span class="menuseq"><span class="menu">#{menu}</span>&#160;&#9656; #{submenu_path} <span class="menuitem">#{node.attr 'menuitem'}</span></span>)
  947. elsif (menuitem = node.attr 'menuitem')
  948. %(<span class="menuseq"><span class="menu">#{menu}</span>&#160;&#9656; <span class="menuitem">#{menuitem}</span></span>)
  949. else
  950. %(<span class="menu">#{menu}</span>)
  951. end
  952. end
  953. def inline_quoted node
  954. open, close, is_tag = QUOTE_TAGS[node.type]
  955. quoted_text = if (role = node.role)
  956. is_tag ? %(#{open.chop} class="#{role}">#{node.text}#{close}) : %(<span class="#{role}">#{open}#{node.text}#{close}</span>)
  957. else
  958. %(#{open}#{node.text}#{close})
  959. end
  960. node.id ? %(<a id="#{node.id}"></a>#{quoted_text}) : quoted_text
  961. end
  962. def append_boolean_attribute name, xml
  963. xml ? %( #{name}="#{name}") : %( #{name})
  964. end
  965. end
  966. end