asciidoctor 1.5.6.2 → 1.5.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of asciidoctor might be problematic. Click here for more details.

Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +330 -143
  3. data/README-fr.adoc +441 -0
  4. data/README-jp.adoc +418 -0
  5. data/README-zh_CN.adoc +430 -0
  6. data/README.adoc +454 -0
  7. data/Rakefile +57 -0
  8. data/asciidoctor.gemspec +7 -1
  9. data/data/locale/attributes-ar.adoc +22 -0
  10. data/data/locale/attributes-bg.adoc +22 -0
  11. data/data/locale/attributes-ca.adoc +22 -0
  12. data/data/locale/attributes-cs.adoc +22 -0
  13. data/data/locale/attributes-da.adoc +22 -0
  14. data/data/locale/attributes-de.adoc +22 -0
  15. data/data/locale/attributes-en.adoc +23 -0
  16. data/data/locale/attributes-es.adoc +22 -0
  17. data/data/locale/attributes-fa.adoc +22 -0
  18. data/data/locale/attributes-fi.adoc +22 -0
  19. data/data/locale/attributes-fr.adoc +22 -0
  20. data/data/locale/attributes-hu.adoc +22 -0
  21. data/data/locale/attributes-id.adoc +22 -0
  22. data/data/locale/attributes-it.adoc +22 -0
  23. data/data/locale/attributes-ja.adoc +22 -0
  24. data/data/locale/attributes-kr.adoc +22 -0
  25. data/data/locale/attributes-nb.adoc +22 -0
  26. data/data/locale/attributes-nl.adoc +22 -0
  27. data/data/locale/attributes-nn.adoc +22 -0
  28. data/data/locale/attributes-pl.adoc +22 -0
  29. data/data/locale/attributes-pt.adoc +22 -0
  30. data/data/locale/attributes-pt_BR.adoc +22 -0
  31. data/data/locale/attributes-ro.adoc +22 -0
  32. data/data/locale/attributes-ru.adoc +22 -0
  33. data/data/locale/attributes-sr.adoc +22 -0
  34. data/data/locale/attributes-sr_Latn.adoc +22 -0
  35. data/data/locale/attributes-tr.adoc +22 -0
  36. data/data/locale/attributes-uk.adoc +22 -0
  37. data/data/locale/attributes-zh_CN.adoc +22 -0
  38. data/data/locale/attributes-zh_TW.adoc +22 -0
  39. data/data/locale/attributes.adoc +8 -649
  40. data/data/stylesheets/asciidoctor-default.css +77 -72
  41. data/features/xref.feature +366 -7
  42. data/lib/asciidoctor.rb +107 -93
  43. data/lib/asciidoctor/abstract_block.rb +247 -239
  44. data/lib/asciidoctor/abstract_node.rb +56 -58
  45. data/lib/asciidoctor/block.rb +3 -3
  46. data/lib/asciidoctor/callouts.rb +1 -1
  47. data/lib/asciidoctor/cli/invoker.rb +36 -9
  48. data/lib/asciidoctor/cli/options.rb +63 -25
  49. data/lib/asciidoctor/converter.rb +23 -13
  50. data/lib/asciidoctor/converter/base.rb +4 -0
  51. data/lib/asciidoctor/converter/docbook45.rb +16 -9
  52. data/lib/asciidoctor/converter/docbook5.rb +115 -97
  53. data/lib/asciidoctor/converter/factory.rb +29 -31
  54. data/lib/asciidoctor/converter/html5.rb +229 -192
  55. data/lib/asciidoctor/converter/manpage.rb +72 -50
  56. data/lib/asciidoctor/converter/template.rb +12 -12
  57. data/lib/asciidoctor/core_ext.rb +5 -1
  58. data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +6 -0
  59. data/lib/asciidoctor/document.rb +168 -77
  60. data/lib/asciidoctor/extensions.rb +79 -47
  61. data/lib/asciidoctor/helpers.rb +33 -11
  62. data/lib/asciidoctor/inline.rb +3 -2
  63. data/lib/asciidoctor/list.rb +2 -1
  64. data/lib/asciidoctor/logging.rb +122 -0
  65. data/lib/asciidoctor/parser.rb +406 -382
  66. data/lib/asciidoctor/path_resolver.rb +169 -162
  67. data/lib/asciidoctor/reader.rb +166 -121
  68. data/lib/asciidoctor/section.rb +45 -28
  69. data/lib/asciidoctor/stylesheets.rb +13 -5
  70. data/lib/asciidoctor/substitutors.rb +328 -254
  71. data/lib/asciidoctor/table.rb +105 -48
  72. data/lib/asciidoctor/timings.rb +34 -6
  73. data/lib/asciidoctor/version.rb +1 -1
  74. data/man/asciidoctor.1 +41 -23
  75. data/man/asciidoctor.adoc +14 -8
  76. data/test/api_test.rb +1004 -0
  77. data/test/attributes_test.rb +241 -50
  78. data/test/blocks_test.rb +549 -124
  79. data/test/converter_test.rb +170 -78
  80. data/test/document_test.rb +208 -767
  81. data/test/extensions_test.rb +188 -53
  82. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +1 -1
  83. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +1 -1
  84. data/test/fixtures/file-with-missing-include.adoc +1 -0
  85. data/test/fixtures/include-file.jsx +8 -0
  86. data/test/fixtures/lists.adoc +96 -0
  87. data/test/fixtures/other-chapters.adoc +11 -0
  88. data/test/fixtures/outer-include.adoc +5 -0
  89. data/test/fixtures/sample.asciidoc +5 -1
  90. data/test/fixtures/subdir/index.adoc +3 -0
  91. data/test/fixtures/subdir/inner-include.adoc +3 -0
  92. data/test/fixtures/subdir/middle-include.adoc +5 -0
  93. data/test/fixtures/tagged-class-enclosed.rb +0 -1
  94. data/test/fixtures/unclosed-tag.adoc +3 -0
  95. data/test/fixtures/unexpected-end-tag.adoc +4 -0
  96. data/test/invoker_test.rb +101 -40
  97. data/test/links_test.rb +266 -72
  98. data/test/lists_test.rb +243 -45
  99. data/test/logger_test.rb +211 -0
  100. data/test/manpage_test.rb +124 -6
  101. data/test/options_test.rb +46 -1
  102. data/test/paragraphs_test.rb +23 -10
  103. data/test/parser_test.rb +30 -1
  104. data/test/paths_test.rb +115 -33
  105. data/test/preamble_test.rb +1 -1
  106. data/test/reader_test.rb +337 -81
  107. data/test/sections_test.rb +656 -72
  108. data/test/substitutions_test.rb +182 -57
  109. data/test/tables_test.rb +324 -57
  110. data/test/test_helper.rb +77 -32
  111. data/test/text_test.rb +7 -7
  112. metadata +67 -3
@@ -37,7 +37,7 @@ module Asciidoctor
37
37
  # the registry of converters is initialized using a normal Hash.
38
38
  #
39
39
  # initialize_singleton - A Boolean to indicate whether the singleton should
40
- # be initialize if it has not already been created.
40
+ # be initialized if it has not already been created.
41
41
  # If false, and a singleton has not been previously
42
42
  # initialized, a fresh instance is returned.
43
43
  #
@@ -46,10 +46,12 @@ module Asciidoctor
46
46
  return @__default__ || new unless initialize_singleton
47
47
  # FIXME this assignment is not thread_safe, may need to use a ::Threadsafe helper here
48
48
  @__default__ ||= begin
49
+ # NOTE .to_s hides require from Opal
49
50
  require 'thread_safe'.to_s unless defined? ::ThreadSafe
50
51
  new ::ThreadSafe::Cache.new
51
52
  rescue ::LoadError
52
- warn 'asciidoctor: WARNING: gem \'thread_safe\' is not installed. This gem is recommended when registering custom converters.'
53
+ include Logging unless include? Logging
54
+ logger.warn 'gem \'thread_safe\' is not installed. This gem is recommended when registering custom converters.'
53
55
  new
54
56
  end
55
57
  end
@@ -183,41 +185,37 @@ module Asciidoctor
183
185
  # Returns the [Converter] object
184
186
  def create backend, opts = {}
185
187
  if (converter = resolve backend)
186
- return ::Class === converter ? (converter.new backend, opts) : converter
187
- end
188
-
189
- base_converter = case backend
190
- when 'html5'
191
- unless defined? ::Asciidoctor::Converter::Html5Converter
192
- require 'asciidoctor/converter/html5'.to_s
193
- end
194
- Html5Converter.new backend, opts
195
- when 'docbook5'
196
- unless defined? ::Asciidoctor::Converter::DocBook5Converter
197
- require 'asciidoctor/converter/docbook5'.to_s
198
- end
199
- DocBook5Converter.new backend, opts
200
- when 'docbook45'
201
- unless defined? ::Asciidoctor::Converter::DocBook45Converter
202
- require 'asciidoctor/converter/docbook45'.to_s
188
+ base_converter = ::Class === converter ? (converter.new backend, opts) : converter
189
+ return base_converter unless Converter::BackendInfo === base_converter && base_converter.supports_templates?
190
+ else
191
+ case backend
192
+ when 'html5'
193
+ # NOTE .to_s hides require from Opal
194
+ require 'asciidoctor/converter/html5'.to_s unless defined? ::Asciidoctor::Converter::Html5Converter
195
+ base_converter = Html5Converter.new backend, opts
196
+ when 'docbook5'
197
+ # NOTE .to_s hides require from Opal
198
+ require 'asciidoctor/converter/docbook5'.to_s unless defined? ::Asciidoctor::Converter::DocBook5Converter
199
+ base_converter = DocBook5Converter.new backend, opts
200
+ when 'docbook45'
201
+ # NOTE .to_s hides require from Opal
202
+ require 'asciidoctor/converter/docbook45'.to_s unless defined? ::Asciidoctor::Converter::DocBook45Converter
203
+ base_converter = DocBook45Converter.new backend, opts
204
+ when 'manpage'
205
+ # NOTE .to_s hides require from Opal
206
+ require 'asciidoctor/converter/manpage'.to_s unless defined? ::Asciidoctor::Converter::ManPageConverter
207
+ base_converter = ManPageConverter.new backend, opts
203
208
  end
204
- DocBook45Converter.new backend, opts
205
- when 'manpage'
206
- unless defined? ::Asciidoctor::Converter::ManPageConverter
207
- require 'asciidoctor/converter/manpage'.to_s
208
- end
209
- ManPageConverter.new backend, opts
210
209
  end
211
210
 
212
211
  return base_converter unless opts.key? :template_dirs
213
212
 
214
- unless defined? ::Asciidoctor::Converter::TemplateConverter
215
- require 'asciidoctor/converter/template'.to_s
216
- end
217
- unless defined? ::Asciidoctor::Converter::CompositeConverter
218
- require 'asciidoctor/converter/composite'.to_s
219
- end
213
+ # NOTE .to_s hides require from Opal
214
+ require 'asciidoctor/converter/template'.to_s unless defined? ::Asciidoctor::Converter::TemplateConverter
220
215
  template_converter = TemplateConverter.new backend, opts[:template_dirs], opts
216
+
217
+ # NOTE .to_s hides require from Opal
218
+ require 'asciidoctor/converter/composite'.to_s unless defined? ::Asciidoctor::Converter::CompositeConverter
221
219
  # QUESTION should we omit the composite converter if built_in_converter is nil?
222
220
  CompositeConverter.new backend, template_converter, base_converter
223
221
  end
@@ -19,6 +19,8 @@ module Asciidoctor
19
19
  #:latexmath => INLINE_MATH_DELIMITERS[:latexmath] + [false]
20
20
  }).default = ['', '', false]
21
21
 
22
+ DropAnchorRx = /<(?:a[^>+]+|\/a)>/
23
+ StemBreakRx = / *\\\n(?:\\?\n)*|\n\n+/
22
24
  SvgPreambleRx = /\A.*?(?=<svg\b)/m
23
25
  SvgStartTagRx = /\A<svg[^>]*>/
24
26
  DimensionAttributeRx = /\s(?:width|height|style)=(["']).*?\1/
@@ -36,10 +38,10 @@ module Asciidoctor
36
38
  asset_uri_scheme = %(#{asset_uri_scheme}:)
37
39
  end
38
40
  cdn_base = %(#{asset_uri_scheme}//cdnjs.cloudflare.com/ajax/libs)
39
- linkcss = node.safe >= SafeMode::SECURE || (node.attr? 'linkcss')
41
+ linkcss = node.attr? 'linkcss'
40
42
  result = ['<!DOCTYPE html>']
41
- lang_attribute = (node.attr? 'nolang') ? nil : %( lang="#{node.attr 'lang', 'en'}")
42
- result << %(<html#{@xml_mode ? ' xmlns="http://www.w3.org/1999/xhtml"' : nil}#{lang_attribute}>)
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}>)
43
45
  result << %(<head>
44
46
  <meta charset="#{node.attr 'encoding', 'UTF-8'}"#{slash}>
45
47
  <!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"#{slash}><![endif]-->
@@ -56,7 +58,7 @@ module Asciidoctor
56
58
  else
57
59
  icon_type = (icon_ext = ::File.extname icon_href) == '.ico' ? 'image/x-icon' : %(image/#{icon_ext[1..-1]})
58
60
  end
59
- result << %(<link rel="shortcut icon" type="#{icon_type}" href="#{icon_href}">)
61
+ result << %(<link rel="icon" type="#{icon_type}" href="#{icon_href}"#{slash}>)
60
62
  end
61
63
  result << %(<title>#{node.doctitle :sanitize => true, :use_fallback => true}</title>)
62
64
 
@@ -81,7 +83,7 @@ module Asciidoctor
81
83
 
82
84
  if node.attr? 'icons', 'font'
83
85
  if node.attr? 'iconfont-remote'
84
- result << %(<link rel="stylesheet" href="#{node.attr 'iconfont-cdn', %[#{cdn_base}/font-awesome/4.6.3/css/font-awesome.min.css]}"#{slash}>)
86
+ result << %(<link rel="stylesheet" href="#{node.attr 'iconfont-cdn', %[#{cdn_base}/font-awesome/#{FONT_AWESOME_VERSION}/css/font-awesome.min.css]}"#{slash}>)
85
87
  else
86
88
  iconfont_stylesheet = %(#{node.attr 'iconfont-name', 'font-awesome'}.css)
87
89
  result << %(<link rel="stylesheet" href="#{node.normalize_web_path iconfont_stylesheet, (node.attr 'stylesdir', ''), false}"#{slash}>)
@@ -113,13 +115,14 @@ module Asciidoctor
113
115
  end
114
116
 
115
117
  result << '</head>'
116
- body_attrs = []
117
- body_attrs << %(id="#{node.id}") if node.id
118
+ body_attrs = node.id ? [%(id="#{node.id}")] : []
118
119
  if (sectioned = node.sections?) && (node.attr? 'toc-class') && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
119
- body_attrs << %(class="#{node.doctype} #{node.attr 'toc-class'} toc-#{node.attr 'toc-position', 'header'}")
120
+ classes = [node.doctype, (node.attr 'toc-class'), %(toc-#{node.attr 'toc-position', 'header'})]
120
121
  else
121
- body_attrs << %(class="#{node.doctype}")
122
+ classes = [node.doctype]
122
123
  end
124
+ classes << (node.attr 'docrole') if node.attr? 'docrole'
125
+ body_attrs << %(class="#{classes * ' '}")
123
126
  body_attrs << %(style="max-width: #{node.attr 'max-width'};") if node.attr? 'max-width'
124
127
  result << %(<body #{body_attrs * ' '}>)
125
128
 
@@ -133,11 +136,7 @@ module Asciidoctor
133
136
  #{outline node}
134
137
  </div>)
135
138
  end
136
- # QUESTION should this h2 have an auto-generated id?
137
- result << %(<h2>#{node.attr 'manname-title'}</h2>
138
- <div class="sectionbody">
139
- <p>#{node.attr 'manname'} - #{node.attr 'manpurpose'}</p>
140
- </div>)
139
+ result << (generate_manname_section node) if node.attr? 'manpurpose'
141
140
  else
142
141
  if node.has_header?
143
142
  result << %(<h1>#{node.header.title}</h1>) unless node.notitle
@@ -190,7 +189,7 @@ module Asciidoctor
190
189
  result << %(<div id="footnotes">
191
190
  <hr#{slash}>)
192
191
  node.footnotes.each do |footnote|
193
- result << %(<div class="footnote" id="_footnote_#{footnote.index}">
192
+ result << %(<div class="footnote" id="_footnotedef_#{footnote.index}">
194
193
  <a href="#_footnoteref_#{footnote.index}">#{footnote.index}</a>. #{footnote.text}
195
194
  </div>)
196
195
  end
@@ -245,7 +244,7 @@ MathJax.Hub.Config({
245
244
  TeX: {#{eqnums_opt}}
246
245
  });
247
246
  </script>
248
- <script src="#{cdn_base}/mathjax/2.6.0/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>)
247
+ <script src="#{cdn_base}/mathjax/#{MATHJAX_VERSION}/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>)
249
248
  end
250
249
 
251
250
  result << '</body>'
@@ -258,17 +257,13 @@ MathJax.Hub.Config({
258
257
  if node.doctype == 'manpage'
259
258
  # QUESTION should notitle control the manual page title?
260
259
  unless node.notitle
261
- id_attr = node.id ? %( id="#{node.id}") : nil
260
+ id_attr = node.id ? %( id="#{node.id}") : ''
262
261
  result << %(<h1#{id_attr}>#{node.doctitle} Manual Page</h1>)
263
262
  end
264
- # QUESTION should this h2 have an auto-generated id?
265
- result << %(<h2>#{node.attr 'manname-title'}</h2>
266
- <div class="sectionbody">
267
- <p>#{node.attr 'manname'} - #{node.attr 'manpurpose'}</p>
268
- </div>)
263
+ result << (generate_manname_section node) if node.attr? 'manpurpose'
269
264
  else
270
265
  if node.has_header? && !node.notitle
271
- id_attr = node.id ? %( id="#{node.id}") : nil
266
+ id_attr = node.id ? %( id="#{node.id}") : ''
272
267
  result << %(<h1#{id_attr}>#{node.header.title}</h1>)
273
268
  end
274
269
  end
@@ -286,7 +281,7 @@ MathJax.Hub.Config({
286
281
  result << %(<div id="footnotes">
287
282
  <hr#{@void_element_slash}>)
288
283
  node.footnotes.each do |footnote|
289
- result << %(<div class="footnote" id="_footnote_#{footnote.index}">
284
+ result << %(<div class="footnote" id="_footnotedef_#{footnote.index}">
290
285
  <a href="#_footnoteref_#{footnote.index}">#{footnote.index}</a>. #{footnote.text}
291
286
  </div>)
292
287
  end
@@ -312,6 +307,7 @@ MathJax.Hub.Config({
312
307
  else
313
308
  stitle = section.title
314
309
  end
310
+ stitle = stitle.gsub DropAnchorRx, '' if stitle.include? '<a'
315
311
  if slevel < toclevels && (child_toc_level = outline section, :toclevels => toclevels, :secnumlevels => sectnumlevels)
316
312
  result << %(<li><a href="##{section.id}">#{stitle}</a>)
317
313
  result << child_toc_level
@@ -325,45 +321,46 @@ MathJax.Hub.Config({
325
321
  end
326
322
 
327
323
  def section node
328
- slevel = node.level
329
- htag = %(h#{slevel + 1})
330
- id_attr = anchor = link_start = link_end = nil
324
+ if (level = node.level) == 0
325
+ sect0 = true
326
+ title = node.numbered && level <= (node.document.attr 'sectnumlevels', 3).to_i ? %(#{node.sectnum} #{node.title}) : node.title
327
+ else
328
+ title = node.numbered && !node.caption && level <= (node.document.attr 'sectnumlevels', 3).to_i ? %(#{node.sectnum} #{node.title}) : node.captioned_title
329
+ end
331
330
  if node.id
332
331
  id_attr = %( id="#{id = node.id}")
333
- if (doc = node.document).attr? 'sectanchors'
334
- anchor = %(<a class="anchor" href="##{id}"></a>)
335
- # possible idea - anchor icons GitHub-style
336
- #if doc.attr? 'icons', 'font'
337
- # anchor = %(<a class="anchor" href="##{id}"><i class="fa fa-anchor"></i></a>)
338
- #else
339
- # anchor = %(<a class="anchor" href="##{id}"></a>)
340
- #end
332
+ if (doc_attrs = node.document.attributes).key? 'sectlinks'
333
+ title = %(<a class="link" href="##{id}">#{title}</a>)
341
334
  end
342
- if doc.attr? 'sectlinks'
343
- link_start = %(<a class="link" href="##{id}">)
344
- link_end = '</a>'
335
+ if doc_attrs.key? 'sectanchors'
336
+ # QUESTION should we add a font-based icon in anchor if icons=font?
337
+ if doc_attrs['sectanchors'] == 'after'
338
+ title = %(#{title}<a class="anchor" href="##{id}"></a>)
339
+ else
340
+ title = %(<a class="anchor" href="##{id}"></a>#{title})
341
+ end
345
342
  end
343
+ else
344
+ id_attr = ''
346
345
  end
347
346
 
348
- if slevel == 0
349
- %(<h1#{id_attr} class="sect0">#{anchor}#{link_start}#{node.title}#{link_end}</h1>
347
+ if sect0
348
+ %(<h1#{id_attr} class="sect0#{(role = node.role) ? " #{role}" : ''}">#{title}</h1>
350
349
  #{node.content})
351
350
  else
352
- class_attr = (role = node.role) ? %( class="sect#{slevel} #{role}") : %( class="sect#{slevel}")
353
- sectnum = if node.numbered && !node.caption && slevel <= (node.document.attr 'sectnumlevels', 3).to_i
354
- %(#{node.sectnum} )
355
- end
356
- %(<div#{class_attr}>
357
- <#{htag}#{id_attr}>#{anchor}#{link_start}#{sectnum}#{node.captioned_title}#{link_end}</#{htag}>
358
- #{slevel == 1 ? %[<div class="sectionbody">\n#{node.content}\n</div>] : node.content}
351
+ %(<div class="sect#{level}#{(role = node.role) ? " #{role}" : ''}">
352
+ <h#{level + 1}#{id_attr}>#{title}</h#{level + 1}>
353
+ #{level == 1 ? %[<div class="sectionbody">
354
+ #{node.content}
355
+ </div>] : node.content}
359
356
  </div>)
360
357
  end
361
358
  end
362
359
 
363
360
  def admonition node
364
- id_attr = node.id ? %( id="#{node.id}") : nil
361
+ id_attr = node.id ? %( id="#{node.id}") : ''
365
362
  name = node.attr 'name'
366
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
363
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
367
364
  if node.document.attr? 'icons'
368
365
  if (node.document.attr? 'icons', 'font') && !(node.attr? 'icon')
369
366
  label = %(<i class="fa icon-#{name}" title="#{node.attr 'textlabel'}"></i>)
@@ -373,7 +370,7 @@ MathJax.Hub.Config({
373
370
  else
374
371
  label = %(<div class="title">#{node.attr 'textlabel'}</div>)
375
372
  end
376
- %(<div#{id_attr} class="admonitionblock #{name}#{(role = node.role) && " #{role}"}">
373
+ %(<div#{id_attr} class="admonitionblock #{name}#{(role = node.role) ? " #{role}" : ''}">
377
374
  <table>
378
375
  <tr>
379
376
  <td class="icon">
@@ -389,16 +386,16 @@ MathJax.Hub.Config({
389
386
 
390
387
  def audio node
391
388
  xml = @xml_mode
392
- id_attribute = node.id ? %( id="#{node.id}") : nil
389
+ id_attribute = node.id ? %( id="#{node.id}") : ''
393
390
  classes = ['audioblock', node.role].compact
394
391
  class_attribute = %( class="#{classes * ' '}")
395
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
392
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
396
393
  start_t = node.attr 'start', nil, false
397
394
  end_t = node.attr 'end', nil, false
398
- time_anchor = (start_t || end_t) ? %(#t=#{start_t}#{end_t ? ',' : nil}#{end_t}) : nil
395
+ time_anchor = (start_t || end_t) ? %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''}) : ''
399
396
  %(<div#{id_attribute}#{class_attribute}>
400
397
  #{title_element}<div class="content">
401
- <audio src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : nil}#{(node.option? 'nocontrols') ? nil : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : nil}>
398
+ <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) : ''}>
402
399
  Your browser does not support the audio tag.
403
400
  </audio>
404
401
  </div>
@@ -407,7 +404,7 @@ Your browser does not support the audio tag.
407
404
 
408
405
  def colist node
409
406
  result = []
410
- id_attribute = node.id ? %( id="#{node.id}") : nil
407
+ id_attribute = node.id ? %( id="#{node.id}") : ''
411
408
  classes = ['colist', node.style, node.role].compact
412
409
  class_attribute = %( class="#{classes * ' '}")
413
410
 
@@ -446,7 +443,7 @@ Your browser does not support the audio tag.
446
443
 
447
444
  def dlist node
448
445
  result = []
449
- id_attribute = node.id ? %( id="#{node.id}") : nil
446
+ id_attribute = node.id ? %( id="#{node.id}") : ''
450
447
 
451
448
  classes = case node.style
452
449
  when 'qanda'
@@ -481,15 +478,15 @@ Your browser does not support the audio tag.
481
478
  result << '<table>'
482
479
  if (node.attr? 'labelwidth') || (node.attr? 'itemwidth')
483
480
  result << '<colgroup>'
484
- col_style_attribute = (node.attr? 'labelwidth') ? %( style="width: #{(node.attr 'labelwidth').chomp '%'}%;") : nil
481
+ col_style_attribute = (node.attr? 'labelwidth') ? %( style="width: #{(node.attr 'labelwidth').chomp '%'}%;") : ''
485
482
  result << %(<col#{col_style_attribute}#{slash}>)
486
- col_style_attribute = (node.attr? 'itemwidth') ? %( style="width: #{(node.attr 'itemwidth').chomp '%'}%;") : nil
483
+ col_style_attribute = (node.attr? 'itemwidth') ? %( style="width: #{(node.attr 'itemwidth').chomp '%'}%;") : ''
487
484
  result << %(<col#{col_style_attribute}#{slash}>)
488
485
  result << '</colgroup>'
489
486
  end
490
487
  node.items.each do |terms, dd|
491
488
  result << '<tr>'
492
- result << %(<td class="hdlist1#{(node.option? 'strong') ? ' strong' : nil}">)
489
+ result << %(<td class="hdlist1#{(node.option? 'strong') ? ' strong' : ''}">)
493
490
  terms_array = [*terms]
494
491
  last_term = terms_array[-1]
495
492
  terms_array.each do |dt|
@@ -508,7 +505,7 @@ Your browser does not support the audio tag.
508
505
  result << '</table>'
509
506
  else
510
507
  result << '<dl>'
511
- dt_style_attribute = node.style ? nil : ' class="hdlist1"'
508
+ dt_style_attribute = node.style ? '' : ' class="hdlist1"'
512
509
  node.items.each do |terms, dd|
513
510
  [*terms].each do |dt|
514
511
  result << %(<dt#{dt_style_attribute}>#{dt.text}</dt>)
@@ -528,10 +525,10 @@ Your browser does not support the audio tag.
528
525
  end
529
526
 
530
527
  def example node
531
- id_attribute = node.id ? %( id="#{node.id}") : nil
532
- title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : nil
528
+ id_attribute = node.id ? %( id="#{node.id}") : ''
529
+ title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : ''
533
530
 
534
- %(<div#{id_attribute} class="exampleblock#{(role = node.role) && " #{role}"}">
531
+ %(<div#{id_attribute} class="exampleblock#{(role = node.role) ? " #{role}" : ''}">
535
532
  #{title_element}<div class="content">
536
533
  #{node.content}
537
534
  </div>
@@ -540,15 +537,15 @@ Your browser does not support the audio tag.
540
537
 
541
538
  def floating_title node
542
539
  tag_name = %(h#{node.level + 1})
543
- id_attribute = node.id ? %( id="#{node.id}") : nil
540
+ id_attribute = node.id ? %( id="#{node.id}") : ''
544
541
  classes = [node.style, node.role].compact
545
542
  %(<#{tag_name}#{id_attribute} class="#{classes * ' '}">#{node.title}</#{tag_name}>)
546
543
  end
547
544
 
548
545
  def image node
549
546
  target = node.attr 'target'
550
- width_attr = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : nil
551
- height_attr = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : nil
547
+ width_attr = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
548
+ height_attr = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : ''
552
549
  if ((node.attr? 'format', 'svg', false) || (target.include? '.svg')) && node.document.safe < SafeMode::SECURE &&
553
550
  ((svg = (node.option? 'inline')) || (obj = (node.option? 'interactive')))
554
551
  if svg
@@ -559,18 +556,17 @@ Your browser does not support the audio tag.
559
556
  end
560
557
  end
561
558
  img ||= %(<img src="#{node.image_uri target}" alt="#{encode_quotes node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>)
562
- if node.attr? 'link'
563
- window_attr = %( target="#{window = node.attr 'window'}"#{window == '_blank' || (node.option? 'noopener') ? ' rel="noopener"' : ''}) if node.attr? 'window'
564
- img = %(<a class="image" href="#{node.attr 'link'}"#{window_attr}>#{img}</a>)
559
+ if node.attr? 'link', nil, false
560
+ img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>)
565
561
  end
566
- id_attr = node.id ? %( id="#{node.id}") : nil
562
+ id_attr = node.id ? %( id="#{node.id}") : ''
567
563
  classes = ['imageblock', node.role].compact
568
564
  class_attr = %( class="#{classes * ' '}")
569
565
  styles = []
570
566
  styles << %(text-align: #{node.attr 'align'}) if node.attr? 'align'
571
567
  styles << %(float: #{node.attr 'float'}) if node.attr? 'float'
572
- style_attr = styles.empty? ? nil : %( style="#{styles * ';'}")
573
- title_el = node.title? ? %(\n<div class="title">#{node.captioned_title}</div>) : nil
568
+ style_attr = styles.empty? ? '' : %( style="#{styles * ';'}")
569
+ title_el = node.title? ? %(\n<div class="title">#{node.captioned_title}</div>) : ''
574
570
  %(<div#{id_attr}#{class_attr}#{style_attr}>
575
571
  <div class="content">
576
572
  #{img}
@@ -584,36 +580,41 @@ Your browser does not support the audio tag.
584
580
  if (language = node.attr 'language', nil, false)
585
581
  code_attrs = %( data-lang="#{language}")
586
582
  else
587
- code_attrs = nil
583
+ code_attrs = ''
588
584
  end
589
585
  case node.document.attr 'source-highlighter'
590
586
  when 'coderay'
591
- pre_class = %( class="CodeRay highlight#{nowrap ? ' nowrap' : nil}")
587
+ pre_class = %( class="CodeRay highlight#{nowrap ? ' nowrap' : ''}")
592
588
  when 'pygments'
593
- pre_class = %( class="pygments highlight#{nowrap ? ' nowrap' : nil}")
589
+ if (node.document.attr? 'pygments-css', 'inline')
590
+ @pygments_bg = @stylesheets.pygments_background(node.document.attr 'pygments-style') unless defined? @pygments_bg
591
+ pre_class = %( class="pygments highlight#{nowrap ? ' nowrap' : ''}" style="background: #{@pygments_bg}")
592
+ else
593
+ pre_class = %( class="pygments highlight#{nowrap ? ' nowrap' : ''}")
594
+ end
594
595
  when 'highlightjs', 'highlight.js'
595
- pre_class = %( class="highlightjs highlight#{nowrap ? ' nowrap' : nil}")
596
+ pre_class = %( class="highlightjs highlight#{nowrap ? ' nowrap' : ''}")
596
597
  code_attrs = %( class="language-#{language} hljs"#{code_attrs}) if language
597
598
  when 'prettify'
598
- pre_class = %( class="prettyprint highlight#{nowrap ? ' nowrap' : nil}#{(node.attr? 'linenums', nil, false) ? ' linenums' : nil}")
599
+ pre_class = %( class="prettyprint highlight#{nowrap ? ' nowrap' : ''}#{(node.attr? 'linenums', nil, false) ? ' linenums' : ''}")
599
600
  code_attrs = %( class="language-#{language}"#{code_attrs}) if language
600
601
  when 'html-pipeline'
601
- pre_class = language ? %( lang="#{language}") : nil
602
- code_attrs = nil
602
+ pre_class = language ? %( lang="#{language}") : ''
603
+ code_attrs = ''
603
604
  else
604
- pre_class = %( class="highlight#{nowrap ? ' nowrap' : nil}")
605
+ pre_class = %( class="highlight#{nowrap ? ' nowrap' : ''}")
605
606
  code_attrs = %( class="language-#{language}"#{code_attrs}) if language
606
607
  end
607
608
  pre_start = %(<pre#{pre_class}><code#{code_attrs}>)
608
609
  pre_end = '</code></pre>'
609
610
  else
610
- pre_start = %(<pre#{nowrap ? ' class="nowrap"' : nil}>)
611
+ pre_start = %(<pre#{nowrap ? ' class="nowrap"' : ''}>)
611
612
  pre_end = '</pre>'
612
613
  end
613
614
 
614
- id_attribute = node.id ? %( id="#{node.id}") : nil
615
- title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : nil
616
- %(<div#{id_attribute} class="listingblock#{(role = node.role) && " #{role}"}">
615
+ id_attribute = node.id ? %( id="#{node.id}") : ''
616
+ title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : ''
617
+ %(<div#{id_attribute} class="listingblock#{(role = node.role) ? " #{role}" : ''}">
617
618
  #{title_element}<div class="content">
618
619
  #{pre_start}#{node.content}#{pre_end}
619
620
  </div>
@@ -621,26 +622,32 @@ Your browser does not support the audio tag.
621
622
  end
622
623
 
623
624
  def literal node
624
- id_attribute = node.id ? %( id="#{node.id}") : nil
625
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
625
+ id_attribute = node.id ? %( id="#{node.id}") : ''
626
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
626
627
  nowrap = !(node.document.attr? 'prewrap') || (node.option? 'nowrap')
627
- %(<div#{id_attribute} class="literalblock#{(role = node.role) && " #{role}"}">
628
+ %(<div#{id_attribute} class="literalblock#{(role = node.role) ? " #{role}" : ''}">
628
629
  #{title_element}<div class="content">
629
- <pre#{nowrap ? ' class="nowrap"' : nil}>#{node.content}</pre>
630
+ <pre#{nowrap ? ' class="nowrap"' : ''}>#{node.content}</pre>
630
631
  </div>
631
632
  </div>)
632
633
  end
633
634
 
634
635
  def stem node
635
- id_attribute = node.id ? %( id="#{node.id}") : nil
636
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
637
- open, close = BLOCK_MATH_DELIMITERS[node.style.to_sym]
636
+ id_attribute = node.id ? %( id="#{node.id}") : ''
637
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
638
+ open, close = BLOCK_MATH_DELIMITERS[style = node.style.to_sym]
639
+ equation = node.content
640
+
641
+ if style == :asciimath && (equation.include? LF)
642
+ br = %(<br#{@void_element_slash}>#{LF})
643
+ equation = equation.gsub(StemBreakRx) { %(#{close}#{br * ($&.count LF)}#{open}) }
644
+ end
638
645
 
639
- unless ((equation = node.content).start_with? open) && (equation.end_with? close)
646
+ unless (equation.start_with? open) && (equation.end_with? close)
640
647
  equation = %(#{open}#{equation}#{close})
641
648
  end
642
649
 
643
- %(<div#{id_attribute} class="stemblock#{(role = node.role) && " #{role}"}">
650
+ %(<div#{id_attribute} class="stemblock#{(role = node.role) ? " #{role}" : ''}">
644
651
  #{title_element}<div class="content">
645
652
  #{equation}
646
653
  </div>
@@ -649,16 +656,16 @@ Your browser does not support the audio tag.
649
656
 
650
657
  def olist node
651
658
  result = []
652
- id_attribute = node.id ? %( id="#{node.id}") : nil
659
+ id_attribute = node.id ? %( id="#{node.id}") : ''
653
660
  classes = ['olist', node.style, node.role].compact
654
661
  class_attribute = %( class="#{classes * ' '}")
655
662
 
656
663
  result << %(<div#{id_attribute}#{class_attribute}>)
657
664
  result << %(<div class="title">#{node.title}</div>) if node.title?
658
665
 
659
- type_attribute = (keyword = node.list_marker_keyword) ? %( type="#{keyword}") : nil
660
- start_attribute = (node.attr? 'start') ? %( start="#{node.attr 'start'}") : nil
661
- reversed_attribute = (node.option? 'reversed') ? (append_boolean_attribute 'reversed', @xml_mode) : nil
666
+ type_attribute = (keyword = node.list_marker_keyword) ? %( type="#{keyword}") : ''
667
+ start_attribute = (node.attr? 'start') ? %( start="#{node.attr 'start'}") : ''
668
+ reversed_attribute = (node.option? 'reversed') ? (append_boolean_attribute 'reversed', @xml_mode) : ''
662
669
  result << %(<ol class="#{node.style}"#{type_attribute}#{start_attribute}#{reversed_attribute}>)
663
670
 
664
671
  node.items.each do |item|
@@ -676,24 +683,24 @@ Your browser does not support the audio tag.
676
683
  def open node
677
684
  if (style = node.style) == 'abstract'
678
685
  if node.parent == node.document && node.document.doctype == 'book'
679
- warn 'asciidoctor: WARNING: abstract block cannot be used in a document without a title when doctype is book. Excluding block content.'
686
+ logger.warn 'abstract block cannot be used in a document without a title when doctype is book. Excluding block content.'
680
687
  ''
681
688
  else
682
- id_attr = node.id ? %( id="#{node.id}") : nil
683
- title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
684
- %(<div#{id_attr} class="quoteblock abstract#{(role = node.role) && " #{role}"}">
689
+ id_attr = node.id ? %( id="#{node.id}") : ''
690
+ title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
691
+ %(<div#{id_attr} class="quoteblock abstract#{(role = node.role) ? " #{role}" : ''}">
685
692
  #{title_el}<blockquote>
686
693
  #{node.content}
687
694
  </blockquote>
688
695
  </div>)
689
696
  end
690
697
  elsif style == 'partintro' && (node.level > 0 || node.parent.context != :section || node.document.doctype != 'book')
691
- warn 'asciidoctor: ERROR: partintro block can only be used when doctype is book and it\'s a child of a book part. Excluding block content.'
698
+ logger.error 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
692
699
  ''
693
700
  else
694
- id_attr = node.id ? %( id="#{node.id}") : nil
695
- title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
696
- %(<div#{id_attr} class="openblock#{style && style != 'open' ? " #{style}" : ''}#{(role = node.role) && " #{role}"}">
701
+ id_attr = node.id ? %( id="#{node.id}") : ''
702
+ title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
703
+ %(<div#{id_attr} class="openblock#{style && style != 'open' ? " #{style}" : ''}#{(role = node.role) ? " #{role}" : ''}">
697
704
  #{title_el}<div class="content">
698
705
  #{node.content}
699
706
  </div>
@@ -729,7 +736,7 @@ Your browser does not support the audio tag.
729
736
  #{outline doc}
730
737
  </div>)
731
738
  else
732
- toc = nil
739
+ toc = ''
733
740
  end
734
741
 
735
742
  %(<div id="preamble">
@@ -740,18 +747,18 @@ Your browser does not support the audio tag.
740
747
  end
741
748
 
742
749
  def quote node
743
- id_attribute = node.id ? %( id="#{node.id}") : nil
750
+ id_attribute = node.id ? %( id="#{node.id}") : ''
744
751
  classes = ['quoteblock', node.role].compact
745
752
  class_attribute = %( class="#{classes * ' '}")
746
- title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : nil
753
+ title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : ''
747
754
  attribution = (node.attr? 'attribution') ? (node.attr 'attribution') : nil
748
755
  citetitle = (node.attr? 'citetitle') ? (node.attr 'citetitle') : nil
749
756
  if attribution || citetitle
750
- cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : nil
751
- attribution_text = attribution ? %(&#8212; #{attribution}#{citetitle ? "<br#{@void_element_slash}>\n" : nil}) : nil
757
+ cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : ''
758
+ attribution_text = attribution ? %(&#8212; #{attribution}#{citetitle ? "<br#{@void_element_slash}>\n" : ''}) : ''
752
759
  attribution_element = %(\n<div class="attribution">\n#{attribution_text}#{cite_element}\n</div>)
753
760
  else
754
- attribution_element = nil
761
+ attribution_element = ''
755
762
  end
756
763
 
757
764
  %(<div#{id_attribute}#{class_attribute}>#{title_element}
@@ -766,9 +773,9 @@ Your browser does not support the audio tag.
766
773
  end
767
774
 
768
775
  def sidebar node
769
- id_attribute = node.id ? %( id="#{node.id}") : nil
770
- title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
771
- %(<div#{id_attribute} class="sidebarblock#{(role = node.role) && " #{role}"}">
776
+ id_attribute = node.id ? %( id="#{node.id}") : ''
777
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
778
+ %(<div#{id_attribute} class="sidebarblock#{(role = node.role) ? " #{role}" : ''}">
772
779
  <div class="content">
773
780
  #{title_element}#{node.content}
774
781
  </div>
@@ -777,36 +784,36 @@ Your browser does not support the audio tag.
777
784
 
778
785
  def table node
779
786
  result = []
780
- id_attribute = node.id ? %( id="#{node.id}") : nil
787
+ id_attribute = node.id ? %( id="#{node.id}") : ''
781
788
  classes = ['tableblock', %(frame-#{node.attr 'frame', 'all'}), %(grid-#{node.attr 'grid', 'all'})]
789
+ if (stripes = node.attr 'stripes')
790
+ classes << %(stripes-#{stripes})
791
+ end
782
792
  styles = []
783
- unless (node.option? 'autowidth') && !(node.attr? 'width', nil, false)
784
- if node.attr? 'tablepcwidth', 100
785
- classes << 'spread'
786
- else
787
- styles << %(width: #{node.attr 'tablepcwidth'}%;)
788
- end
793
+ if (autowidth = node.attributes['autowidth-option']) && !(node.attr? 'width', nil, false)
794
+ classes << 'fit-content'
795
+ elsif (tablewidth = node.attr 'tablepcwidth') == 100
796
+ classes << 'stretch'
797
+ else
798
+ styles << %(width: #{tablewidth}%;)
789
799
  end
790
800
  if (role = node.role)
791
801
  classes << role
792
802
  end
793
803
  class_attribute = %( class="#{classes * ' '}")
794
804
  styles << %(float: #{node.attr 'float'};) if node.attr? 'float'
795
- style_attribute = styles.empty? ? nil : %( style="#{styles * ' '}")
805
+ style_attribute = styles.empty? ? '' : %( style="#{styles * ' '}")
796
806
 
797
807
  result << %(<table#{id_attribute}#{class_attribute}#{style_attribute}>)
798
808
  result << %(<caption class="title">#{node.captioned_title}</caption>) if node.title?
799
809
  if (node.attr 'rowcount') > 0
800
810
  slash = @void_element_slash
801
811
  result << '<colgroup>'
802
- if node.option? 'autowidth'
803
- tag = %(<col#{slash}>)
804
- node.columns.size.times do
805
- result << tag
806
- end
812
+ if autowidth
813
+ result += (Array.new node.columns.size, %(<col#{slash}>))
807
814
  else
808
815
  node.columns.each do |col|
809
- result << %(<col style="width: #{col.attr 'colpcwidth'}%;"#{slash}>)
816
+ result << (col.attributes['autowidth-option'] ? %(<col#{slash}>) : %(<col style="width: #{col.attr 'colpcwidth'}%;"#{slash}>))
810
817
  end
811
818
  end
812
819
  result << '</colgroup>'
@@ -821,7 +828,7 @@ Your browser does not support the audio tag.
821
828
  else
822
829
  case cell.style
823
830
  when :asciidoc
824
- cell_content = %(<div>#{cell.content}</div>)
831
+ cell_content = %(<div class="content">#{cell.content}</div>)
825
832
  when :verse
826
833
  cell_content = %(<div class="verse">#{cell.text}</div>)
827
834
  when :literal
@@ -834,9 +841,9 @@ Your browser does not support the audio tag.
834
841
 
835
842
  cell_tag_name = (tsec == :head || cell.style == :header ? 'th' : 'td')
836
843
  cell_class_attribute = %( class="tableblock halign-#{cell.attr 'halign'} valign-#{cell.attr 'valign'}")
837
- cell_colspan_attribute = cell.colspan ? %( colspan="#{cell.colspan}") : nil
838
- cell_rowspan_attribute = cell.rowspan ? %( rowspan="#{cell.rowspan}") : nil
839
- cell_style_attribute = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'};") : nil
844
+ cell_colspan_attribute = cell.colspan ? %( colspan="#{cell.colspan}") : ''
845
+ cell_rowspan_attribute = cell.rowspan ? %( rowspan="#{cell.rowspan}") : ''
846
+ cell_style_attribute = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'};") : ''
840
847
  result << %(<#{cell_tag_name}#{cell_class_attribute}#{cell_colspan_attribute}#{cell_rowspan_attribute}#{cell_style_attribute}>#{cell_content}</#{cell_tag_name}>)
841
848
  end
842
849
  result << '</tr>'
@@ -872,10 +879,9 @@ Your browser does not support the audio tag.
872
879
 
873
880
  def ulist node
874
881
  result = []
875
- id_attribute = node.id ? %( id="#{node.id}") : nil
882
+ id_attribute = node.id ? %( id="#{node.id}") : ''
876
883
  div_classes = ['ulist', node.style, node.role].compact
877
- marker_checked = nil
878
- marker_unchecked = nil
884
+ marker_checked = marker_unchecked = ''
879
885
  if (checklist = node.option? 'checklist')
880
886
  div_classes.unshift div_classes.shift, 'checklist'
881
887
  ul_class_attribute = ' class="checklist"'
@@ -897,7 +903,7 @@ Your browser does not support the audio tag.
897
903
  end
898
904
  end
899
905
  else
900
- ul_class_attribute = node.style ? %( class="#{node.style}") : nil
906
+ ul_class_attribute = node.style ? %( class="#{node.style}") : ''
901
907
  end
902
908
  result << %(<div#{id_attribute} class="#{div_classes * ' '}">)
903
909
  result << %(<div class="title">#{node.title}</div>) if node.title?
@@ -920,18 +926,18 @@ Your browser does not support the audio tag.
920
926
  end
921
927
 
922
928
  def verse node
923
- id_attribute = node.id ? %( id="#{node.id}") : nil
929
+ id_attribute = node.id ? %( id="#{node.id}") : ''
924
930
  classes = ['verseblock', node.role].compact
925
931
  class_attribute = %( class="#{classes * ' '}")
926
- title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : nil
932
+ title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : ''
927
933
  attribution = (node.attr? 'attribution') ? (node.attr 'attribution') : nil
928
934
  citetitle = (node.attr? 'citetitle') ? (node.attr 'citetitle') : nil
929
935
  if attribution || citetitle
930
- cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : nil
931
- attribution_text = attribution ? %(&#8212; #{attribution}#{citetitle ? "<br#{@void_element_slash}>\n" : nil}) : nil
936
+ cite_element = citetitle ? %(<cite>#{citetitle}</cite>) : ''
937
+ attribution_text = attribution ? %(&#8212; #{attribution}#{citetitle ? "<br#{@void_element_slash}>\n" : ''}) : ''
932
938
  attribution_element = %(\n<div class="attribution">\n#{attribution_text}#{cite_element}\n</div>)
933
939
  else
934
- attribution_element = nil
940
+ attribution_element = ''
935
941
  end
936
942
 
937
943
  %(<div#{id_attribute}#{class_attribute}>#{title_element}
@@ -941,25 +947,29 @@ Your browser does not support the audio tag.
941
947
 
942
948
  def video node
943
949
  xml = @xml_mode
944
- id_attribute = node.id ? %( id="#{node.id}") : nil
950
+ id_attribute = node.id ? %( id="#{node.id}") : ''
945
951
  classes = ['videoblock', node.role].compact
946
952
  class_attribute = %( class="#{classes * ' '}")
947
- title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : nil
948
- width_attribute = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : nil
949
- height_attribute = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : nil
953
+ title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : ''
954
+ width_attribute = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
955
+ height_attribute = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : ''
950
956
  case node.attr 'poster'
951
957
  when 'vimeo'
952
958
  unless (asset_uri_scheme = (node.document.attr 'asset-uri-scheme', 'https')).empty?
953
959
  asset_uri_scheme = %(#{asset_uri_scheme}:)
954
960
  end
955
- start_anchor = (node.attr? 'start', nil, false) ? %(#at=#{node.attr 'start'}) : nil
961
+ start_anchor = (node.attr? 'start', nil, false) ? %(#at=#{node.attr 'start'}) : ''
956
962
  delimiter = '?'
957
- autoplay_param = (node.option? 'autoplay') ? %(#{delimiter}autoplay=1) : nil
958
- delimiter = '&amp;' if autoplay_param
959
- loop_param = (node.option? 'loop') ? %(#{delimiter}loop=1) : nil
963
+ if node.option? 'autoplay'
964
+ autoplay_param = %(#{delimiter}autoplay=1)
965
+ delimiter = '&amp;'
966
+ else
967
+ autoplay_param = ''
968
+ end
969
+ loop_param = (node.option? 'loop') ? %(#{delimiter}loop=1) : ''
960
970
  %(<div#{id_attribute}#{class_attribute}>#{title_element}
961
971
  <div class="content">
962
- <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') ? nil : (append_boolean_attribute 'allowfullscreen', xml)}></iframe>
972
+ <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>
963
973
  </div>
964
974
  </div>)
965
975
  when 'youtube'
@@ -968,22 +978,22 @@ Your browser does not support the audio tag.
968
978
  end
969
979
  rel_param_val = (node.option? 'related') ? 1 : 0
970
980
  # NOTE start and end must be seconds (t parameter allows XmYs where X is minutes and Y is seconds)
971
- start_param = (node.attr? 'start', nil, false) ? %(&amp;start=#{node.attr 'start'}) : nil
972
- end_param = (node.attr? 'end', nil, false) ? %(&amp;end=#{node.attr 'end'}) : nil
973
- autoplay_param = (node.option? 'autoplay') ? '&amp;autoplay=1' : nil
974
- loop_param = (node.option? 'loop') ? '&amp;loop=1' : nil
975
- controls_param = (node.option? 'nocontrols') ? '&amp;controls=0' : nil
981
+ start_param = (node.attr? 'start', nil, false) ? %(&amp;start=#{node.attr 'start'}) : ''
982
+ end_param = (node.attr? 'end', nil, false) ? %(&amp;end=#{node.attr 'end'}) : ''
983
+ autoplay_param = (node.option? 'autoplay') ? '&amp;autoplay=1' : ''
984
+ loop_param = (has_loop_param = node.option? 'loop') ? '&amp;loop=1' : ''
985
+ controls_param = (node.option? 'nocontrols') ? '&amp;controls=0' : ''
976
986
  # cover both ways of controlling fullscreen option
977
987
  if node.option? 'nofullscreen'
978
988
  fs_param = '&amp;fs=0'
979
- fs_attribute = nil
989
+ fs_attribute = ''
980
990
  else
981
- fs_param = nil
991
+ fs_param = ''
982
992
  fs_attribute = append_boolean_attribute 'allowfullscreen', xml
983
993
  end
984
- modest_param = (node.option? 'modest') ? '&amp;modestbranding=1' : nil
985
- theme_param = (node.attr? 'theme', nil, false) ? %(&amp;theme=#{node.attr 'theme'}) : nil
986
- hl_param = (node.attr? 'lang') ? %(&amp;hl=#{node.attr 'lang'}) : nil
994
+ modest_param = (node.option? 'modest') ? '&amp;modestbranding=1' : ''
995
+ theme_param = (node.attr? 'theme', nil, false) ? %(&amp;theme=#{node.attr 'theme'}) : ''
996
+ hl_param = (node.attr? 'lang') ? %(&amp;hl=#{node.attr 'lang'}) : ''
987
997
 
988
998
  # parse video_id/list_id syntax where list_id (i.e., playlist) is optional
989
999
  target, list = (node.attr 'target').split '/', 2
@@ -997,7 +1007,7 @@ Your browser does not support the audio tag.
997
1007
  list_param = %(&amp;playlist=#{playlist})
998
1008
  else
999
1009
  # NOTE for loop to work, playlist must be specified; use VIDEO_ID if there's no explicit playlist
1000
- list_param = loop_param ? %(&amp;playlist=#{target}) : nil
1010
+ list_param = has_loop_param ? %(&amp;playlist=#{target}) : ''
1001
1011
  end
1002
1012
  end
1003
1013
 
@@ -1007,14 +1017,14 @@ Your browser does not support the audio tag.
1007
1017
  </div>
1008
1018
  </div>)
1009
1019
  else
1010
- poster_attribute = (val = node.attr 'poster', nil, false).nil_or_empty? ? nil : %( poster="#{node.media_uri val}")
1011
- preload_attribute = (val = node.attr 'preload', nil, false).nil_or_empty? ? nil : %( preload="#{val}")
1020
+ poster_attribute = (val = node.attr 'poster', nil, false).nil_or_empty? ? '' : %( poster="#{node.media_uri val}")
1021
+ preload_attribute = (val = node.attr 'preload', nil, false).nil_or_empty? ? '' : %( preload="#{val}")
1012
1022
  start_t = node.attr 'start', nil, false
1013
1023
  end_t = node.attr 'end', nil, false
1014
- time_anchor = (start_t || end_t) ? %(#t=#{start_t}#{end_t ? ',' : nil}#{end_t}) : nil
1024
+ time_anchor = (start_t || end_t) ? %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''}) : ''
1015
1025
  %(<div#{id_attribute}#{class_attribute}>#{title_element}
1016
1026
  <div class="content">
1017
- <video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : nil}#{(node.option? 'nocontrols') ? nil : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : nil}#{preload_attribute}>
1027
+ <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}>
1018
1028
  Your browser does not support the video tag.
1019
1029
  </video>
1020
1030
  </div>
@@ -1025,29 +1035,34 @@ Your browser does not support the video tag.
1025
1035
  def inline_anchor node
1026
1036
  case node.type
1027
1037
  when :xref
1028
- unless (text = node.text) || (text = node.attributes['path'])
1029
- if AbstractNode === (ref = node.document.catalog[:refs][refid = node.attributes['refid']])
1030
- text = ref.xreftext((@xrefstyle ||= node.document.attributes['xrefstyle'])) || %([#{refid}])
1031
- else
1032
- text = %([#{refid}])
1038
+ if (path = node.attributes['path'])
1039
+ attrs = (append_link_constraint_attrs node, node.role ? [%( class="#{node.role}")] : []).join
1040
+ text = node.text || path
1041
+ else
1042
+ attrs = node.role ? %( class="#{node.role}") : ''
1043
+ unless (text = node.text)
1044
+ refid = node.attributes['refid']
1045
+ if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid])
1046
+ text = (ref.xreftext node.attr('xrefstyle')) || %([#{refid}])
1047
+ else
1048
+ text = %([#{refid}])
1049
+ end
1033
1050
  end
1034
1051
  end
1035
- %(<a href="#{node.target}">#{text}</a>)
1052
+ %(<a href="#{node.target}"#{attrs}>#{text}</a>)
1036
1053
  when :ref
1037
1054
  %(<a id="#{node.id}"></a>)
1038
1055
  when :link
1039
1056
  attrs = node.id ? [%( id="#{node.id}")] : []
1040
- if (role = node.role)
1041
- attrs << %( class="#{role}")
1042
- end
1057
+ attrs << %( class="#{node.role}") if node.role
1043
1058
  attrs << %( title="#{node.attr 'title'}") if node.attr? 'title', nil, false
1044
- attrs << %( target="#{window = node.attr 'window'}"#{window == '_blank' || (node.option? 'noopener') ? ' rel="noopener"' : ''}) if node.attr? 'window', nil, false
1045
- %(<a href="#{node.target}"#{attrs.join}>#{node.text}</a>)
1059
+ %(<a href="#{node.target}"#{(append_link_constraint_attrs node, attrs).join}>#{node.text}</a>)
1046
1060
  when :bibref
1047
1061
  # NOTE technically node.text should be node.reftext, but subs have already been applied to text
1048
1062
  %(<a id="#{node.id}"></a>#{node.text})
1049
1063
  else
1050
- warn %(asciidoctor: WARNING: unknown anchor type: #{node.type.inspect})
1064
+ logger.warn %(unknown anchor type: #{node.type.inspect})
1065
+ nil
1051
1066
  end
1052
1067
  end
1053
1068
 
@@ -1073,10 +1088,10 @@ Your browser does not support the video tag.
1073
1088
  def inline_footnote node
1074
1089
  if (index = node.attr 'index', nil, false)
1075
1090
  if node.type == :xref
1076
- %(<sup class="footnoteref">[<a class="footnote" href="#_footnote_#{index}" title="View footnote.">#{index}</a>]</sup>)
1091
+ %(<sup class="footnoteref">[<a class="footnote" href="#_footnotedef_#{index}" title="View footnote.">#{index}</a>]</sup>)
1077
1092
  else
1078
- id_attr = node.id ? %( id="_footnote_#{node.id}") : nil
1079
- %(<sup class="footnote"#{id_attr}>[<a id="_footnoteref_#{index}" class="footnote" href="#_footnote_#{index}" title="View footnote.">#{index}</a>]</sup>)
1093
+ id_attr = node.id ? %( id="_footnote_#{node.id}") : ''
1094
+ %(<sup class="footnote"#{id_attr}>[<a id="_footnoteref_#{index}" class="footnote" href="#_footnotedef_#{index}" title="View footnote.">#{index}</a>]</sup>)
1080
1095
  end
1081
1096
  elsif node.type == :xref
1082
1097
  %(<sup class="footnoteref red" title="Unresolved footnote reference.">[#{node.text}]</sup>)
@@ -1089,13 +1104,13 @@ Your browser does not support the video tag.
1089
1104
  {'size' => 'fa-', 'rotate' => 'fa-rotate-', 'flip' => 'fa-flip-'}.each do |key, prefix|
1090
1105
  class_attr_val = %(#{class_attr_val} #{prefix}#{node.attr key}) if node.attr? key
1091
1106
  end
1092
- title_attr = (node.attr? 'title') ? %( title="#{node.attr 'title'}") : nil
1107
+ title_attr = (node.attr? 'title') ? %( title="#{node.attr 'title'}") : ''
1093
1108
  img = %(<i class="#{class_attr_val}"#{title_attr}></i>)
1094
1109
  elsif type == 'icon' && !(node.document.attr? 'icons')
1095
1110
  img = %([#{node.alt}])
1096
1111
  else
1097
1112
  target = node.target
1098
- attrs = ['width', 'height', 'title'].map {|name| (node.attr? name) ? %( #{name}="#{node.attr name}") : nil }.join
1113
+ attrs = ['width', 'height', 'title'].map {|name| (node.attr? name) ? %( #{name}="#{node.attr name}") : '' }.join
1099
1114
  if type != 'icon' && ((node.attr? 'format', 'svg', false) || (target.include? '.svg')) &&
1100
1115
  node.document.safe < SafeMode::SECURE && ((svg = (node.option? 'inline')) || (obj = (node.option? 'interactive')))
1101
1116
  if svg
@@ -1107,12 +1122,11 @@ Your browser does not support the video tag.
1107
1122
  end
1108
1123
  img ||= %(<img src="#{type == 'icon' ? (node.icon_uri target) : (node.image_uri target)}" alt="#{encode_quotes node.alt}"#{attrs}#{@void_element_slash}>)
1109
1124
  end
1110
- if node.attr? 'link'
1111
- window_attr = %( target="#{window = node.attr 'window'}"#{window == '_blank' || (node.option? 'noopener') ? ' rel="noopener"' : ''}) if node.attr? 'window'
1112
- img = %(<a class="image" href="#{node.attr 'link'}"#{window_attr}>#{img}</a>)
1125
+ if node.attr? 'link', nil, false
1126
+ img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>)
1113
1127
  end
1114
1128
  class_attr_val = (role = node.role) ? %(#{type} #{role}) : type
1115
- style_attr = (node.attr? 'float') ? %( style="float: #{node.attr 'float'}") : nil
1129
+ style_attr = (node.attr? 'float') ? %( style="float: #{node.attr 'float'}") : ''
1116
1130
  %(<span class="#{class_attr_val}"#{style_attr}>#{img}</span>)
1117
1131
  end
1118
1132
 
@@ -1145,17 +1159,17 @@ Your browser does not support the video tag.
1145
1159
 
1146
1160
  def inline_quoted node
1147
1161
  open, close, is_tag = QUOTE_TAGS[node.type]
1148
- if node.role
1162
+ class_attr = %( class="#{node.role}") if node.role
1163
+ id_attr = %( id="#{node.id}") if node.id
1164
+ if class_attr || id_attr
1149
1165
  if is_tag
1150
- quoted_text = %(#{open.chop} class="#{node.role}">#{node.text}#{close})
1166
+ %(#{open.chop}#{id_attr || ''}#{class_attr || ''}>#{node.text}#{close})
1151
1167
  else
1152
- quoted_text = %(<span class="#{node.role}">#{open}#{node.text}#{close}</span>)
1168
+ %(<span#{id_attr || ''}#{class_attr || ''}>#{open}#{node.text}#{close}</span>)
1153
1169
  end
1154
1170
  else
1155
- quoted_text = %(#{open}#{node.text}#{close})
1171
+ %(#{open}#{node.text}#{close})
1156
1172
  end
1157
-
1158
- node.id ? %(<a id="#{node.id}"></a>#{quoted_text}) : quoted_text
1159
1173
  end
1160
1174
 
1161
1175
  def append_boolean_attribute name, xml
@@ -1166,6 +1180,29 @@ Your browser does not support the video tag.
1166
1180
  (val.include? '"') ? (val.gsub '"', '&quot;') : val
1167
1181
  end
1168
1182
 
1183
+ def generate_manname_section node
1184
+ manname_title = (node.attr 'manname-title') || 'Name'
1185
+ if (next_section = node.sections[0]) && (next_section_title = next_section.title) == next_section_title.upcase
1186
+ manname_title = manname_title.upcase
1187
+ end
1188
+ manname_id_attr = (manname_id = node.attr 'manname-id') ? %( id="#{manname_id}") : ''
1189
+ %(<h2#{manname_id_attr}>#{manname_title}</h2>
1190
+ <div class="sectionbody">
1191
+ <p>#{node.attr 'manname'} - #{node.attr 'manpurpose'}</p>
1192
+ </div>)
1193
+ end
1194
+
1195
+ def append_link_constraint_attrs node, attrs = []
1196
+ rel = 'nofollow' if node.option? 'nofollow'
1197
+ if (window = node.attributes['window'])
1198
+ attrs << %( target="#{window}")
1199
+ attrs << (rel ? %( rel="#{rel} noopener") : ' rel="noopener"') if window == '_blank' || (node.option? 'noopener')
1200
+ elsif rel
1201
+ attrs << %( rel="#{rel}")
1202
+ end
1203
+ attrs
1204
+ end
1205
+
1169
1206
  def read_svg_contents node, target
1170
1207
  if (svg = node.read_contents target, :start => (node.document.attr 'imagesdir'), :normalize => true, :label => 'SVG')
1171
1208
  svg = svg.sub SvgPreambleRx, '' unless svg.start_with? '<svg'