asciidoctor 1.5.8 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/CHANGELOG.adoc +628 -45
  4. data/LICENSE +2 -1
  5. data/README-de.adoc +28 -38
  6. data/README-fr.adoc +30 -43
  7. data/README-jp.adoc +255 -201
  8. data/README-zh_CN.adoc +40 -44
  9. data/README.adoc +170 -143
  10. data/asciidoctor.gemspec +22 -34
  11. data/bin/asciidoctor +5 -4
  12. data/data/locale/attributes-ar.adoc +4 -3
  13. data/data/locale/attributes-be.adoc +23 -0
  14. data/data/locale/attributes-bg.adoc +4 -3
  15. data/data/locale/attributes-ca.adoc +6 -5
  16. data/data/locale/attributes-cs.adoc +4 -3
  17. data/data/locale/attributes-da.adoc +6 -5
  18. data/data/locale/attributes-de.adoc +6 -5
  19. data/data/locale/attributes-en.adoc +4 -4
  20. data/data/locale/attributes-es.adoc +6 -5
  21. data/data/locale/attributes-fa.adoc +4 -3
  22. data/data/locale/attributes-fi.adoc +4 -3
  23. data/data/locale/attributes-fr.adoc +8 -7
  24. data/data/locale/attributes-hu.adoc +4 -3
  25. data/data/locale/attributes-id.adoc +4 -3
  26. data/data/locale/attributes-it.adoc +6 -5
  27. data/data/locale/attributes-ja.adoc +4 -3
  28. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  29. data/data/locale/attributes-nb.adoc +4 -3
  30. data/data/locale/attributes-nl.adoc +6 -5
  31. data/data/locale/attributes-nn.adoc +4 -3
  32. data/data/locale/attributes-pl.adoc +8 -7
  33. data/data/locale/attributes-pt.adoc +6 -5
  34. data/data/locale/attributes-pt_BR.adoc +6 -5
  35. data/data/locale/attributes-ro.adoc +4 -3
  36. data/data/locale/attributes-ru.adoc +6 -5
  37. data/data/locale/attributes-sr.adoc +4 -4
  38. data/data/locale/attributes-sr_Latn.adoc +4 -4
  39. data/data/locale/attributes-sv.adoc +4 -4
  40. data/data/locale/attributes-th.adoc +23 -0
  41. data/data/locale/attributes-tr.adoc +4 -3
  42. data/data/locale/attributes-uk.adoc +6 -5
  43. data/data/locale/attributes-vi.adoc +23 -0
  44. data/data/locale/attributes-zh_CN.adoc +4 -3
  45. data/data/locale/attributes-zh_TW.adoc +4 -3
  46. data/data/reference/syntax.adoc +296 -0
  47. data/data/stylesheets/asciidoctor-default.css +120 -114
  48. data/data/stylesheets/coderay-asciidoctor.css +15 -17
  49. data/lib/asciidoctor/abstract_block.rb +146 -140
  50. data/lib/asciidoctor/abstract_node.rb +152 -170
  51. data/lib/asciidoctor/attribute_list.rb +77 -89
  52. data/lib/asciidoctor/block.rb +29 -28
  53. data/lib/asciidoctor/callouts.rb +4 -2
  54. data/lib/asciidoctor/cli/invoker.rb +20 -24
  55. data/lib/asciidoctor/cli/options.rb +107 -96
  56. data/lib/asciidoctor/cli.rb +3 -2
  57. data/lib/asciidoctor/convert.rb +199 -0
  58. data/lib/asciidoctor/converter/composite.rb +40 -48
  59. data/lib/asciidoctor/converter/docbook5.rb +627 -644
  60. data/lib/asciidoctor/converter/html5.rb +1053 -951
  61. data/lib/asciidoctor/converter/manpage.rb +581 -532
  62. data/lib/asciidoctor/converter/template.rb +232 -271
  63. data/lib/asciidoctor/converter.rb +370 -185
  64. data/lib/asciidoctor/core_ext/float/truncate.rb +20 -0
  65. data/lib/asciidoctor/core_ext/hash/merge.rb +8 -0
  66. data/lib/asciidoctor/core_ext/match_data/names.rb +7 -0
  67. data/lib/asciidoctor/core_ext/nil_or_empty.rb +1 -0
  68. data/lib/asciidoctor/core_ext/regexp/is_match.rb +4 -2
  69. data/lib/asciidoctor/core_ext.rb +8 -17
  70. data/lib/asciidoctor/document.rb +503 -461
  71. data/lib/asciidoctor/extensions.rb +127 -174
  72. data/lib/asciidoctor/helpers.rb +184 -107
  73. data/lib/asciidoctor/inline.rb +9 -12
  74. data/lib/asciidoctor/list.rb +11 -29
  75. data/lib/asciidoctor/load.rb +119 -0
  76. data/lib/asciidoctor/logging.rb +22 -17
  77. data/lib/asciidoctor/parser.rb +673 -719
  78. data/lib/asciidoctor/path_resolver.rb +48 -33
  79. data/lib/asciidoctor/reader.rb +383 -338
  80. data/lib/asciidoctor/rouge_ext.rb +39 -0
  81. data/lib/asciidoctor/rx.rb +723 -0
  82. data/lib/asciidoctor/section.rb +17 -16
  83. data/lib/asciidoctor/stylesheets.rb +19 -37
  84. data/lib/asciidoctor/substitutors.rb +926 -1022
  85. data/lib/asciidoctor/syntax_highlighter/coderay.rb +88 -0
  86. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +34 -0
  87. data/lib/asciidoctor/syntax_highlighter/html_pipeline.rb +10 -0
  88. data/lib/asciidoctor/syntax_highlighter/prettify.rb +30 -0
  89. data/lib/asciidoctor/syntax_highlighter/pygments.rb +157 -0
  90. data/lib/asciidoctor/syntax_highlighter/rouge.rb +143 -0
  91. data/lib/asciidoctor/syntax_highlighter.rb +253 -0
  92. data/lib/asciidoctor/table.rb +152 -114
  93. data/lib/asciidoctor/timings.rb +7 -5
  94. data/lib/asciidoctor/version.rb +2 -1
  95. data/lib/asciidoctor/writer.rb +30 -0
  96. data/lib/asciidoctor.rb +266 -1340
  97. data/man/asciidoctor.1 +49 -47
  98. data/man/asciidoctor.adoc +54 -45
  99. metadata +50 -245
  100. data/CONTRIBUTING.adoc +0 -185
  101. data/Gemfile +0 -60
  102. data/Rakefile +0 -129
  103. data/bin/asciidoctor-safe +0 -15
  104. data/features/open_block.feature +0 -92
  105. data/features/pass_block.feature +0 -66
  106. data/features/step_definitions.rb +0 -49
  107. data/features/text_formatting.feature +0 -57
  108. data/features/xref.feature +0 -1039
  109. data/lib/asciidoctor/converter/base.rb +0 -59
  110. data/lib/asciidoctor/converter/docbook45.rb +0 -93
  111. data/lib/asciidoctor/converter/factory.rb +0 -226
  112. data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +0 -6
  113. data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +0 -5
  114. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +0 -4
  115. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +0 -6
  116. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +0 -5
  117. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +0 -6
  118. data/lib/asciidoctor/core_ext/1.8.7/string/limit_bytesize.rb +0 -29
  119. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +0 -6
  120. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +0 -6
  121. data/lib/asciidoctor/core_ext/string/limit_bytesize.rb +0 -10
  122. data/test/api_test.rb +0 -1240
  123. data/test/attribute_list_test.rb +0 -242
  124. data/test/attributes_test.rb +0 -1623
  125. data/test/blocks_test.rb +0 -3870
  126. data/test/converter_test.rb +0 -470
  127. data/test/document_test.rb +0 -1853
  128. data/test/extensions_test.rb +0 -1560
  129. data/test/fixtures/asciidoc_index.txt +0 -521
  130. data/test/fixtures/basic-docinfo-footer.html +0 -6
  131. data/test/fixtures/basic-docinfo-footer.xml +0 -8
  132. data/test/fixtures/basic-docinfo.html +0 -1
  133. data/test/fixtures/basic-docinfo.xml +0 -4
  134. data/test/fixtures/basic.asciidoc +0 -5
  135. data/test/fixtures/chapter-a.adoc +0 -3
  136. data/test/fixtures/child-include.adoc +0 -5
  137. data/test/fixtures/circle.svg +0 -9
  138. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +0 -6
  139. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +0 -6
  140. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +0 -3
  141. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +0 -5
  142. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +0 -1
  143. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +0 -6
  144. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +0 -3
  145. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +0 -5
  146. data/test/fixtures/custom-docinfodir/basic-docinfo.html +0 -1
  147. data/test/fixtures/custom-docinfodir/docinfo.html +0 -1
  148. data/test/fixtures/docinfo-footer.html +0 -1
  149. data/test/fixtures/docinfo-footer.xml +0 -9
  150. data/test/fixtures/docinfo.html +0 -1
  151. data/test/fixtures/docinfo.xml +0 -3
  152. data/test/fixtures/doctime-localtime.adoc +0 -2
  153. data/test/fixtures/dot.gif +0 -0
  154. data/test/fixtures/encoding.asciidoc +0 -13
  155. data/test/fixtures/file-with-missing-include.adoc +0 -1
  156. data/test/fixtures/grandchild-include.adoc +0 -3
  157. data/test/fixtures/hello-asciidoctor.pdf +0 -69
  158. data/test/fixtures/include-file.asciidoc +0 -24
  159. data/test/fixtures/include-file.jsx +0 -8
  160. data/test/fixtures/include-file.ml +0 -3
  161. data/test/fixtures/include-file.xml +0 -5
  162. data/test/fixtures/lists.adoc +0 -96
  163. data/test/fixtures/master.adoc +0 -5
  164. data/test/fixtures/mismatched-end-tag.adoc +0 -7
  165. data/test/fixtures/other-chapters.adoc +0 -11
  166. data/test/fixtures/outer-include.adoc +0 -5
  167. data/test/fixtures/parent-include-restricted.adoc +0 -5
  168. data/test/fixtures/parent-include.adoc +0 -5
  169. data/test/fixtures/sample.asciidoc +0 -30
  170. data/test/fixtures/section-a.adoc +0 -4
  171. data/test/fixtures/stylesheets/custom.css +0 -3
  172. data/test/fixtures/subdir/index.adoc +0 -3
  173. data/test/fixtures/subdir/inner-include.adoc +0 -3
  174. data/test/fixtures/subdir/middle-include.adoc +0 -5
  175. data/test/fixtures/subs-docinfo.html +0 -2
  176. data/test/fixtures/subs.adoc +0 -6
  177. data/test/fixtures/tagged-class-enclosed.rb +0 -25
  178. data/test/fixtures/tagged-class.rb +0 -23
  179. data/test/fixtures/tip.gif +0 -0
  180. data/test/fixtures/unclosed-tag.adoc +0 -3
  181. data/test/fixtures/unexpected-end-tag.adoc +0 -4
  182. data/test/invoker_test.rb +0 -745
  183. data/test/links_test.rb +0 -855
  184. data/test/lists_test.rb +0 -5151
  185. data/test/logger_test.rb +0 -211
  186. data/test/manpage_test.rb +0 -660
  187. data/test/options_test.rb +0 -262
  188. data/test/paragraphs_test.rb +0 -562
  189. data/test/parser_test.rb +0 -742
  190. data/test/paths_test.rb +0 -395
  191. data/test/preamble_test.rb +0 -173
  192. data/test/reader_test.rb +0 -2161
  193. data/test/sections_test.rb +0 -3575
  194. data/test/substitutions_test.rb +0 -2066
  195. data/test/tables_test.rb +0 -2036
  196. data/test/test_helper.rb +0 -447
  197. data/test/text_test.rb +0 -309
@@ -1,227 +1,272 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
2
  module Asciidoctor
3
- # A built-in {Converter} implementation that generates HTML 5 output
4
- # consistent with the html5 backend from AsciiDoc Python.
5
- class Converter::Html5Converter < Converter::BuiltIn
6
- (QUOTE_TAGS = {
7
- :monospaced => ['<code>', '</code>', true],
8
- :emphasis => ['<em>', '</em>', true],
9
- :strong => ['<strong>', '</strong>', true],
10
- :double => ['&#8220;', '&#8221;', false],
11
- :single => ['&#8216;', '&#8217;', false],
12
- :mark => ['<mark>', '</mark>', true],
13
- :superscript => ['<sup>', '</sup>', true],
14
- :subscript => ['<sub>', '</sub>', true],
15
- :asciimath => ['\$', '\$', false],
16
- :latexmath => ['\(', '\)', false]
17
- # Opal can't resolve these constants when referenced here
18
- #:asciimath => INLINE_MATH_DELIMITERS[:asciimath] + [false],
19
- #:latexmath => INLINE_MATH_DELIMITERS[:latexmath] + [false]
20
- }).default = ['', '', false]
21
-
22
- DropAnchorRx = /<(?:a[^>+]+|\/a)>/
23
- StemBreakRx = / *\\\n(?:\\?\n)*|\n\n+/
24
- SvgPreambleRx = /\A.*?(?=<svg\b)/m
25
- SvgStartTagRx = /\A<svg[^>]*>/
26
- DimensionAttributeRx = /\s(?:width|height|style)=(["']).*?\1/
27
-
28
- def initialize backend, opts = {}
29
- @xml_mode = opts[:htmlsyntax] == 'xml'
30
- @void_element_slash = @xml_mode ? '/' : nil
31
- @stylesheets = Stylesheets.instance
3
+ # A built-in {Converter} implementation that generates HTML 5 output
4
+ # consistent with the html5 backend from AsciiDoc.py.
5
+ class Converter::Html5Converter < Converter::Base
6
+ register_for 'html5'
7
+
8
+ (QUOTE_TAGS = {
9
+ monospaced: ['<code>', '</code>', true],
10
+ emphasis: ['<em>', '</em>', true],
11
+ strong: ['<strong>', '</strong>', true],
12
+ double: ['&#8220;', '&#8221;'],
13
+ single: ['&#8216;', '&#8217;'],
14
+ mark: ['<mark>', '</mark>', true],
15
+ superscript: ['<sup>', '</sup>', true],
16
+ subscript: ['<sub>', '</sub>', true],
17
+ asciimath: ['\$', '\$'],
18
+ latexmath: ['\(', '\)'],
19
+ # Opal can't resolve these constants when referenced here
20
+ #asciimath: INLINE_MATH_DELIMITERS[:asciimath] + [false],
21
+ #latexmath: INLINE_MATH_DELIMITERS[:latexmath] + [false],
22
+ }).default = ['', '']
23
+
24
+ DropAnchorRx = %r(<(?:a\b[^>]*|/a)>)
25
+ StemBreakRx = / *\\\n(?:\\?\n)*|\n\n+/
26
+ if RUBY_ENGINE == 'opal'
27
+ # NOTE In JavaScript, ^ matches the start of the string when the m flag is not set
28
+ SvgPreambleRx = /^#{CC_ALL}*?(?=<svg[\s>])/
29
+ SvgStartTagRx = /^<svg(?:\s[^>]*)?>/
30
+ else
31
+ SvgPreambleRx = /\A.*?(?=<svg[\s>])/m
32
+ SvgStartTagRx = /\A<svg(?:\s[^>]*)?>/
33
+ end
34
+ DimensionAttributeRx = /\s(?:width|height|style)=(["'])#{CC_ANY}*?\1/
35
+
36
+ def initialize backend, opts = {}
37
+ @backend = backend
38
+ if opts[:htmlsyntax] == 'xml'
39
+ syntax = 'xml'
40
+ @xml_mode = true
41
+ @void_element_slash = '/'
42
+ else
43
+ syntax = 'html'
44
+ @xml_mode = nil
45
+ @void_element_slash = ''
32
46
  end
47
+ init_backend_traits basebackend: 'html', filetype: 'html', htmlsyntax: syntax, outfilesuffix: '.html', supports_templates: true
48
+ end
33
49
 
34
- def document node
35
- slash = @void_element_slash
36
- br = %(<br#{slash}>)
37
- unless (asset_uri_scheme = (node.attr 'asset-uri-scheme', 'https')).empty?
38
- asset_uri_scheme = %(#{asset_uri_scheme}:)
39
- end
40
- cdn_base = %(#{asset_uri_scheme}//cdnjs.cloudflare.com/ajax/libs)
41
- linkcss = node.attr? 'linkcss'
42
- result = ['<!DOCTYPE html>']
43
- lang_attribute = (node.attr? 'nolang') ? '' : %( lang="#{node.attr 'lang', 'en'}")
44
- result << %(<html#{@xml_mode ? ' xmlns="http://www.w3.org/1999/xhtml"' : ''}#{lang_attribute}>)
45
- result << %(<head>
50
+ def convert node, transform = node.node_name, opts = nil
51
+ case transform
52
+ when 'inline_quoted' then convert_inline_quoted node
53
+ when 'paragraph' then convert_paragraph node
54
+ when 'inline_anchor' then convert_inline_anchor node
55
+ when 'section' then convert_section node
56
+ when 'listing' then convert_listing node
57
+ when 'literal' then convert_literal node
58
+ when 'ulist' then convert_ulist node
59
+ when 'olist' then convert_olist node
60
+ when 'dlist' then convert_dlist node
61
+ when 'admonition' then convert_admonition node
62
+ when 'colist' then convert_colist node
63
+ when 'embedded' then convert_embedded node
64
+ when 'example' then convert_example node
65
+ when 'floating_title' then convert_floating_title node
66
+ when 'image' then convert_image node
67
+ when 'inline_break' then convert_inline_break node
68
+ when 'inline_button' then convert_inline_button node
69
+ when 'inline_callout' then convert_inline_callout node
70
+ when 'inline_footnote' then convert_inline_footnote node
71
+ when 'inline_image' then convert_inline_image node
72
+ when 'inline_indexterm' then convert_inline_indexterm node
73
+ when 'inline_kbd' then convert_inline_kbd node
74
+ when 'inline_menu' then convert_inline_menu node
75
+ when 'open' then convert_open node
76
+ when 'page_break' then convert_page_break node
77
+ when 'preamble' then convert_preamble node
78
+ when 'quote' then convert_quote node
79
+ when 'sidebar' then convert_sidebar node
80
+ when 'stem' then convert_stem node
81
+ when 'table' then convert_table node
82
+ when 'thematic_break' then convert_thematic_break node
83
+ when 'verse' then convert_verse node
84
+ when 'video' then convert_video node
85
+ when 'document' then convert_document node
86
+ when 'toc' then convert_toc node
87
+ when 'pass' then convert_pass node
88
+ when 'audio' then convert_audio node
89
+ else; super
90
+ end
91
+ end
92
+
93
+ def convert_document node
94
+ br = %(<br#{slash = @void_element_slash}>)
95
+ unless (asset_uri_scheme = (node.attr 'asset-uri-scheme', 'https')).empty?
96
+ asset_uri_scheme = %(#{asset_uri_scheme}:)
97
+ end
98
+ cdn_base_url = %(#{asset_uri_scheme}//cdnjs.cloudflare.com/ajax/libs)
99
+ linkcss = node.attr? 'linkcss'
100
+ max_width_attr = (node.attr? 'max-width') ? %( style="max-width: #{node.attr 'max-width'};") : ''
101
+ result = ['<!DOCTYPE html>']
102
+ lang_attribute = (node.attr? 'nolang') ? '' : %( lang="#{node.attr 'lang', 'en'}")
103
+ result << %(<html#{@xml_mode ? ' xmlns="http://www.w3.org/1999/xhtml"' : ''}#{lang_attribute}>)
104
+ result << %(<head>
46
105
  <meta charset="#{node.attr 'encoding', 'UTF-8'}"#{slash}>
47
- <!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"#{slash}><![endif]-->
106
+ <meta http-equiv="X-UA-Compatible" content="IE=edge"#{slash}>
48
107
  <meta name="viewport" content="width=device-width, initial-scale=1.0"#{slash}>
49
108
  <meta name="generator" content="Asciidoctor #{node.attr 'asciidoctor-version'}"#{slash}>)
50
- result << %(<meta name="application-name" content="#{node.attr 'app-name'}"#{slash}>) if node.attr? 'app-name'
51
- result << %(<meta name="description" content="#{node.attr 'description'}"#{slash}>) if node.attr? 'description'
52
- result << %(<meta name="keywords" content="#{node.attr 'keywords'}"#{slash}>) if node.attr? 'keywords'
53
- result << %(<meta name="author" content="#{((authors = node.attr 'authors').include? '<') ? (authors.gsub XmlSanitizeRx, '') : authors}"#{slash}>) if node.attr? 'authors'
54
- result << %(<meta name="copyright" content="#{node.attr 'copyright'}"#{slash}>) if node.attr? 'copyright'
55
- if node.attr? 'favicon'
56
- if (icon_href = node.attr 'favicon').empty?
57
- icon_href, icon_type = 'favicon.ico', 'image/x-icon'
58
- else
59
- icon_type = (icon_ext = ::File.extname icon_href) == '.ico' ? 'image/x-icon' : %(image/#{icon_ext.slice 1, icon_ext.length})
60
- end
61
- result << %(<link rel="icon" type="#{icon_type}" href="#{icon_href}"#{slash}>)
109
+ result << %(<meta name="application-name" content="#{node.attr 'app-name'}"#{slash}>) if node.attr? 'app-name'
110
+ result << %(<meta name="description" content="#{node.attr 'description'}"#{slash}>) if node.attr? 'description'
111
+ result << %(<meta name="keywords" content="#{node.attr 'keywords'}"#{slash}>) if node.attr? 'keywords'
112
+ result << %(<meta name="author" content="#{((authors = node.sub_replacements node.attr 'authors').include? '<') ? (authors.gsub XmlSanitizeRx, '') : authors}"#{slash}>) if node.attr? 'authors'
113
+ result << %(<meta name="copyright" content="#{node.attr 'copyright'}"#{slash}>) if node.attr? 'copyright'
114
+ if node.attr? 'favicon'
115
+ if (icon_href = node.attr 'favicon').empty?
116
+ icon_href = 'favicon.ico'
117
+ icon_type = 'image/x-icon'
118
+ elsif (icon_ext = Helpers.extname icon_href, nil)
119
+ icon_type = icon_ext == '.ico' ? 'image/x-icon' : %(image/#{icon_ext.slice 1, icon_ext.length})
120
+ else
121
+ icon_type = 'image/x-icon'
62
122
  end
63
- result << %(<title>#{node.doctitle :sanitize => true, :use_fallback => true}</title>)
123
+ result << %(<link rel="icon" type="#{icon_type}" href="#{icon_href}"#{slash}>)
124
+ end
125
+ result << %(<title>#{node.doctitle sanitize: true, use_fallback: true}</title>)
64
126
 
65
- if DEFAULT_STYLESHEET_KEYS.include?(node.attr 'stylesheet')
66
- if (webfonts = node.attr 'webfonts')
67
- result << %(<link rel="stylesheet" href="#{asset_uri_scheme}//fonts.googleapis.com/css?family=#{webfonts.empty? ? 'Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700' : webfonts}"#{slash}>)
68
- end
69
- if linkcss
70
- result << %(<link rel="stylesheet" href="#{node.normalize_web_path DEFAULT_STYLESHEET_NAME, (node.attr 'stylesdir', ''), false}"#{slash}>)
71
- else
72
- result << @stylesheets.embed_primary_stylesheet
73
- end
74
- elsif node.attr? 'stylesheet'
75
- if linkcss
76
- result << %(<link rel="stylesheet" href="#{node.normalize_web_path((node.attr 'stylesheet'), (node.attr 'stylesdir', ''))}"#{slash}>)
77
- else
78
- result << %(<style>
79
- #{node.read_asset node.normalize_system_path((node.attr 'stylesheet'), (node.attr 'stylesdir', '')), :warn_on_failure => true, :label => 'stylesheet'}
127
+ if DEFAULT_STYLESHEET_KEYS.include?(node.attr 'stylesheet')
128
+ if (webfonts = node.attr 'webfonts')
129
+ result << %(<link rel="stylesheet" href="#{asset_uri_scheme}//fonts.googleapis.com/css?family=#{webfonts.empty? ? 'Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700' : webfonts}"#{slash}>)
130
+ end
131
+ if linkcss
132
+ result << %(<link rel="stylesheet" href="#{node.normalize_web_path DEFAULT_STYLESHEET_NAME, (node.attr 'stylesdir', ''), false}"#{slash}>)
133
+ else
134
+ result << %(<style>
135
+ #{Stylesheets.instance.primary_stylesheet_data}
80
136
  </style>)
81
- end
82
137
  end
83
-
84
- if node.attr? 'icons', 'font'
85
- if node.attr? 'iconfont-remote'
86
- result << %(<link rel="stylesheet" href="#{node.attr 'iconfont-cdn', %[#{cdn_base}/font-awesome/#{FONT_AWESOME_VERSION}/css/font-awesome.min.css]}"#{slash}>)
87
- else
88
- iconfont_stylesheet = %(#{node.attr 'iconfont-name', 'font-awesome'}.css)
89
- result << %(<link rel="stylesheet" href="#{node.normalize_web_path iconfont_stylesheet, (node.attr 'stylesdir', ''), false}"#{slash}>)
90
- end
138
+ elsif node.attr? 'stylesheet'
139
+ if linkcss
140
+ result << %(<link rel="stylesheet" href="#{node.normalize_web_path((node.attr 'stylesheet'), (node.attr 'stylesdir', ''))}"#{slash}>)
141
+ else
142
+ result << %(<style>
143
+ #{node.read_contents (node.attr 'stylesheet'), start: (node.attr 'stylesdir'), warn_on_failure: true, label: 'stylesheet'}
144
+ </style>)
91
145
  end
146
+ end
92
147
 
93
- case (highlighter = node.attr 'source-highlighter')
94
- when 'coderay'
95
- if (node.attr 'coderay-css', 'class') == 'class'
96
- if linkcss
97
- result << %(<link rel="stylesheet" href="#{node.normalize_web_path @stylesheets.coderay_stylesheet_name, (node.attr 'stylesdir', ''), false}"#{slash}>)
98
- else
99
- result << @stylesheets.embed_coderay_stylesheet
100
- end
101
- end
102
- when 'pygments'
103
- if (node.attr 'pygments-css', 'class') == 'class'
104
- pygments_style = node.attr 'pygments-style'
105
- if linkcss
106
- result << %(<link rel="stylesheet" href="#{node.normalize_web_path @stylesheets.pygments_stylesheet_name(pygments_style), (node.attr 'stylesdir', ''), false}"#{slash}>)
107
- else
108
- result << (@stylesheets.embed_pygments_stylesheet pygments_style)
109
- end
110
- end
148
+ if node.attr? 'icons', 'font'
149
+ if node.attr? 'iconfont-remote'
150
+ result << %(<link rel="stylesheet" href="#{node.attr 'iconfont-cdn', %[#{cdn_base_url}/font-awesome/#{FONT_AWESOME_VERSION}/css/font-awesome.min.css]}"#{slash}>)
151
+ else
152
+ iconfont_stylesheet = %(#{node.attr 'iconfont-name', 'font-awesome'}.css)
153
+ result << %(<link rel="stylesheet" href="#{node.normalize_web_path iconfont_stylesheet, (node.attr 'stylesdir', ''), false}"#{slash}>)
111
154
  end
155
+ end
112
156
 
113
- unless (docinfo_content = node.docinfo).empty?
114
- result << docinfo_content
115
- end
157
+ if (syntax_hl = node.syntax_highlighter)
158
+ result << (syntax_hl_docinfo_head_idx = result.size)
159
+ end
116
160
 
117
- result << '</head>'
118
- body_attrs = node.id ? [%(id="#{node.id}")] : []
119
- if (sectioned = node.sections?) && (node.attr? 'toc-class') && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
120
- classes = [node.doctype, (node.attr 'toc-class'), %(toc-#{node.attr 'toc-position', 'header'})]
121
- else
122
- classes = [node.doctype]
123
- end
124
- classes << (node.attr 'docrole') if node.attr? 'docrole'
125
- body_attrs << %(class="#{classes.join ' '}")
126
- body_attrs << %(style="max-width: #{node.attr 'max-width'};") if node.attr? 'max-width'
127
- result << %(<body #{body_attrs.join ' '}>)
128
-
129
- unless node.noheader
130
- result << '<div id="header">'
131
- if node.doctype == 'manpage'
132
- result << %(<h1>#{node.doctitle} Manual Page</h1>)
133
- if sectioned && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
134
- result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
161
+ unless (docinfo_content = node.docinfo).empty?
162
+ result << docinfo_content
163
+ end
164
+
165
+ result << '</head>'
166
+ id_attr = node.id ? %( id="#{node.id}") : ''
167
+ if (sectioned = node.sections?) && (node.attr? 'toc-class') && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
168
+ classes = [node.doctype, (node.attr 'toc-class'), %(toc-#{node.attr 'toc-position', 'header'})]
169
+ else
170
+ classes = [node.doctype]
171
+ end
172
+ classes << node.role if node.role?
173
+ result << %(<body#{id_attr} class="#{classes.join ' '}">)
174
+
175
+ unless (docinfo_content = node.docinfo :header).empty?
176
+ result << docinfo_content
177
+ end
178
+
179
+ unless node.noheader
180
+ result << %(<div id="header"#{max_width_attr}>)
181
+ if node.doctype == 'manpage'
182
+ result << %(<h1>#{node.doctitle} Manual Page</h1>)
183
+ if sectioned && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
184
+ result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
135
185
  <div id="toctitle">#{node.attr 'toc-title'}</div>
136
- #{outline node}
186
+ #{node.converter.convert node, 'outline'}
137
187
  </div>)
188
+ end
189
+ result << (generate_manname_section node) if node.attr? 'manpurpose'
190
+ else
191
+ if node.header?
192
+ result << %(<h1>#{node.header.title}</h1>) unless node.notitle
193
+ details = []
194
+ idx = 1
195
+ node.authors.each do |author|
196
+ details << %(<span id="author#{idx > 1 ? idx : ''}" class="author">#{node.sub_replacements author.name}</span>#{br})
197
+ details << %(<span id="email#{idx > 1 ? idx : ''}" class="email">#{node.sub_macros author.email}</span>#{br}) if author.email
198
+ idx += 1
138
199
  end
139
- result << (generate_manname_section node) if node.attr? 'manpurpose'
140
- else
141
- if node.has_header?
142
- result << %(<h1>#{node.header.title}</h1>) unless node.notitle
143
- details = []
144
- idx = 1
145
- node.authors.each do |author|
146
- details << %(<span id="author#{idx > 1 ? idx : ''}" class="author">#{author.name}</span>#{br})
147
- details << %(<span id="email#{idx > 1 ? idx : ''}" class="email">#{node.sub_macros author.email}</span>#{br}) if author.email
148
- idx += 1
149
- end
150
- if node.attr? 'revnumber'
151
- details << %(<span id="revnumber">#{((node.attr 'version-label') || '').downcase} #{node.attr 'revnumber'}#{(node.attr? 'revdate') ? ',' : ''}</span>)
152
- end
153
- if node.attr? 'revdate'
154
- details << %(<span id="revdate">#{node.attr 'revdate'}</span>)
155
- end
156
- if node.attr? 'revremark'
157
- details << %(#{br}<span id="revremark">#{node.attr 'revremark'}</span>)
158
- end
159
- unless details.empty?
160
- result << '<div class="details">'
161
- result.concat details
162
- result << '</div>'
163
- end
200
+ if node.attr? 'revnumber'
201
+ details << %(<span id="revnumber">#{((node.attr 'version-label') || '').downcase} #{node.attr 'revnumber'}#{(node.attr? 'revdate') ? ',' : ''}</span>)
202
+ end
203
+ if node.attr? 'revdate'
204
+ details << %(<span id="revdate">#{node.attr 'revdate'}</span>)
205
+ end
206
+ if node.attr? 'revremark'
207
+ details << %(#{br}<span id="revremark">#{node.attr 'revremark'}</span>)
164
208
  end
209
+ unless details.empty?
210
+ result << '<div class="details">'
211
+ result.concat details
212
+ result << '</div>'
213
+ end
214
+ end
165
215
 
166
- if sectioned && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
167
- result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
216
+ if sectioned && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
217
+ result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
168
218
  <div id="toctitle">#{node.attr 'toc-title'}</div>
169
- #{outline node}
219
+ #{node.converter.convert node, 'outline'}
170
220
  </div>)
171
- end
172
221
  end
173
- result << '</div>'
174
222
  end
223
+ result << '</div>'
224
+ end
175
225
 
176
- result << %(<div id="content">
226
+ result << %(<div id="content"#{max_width_attr}>
177
227
  #{node.content}
178
228
  </div>)
179
229
 
180
- if node.footnotes? && !(node.attr? 'nofootnotes')
181
- result << %(<div id="footnotes">
230
+ if node.footnotes? && !(node.attr? 'nofootnotes')
231
+ result << %(<div id="footnotes"#{max_width_attr}>
182
232
  <hr#{slash}>)
183
- node.footnotes.each do |footnote|
184
- result << %(<div class="footnote" id="_footnotedef_#{footnote.index}">
233
+ node.footnotes.each do |footnote|
234
+ result << %(<div class="footnote" id="_footnotedef_#{footnote.index}">
185
235
  <a href="#_footnoteref_#{footnote.index}">#{footnote.index}</a>. #{footnote.text}
186
236
  </div>)
187
- end
188
- result << '</div>'
189
237
  end
238
+ result << '</div>'
239
+ end
190
240
 
191
- unless node.nofooter
192
- result << '<div id="footer">'
193
- result << '<div id="footer-text">'
194
- result << %(#{node.attr 'version-label'} #{node.attr 'revnumber'}#{br}) if node.attr? 'revnumber'
195
- result << %(#{node.attr 'last-update-label'} #{node.attr 'docdatetime'}) if (node.attr? 'last-update-label') && !(node.attr? 'reproducible')
196
- result << '</div>'
197
- result << '</div>'
198
- end
241
+ unless node.nofooter
242
+ result << %(<div id="footer"#{max_width_attr}>)
243
+ result << '<div id="footer-text">'
244
+ result << %(#{node.attr 'version-label'} #{node.attr 'revnumber'}#{br}) if node.attr? 'revnumber'
245
+ result << %(#{node.attr 'last-update-label'} #{node.attr 'docdatetime'}) if (node.attr? 'last-update-label') && !(node.attr? 'reproducible')
246
+ result << '</div>'
247
+ result << '</div>'
248
+ end
199
249
 
200
- unless (docinfo_content = node.docinfo :footer).empty?
201
- result << docinfo_content
202
- end
250
+ # JavaScript (and auxiliary stylesheets) loaded at the end of body for performance reasons
251
+ # See http://www.html5rocks.com/en/tutorials/speed/script-loading/
203
252
 
204
- # Load Javascript at the end of body for performance
205
- # See http://www.html5rocks.com/en/tutorials/speed/script-loading/
206
- case highlighter
207
- when 'highlightjs', 'highlight.js'
208
- highlightjs_path = node.attr 'highlightjsdir', %(#{cdn_base}/highlight.js/9.13.1)
209
- result << %(<link rel="stylesheet" href="#{highlightjs_path}/styles/#{node.attr 'highlightjs-theme', 'github'}.min.css"#{slash}>)
210
- result << %(<script src="#{highlightjs_path}/highlight.min.js"></script>
211
- <script>hljs.initHighlighting()</script>)
212
- when 'prettify'
213
- prettify_path = node.attr 'prettifydir', %(#{cdn_base}/prettify/r298)
214
- result << %(<link rel="stylesheet" href="#{prettify_path}/#{node.attr 'prettify-theme', 'prettify'}.min.css"#{slash}>)
215
- result << %(<script src="#{prettify_path}/prettify.min.js"></script>
216
- <script>prettyPrint()</script>)
253
+ if syntax_hl
254
+ if syntax_hl.docinfo? :head
255
+ result[syntax_hl_docinfo_head_idx] = syntax_hl.docinfo :head, node, cdn_base_url: cdn_base_url, linkcss: linkcss, self_closing_tag_slash: slash
256
+ else
257
+ result.delete_at syntax_hl_docinfo_head_idx
217
258
  end
259
+ if syntax_hl.docinfo? :footer
260
+ result << (syntax_hl.docinfo :footer, node, cdn_base_url: cdn_base_url, linkcss: linkcss, self_closing_tag_slash: slash)
261
+ end
262
+ end
218
263
 
219
- if node.attr? 'stem'
220
- eqnums_val = node.attr 'eqnums', 'none'
221
- eqnums_val = 'AMS' if eqnums_val.empty?
222
- eqnums_opt = %( equationNumbers: { autoNumber: "#{eqnums_val}" } )
223
- # IMPORTANT inspect calls on delimiter arrays are intentional for JavaScript compat (emulates JSON.stringify)
224
- result << %(<script type="text/x-mathjax-config">
264
+ if node.attr? 'stem'
265
+ eqnums_val = node.attr 'eqnums', 'none'
266
+ eqnums_val = 'AMS' if eqnums_val.empty?
267
+ eqnums_opt = %( equationNumbers: { autoNumber: "#{eqnums_val}" } )
268
+ # IMPORTANT inspect calls on delimiter arrays are intentional for JavaScript compat (emulates JSON.stringify)
269
+ result << %(<script type="text/x-mathjax-config">
225
270
  MathJax.Hub.Config({
226
271
  messageStyle: "none",
227
272
  tex2jax: {
@@ -234,157 +279,169 @@ MathJax.Hub.Config({
234
279
  ignoreClass: "nostem|noasciimath"
235
280
  },
236
281
  TeX: {#{eqnums_opt}}
237
- });
282
+ })
283
+ MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
284
+ MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
285
+ if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
286
+ data.math.root.display = "block"
287
+ }
288
+ return data
289
+ })
290
+ })
238
291
  </script>
239
- <script src="#{cdn_base}/mathjax/#{MATHJAX_VERSION}/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>)
240
- end
292
+ <script src="#{cdn_base_url}/mathjax/#{MATHJAX_VERSION}/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>)
293
+ end
241
294
 
242
- result << '</body>'
243
- result << '</html>'
244
- result.join LF
295
+ unless (docinfo_content = node.docinfo :footer).empty?
296
+ result << docinfo_content
245
297
  end
246
298
 
247
- def embedded node
248
- result = []
249
- if node.doctype == 'manpage'
250
- # QUESTION should notitle control the manual page title?
251
- unless node.notitle
252
- id_attr = node.id ? %( id="#{node.id}") : ''
253
- result << %(<h1#{id_attr}>#{node.doctitle} Manual Page</h1>)
254
- end
255
- result << (generate_manname_section node) if node.attr? 'manpurpose'
256
- else
257
- if node.has_header? && !node.notitle
258
- id_attr = node.id ? %( id="#{node.id}") : ''
259
- result << %(<h1#{id_attr}>#{node.header.title}</h1>)
260
- end
299
+ result << '</body>'
300
+ result << '</html>'
301
+ result.join LF
302
+ end
303
+
304
+ def convert_embedded node
305
+ result = []
306
+ if node.doctype == 'manpage'
307
+ # QUESTION should notitle control the manual page title?
308
+ unless node.notitle
309
+ id_attr = node.id ? %( id="#{node.id}") : ''
310
+ result << %(<h1#{id_attr}>#{node.doctitle} Manual Page</h1>)
261
311
  end
312
+ result << (generate_manname_section node) if node.attr? 'manpurpose'
313
+ elsif node.header? && !node.notitle
314
+ id_attr = node.id ? %( id="#{node.id}") : ''
315
+ result << %(<h1#{id_attr}>#{node.header.title}</h1>)
316
+ end
262
317
 
263
- if node.sections? && (node.attr? 'toc') && (toc_p = node.attr 'toc-placement') != 'macro' && toc_p != 'preamble'
264
- result << %(<div id="toc" class="toc">
318
+ if node.sections? && (node.attr? 'toc') && (toc_p = node.attr 'toc-placement') != 'macro' && toc_p != 'preamble'
319
+ result << %(<div id="toc" class="toc">
265
320
  <div id="toctitle">#{node.attr 'toc-title'}</div>
266
- #{outline node}
321
+ #{node.converter.convert node, 'outline'}
267
322
  </div>)
268
- end
323
+ end
269
324
 
270
- result << node.content
325
+ result << node.content
271
326
 
272
- if node.footnotes? && !(node.attr? 'nofootnotes')
273
- result << %(<div id="footnotes">
327
+ if node.footnotes? && !(node.attr? 'nofootnotes')
328
+ result << %(<div id="footnotes">
274
329
  <hr#{@void_element_slash}>)
275
- node.footnotes.each do |footnote|
276
- result << %(<div class="footnote" id="_footnotedef_#{footnote.index}">
330
+ node.footnotes.each do |footnote|
331
+ result << %(<div class="footnote" id="_footnotedef_#{footnote.index}">
277
332
  <a href="#_footnoteref_#{footnote.index}">#{footnote.index}</a>. #{footnote.text}
278
333
  </div>)
279
- end
280
- result << '</div>'
281
334
  end
282
-
283
- result.join LF
335
+ result << '</div>'
284
336
  end
285
337
 
286
- def outline node, opts = {}
287
- return unless node.sections?
288
- sectnumlevels = opts[:sectnumlevels] || (node.document.attributes['sectnumlevels'] || 3).to_i
289
- toclevels = opts[:toclevels] || (node.document.attributes['toclevels'] || 2).to_i
290
- sections = node.sections
291
- # FIXME top level is incorrect if a multipart book starts with a special section defined at level 0
292
- result = [%(<ul class="sectlevel#{sections[0].level}">)]
293
- sections.each do |section|
294
- slevel = section.level
295
- if section.caption
296
- stitle = section.captioned_title
297
- elsif section.numbered && slevel <= sectnumlevels
298
- if slevel < 2 && node.document.doctype == 'book'
299
- if section.sectname == 'chapter'
300
- stitle = %(#{(signifier = node.document.attributes['chapter-signifier']) ? "#{signifier} " : ''}#{section.sectnum} #{section.title})
301
- elsif section.sectname == 'part'
302
- stitle = %(#{(signifier = node.document.attributes['part-signifier']) ? "#{signifier} " : ''}#{section.sectnum nil, ':'} #{section.title})
303
- else
304
- stitle = %(#{section.sectnum} #{section.title})
305
- end
338
+ result.join LF
339
+ end
340
+
341
+ def convert_outline node, opts = {}
342
+ return unless node.sections?
343
+ sectnumlevels = opts[:sectnumlevels] || (node.document.attributes['sectnumlevels'] || 3).to_i
344
+ toclevels = opts[:toclevels] || (node.document.attributes['toclevels'] || 2).to_i
345
+ sections = node.sections
346
+ # FIXME top level is incorrect if a multipart book starts with a special section defined at level 0
347
+ result = [%(<ul class="sectlevel#{sections[0].level}">)]
348
+ sections.each do |section|
349
+ slevel = section.level
350
+ if section.caption
351
+ stitle = section.captioned_title
352
+ elsif section.numbered && slevel <= sectnumlevels
353
+ if slevel < 2 && node.document.doctype == 'book'
354
+ case section.sectname
355
+ when 'chapter'
356
+ stitle = %(#{(signifier = node.document.attributes['chapter-signifier']) ? "#{signifier} " : ''}#{section.sectnum} #{section.title})
357
+ when 'part'
358
+ stitle = %(#{(signifier = node.document.attributes['part-signifier']) ? "#{signifier} " : ''}#{section.sectnum nil, ':'} #{section.title})
306
359
  else
307
360
  stitle = %(#{section.sectnum} #{section.title})
308
361
  end
309
362
  else
310
- stitle = section.title
311
- end
312
- stitle = stitle.gsub DropAnchorRx, '' if stitle.include? '<a'
313
- if slevel < toclevels && (child_toc_level = outline section, :toclevels => toclevels, :sectnumlevels => sectnumlevels)
314
- result << %(<li><a href="##{section.id}">#{stitle}</a>)
315
- result << child_toc_level
316
- result << '</li>'
317
- else
318
- result << %(<li><a href="##{section.id}">#{stitle}</a></li>)
363
+ stitle = %(#{section.sectnum} #{section.title})
319
364
  end
365
+ else
366
+ stitle = section.title
367
+ end
368
+ stitle = stitle.gsub DropAnchorRx, '' if stitle.include? '<a'
369
+ if slevel < toclevels && (child_toc_level = convert_outline section, toclevels: toclevels, sectnumlevels: sectnumlevels)
370
+ result << %(<li><a href="##{section.id}">#{stitle}</a>)
371
+ result << child_toc_level
372
+ result << '</li>'
373
+ else
374
+ result << %(<li><a href="##{section.id}">#{stitle}</a></li>)
320
375
  end
321
- result << '</ul>'
322
- result.join LF
323
376
  end
377
+ result << '</ul>'
378
+ result.join LF
379
+ end
324
380
 
325
- def section node
326
- doc_attrs = node.document.attributes
327
- level = node.level
328
- if node.caption
329
- title = node.captioned_title
330
- elsif node.numbered && level <= (doc_attrs['sectnumlevels'] || 3).to_i
331
- if level < 2 && node.document.doctype == 'book'
332
- if node.sectname == 'chapter'
333
- title = %(#{(signifier = doc_attrs['chapter-signifier']) ? "#{signifier} " : ''}#{node.sectnum} #{node.title})
334
- elsif node.sectname == 'part'
335
- title = %(#{(signifier = doc_attrs['part-signifier']) ? "#{signifier} " : ''}#{node.sectnum nil, ':'} #{node.title})
336
- else
337
- title = %(#{node.sectnum} #{node.title})
338
- end
381
+ def convert_section node
382
+ doc_attrs = node.document.attributes
383
+ level = node.level
384
+ if node.caption
385
+ title = node.captioned_title
386
+ elsif node.numbered && level <= (doc_attrs['sectnumlevels'] || 3).to_i
387
+ if level < 2 && node.document.doctype == 'book'
388
+ case node.sectname
389
+ when 'chapter'
390
+ title = %(#{(signifier = doc_attrs['chapter-signifier']) ? "#{signifier} " : ''}#{node.sectnum} #{node.title})
391
+ when 'part'
392
+ title = %(#{(signifier = doc_attrs['part-signifier']) ? "#{signifier} " : ''}#{node.sectnum nil, ':'} #{node.title})
339
393
  else
340
394
  title = %(#{node.sectnum} #{node.title})
341
395
  end
342
396
  else
343
- title = node.title
397
+ title = %(#{node.sectnum} #{node.title})
344
398
  end
345
- if node.id
346
- id_attr = %( id="#{id = node.id}")
347
- if doc_attrs['sectlinks']
348
- title = %(<a class="link" href="##{id}">#{title}</a>)
349
- end
350
- if doc_attrs['sectanchors']
351
- # QUESTION should we add a font-based icon in anchor if icons=font?
352
- if doc_attrs['sectanchors'] == 'after'
353
- title = %(#{title}<a class="anchor" href="##{id}"></a>)
354
- else
355
- title = %(<a class="anchor" href="##{id}"></a>#{title})
356
- end
399
+ else
400
+ title = node.title
401
+ end
402
+ if node.id
403
+ id_attr = %( id="#{id = node.id}")
404
+ if doc_attrs['sectlinks']
405
+ title = %(<a class="link" href="##{id}">#{title}</a>)
406
+ end
407
+ if doc_attrs['sectanchors']
408
+ # QUESTION should we add a font-based icon in anchor if icons=font?
409
+ if doc_attrs['sectanchors'] == 'after'
410
+ title = %(#{title}<a class="anchor" href="##{id}"></a>)
411
+ else
412
+ title = %(<a class="anchor" href="##{id}"></a>#{title})
357
413
  end
358
- else
359
- id_attr = ''
360
414
  end
361
- if level == 0
362
- %(<h1#{id_attr} class="sect0#{(role = node.role) ? " #{role}" : ''}">#{title}</h1>
415
+ else
416
+ id_attr = ''
417
+ end
418
+ if level == 0
419
+ %(<h1#{id_attr} class="sect0#{(role = node.role) ? " #{role}" : ''}">#{title}</h1>
363
420
  #{node.content})
364
- else
365
- %(<div class="sect#{level}#{(role = node.role) ? " #{role}" : ''}">
421
+ else
422
+ %(<div class="sect#{level}#{(role = node.role) ? " #{role}" : ''}">
366
423
  <h#{level + 1}#{id_attr}>#{title}</h#{level + 1}>
367
424
  #{level == 1 ? %[<div class="sectionbody">
368
425
  #{node.content}
369
426
  </div>] : node.content}
370
427
  </div>)
371
- end
372
428
  end
429
+ end
373
430
 
374
- def admonition node
375
- id_attr = node.id ? %( id="#{node.id}") : ''
376
- name = node.attr 'name'
377
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
378
- if node.document.attr? 'icons'
379
- if (node.document.attr? 'icons', 'font') && !(node.attr? 'icon')
380
- label = %(<i class="fa icon-#{name}" title="#{node.attr 'textlabel'}"></i>)
381
- else
382
- label = %(<img src="#{node.icon_uri name}" alt="#{node.attr 'textlabel'}"#{@void_element_slash}>)
383
- end
431
+ def convert_admonition node
432
+ id_attr = node.id ? %( id="#{node.id}") : ''
433
+ name = node.attr 'name'
434
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
435
+ if node.document.attr? 'icons'
436
+ if (node.document.attr? 'icons', 'font') && !(node.attr? 'icon')
437
+ label = %(<i class="fa icon-#{name}" title="#{node.attr 'textlabel'}"></i>)
384
438
  else
385
- label = %(<div class="title">#{node.attr 'textlabel'}</div>)
439
+ label = %(<img src="#{node.icon_uri name}" alt="#{node.attr 'textlabel'}"#{@void_element_slash}>)
386
440
  end
387
- %(<div#{id_attr} class="admonitionblock #{name}#{(role = node.role) ? " #{role}" : ''}">
441
+ else
442
+ label = %(<div class="title">#{node.attr 'textlabel'}</div>)
443
+ end
444
+ %(<div#{id_attr} class="admonitionblock #{name}#{(role = node.role) ? " #{role}" : ''}">
388
445
  <table>
389
446
  <tr>
390
447
  <td class="icon">
@@ -396,853 +453,898 @@ MathJax.Hub.Config({
396
453
  </tr>
397
454
  </table>
398
455
  </div>)
399
- end
456
+ end
400
457
 
401
- def audio node
402
- xml = @xml_mode
403
- id_attribute = node.id ? %( id="#{node.id}") : ''
404
- classes = ['audioblock', node.role].compact
405
- class_attribute = %( class="#{classes.join ' '}")
406
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
407
- start_t = node.attr 'start', nil, false
408
- end_t = node.attr 'end', nil, false
409
- time_anchor = (start_t || end_t) ? %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''}) : ''
410
- %(<div#{id_attribute}#{class_attribute}>
458
+ def convert_audio node
459
+ xml = @xml_mode
460
+ id_attribute = node.id ? %( id="#{node.id}") : ''
461
+ classes = ['audioblock', node.role].compact
462
+ class_attribute = %( class="#{classes.join ' '}")
463
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
464
+ start_t = node.attr 'start'
465
+ end_t = node.attr 'end'
466
+ time_anchor = (start_t || end_t) ? %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''}) : ''
467
+ %(<div#{id_attribute}#{class_attribute}>
411
468
  #{title_element}<div class="content">
412
469
  <audio src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : ''}#{(node.option? 'nocontrols') ? '' : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : ''}>
413
470
  Your browser does not support the audio tag.
414
471
  </audio>
415
472
  </div>
416
473
  </div>)
417
- end
474
+ end
418
475
 
419
- def colist node
420
- result = []
421
- id_attribute = node.id ? %( id="#{node.id}") : ''
422
- classes = ['colist', node.style, node.role].compact
423
- class_attribute = %( class="#{classes.join ' '}")
424
-
425
- result << %(<div#{id_attribute}#{class_attribute}>)
426
- result << %(<div class="title">#{node.title}</div>) if node.title?
427
-
428
- if node.document.attr? 'icons'
429
- result << '<table>'
430
- font_icons, num = (node.document.attr? 'icons', 'font'), 0
431
- node.items.each do |item|
432
- num += 1
433
- if font_icons
434
- num_label = %(<i class="conum" data-value="#{num}"></i><b>#{num}</b>)
435
- else
436
- num_label = %(<img src="#{node.icon_uri "callouts/#{num}"}" alt="#{num}"#{@void_element_slash}>)
437
- end
438
- result << %(<tr>
476
+ def convert_colist node
477
+ result = []
478
+ id_attribute = node.id ? %( id="#{node.id}") : ''
479
+ classes = ['colist', node.style, node.role].compact
480
+ class_attribute = %( class="#{classes.join ' '}")
481
+
482
+ result << %(<div#{id_attribute}#{class_attribute}>)
483
+ result << %(<div class="title">#{node.title}</div>) if node.title?
484
+
485
+ if node.document.attr? 'icons'
486
+ result << '<table>'
487
+ font_icons, num = (node.document.attr? 'icons', 'font'), 0
488
+ node.items.each do |item|
489
+ num += 1
490
+ if font_icons
491
+ num_label = %(<i class="conum" data-value="#{num}"></i><b>#{num}</b>)
492
+ else
493
+ num_label = %(<img src="#{node.icon_uri "callouts/#{num}"}" alt="#{num}"#{@void_element_slash}>)
494
+ end
495
+ result << %(<tr>
439
496
  <td>#{num_label}</td>
440
497
  <td>#{item.text}#{item.blocks? ? LF + item.content : ''}</td>
441
498
  </tr>)
442
- end
443
- result << '</table>'
444
- else
445
- result << '<ol>'
446
- node.items.each do |item|
447
- result << %(<li>
499
+ end
500
+ result << '</table>'
501
+ else
502
+ result << '<ol>'
503
+ node.items.each do |item|
504
+ result << %(<li>
448
505
  <p>#{item.text}</p>#{item.blocks? ? LF + item.content : ''}
449
506
  </li>)
450
- end
451
- result << '</ol>'
452
507
  end
508
+ result << '</ol>'
509
+ end
453
510
 
454
- result << '</div>'
455
- result.join LF
511
+ result << '</div>'
512
+ result.join LF
513
+ end
514
+
515
+ def convert_dlist node
516
+ result = []
517
+ id_attribute = node.id ? %( id="#{node.id}") : ''
518
+
519
+ case node.style
520
+ when 'qanda'
521
+ classes = ['qlist', 'qanda', node.role]
522
+ when 'horizontal'
523
+ classes = ['hdlist', node.role]
524
+ else
525
+ classes = ['dlist', node.style, node.role]
456
526
  end
457
527
 
458
- def dlist node
459
- result = []
460
- id_attribute = node.id ? %( id="#{node.id}") : ''
528
+ class_attribute = %( class="#{classes.compact.join ' '}")
461
529
 
462
- classes = case node.style
463
- when 'qanda'
464
- ['qlist', 'qanda', node.role]
465
- when 'horizontal'
466
- ['hdlist', node.role]
467
- else
468
- ['dlist', node.style, node.role]
469
- end.compact
470
-
471
- class_attribute = %( class="#{classes.join ' '}")
472
-
473
- result << %(<div#{id_attribute}#{class_attribute}>)
474
- result << %(<div class="title">#{node.title}</div>) if node.title?
475
- case node.style
476
- when 'qanda'
477
- result << '<ol>'
478
- node.items.each do |terms, dd|
479
- result << '<li>'
480
- [*terms].each do |dt|
481
- result << %(<p><em>#{dt.text}</em></p>)
482
- end
483
- if dd
484
- result << %(<p>#{dd.text}</p>) if dd.text?
485
- result << dd.content if dd.blocks?
486
- end
487
- result << '</li>'
530
+ result << %(<div#{id_attribute}#{class_attribute}>)
531
+ result << %(<div class="title">#{node.title}</div>) if node.title?
532
+ case node.style
533
+ when 'qanda'
534
+ result << '<ol>'
535
+ node.items.each do |terms, dd|
536
+ result << '<li>'
537
+ terms.each do |dt|
538
+ result << %(<p><em>#{dt.text}</em></p>)
488
539
  end
489
- result << '</ol>'
490
- when 'horizontal'
491
- slash = @void_element_slash
492
- result << '<table>'
493
- if (node.attr? 'labelwidth') || (node.attr? 'itemwidth')
494
- result << '<colgroup>'
495
- col_style_attribute = (node.attr? 'labelwidth') ? %( style="width: #{(node.attr 'labelwidth').chomp '%'}%;") : ''
496
- result << %(<col#{col_style_attribute}#{slash}>)
497
- col_style_attribute = (node.attr? 'itemwidth') ? %( style="width: #{(node.attr 'itemwidth').chomp '%'}%;") : ''
498
- result << %(<col#{col_style_attribute}#{slash}>)
499
- result << '</colgroup>'
540
+ if dd
541
+ result << %(<p>#{dd.text}</p>) if dd.text?
542
+ result << dd.content if dd.blocks?
500
543
  end
501
- node.items.each do |terms, dd|
502
- result << '<tr>'
503
- result << %(<td class="hdlist1#{(node.option? 'strong') ? ' strong' : ''}">)
504
- terms_array = [*terms]
505
- last_term = terms_array[-1]
506
- terms_array.each do |dt|
507
- result << dt.text
508
- result << %(<br#{slash}>) if dt != last_term
509
- end
510
- result << '</td>'
511
- result << '<td class="hdlist2">'
512
- if dd
513
- result << %(<p>#{dd.text}</p>) if dd.text?
514
- result << dd.content if dd.blocks?
515
- end
516
- result << '</td>'
517
- result << '</tr>'
544
+ result << '</li>'
545
+ end
546
+ result << '</ol>'
547
+ when 'horizontal'
548
+ slash = @void_element_slash
549
+ result << '<table>'
550
+ if (node.attr? 'labelwidth') || (node.attr? 'itemwidth')
551
+ result << '<colgroup>'
552
+ col_style_attribute = (node.attr? 'labelwidth') ? %( style="width: #{(node.attr 'labelwidth').chomp '%'}%;") : ''
553
+ result << %(<col#{col_style_attribute}#{slash}>)
554
+ col_style_attribute = (node.attr? 'itemwidth') ? %( style="width: #{(node.attr 'itemwidth').chomp '%'}%;") : ''
555
+ result << %(<col#{col_style_attribute}#{slash}>)
556
+ result << '</colgroup>'
557
+ end
558
+ node.items.each do |terms, dd|
559
+ result << '<tr>'
560
+ result << %(<td class="hdlist1#{(node.option? 'strong') ? ' strong' : ''}">)
561
+ first_term = true
562
+ terms.each do |dt|
563
+ result << %(<br#{slash}>) unless first_term
564
+ result << dt.text
565
+ first_term = nil
518
566
  end
519
- result << '</table>'
520
- else
521
- result << '<dl>'
522
- dt_style_attribute = node.style ? '' : ' class="hdlist1"'
523
- node.items.each do |terms, dd|
524
- [*terms].each do |dt|
525
- result << %(<dt#{dt_style_attribute}>#{dt.text}</dt>)
526
- end
527
- if dd
528
- result << '<dd>'
529
- result << %(<p>#{dd.text}</p>) if dd.text?
530
- result << dd.content if dd.blocks?
531
- result << '</dd>'
532
- end
567
+ result << '</td>'
568
+ result << '<td class="hdlist2">'
569
+ if dd
570
+ result << %(<p>#{dd.text}</p>) if dd.text?
571
+ result << dd.content if dd.blocks?
533
572
  end
534
- result << '</dl>'
573
+ result << '</td>'
574
+ result << '</tr>'
535
575
  end
536
-
537
- result << '</div>'
538
- result.join LF
576
+ result << '</table>'
577
+ else
578
+ result << '<dl>'
579
+ dt_style_attribute = node.style ? '' : ' class="hdlist1"'
580
+ node.items.each do |terms, dd|
581
+ terms.each do |dt|
582
+ result << %(<dt#{dt_style_attribute}>#{dt.text}</dt>)
583
+ end
584
+ next unless dd
585
+ result << '<dd>'
586
+ result << %(<p>#{dd.text}</p>) if dd.text?
587
+ result << dd.content if dd.blocks?
588
+ result << '</dd>'
589
+ end
590
+ result << '</dl>'
539
591
  end
540
592
 
541
- def example node
542
- id_attribute = node.id ? %( id="#{node.id}") : ''
543
- title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : ''
593
+ result << '</div>'
594
+ result.join LF
595
+ end
544
596
 
597
+ def convert_example node
598
+ id_attribute = node.id ? %( id="#{node.id}") : ''
599
+ if node.option? 'collapsible'
600
+ class_attribute = node.role ? %( class="#{node.role}") : ''
601
+ summary_element = node.title? ? %(<summary class="title">#{node.title}</summary>) : '<summary class="title">Details</summary>'
602
+ %(<details#{id_attribute}#{class_attribute}#{(node.option? 'open') ? ' open' : ''}>
603
+ #{summary_element}
604
+ <div class="content">
605
+ #{node.content}
606
+ </div>
607
+ </details>)
608
+ else
609
+ title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : ''
545
610
  %(<div#{id_attribute} class="exampleblock#{(role = node.role) ? " #{role}" : ''}">
546
611
  #{title_element}<div class="content">
547
612
  #{node.content}
548
613
  </div>
549
614
  </div>)
550
615
  end
616
+ end
551
617
 
552
- def floating_title node
553
- tag_name = %(h#{node.level + 1})
554
- id_attribute = node.id ? %( id="#{node.id}") : ''
555
- classes = [node.style, node.role].compact
556
- %(<#{tag_name}#{id_attribute} class="#{classes.join ' '}">#{node.title}</#{tag_name}>)
557
- end
618
+ def convert_floating_title node
619
+ tag_name = %(h#{node.level + 1})
620
+ id_attribute = node.id ? %( id="#{node.id}") : ''
621
+ classes = [node.style, node.role].compact
622
+ %(<#{tag_name}#{id_attribute} class="#{classes.join ' '}">#{node.title}</#{tag_name}>)
623
+ end
558
624
 
559
- def image node
560
- target = node.attr 'target'
561
- width_attr = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
562
- height_attr = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : ''
563
- if ((node.attr? 'format', 'svg', false) || (target.include? '.svg')) && node.document.safe < SafeMode::SECURE &&
564
- ((svg = (node.option? 'inline')) || (obj = (node.option? 'interactive')))
565
- if svg
566
- img = (read_svg_contents node, target) || %(<span class="alt">#{node.alt}</span>)
567
- elsif obj
568
- fallback = (node.attr? 'fallback') ? %(<img src="#{node.image_uri(node.attr 'fallback')}" alt="#{encode_quotes node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>) : %(<span class="alt">#{node.alt}</span>)
569
- img = %(<object type="image/svg+xml" data="#{node.image_uri target}"#{width_attr}#{height_attr}>#{fallback}</object>)
570
- end
571
- end
572
- img ||= %(<img src="#{node.image_uri target}" alt="#{encode_quotes node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>)
573
- if node.attr? 'link', nil, false
574
- img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>)
625
+ def convert_image node
626
+ target = node.attr 'target'
627
+ width_attr = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
628
+ height_attr = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : ''
629
+ if ((node.attr? 'format', 'svg') || (target.include? '.svg')) && node.document.safe < SafeMode::SECURE
630
+ if node.option? 'inline'
631
+ img = (read_svg_contents node, target) || %(<span class="alt">#{node.alt}</span>)
632
+ elsif node.option? 'interactive'
633
+ fallback = (node.attr? 'fallback') ? %(<img src="#{node.image_uri node.attr 'fallback'}" alt="#{encode_attribute_value node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>) : %(<span class="alt">#{node.alt}</span>)
634
+ img = %(<object type="image/svg+xml" data="#{node.image_uri target}"#{width_attr}#{height_attr}>#{fallback}</object>)
635
+ else
636
+ img = %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>)
575
637
  end
576
- id_attr = node.id ? %( id="#{node.id}") : ''
577
- classes = ['imageblock']
578
- classes << (node.attr 'float') if node.attr? 'float'
579
- classes << %(text-#{node.attr 'align'}) if node.attr? 'align'
580
- classes << node.role if node.role
581
- class_attr = %( class="#{classes.join ' '}")
582
- title_el = node.title? ? %(\n<div class="title">#{node.captioned_title}</div>) : ''
583
- %(<div#{id_attr}#{class_attr}>
638
+ else
639
+ img = %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>)
640
+ end
641
+ img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>) if node.attr? 'link'
642
+ id_attr = node.id ? %( id="#{node.id}") : ''
643
+ classes = ['imageblock']
644
+ classes << (node.attr 'float') if node.attr? 'float'
645
+ classes << %(text-#{node.attr 'align'}) if node.attr? 'align'
646
+ classes << node.role if node.role
647
+ class_attr = %( class="#{classes.join ' '}")
648
+ title_el = node.title? ? %(\n<div class="title">#{node.captioned_title}</div>) : ''
649
+ %(<div#{id_attr}#{class_attr}>
584
650
  <div class="content">
585
651
  #{img}
586
652
  </div>#{title_el}
587
653
  </div>)
588
- end
654
+ end
589
655
 
590
- def listing node
591
- nowrap = !(node.document.attr? 'prewrap') || (node.option? 'nowrap')
592
- if node.style == 'source'
593
- if (language = node.attr 'language', nil, false)
594
- code_attrs = %( data-lang="#{language}")
595
- else
596
- code_attrs = ''
597
- end
598
- case node.document.attr 'source-highlighter'
599
- when 'coderay'
600
- pre_class = %( class="CodeRay highlight#{nowrap ? ' nowrap' : ''}")
601
- when 'pygments'
602
- if (node.document.attr? 'pygments-css', 'inline')
603
- @pygments_bg = @stylesheets.pygments_background(node.document.attr 'pygments-style') unless defined? @pygments_bg
604
- pre_class = %( class="pygments highlight#{nowrap ? ' nowrap' : ''}" style="background: #{@pygments_bg}")
605
- else
606
- pre_class = %( class="pygments highlight#{nowrap ? ' nowrap' : ''}")
607
- end
608
- when 'highlightjs', 'highlight.js'
609
- pre_class = %( class="highlightjs highlight#{nowrap ? ' nowrap' : ''}")
610
- code_attrs = %( class="language-#{language} hljs"#{code_attrs}) if language
611
- when 'prettify'
612
- pre_class = %( class="prettyprint highlight#{nowrap ? ' nowrap' : ''}#{(node.attr? 'linenums', nil, false) ? ' linenums' : ''}")
613
- code_attrs = %( class="language-#{language}"#{code_attrs}) if language
614
- when 'html-pipeline'
615
- pre_class = language ? %( lang="#{language}") : ''
616
- code_attrs = ''
617
- else
618
- pre_class = %( class="highlight#{nowrap ? ' nowrap' : ''}")
619
- code_attrs = %( class="language-#{language}"#{code_attrs}) if language
620
- end
621
- pre_start = %(<pre#{pre_class}><code#{code_attrs}>)
622
- pre_end = '</code></pre>'
656
+ def convert_listing node
657
+ nowrap = (node.option? 'nowrap') || !(node.document.attr? 'prewrap')
658
+ if node.style == 'source'
659
+ lang = node.attr 'language'
660
+ if (syntax_hl = node.document.syntax_highlighter)
661
+ opts = syntax_hl.highlight? ? {
662
+ css_mode: ((doc_attrs = node.document.attributes)[%(#{syntax_hl.name}-css)] || :class).to_sym,
663
+ style: doc_attrs[%(#{syntax_hl.name}-style)],
664
+ } : {}
665
+ opts[:nowrap] = nowrap
623
666
  else
624
- pre_start = %(<pre#{nowrap ? ' class="nowrap"' : ''}>)
625
- pre_end = '</pre>'
667
+ pre_open = %(<pre class="highlight#{nowrap ? ' nowrap' : ''}"><code#{lang ? %[ class="language-#{lang}" data-lang="#{lang}"] : ''}>)
668
+ pre_close = '</code></pre>'
626
669
  end
627
-
628
- id_attribute = node.id ? %( id="#{node.id}") : ''
629
- title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : ''
630
- %(<div#{id_attribute} class="listingblock#{(role = node.role) ? " #{role}" : ''}">
670
+ else
671
+ pre_open = %(<pre#{nowrap ? ' class="nowrap"' : ''}>)
672
+ pre_close = '</pre>'
673
+ end
674
+ id_attribute = node.id ? %( id="#{node.id}") : ''
675
+ title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : ''
676
+ %(<div#{id_attribute} class="listingblock#{(role = node.role) ? " #{role}" : ''}">
631
677
  #{title_element}<div class="content">
632
- #{pre_start}#{node.content}#{pre_end}
678
+ #{syntax_hl ? (syntax_hl.format node, lang, opts) : pre_open + (node.content || '') + pre_close}
633
679
  </div>
634
680
  </div>)
635
- end
681
+ end
636
682
 
637
- def literal node
638
- id_attribute = node.id ? %( id="#{node.id}") : ''
639
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
640
- nowrap = !(node.document.attr? 'prewrap') || (node.option? 'nowrap')
641
- %(<div#{id_attribute} class="literalblock#{(role = node.role) ? " #{role}" : ''}">
683
+ def convert_literal node
684
+ id_attribute = node.id ? %( id="#{node.id}") : ''
685
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
686
+ nowrap = !(node.document.attr? 'prewrap') || (node.option? 'nowrap')
687
+ %(<div#{id_attribute} class="literalblock#{(role = node.role) ? " #{role}" : ''}">
642
688
  #{title_element}<div class="content">
643
689
  <pre#{nowrap ? ' class="nowrap"' : ''}>#{node.content}</pre>
644
690
  </div>
645
691
  </div>)
646
- end
647
-
648
- def stem node
649
- id_attribute = node.id ? %( id="#{node.id}") : ''
650
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
651
- open, close = BLOCK_MATH_DELIMITERS[style = node.style.to_sym]
652
- equation = node.content
692
+ end
653
693
 
694
+ def convert_stem node
695
+ id_attribute = node.id ? %( id="#{node.id}") : ''
696
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
697
+ open, close = BLOCK_MATH_DELIMITERS[style = node.style.to_sym]
698
+ if (equation = node.content)
654
699
  if style == :asciimath && (equation.include? LF)
655
- br = %(<br#{@void_element_slash}>#{LF})
656
- equation = equation.gsub(StemBreakRx) { %(#{close}#{br * ($&.count LF)}#{open}) }
700
+ br = %(#{LF}<br#{@void_element_slash}>)
701
+ equation = equation.gsub(StemBreakRx) { %(#{close}#{br * (($&.count LF) - 1)}#{LF}#{open}) }
657
702
  end
658
-
659
703
  unless (equation.start_with? open) && (equation.end_with? close)
660
704
  equation = %(#{open}#{equation}#{close})
661
705
  end
662
-
663
- %(<div#{id_attribute} class="stemblock#{(role = node.role) ? " #{role}" : ''}">
706
+ else
707
+ equation = ''
708
+ end
709
+ %(<div#{id_attribute} class="stemblock#{(role = node.role) ? " #{role}" : ''}">
664
710
  #{title_element}<div class="content">
665
711
  #{equation}
666
712
  </div>
667
713
  </div>)
668
- end
669
-
670
- def olist node
671
- result = []
672
- id_attribute = node.id ? %( id="#{node.id}") : ''
673
- classes = ['olist', node.style, node.role].compact
674
- class_attribute = %( class="#{classes.join ' '}")
675
-
676
- result << %(<div#{id_attribute}#{class_attribute}>)
677
- result << %(<div class="title">#{node.title}</div>) if node.title?
678
-
679
- type_attribute = (keyword = node.list_marker_keyword) ? %( type="#{keyword}") : ''
680
- start_attribute = (node.attr? 'start') ? %( start="#{node.attr 'start'}") : ''
681
- reversed_attribute = (node.option? 'reversed') ? (append_boolean_attribute 'reversed', @xml_mode) : ''
682
- result << %(<ol class="#{node.style}"#{type_attribute}#{start_attribute}#{reversed_attribute}>)
714
+ end
683
715
 
684
- node.items.each do |item|
716
+ def convert_olist node
717
+ result = []
718
+ id_attribute = node.id ? %( id="#{node.id}") : ''
719
+ classes = ['olist', node.style, node.role].compact
720
+ class_attribute = %( class="#{classes.join ' '}")
721
+
722
+ result << %(<div#{id_attribute}#{class_attribute}>)
723
+ result << %(<div class="title">#{node.title}</div>) if node.title?
724
+
725
+ type_attribute = (keyword = node.list_marker_keyword) ? %( type="#{keyword}") : ''
726
+ start_attribute = (node.attr? 'start') ? %( start="#{node.attr 'start'}") : ''
727
+ reversed_attribute = (node.option? 'reversed') ? (append_boolean_attribute 'reversed', @xml_mode) : ''
728
+ result << %(<ol class="#{node.style}"#{type_attribute}#{start_attribute}#{reversed_attribute}>)
729
+
730
+ node.items.each do |item|
731
+ if item.id
732
+ result << %(<li id="#{item.id}"#{item.role ? %[ class="#{item.role}"] : ''}>)
733
+ elsif item.role
734
+ result << %(<li class="#{item.role}">)
735
+ else
685
736
  result << '<li>'
686
- result << %(<p>#{item.text}</p>)
687
- result << item.content if item.blocks?
688
- result << '</li>'
689
737
  end
690
-
691
- result << '</ol>'
692
- result << '</div>'
693
- result.join LF
738
+ result << %(<p>#{item.text}</p>)
739
+ result << item.content if item.blocks?
740
+ result << '</li>'
694
741
  end
695
742
 
696
- def open node
697
- if (style = node.style) == 'abstract'
698
- if node.parent == node.document && node.document.doctype == 'book'
699
- logger.warn 'abstract block cannot be used in a document without a title when doctype is book. Excluding block content.'
700
- ''
701
- else
702
- id_attr = node.id ? %( id="#{node.id}") : ''
703
- title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
704
- %(<div#{id_attr} class="quoteblock abstract#{(role = node.role) ? " #{role}" : ''}">
743
+ result << '</ol>'
744
+ result << '</div>'
745
+ result.join LF
746
+ end
747
+
748
+ def convert_open node
749
+ if (style = node.style) == 'abstract'
750
+ if node.parent == node.document && node.document.doctype == 'book'
751
+ logger.warn 'abstract block cannot be used in a document without a title when doctype is book. Excluding block content.'
752
+ ''
753
+ else
754
+ id_attr = node.id ? %( id="#{node.id}") : ''
755
+ title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
756
+ %(<div#{id_attr} class="quoteblock abstract#{(role = node.role) ? " #{role}" : ''}">
705
757
  #{title_el}<blockquote>
706
758
  #{node.content}
707
759
  </blockquote>
708
760
  </div>)
709
- end
710
- elsif style == 'partintro' && (node.level > 0 || node.parent.context != :section || node.document.doctype != 'book')
711
- logger.error 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
712
- ''
713
- else
714
- id_attr = node.id ? %( id="#{node.id}") : ''
715
- title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
716
- %(<div#{id_attr} class="openblock#{style && style != 'open' ? " #{style}" : ''}#{(role = node.role) ? " #{role}" : ''}">
761
+ end
762
+ elsif style == 'partintro' && (node.level > 0 || node.parent.context != :section || node.document.doctype != 'book')
763
+ logger.error 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
764
+ ''
765
+ else
766
+ id_attr = node.id ? %( id="#{node.id}") : ''
767
+ title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
768
+ %(<div#{id_attr} class="openblock#{style && style != 'open' ? " #{style}" : ''}#{(role = node.role) ? " #{role}" : ''}">
717
769
  #{title_el}<div class="content">
718
770
  #{node.content}
719
771
  </div>
720
772
  </div>)
721
- end
722
- end
723
-
724
- def page_break node
725
- '<div style="page-break-after: always;"></div>'
726
773
  end
774
+ end
727
775
 
728
- def paragraph node
729
- class_attribute = node.role ? %(class="paragraph #{node.role}") : 'class="paragraph"'
730
- attributes = node.id ? %(id="#{node.id}" #{class_attribute}) : class_attribute
776
+ def convert_page_break node
777
+ '<div style="page-break-after: always;"></div>'
778
+ end
731
779
 
732
- if node.title?
733
- %(<div #{attributes}>
780
+ def convert_paragraph node
781
+ if node.role
782
+ attributes = %(#{node.id ? %[ id="#{node.id}"] : ''} class="paragraph #{node.role}")
783
+ elsif node.id
784
+ attributes = %( id="#{node.id}" class="paragraph")
785
+ else
786
+ attributes = ' class="paragraph"'
787
+ end
788
+ if node.title?
789
+ %(<div#{attributes}>
734
790
  <div class="title">#{node.title}</div>
735
791
  <p>#{node.content}</p>
736
792
  </div>)
737
- else
738
- %(<div #{attributes}>
793
+ else
794
+ %(<div#{attributes}>
739
795
  <p>#{node.content}</p>
740
796
  </div>)
741
- end
742
797
  end
798
+ end
799
+
800
+ alias convert_pass content_only
743
801
 
744
- def preamble node
745
- if (doc = node.document).attr?('toc-placement', 'preamble') && doc.sections? && (doc.attr? 'toc')
746
- toc = %(
802
+ def convert_preamble node
803
+ if (doc = node.document).attr?('toc-placement', 'preamble') && doc.sections? && (doc.attr? 'toc')
804
+ toc = %(
747
805
  <div id="toc" class="#{doc.attr 'toc-class', 'toc'}">
748
806
  <div id="toctitle">#{doc.attr 'toc-title'}</div>
749
- #{outline doc}
807
+ #{doc.converter.convert doc, 'outline'}
750
808
  </div>)
751
- else
752
- toc = ''
753
- end
809
+ else
810
+ toc = ''
811
+ end
754
812
 
755
- %(<div id="preamble">
813
+ %(<div id="preamble">
756
814
  <div class="sectionbody">
757
815
  #{node.content}
758
816
  </div>#{toc}
759
817
  </div>)
760
- end
818
+ end
761
819
 
762
- def quote node
763
- id_attribute = node.id ? %( id="#{node.id}") : ''
764
- classes = ['quoteblock', node.role].compact
765
- class_attribute = %( class="#{classes.join ' '}")
766
- title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : ''
767
- attribution = (node.attr? 'attribution') ? (node.attr 'attribution') : nil
768
- citetitle = (node.attr? 'citetitle') ? (node.attr 'citetitle') : nil
769
- if attribution || citetitle
770
- cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : ''
771
- attribution_text = attribution ? %(&#8212; #{attribution}#{citetitle ? "<br#{@void_element_slash}>\n" : ''}) : ''
772
- attribution_element = %(\n<div class="attribution">\n#{attribution_text}#{cite_element}\n</div>)
773
- else
774
- attribution_element = ''
775
- end
820
+ def convert_quote node
821
+ id_attribute = node.id ? %( id="#{node.id}") : ''
822
+ classes = ['quoteblock', node.role].compact
823
+ class_attribute = %( class="#{classes.join ' '}")
824
+ title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : ''
825
+ attribution = (node.attr? 'attribution') ? (node.attr 'attribution') : nil
826
+ citetitle = (node.attr? 'citetitle') ? (node.attr 'citetitle') : nil
827
+ if attribution || citetitle
828
+ cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : ''
829
+ attribution_text = attribution ? %(&#8212; #{attribution}#{citetitle ? "<br#{@void_element_slash}>\n" : ''}) : ''
830
+ attribution_element = %(\n<div class="attribution">\n#{attribution_text}#{cite_element}\n</div>)
831
+ else
832
+ attribution_element = ''
833
+ end
776
834
 
777
- %(<div#{id_attribute}#{class_attribute}>#{title_element}
835
+ %(<div#{id_attribute}#{class_attribute}>#{title_element}
778
836
  <blockquote>
779
837
  #{node.content}
780
838
  </blockquote>#{attribution_element}
781
839
  </div>)
782
- end
840
+ end
783
841
 
784
- def thematic_break node
785
- %(<hr#{@void_element_slash}>)
786
- end
842
+ def convert_thematic_break node
843
+ %(<hr#{@void_element_slash}>)
844
+ end
787
845
 
788
- def sidebar node
789
- id_attribute = node.id ? %( id="#{node.id}") : ''
790
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
791
- %(<div#{id_attribute} class="sidebarblock#{(role = node.role) ? " #{role}" : ''}">
846
+ def convert_sidebar node
847
+ id_attribute = node.id ? %( id="#{node.id}") : ''
848
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
849
+ %(<div#{id_attribute} class="sidebarblock#{(role = node.role) ? " #{role}" : ''}">
792
850
  <div class="content">
793
851
  #{title_element}#{node.content}
794
852
  </div>
795
853
  </div>)
854
+ end
855
+
856
+ def convert_table node
857
+ result = []
858
+ id_attribute = node.id ? %( id="#{node.id}") : ''
859
+ frame = 'ends' if (frame = node.attr 'frame', 'all', 'table-frame') == 'topbot'
860
+ classes = ['tableblock', %(frame-#{frame}), %(grid-#{node.attr 'grid', 'all', 'table-grid'})]
861
+ if (stripes = node.attr 'stripes', nil, 'table-stripes')
862
+ classes << %(stripes-#{stripes})
863
+ end
864
+ style_attribute = ''
865
+ if (autowidth = node.option? 'autowidth') && !(node.attr? 'width')
866
+ classes << 'fit-content'
867
+ elsif (tablewidth = node.attr 'tablepcwidth') == 100
868
+ classes << 'stretch'
869
+ else
870
+ style_attribute = %( style="width: #{tablewidth}%;")
871
+ end
872
+ classes << (node.attr 'float') if node.attr? 'float'
873
+ if (role = node.role)
874
+ classes << role
796
875
  end
876
+ class_attribute = %( class="#{classes.join ' '}")
797
877
 
798
- def table node
799
- result = []
800
- id_attribute = node.id ? %( id="#{node.id}") : ''
801
- classes = ['tableblock', %(frame-#{node.attr 'frame', 'all'}), %(grid-#{node.attr 'grid', 'all'})]
802
- if (stripes = node.attr 'stripes')
803
- classes << %(stripes-#{stripes})
804
- end
805
- styles = []
806
- if (autowidth = node.attributes['autowidth-option']) && !(node.attr? 'width', nil, false)
807
- classes << 'fit-content'
808
- elsif (tablewidth = node.attr 'tablepcwidth') == 100
809
- classes << 'stretch'
878
+ result << %(<table#{id_attribute}#{class_attribute}#{style_attribute}>)
879
+ result << %(<caption class="title">#{node.captioned_title}</caption>) if node.title?
880
+ if (node.attr 'rowcount') > 0
881
+ slash = @void_element_slash
882
+ result << '<colgroup>'
883
+ if autowidth
884
+ result += (Array.new node.columns.size, %(<col#{slash}>))
810
885
  else
811
- styles << %(width: #{tablewidth}%;)
812
- end
813
- classes << (node.attr 'float') if node.attr? 'float'
814
- if (role = node.role)
815
- classes << role
816
- end
817
- class_attribute = %( class="#{classes.join ' '}")
818
- style_attribute = styles.empty? ? '' : %( style="#{styles.join ' '}")
819
-
820
- result << %(<table#{id_attribute}#{class_attribute}#{style_attribute}>)
821
- result << %(<caption class="title">#{node.captioned_title}</caption>) if node.title?
822
- if (node.attr 'rowcount') > 0
823
- slash = @void_element_slash
824
- result << '<colgroup>'
825
- if autowidth
826
- result += (Array.new node.columns.size, %(<col#{slash}>))
827
- else
828
- node.columns.each do |col|
829
- result << (col.attributes['autowidth-option'] ? %(<col#{slash}>) : %(<col style="width: #{col.attr 'colpcwidth'}%;"#{slash}>))
830
- end
886
+ node.columns.each do |col|
887
+ result << ((col.option? 'autowidth') ? %(<col#{slash}>) : %(<col style="width: #{col.attr 'colpcwidth'}%;"#{slash}>))
831
888
  end
832
- result << '</colgroup>'
833
- node.rows.by_section.each do |tsec, rows|
834
- next if rows.empty?
835
- result << %(<t#{tsec}>)
836
- rows.each do |row|
837
- result << '<tr>'
838
- row.each do |cell|
839
- if tsec == :head
840
- cell_content = cell.text
889
+ end
890
+ result << '</colgroup>'
891
+ node.rows.to_h.each do |tsec, rows|
892
+ next if rows.empty?
893
+ result << %(<t#{tsec}>)
894
+ rows.each do |row|
895
+ result << '<tr>'
896
+ row.each do |cell|
897
+ if tsec == :head
898
+ cell_content = cell.text
899
+ else
900
+ case cell.style
901
+ when :asciidoc
902
+ cell_content = %(<div class="content">#{cell.content}</div>)
903
+ when :literal
904
+ cell_content = %(<div class="literal"><pre>#{cell.text}</pre></div>)
841
905
  else
842
- case cell.style
843
- when :asciidoc
844
- cell_content = %(<div class="content">#{cell.content}</div>)
845
- when :verse
846
- cell_content = %(<div class="verse">#{cell.text}</div>)
847
- when :literal
848
- cell_content = %(<div class="literal"><pre>#{cell.text}</pre></div>)
849
- else
850
- cell_content = (cell_content = cell.content).empty? ? '' : %(<p class="tableblock">#{cell_content.join '</p>
906
+ cell_content = (cell_content = cell.content).empty? ? '' : %(<p class="tableblock">#{cell_content.join '</p>
851
907
  <p class="tableblock">'}</p>)
852
- end
853
908
  end
854
-
855
- cell_tag_name = (tsec == :head || cell.style == :header ? 'th' : 'td')
856
- cell_class_attribute = %( class="tableblock halign-#{cell.attr 'halign'} valign-#{cell.attr 'valign'}")
857
- cell_colspan_attribute = cell.colspan ? %( colspan="#{cell.colspan}") : ''
858
- cell_rowspan_attribute = cell.rowspan ? %( rowspan="#{cell.rowspan}") : ''
859
- cell_style_attribute = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'};") : ''
860
- result << %(<#{cell_tag_name}#{cell_class_attribute}#{cell_colspan_attribute}#{cell_rowspan_attribute}#{cell_style_attribute}>#{cell_content}</#{cell_tag_name}>)
861
909
  end
862
- result << '</tr>'
910
+
911
+ cell_tag_name = (tsec == :head || cell.style == :header ? 'th' : 'td')
912
+ cell_class_attribute = %( class="tableblock halign-#{cell.attr 'halign'} valign-#{cell.attr 'valign'}")
913
+ cell_colspan_attribute = cell.colspan ? %( colspan="#{cell.colspan}") : ''
914
+ cell_rowspan_attribute = cell.rowspan ? %( rowspan="#{cell.rowspan}") : ''
915
+ cell_style_attribute = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'};") : ''
916
+ result << %(<#{cell_tag_name}#{cell_class_attribute}#{cell_colspan_attribute}#{cell_rowspan_attribute}#{cell_style_attribute}>#{cell_content}</#{cell_tag_name}>)
863
917
  end
864
- result << %(</t#{tsec}>)
918
+ result << '</tr>'
865
919
  end
920
+ result << %(</t#{tsec}>)
866
921
  end
867
- result << '</table>'
868
- result.join LF
869
922
  end
923
+ result << '</table>'
924
+ result.join LF
925
+ end
870
926
 
871
- def toc node
872
- unless (doc = node.document).attr?('toc-placement', 'macro') && doc.sections? && (doc.attr? 'toc')
873
- return '<!-- toc disabled -->'
874
- end
927
+ def convert_toc node
928
+ unless (doc = node.document).attr?('toc-placement', 'macro') && doc.sections? && (doc.attr? 'toc')
929
+ return '<!-- toc disabled -->'
930
+ end
875
931
 
876
- if node.id
877
- id_attr = %( id="#{node.id}")
878
- title_id_attr = %( id="#{node.id}title")
879
- else
880
- id_attr = ' id="toc"'
881
- title_id_attr = ' id="toctitle"'
882
- end
883
- title = node.title? ? node.title : (doc.attr 'toc-title')
884
- levels = (node.attr? 'levels') ? (node.attr 'levels').to_i : nil
885
- role = node.role? ? node.role : (doc.attr 'toc-class', 'toc')
932
+ if node.id
933
+ id_attr = %( id="#{node.id}")
934
+ title_id_attr = %( id="#{node.id}title")
935
+ else
936
+ id_attr = ' id="toc"'
937
+ title_id_attr = ' id="toctitle"'
938
+ end
939
+ title = node.title? ? node.title : (doc.attr 'toc-title')
940
+ levels = (node.attr? 'levels') ? (node.attr 'levels').to_i : nil
941
+ role = node.role? ? node.role : (doc.attr 'toc-class', 'toc')
886
942
 
887
- %(<div#{id_attr} class="#{role}">
943
+ %(<div#{id_attr} class="#{role}">
888
944
  <div#{title_id_attr} class="title">#{title}</div>
889
- #{outline doc, :toclevels => levels}
945
+ #{doc.converter.convert doc, 'outline', toclevels: levels}
890
946
  </div>)
891
- end
947
+ end
892
948
 
893
- def ulist node
894
- result = []
895
- id_attribute = node.id ? %( id="#{node.id}") : ''
896
- div_classes = ['ulist', node.style, node.role].compact
897
- marker_checked = marker_unchecked = ''
898
- if (checklist = node.option? 'checklist')
899
- div_classes.unshift div_classes.shift, 'checklist'
900
- ul_class_attribute = ' class="checklist"'
901
- if node.option? 'interactive'
902
- if @xml_mode
903
- marker_checked = '<input type="checkbox" data-item-complete="1" checked="checked"/> '
904
- marker_unchecked = '<input type="checkbox" data-item-complete="0"/> '
905
- else
906
- marker_checked = '<input type="checkbox" data-item-complete="1" checked> '
907
- marker_unchecked = '<input type="checkbox" data-item-complete="0"> '
908
- end
949
+ def convert_ulist node
950
+ result = []
951
+ id_attribute = node.id ? %( id="#{node.id}") : ''
952
+ div_classes = ['ulist', node.style, node.role].compact
953
+ marker_checked = marker_unchecked = ''
954
+ if (checklist = node.option? 'checklist')
955
+ div_classes.unshift div_classes.shift, 'checklist'
956
+ ul_class_attribute = ' class="checklist"'
957
+ if node.option? 'interactive'
958
+ if @xml_mode
959
+ marker_checked = '<input type="checkbox" data-item-complete="1" checked="checked"/> '
960
+ marker_unchecked = '<input type="checkbox" data-item-complete="0"/> '
909
961
  else
910
- if node.document.attr? 'icons', 'font'
911
- marker_checked = '<i class="fa fa-check-square-o"></i> '
912
- marker_unchecked = '<i class="fa fa-square-o"></i> '
913
- else
914
- marker_checked = '&#10003; '
915
- marker_unchecked = '&#10063; '
916
- end
962
+ marker_checked = '<input type="checkbox" data-item-complete="1" checked> '
963
+ marker_unchecked = '<input type="checkbox" data-item-complete="0"> '
917
964
  end
965
+ elsif node.document.attr? 'icons', 'font'
966
+ marker_checked = '<i class="fa fa-check-square-o"></i> '
967
+ marker_unchecked = '<i class="fa fa-square-o"></i> '
918
968
  else
919
- ul_class_attribute = node.style ? %( class="#{node.style}") : ''
969
+ marker_checked = '&#10003; '
970
+ marker_unchecked = '&#10063; '
920
971
  end
921
- result << %(<div#{id_attribute} class="#{div_classes.join ' '}">)
922
- result << %(<div class="title">#{node.title}</div>) if node.title?
923
- result << %(<ul#{ul_class_attribute}>)
924
-
925
- node.items.each do |item|
972
+ else
973
+ ul_class_attribute = node.style ? %( class="#{node.style}") : ''
974
+ end
975
+ result << %(<div#{id_attribute} class="#{div_classes.join ' '}">)
976
+ result << %(<div class="title">#{node.title}</div>) if node.title?
977
+ result << %(<ul#{ul_class_attribute}>)
978
+
979
+ node.items.each do |item|
980
+ if item.id
981
+ result << %(<li id="#{item.id}"#{item.role ? %[ class="#{item.role}"] : ''}>)
982
+ elsif item.role
983
+ result << %(<li class="#{item.role}">)
984
+ else
926
985
  result << '<li>'
927
- if checklist && (item.attr? 'checkbox')
928
- result << %(<p>#{(item.attr? 'checked') ? marker_checked : marker_unchecked}#{item.text}</p>)
929
- else
930
- result << %(<p>#{item.text}</p>)
931
- end
932
- result << item.content if item.blocks?
933
- result << '</li>'
934
986
  end
935
-
936
- result << '</ul>'
937
- result << '</div>'
938
- result.join LF
939
- end
940
-
941
- def verse node
942
- id_attribute = node.id ? %( id="#{node.id}") : ''
943
- classes = ['verseblock', node.role].compact
944
- class_attribute = %( class="#{classes.join ' '}")
945
- title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : ''
946
- attribution = (node.attr? 'attribution') ? (node.attr 'attribution') : nil
947
- citetitle = (node.attr? 'citetitle') ? (node.attr 'citetitle') : nil
948
- if attribution || citetitle
949
- cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : ''
950
- attribution_text = attribution ? %(&#8212; #{attribution}#{citetitle ? "<br#{@void_element_slash}>\n" : ''}) : ''
951
- attribution_element = %(\n<div class="attribution">\n#{attribution_text}#{cite_element}\n</div>)
987
+ if checklist && (item.attr? 'checkbox')
988
+ result << %(<p>#{(item.attr? 'checked') ? marker_checked : marker_unchecked}#{item.text}</p>)
952
989
  else
953
- attribution_element = ''
990
+ result << %(<p>#{item.text}</p>)
954
991
  end
992
+ result << item.content if item.blocks?
993
+ result << '</li>'
994
+ end
955
995
 
956
- %(<div#{id_attribute}#{class_attribute}>#{title_element}
996
+ result << '</ul>'
997
+ result << '</div>'
998
+ result.join LF
999
+ end
1000
+
1001
+ def convert_verse node
1002
+ id_attribute = node.id ? %( id="#{node.id}") : ''
1003
+ classes = ['verseblock', node.role].compact
1004
+ class_attribute = %( class="#{classes.join ' '}")
1005
+ title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : ''
1006
+ attribution = (node.attr? 'attribution') ? (node.attr 'attribution') : nil
1007
+ citetitle = (node.attr? 'citetitle') ? (node.attr 'citetitle') : nil
1008
+ if attribution || citetitle
1009
+ cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : ''
1010
+ attribution_text = attribution ? %(&#8212; #{attribution}#{citetitle ? "<br#{@void_element_slash}>\n" : ''}) : ''
1011
+ attribution_element = %(\n<div class="attribution">\n#{attribution_text}#{cite_element}\n</div>)
1012
+ else
1013
+ attribution_element = ''
1014
+ end
1015
+
1016
+ %(<div#{id_attribute}#{class_attribute}>#{title_element}
957
1017
  <pre class="content">#{node.content}</pre>#{attribution_element}
958
1018
  </div>)
959
- end
1019
+ end
960
1020
 
961
- def video node
962
- xml = @xml_mode
963
- id_attribute = node.id ? %( id="#{node.id}") : ''
964
- classes = ['videoblock']
965
- classes << (node.attr 'float') if node.attr? 'float'
966
- classes << %(text-#{node.attr 'align'}) if node.attr? 'align'
967
- classes << node.role if node.role
968
- class_attribute = %( class="#{classes.join ' '}")
969
- title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : ''
970
- width_attribute = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
971
- height_attribute = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : ''
972
- case node.attr 'poster'
973
- when 'vimeo'
974
- unless (asset_uri_scheme = (node.document.attr 'asset-uri-scheme', 'https')).empty?
975
- asset_uri_scheme = %(#{asset_uri_scheme}:)
976
- end
977
- start_anchor = (node.attr? 'start', nil, false) ? %(#at=#{node.attr 'start'}) : ''
978
- delimiter = '?'
979
- if node.option? 'autoplay'
980
- autoplay_param = %(#{delimiter}autoplay=1)
981
- delimiter = '&amp;'
982
- else
983
- autoplay_param = ''
984
- end
985
- loop_param = (node.option? 'loop') ? %(#{delimiter}loop=1) : ''
986
- %(<div#{id_attribute}#{class_attribute}>#{title_element}
1021
+ def convert_video node
1022
+ xml = @xml_mode
1023
+ id_attribute = node.id ? %( id="#{node.id}") : ''
1024
+ classes = ['videoblock']
1025
+ classes << (node.attr 'float') if node.attr? 'float'
1026
+ classes << %(text-#{node.attr 'align'}) if node.attr? 'align'
1027
+ classes << node.role if node.role
1028
+ class_attribute = %( class="#{classes.join ' '}")
1029
+ title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : ''
1030
+ width_attribute = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
1031
+ height_attribute = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : ''
1032
+ case node.attr 'poster'
1033
+ when 'vimeo'
1034
+ unless (asset_uri_scheme = (node.document.attr 'asset-uri-scheme', 'https')).empty?
1035
+ asset_uri_scheme = %(#{asset_uri_scheme}:)
1036
+ end
1037
+ start_anchor = (node.attr? 'start') ? %(#at=#{node.attr 'start'}) : ''
1038
+ delimiter = ['?']
1039
+ target, hash = (node.attr 'target').split '/', 2
1040
+ hash_param = (hash ||= node.attr 'hash') ? %(#{delimiter.pop || '&amp;'}h=#{hash}) : ''
1041
+ autoplay_param = (node.option? 'autoplay') ? %(#{delimiter.pop || '&amp;'}autoplay=1) : ''
1042
+ loop_param = (node.option? 'loop') ? %(#{delimiter.pop || '&amp;'}loop=1) : ''
1043
+ muted_param = (node.option? 'muted') ? %(#{delimiter.pop || '&amp;'}muted=1) : ''
1044
+ %(<div#{id_attribute}#{class_attribute}>#{title_element}
987
1045
  <div class="content">
988
- <iframe#{width_attribute}#{height_attribute} src="#{asset_uri_scheme}//player.vimeo.com/video/#{node.attr 'target'}#{start_anchor}#{autoplay_param}#{loop_param}" frameborder="0"#{(node.option? 'nofullscreen') ? '' : (append_boolean_attribute 'allowfullscreen', xml)}></iframe>
1046
+ <iframe#{width_attribute}#{height_attribute} src="#{asset_uri_scheme}//player.vimeo.com/video/#{target}#{hash_param}#{autoplay_param}#{loop_param}#{muted_param}#{start_anchor}" frameborder="0"#{(node.option? 'nofullscreen') ? '' : (append_boolean_attribute 'allowfullscreen', xml)}></iframe>
989
1047
  </div>
990
1048
  </div>)
991
- when 'youtube'
992
- unless (asset_uri_scheme = (node.document.attr 'asset-uri-scheme', 'https')).empty?
993
- asset_uri_scheme = %(#{asset_uri_scheme}:)
994
- end
995
- rel_param_val = (node.option? 'related') ? 1 : 0
996
- # NOTE start and end must be seconds (t parameter allows XmYs where X is minutes and Y is seconds)
997
- start_param = (node.attr? 'start', nil, false) ? %(&amp;start=#{node.attr 'start'}) : ''
998
- end_param = (node.attr? 'end', nil, false) ? %(&amp;end=#{node.attr 'end'}) : ''
999
- autoplay_param = (node.option? 'autoplay') ? '&amp;autoplay=1' : ''
1000
- loop_param = (has_loop_param = node.option? 'loop') ? '&amp;loop=1' : ''
1001
- controls_param = (node.option? 'nocontrols') ? '&amp;controls=0' : ''
1002
- # cover both ways of controlling fullscreen option
1003
- if node.option? 'nofullscreen'
1004
- fs_param = '&amp;fs=0'
1005
- fs_attribute = ''
1006
- else
1007
- fs_param = ''
1008
- fs_attribute = append_boolean_attribute 'allowfullscreen', xml
1009
- end
1010
- modest_param = (node.option? 'modest') ? '&amp;modestbranding=1' : ''
1011
- theme_param = (node.attr? 'theme', nil, false) ? %(&amp;theme=#{node.attr 'theme'}) : ''
1012
- hl_param = (node.attr? 'lang') ? %(&amp;hl=#{node.attr 'lang'}) : ''
1013
-
1014
- # parse video_id/list_id syntax where list_id (i.e., playlist) is optional
1015
- target, list = (node.attr 'target').split '/', 2
1016
- if (list ||= (node.attr 'list', nil, false))
1017
- list_param = %(&amp;list=#{list})
1049
+ when 'youtube'
1050
+ unless (asset_uri_scheme = (node.document.attr 'asset-uri-scheme', 'https')).empty?
1051
+ asset_uri_scheme = %(#{asset_uri_scheme}:)
1052
+ end
1053
+ rel_param_val = (node.option? 'related') ? 1 : 0
1054
+ # NOTE start and end must be seconds (t parameter allows XmYs where X is minutes and Y is seconds)
1055
+ start_param = (node.attr? 'start') ? %(&amp;start=#{node.attr 'start'}) : ''
1056
+ end_param = (node.attr? 'end') ? %(&amp;end=#{node.attr 'end'}) : ''
1057
+ autoplay_param = (node.option? 'autoplay') ? '&amp;autoplay=1' : ''
1058
+ loop_param = (has_loop_param = node.option? 'loop') ? '&amp;loop=1' : ''
1059
+ mute_param = (node.option? 'muted') ? '&amp;mute=1' : ''
1060
+ controls_param = (node.option? 'nocontrols') ? '&amp;controls=0' : ''
1061
+ # cover both ways of controlling fullscreen option
1062
+ if node.option? 'nofullscreen'
1063
+ fs_param = '&amp;fs=0'
1064
+ fs_attribute = ''
1065
+ else
1066
+ fs_param = ''
1067
+ fs_attribute = append_boolean_attribute 'allowfullscreen', xml
1068
+ end
1069
+ modest_param = (node.option? 'modest') ? '&amp;modestbranding=1' : ''
1070
+ theme_param = (node.attr? 'theme') ? %(&amp;theme=#{node.attr 'theme'}) : ''
1071
+ hl_param = (node.attr? 'lang') ? %(&amp;hl=#{node.attr 'lang'}) : ''
1072
+
1073
+ # parse video_id/list_id syntax where list_id (i.e., playlist) is optional
1074
+ target, list = (node.attr 'target').split '/', 2
1075
+ if (list ||= (node.attr 'list'))
1076
+ list_param = %(&amp;list=#{list})
1077
+ else
1078
+ # parse dynamic playlist syntax: video_id1,video_id2,...
1079
+ target, playlist = target.split ',', 2
1080
+ if (playlist ||= (node.attr 'playlist'))
1081
+ # INFO playlist bar doesn't appear in Firefox unless showinfo=1 and modestbranding=1
1082
+ list_param = %(&amp;playlist=#{target},#{playlist})
1018
1083
  else
1019
- # parse dynamic playlist syntax: video_id1,video_id2,...
1020
- target, playlist = target.split ',', 2
1021
- if (playlist ||= (node.attr 'playlist', nil, false))
1022
- # INFO playlist bar doesn't appear in Firefox unless showinfo=1 and modestbranding=1
1023
- list_param = %(&amp;playlist=#{playlist})
1024
- else
1025
- # NOTE for loop to work, playlist must be specified; use VIDEO_ID if there's no explicit playlist
1026
- list_param = has_loop_param ? %(&amp;playlist=#{target}) : ''
1027
- end
1084
+ # NOTE for loop to work, playlist must be specified; use VIDEO_ID if there's no explicit playlist
1085
+ list_param = has_loop_param ? %(&amp;playlist=#{target}) : ''
1028
1086
  end
1087
+ end
1029
1088
 
1030
- %(<div#{id_attribute}#{class_attribute}>#{title_element}
1089
+ %(<div#{id_attribute}#{class_attribute}>#{title_element}
1031
1090
  <div class="content">
1032
- <iframe#{width_attribute}#{height_attribute} src="#{asset_uri_scheme}//www.youtube.com/embed/#{target}?rel=#{rel_param_val}#{start_param}#{end_param}#{autoplay_param}#{loop_param}#{controls_param}#{list_param}#{fs_param}#{modest_param}#{theme_param}#{hl_param}" frameborder="0"#{fs_attribute}></iframe>
1091
+ <iframe#{width_attribute}#{height_attribute} src="#{asset_uri_scheme}//www.youtube.com/embed/#{target}?rel=#{rel_param_val}#{start_param}#{end_param}#{autoplay_param}#{loop_param}#{mute_param}#{controls_param}#{list_param}#{fs_param}#{modest_param}#{theme_param}#{hl_param}" frameborder="0"#{fs_attribute}></iframe>
1033
1092
  </div>
1034
1093
  </div>)
1035
- else
1036
- poster_attribute = (val = node.attr 'poster', nil, false).nil_or_empty? ? '' : %( poster="#{node.media_uri val}")
1037
- preload_attribute = (val = node.attr 'preload', nil, false).nil_or_empty? ? '' : %( preload="#{val}")
1038
- start_t = node.attr 'start', nil, false
1039
- end_t = node.attr 'end', nil, false
1040
- time_anchor = (start_t || end_t) ? %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''}) : ''
1041
- %(<div#{id_attribute}#{class_attribute}>#{title_element}
1094
+ else
1095
+ poster_attribute = (val = node.attr 'poster').nil_or_empty? ? '' : %( poster="#{node.media_uri val}")
1096
+ preload_attribute = (val = node.attr 'preload').nil_or_empty? ? '' : %( preload="#{val}")
1097
+ start_t = node.attr 'start'
1098
+ end_t = node.attr 'end'
1099
+ time_anchor = (start_t || end_t) ? %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''}) : ''
1100
+ %(<div#{id_attribute}#{class_attribute}>#{title_element}
1042
1101
  <div class="content">
1043
- <video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : ''}#{(node.option? 'nocontrols') ? '' : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : ''}#{preload_attribute}>
1102
+ <video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : ''}#{(node.option? 'muted') ? (append_boolean_attribute 'muted', xml) : ''}#{(node.option? 'nocontrols') ? '' : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : ''}#{preload_attribute}>
1044
1103
  Your browser does not support the video tag.
1045
1104
  </video>
1046
1105
  </div>
1047
1106
  </div>)
1048
- end
1049
1107
  end
1108
+ end
1050
1109
 
1051
- def inline_anchor node
1052
- case node.type
1053
- when :xref
1054
- if (path = node.attributes['path'])
1055
- attrs = (append_link_constraint_attrs node, node.role ? [%( class="#{node.role}")] : []).join
1056
- text = node.text || path
1057
- else
1058
- attrs = node.role ? %( class="#{node.role}") : ''
1059
- unless (text = node.text)
1060
- refid = node.attributes['refid']
1061
- if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid])
1062
- text = (ref.xreftext node.attr('xrefstyle')) || %([#{refid}])
1110
+ def convert_inline_anchor node
1111
+ case node.type
1112
+ when :xref
1113
+ if (path = node.attributes['path'])
1114
+ attrs = (append_link_constraint_attrs node, node.role ? [%( class="#{node.role}")] : []).join
1115
+ text = node.text || path
1116
+ else
1117
+ attrs = node.role ? %( class="#{node.role}") : ''
1118
+ unless (text = node.text)
1119
+ if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid = node.attributes['refid']] || (refid.nil_or_empty? ? (top = get_root_document node) : nil))
1120
+ if (@resolving_xref ||= (outer = true)) && outer
1121
+ if (text = ref.xreftext node.attr 'xrefstyle', nil, true)
1122
+ text = text.gsub DropAnchorRx, '' if text.include? '<a'
1123
+ else
1124
+ text = top ? '[^top]' : %([#{refid}])
1125
+ end
1126
+ @resolving_xref = nil
1063
1127
  else
1064
- text = %([#{refid}])
1128
+ text = top ? '[^top]' : %([#{refid}])
1065
1129
  end
1130
+ else
1131
+ text = %([#{refid}])
1066
1132
  end
1067
1133
  end
1068
- %(<a href="#{node.target}"#{attrs}>#{text}</a>)
1069
- when :ref
1070
- %(<a id="#{node.id}"></a>)
1071
- when :link
1072
- attrs = node.id ? [%( id="#{node.id}")] : []
1073
- attrs << %( class="#{node.role}") if node.role
1074
- attrs << %( title="#{node.attr 'title'}") if node.attr? 'title', nil, false
1075
- %(<a href="#{node.target}"#{(append_link_constraint_attrs node, attrs).join}>#{node.text}</a>)
1076
- when :bibref
1077
- # NOTE technically node.text should be node.reftext, but subs have already been applied to text
1078
- %(<a id="#{node.id}"></a>#{node.text})
1079
- else
1080
- logger.warn %(unknown anchor type: #{node.type.inspect})
1081
- nil
1082
1134
  end
1135
+ %(<a href="#{node.target}"#{attrs}>#{text}</a>)
1136
+ when :ref
1137
+ %(<a id="#{node.id}"></a>)
1138
+ when :link
1139
+ attrs = node.id ? [%( id="#{node.id}")] : []
1140
+ attrs << %( class="#{node.role}") if node.role
1141
+ attrs << %( title="#{node.attr 'title'}") if node.attr? 'title'
1142
+ %(<a href="#{node.target}"#{(append_link_constraint_attrs node, attrs).join}>#{node.text}</a>)
1143
+ when :bibref
1144
+ %(<a id="#{node.id}"></a>[#{node.reftext || node.id}])
1145
+ else
1146
+ logger.warn %(unknown anchor type: #{node.type.inspect})
1147
+ nil
1083
1148
  end
1149
+ end
1084
1150
 
1085
- def inline_break node
1086
- %(#{node.text}<br#{@void_element_slash}>)
1087
- end
1151
+ def convert_inline_break node
1152
+ %(#{node.text}<br#{@void_element_slash}>)
1153
+ end
1088
1154
 
1089
- def inline_button node
1090
- %(<b class="button">#{node.text}</b>)
1091
- end
1155
+ def convert_inline_button node
1156
+ %(<b class="button">#{node.text}</b>)
1157
+ end
1092
1158
 
1093
- def inline_callout node
1094
- if node.document.attr? 'icons', 'font'
1095
- %(<i class="conum" data-value="#{node.text}"></i><b>(#{node.text})</b>)
1096
- elsif node.document.attr? 'icons'
1097
- src = node.icon_uri("callouts/#{node.text}")
1098
- %(<img src="#{src}" alt="#{node.text}"#{@void_element_slash}>)
1099
- else
1100
- %(#{node.attributes['guard']}<b class="conum">(#{node.text})</b>)
1101
- end
1159
+ def convert_inline_callout node
1160
+ if node.document.attr? 'icons', 'font'
1161
+ %(<i class="conum" data-value="#{node.text}"></i><b>(#{node.text})</b>)
1162
+ elsif node.document.attr? 'icons'
1163
+ src = node.icon_uri("callouts/#{node.text}")
1164
+ %(<img src="#{src}" alt="#{node.text}"#{@void_element_slash}>)
1165
+ elsif ::Array === (guard = node.attributes['guard'])
1166
+ %(&lt;!--<b class="conum">(#{node.text})</b>--&gt;)
1167
+ else
1168
+ %(#{guard}<b class="conum">(#{node.text})</b>)
1102
1169
  end
1170
+ end
1103
1171
 
1104
- def inline_footnote node
1105
- if (index = node.attr 'index', nil, false)
1106
- if node.type == :xref
1107
- %(<sup class="footnoteref">[<a class="footnote" href="#_footnotedef_#{index}" title="View footnote.">#{index}</a>]</sup>)
1108
- else
1109
- id_attr = node.id ? %( id="_footnote_#{node.id}") : ''
1110
- %(<sup class="footnote"#{id_attr}>[<a id="_footnoteref_#{index}" class="footnote" href="#_footnotedef_#{index}" title="View footnote.">#{index}</a>]</sup>)
1111
- end
1112
- elsif node.type == :xref
1113
- %(<sup class="footnoteref red" title="Unresolved footnote reference.">[#{node.text}]</sup>)
1172
+ def convert_inline_footnote node
1173
+ if (index = node.attr 'index')
1174
+ if node.type == :xref
1175
+ %(<sup class="footnoteref">[<a class="footnote" href="#_footnotedef_#{index}" title="View footnote.">#{index}</a>]</sup>)
1176
+ else
1177
+ id_attr = node.id ? %( id="_footnote_#{node.id}") : ''
1178
+ %(<sup class="footnote"#{id_attr}>[<a id="_footnoteref_#{index}" class="footnote" href="#_footnotedef_#{index}" title="View footnote.">#{index}</a>]</sup>)
1114
1179
  end
1180
+ elsif node.type == :xref
1181
+ %(<sup class="footnoteref red" title="Unresolved footnote reference.">[#{node.text}]</sup>)
1115
1182
  end
1183
+ end
1116
1184
 
1117
- def inline_image node
1118
- if (type = node.type) == 'icon' && (node.document.attr? 'icons', 'font')
1119
- class_attr_val = %(fa fa-#{node.target})
1120
- {'size' => 'fa-', 'rotate' => 'fa-rotate-', 'flip' => 'fa-flip-'}.each do |key, prefix|
1121
- class_attr_val = %(#{class_attr_val} #{prefix}#{node.attr key}) if node.attr? key
1185
+ def convert_inline_image node
1186
+ target = node.target
1187
+ if (type = node.type || 'image') == 'icon'
1188
+ if (icons = node.document.attr 'icons') == 'font'
1189
+ i_class_attr_val = %(fa fa-#{target})
1190
+ i_class_attr_val = %(#{i_class_attr_val} fa-#{node.attr 'size'}) if node.attr? 'size'
1191
+ if node.attr? 'flip'
1192
+ i_class_attr_val = %(#{i_class_attr_val} fa-flip-#{node.attr 'flip'})
1193
+ elsif node.attr? 'rotate'
1194
+ i_class_attr_val = %(#{i_class_attr_val} fa-rotate-#{node.attr 'rotate'})
1122
1195
  end
1123
- title_attr = (node.attr? 'title') ? %( title="#{node.attr 'title'}") : ''
1124
- img = %(<i class="#{class_attr_val}"#{title_attr}></i>)
1125
- elsif type == 'icon' && !(node.document.attr? 'icons')
1126
- img = %([#{node.alt}])
1196
+ attrs = (node.attr? 'title') ? %( title="#{node.attr 'title'}") : ''
1197
+ img = %(<i class="#{i_class_attr_val}"#{attrs}></i>)
1198
+ elsif icons
1199
+ attrs = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
1200
+ attrs = %(#{attrs} height="#{node.attr 'height'}") if node.attr? 'height'
1201
+ attrs = %(#{attrs} title="#{node.attr 'title'}") if node.attr? 'title'
1202
+ img = %(<img src="#{node.icon_uri target}" alt="#{encode_attribute_value node.alt}"#{attrs}#{@void_element_slash}>)
1127
1203
  else
1128
- target = node.target
1129
- attrs = ['width', 'height', 'title'].map {|name| (node.attr? name) ? %( #{name}="#{node.attr name}") : '' }.join
1130
- if type != 'icon' && ((node.attr? 'format', 'svg', false) || (target.include? '.svg')) &&
1131
- node.document.safe < SafeMode::SECURE && ((svg = (node.option? 'inline')) || (obj = (node.option? 'interactive')))
1132
- if svg
1133
- img = (read_svg_contents node, target) || %(<span class="alt">#{node.alt}</span>)
1134
- elsif obj
1135
- fallback = (node.attr? 'fallback') ? %(<img src="#{node.image_uri(node.attr 'fallback')}" alt="#{encode_quotes node.alt}"#{attrs}#{@void_element_slash}>) : %(<span class="alt">#{node.alt}</span>)
1136
- img = %(<object type="image/svg+xml" data="#{node.image_uri target}"#{attrs}>#{fallback}</object>)
1137
- end
1138
- end
1139
- img ||= %(<img src="#{type == 'icon' ? (node.icon_uri target) : (node.image_uri target)}" alt="#{encode_quotes node.alt}"#{attrs}#{@void_element_slash}>)
1140
- end
1141
- if node.attr? 'link', nil, false
1142
- img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>)
1204
+ img = %([#{node.alt}&#93;)
1143
1205
  end
1144
- if (role = node.role)
1145
- if node.attr? 'float'
1146
- class_attr_val = %(#{type} #{node.attr 'float'} #{role})
1206
+ else
1207
+ attrs = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
1208
+ attrs = %(#{attrs} height="#{node.attr 'height'}") if node.attr? 'height'
1209
+ attrs = %(#{attrs} title="#{node.attr 'title'}") if node.attr? 'title'
1210
+ if ((node.attr? 'format', 'svg') || (target.include? '.svg')) && node.document.safe < SafeMode::SECURE
1211
+ if node.option? 'inline'
1212
+ img = (read_svg_contents node, target) || %(<span class="alt">#{node.alt}</span>)
1213
+ elsif node.option? 'interactive'
1214
+ fallback = (node.attr? 'fallback') ? %(<img src="#{node.image_uri node.attr 'fallback'}" alt="#{encode_attribute_value node.alt}"#{attrs}#{@void_element_slash}>) : %(<span class="alt">#{node.alt}</span>)
1215
+ img = %(<object type="image/svg+xml" data="#{node.image_uri target}"#{attrs}>#{fallback}</object>)
1147
1216
  else
1148
- class_attr_val = %(#{type} #{role})
1217
+ img = %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{attrs}#{@void_element_slash}>)
1149
1218
  end
1150
- elsif node.attr? 'float'
1151
- class_attr_val = %(#{type} #{node.attr 'float'})
1152
1219
  else
1153
- class_attr_val = type
1220
+ img = %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{attrs}#{@void_element_slash}>)
1154
1221
  end
1155
- %(<span class="#{class_attr_val}">#{img}</span>)
1156
1222
  end
1223
+ img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>) if node.attr? 'link'
1224
+ class_attr_val = type
1225
+ if (role = node.role)
1226
+ class_attr_val = (node.attr? 'float') ? %(#{class_attr_val} #{node.attr 'float'} #{role}) : %(#{class_attr_val} #{role})
1227
+ elsif node.attr? 'float'
1228
+ class_attr_val = %(#{class_attr_val} #{node.attr 'float'})
1229
+ end
1230
+ %(<span class="#{class_attr_val}">#{img}</span>)
1231
+ end
1157
1232
 
1158
- def inline_indexterm node
1159
- node.type == :visible ? node.text : ''
1233
+ def convert_inline_indexterm node
1234
+ node.type == :visible ? node.text : ''
1235
+ end
1236
+
1237
+ def convert_inline_kbd node
1238
+ if (keys = node.attr 'keys').size == 1
1239
+ %(<kbd>#{keys[0]}</kbd>)
1240
+ else
1241
+ %(<span class="keyseq"><kbd>#{keys.join '</kbd>+<kbd>'}</kbd></span>)
1160
1242
  end
1243
+ end
1161
1244
 
1162
- def inline_kbd node
1163
- if (keys = node.attr 'keys').size == 1
1164
- %(<kbd>#{keys[0]}</kbd>)
1245
+ def convert_inline_menu node
1246
+ caret = (node.document.attr? 'icons', 'font') ? '&#160;<i class="fa fa-angle-right caret"></i> ' : '&#160;<b class="caret">&#8250;</b> '
1247
+ submenu_joiner = %(</b>#{caret}<b class="submenu">)
1248
+ menu = node.attr 'menu'
1249
+ if (submenus = node.attr 'submenus').empty?
1250
+ if (menuitem = node.attr 'menuitem')
1251
+ %(<span class="menuseq"><b class="menu">#{menu}</b>#{caret}<b class="menuitem">#{menuitem}</b></span>)
1165
1252
  else
1166
- %(<span class="keyseq"><kbd>#{keys.join '</kbd>+<kbd>'}</kbd></span>)
1253
+ %(<b class="menuref">#{menu}</b>)
1167
1254
  end
1255
+ else
1256
+ %(<span class="menuseq"><b class="menu">#{menu}</b>#{caret}<b class="submenu">#{submenus.join submenu_joiner}</b>#{caret}<b class="menuitem">#{node.attr 'menuitem'}</b></span>)
1168
1257
  end
1258
+ end
1169
1259
 
1170
- def inline_menu node
1171
- caret = (node.document.attr? 'icons', 'font') ? '&#160;<i class="fa fa-angle-right caret"></i> ' : '&#160;<b class="caret">&#8250;</b> '
1172
- submenu_joiner = %(</b>#{caret}<b class="submenu">)
1173
- menu = node.attr 'menu'
1174
- if (submenus = node.attr 'submenus').empty?
1175
- if (menuitem = node.attr 'menuitem', nil, false)
1176
- %(<span class="menuseq"><b class="menu">#{menu}</b>#{caret}<b class="menuitem">#{menuitem}</b></span>)
1177
- else
1178
- %(<b class="menuref">#{menu}</b>)
1179
- end
1260
+ def convert_inline_quoted node
1261
+ open, close, tag = QUOTE_TAGS[node.type]
1262
+ if node.id
1263
+ class_attr = node.role ? %( class="#{node.role}") : ''
1264
+ if tag
1265
+ %(#{open.chop} id="#{node.id}"#{class_attr}>#{node.text}#{close})
1266
+ else
1267
+ %(<span id="#{node.id}"#{class_attr}>#{open}#{node.text}#{close}</span>)
1268
+ end
1269
+ elsif node.role
1270
+ if tag
1271
+ %(#{open.chop} class="#{node.role}">#{node.text}#{close})
1180
1272
  else
1181
- %(<span class="menuseq"><b class="menu">#{menu}</b>#{caret}<b class="submenu">#{submenus.join submenu_joiner}</b>#{caret}<b class="menuitem">#{node.attr 'menuitem'}</b></span>)
1273
+ %(<span class="#{node.role}">#{open}#{node.text}#{close}</span>)
1182
1274
  end
1275
+ else
1276
+ %(#{open}#{node.text}#{close})
1183
1277
  end
1278
+ end
1184
1279
 
1185
- def inline_quoted node
1186
- open, close, is_tag = QUOTE_TAGS[node.type]
1187
- class_attr = %( class="#{node.role}") if node.role
1188
- id_attr = %( id="#{node.id}") if node.id
1189
- if class_attr || id_attr
1190
- if is_tag
1191
- %(#{open.chop}#{id_attr || ''}#{class_attr || ''}>#{node.text}#{close})
1192
- else
1193
- %(<span#{id_attr || ''}#{class_attr || ''}>#{open}#{node.text}#{close}</span>)
1280
+ # NOTE expose read_svg_contents for Bespoke converter
1281
+ def read_svg_contents node, target
1282
+ if (svg = node.read_contents target, start: (node.document.attr 'imagesdir'), normalize: true, label: 'SVG', warn_if_empty: true)
1283
+ return if svg.empty?
1284
+ svg = svg.sub SvgPreambleRx, '' unless svg.start_with? '<svg'
1285
+ old_start_tag = new_start_tag = start_tag_match = nil
1286
+ # NOTE width, height and style attributes are removed if either width or height is specified
1287
+ ['width', 'height'].each do |dim|
1288
+ next unless node.attr? dim
1289
+ unless new_start_tag
1290
+ next if (start_tag_match ||= (svg.match SvgStartTagRx) || :no_match) == :no_match
1291
+ new_start_tag = (old_start_tag = start_tag_match[0]).gsub DimensionAttributeRx, ''
1194
1292
  end
1195
- else
1196
- %(#{open}#{node.text}#{close})
1293
+ # NOTE a unitless value in HTML is assumed to be px, so we can pass the value straight through
1294
+ new_start_tag = %(#{new_start_tag.chop} #{dim}="#{node.attr dim}">)
1197
1295
  end
1296
+ svg = %(#{new_start_tag}#{svg[old_start_tag.length..-1]}) if new_start_tag
1198
1297
  end
1298
+ svg
1299
+ end
1199
1300
 
1200
- def append_boolean_attribute name, xml
1201
- xml ? %( #{name}="#{name}") : %( #{name})
1202
- end
1301
+ private
1302
+
1303
+ def append_boolean_attribute name, xml
1304
+ xml ? %( #{name}="#{name}") : %( #{name})
1305
+ end
1203
1306
 
1204
- def encode_quotes val
1205
- (val.include? '"') ? (val.gsub '"', '&quot;') : val
1307
+ def append_link_constraint_attrs node, attrs = []
1308
+ rel = 'nofollow' if node.option? 'nofollow'
1309
+ if (window = node.attributes['window'])
1310
+ attrs << %( target="#{window}")
1311
+ attrs << (rel ? %( rel="#{rel} noopener") : ' rel="noopener"') if window == '_blank' || (node.option? 'noopener')
1312
+ elsif rel
1313
+ attrs << %( rel="#{rel}")
1206
1314
  end
1315
+ attrs
1316
+ end
1207
1317
 
1208
- def generate_manname_section node
1209
- manname_title = node.attr 'manname-title', 'Name'
1210
- if (next_section = node.sections[0]) && (next_section_title = next_section.title) == next_section_title.upcase
1211
- manname_title = manname_title.upcase
1212
- end
1213
- manname_id_attr = (manname_id = node.attr 'manname-id') ? %( id="#{manname_id}") : ''
1214
- %(<h2#{manname_id_attr}>#{manname_title}</h2>
1318
+ def encode_attribute_value val
1319
+ (val.include? '"') ? (val.gsub '"', '&quot;') : val
1320
+ end
1321
+
1322
+ def generate_manname_section node
1323
+ manname_title = node.attr 'manname-title', 'Name'
1324
+ if (next_section = node.sections[0]) && (next_section_title = next_section.title) == next_section_title.upcase
1325
+ manname_title = manname_title.upcase
1326
+ end
1327
+ manname_id_attr = (manname_id = node.attr 'manname-id') ? %( id="#{manname_id}") : ''
1328
+ %(<h2#{manname_id_attr}>#{manname_title}</h2>
1215
1329
  <div class="sectionbody">
1216
- <p>#{node.attr 'manname'} - #{node.attr 'manpurpose'}</p>
1330
+ <p>#{(node.attr 'mannames').join ', '} - #{node.attr 'manpurpose'}</p>
1217
1331
  </div>)
1218
- end
1332
+ end
1219
1333
 
1220
- def append_link_constraint_attrs node, attrs = []
1221
- rel = 'nofollow' if node.option? 'nofollow'
1222
- if (window = node.attributes['window'])
1223
- attrs << %( target="#{window}")
1224
- attrs << (rel ? %( rel="#{rel} noopener") : ' rel="noopener"') if window == '_blank' || (node.option? 'noopener')
1225
- elsif rel
1226
- attrs << %( rel="#{rel}")
1227
- end
1228
- attrs
1334
+ def get_root_document node
1335
+ while (node = node.document).nested?
1336
+ node = node.parent_document
1229
1337
  end
1338
+ node
1339
+ end
1230
1340
 
1231
- def read_svg_contents node, target
1232
- if (svg = node.read_contents target, :start => (node.document.attr 'imagesdir'), :normalize => true, :label => 'SVG')
1233
- svg = svg.sub SvgPreambleRx, '' unless svg.start_with? '<svg'
1234
- old_start_tag = new_start_tag = nil
1235
- # NOTE width, height and style attributes are removed if either width or height is specified
1236
- ['width', 'height'].each do |dim|
1237
- if node.attr? dim
1238
- new_start_tag = (old_start_tag = (svg.match SvgStartTagRx)[0]).gsub DimensionAttributeRx, '' unless new_start_tag
1239
- # QUESTION should we add px since it's already the default?
1240
- new_start_tag = %(#{new_start_tag.chop} #{dim}="#{node.attr dim}px">)
1241
- end
1242
- end
1243
- svg = %(#{new_start_tag}#{svg[old_start_tag.length..-1]}) if new_start_tag
1244
- end
1245
- svg
1246
- end
1341
+ # NOTE adapt to older converters that relied on unprefixed method names
1342
+ def method_missing id, *args
1343
+ !((name = id.to_s).start_with? 'convert_') && (handles? name) ? (send %(convert_#{name}), *args) : super
1247
1344
  end
1345
+
1346
+ def respond_to_missing? id, *options
1347
+ !((name = id.to_s).start_with? 'convert_') && (handles? name)
1348
+ end
1349
+ end
1248
1350
  end