asciidoctor 1.5.5 → 1.5.6

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +216 -1
  3. data/CONTRIBUTING.adoc +2 -2
  4. data/Gemfile +20 -1
  5. data/LICENSE.adoc +1 -1
  6. data/README-fr.adoc +4 -3
  7. data/README-jp.adoc +11 -10
  8. data/README-zh_CN.adoc +4 -3
  9. data/README.adoc +17 -202
  10. data/Rakefile +41 -25
  11. data/asciidoctor.gemspec +9 -10
  12. data/data/locale/attributes.adoc +216 -34
  13. data/data/stylesheets/asciidoctor-default.css +23 -16
  14. data/features/step_definitions.rb +15 -19
  15. data/features/xref.feature +584 -20
  16. data/lib/asciidoctor.rb +292 -278
  17. data/lib/asciidoctor/abstract_block.rb +155 -94
  18. data/lib/asciidoctor/abstract_node.rb +108 -94
  19. data/lib/asciidoctor/attribute_list.rb +30 -22
  20. data/lib/asciidoctor/block.rb +7 -7
  21. data/lib/asciidoctor/cli/invoker.rb +47 -34
  22. data/lib/asciidoctor/cli/options.rb +22 -11
  23. data/lib/asciidoctor/converter.rb +3 -3
  24. data/lib/asciidoctor/converter/base.rb +2 -2
  25. data/lib/asciidoctor/converter/composite.rb +1 -1
  26. data/lib/asciidoctor/converter/docbook45.rb +2 -2
  27. data/lib/asciidoctor/converter/docbook5.rb +132 -87
  28. data/lib/asciidoctor/converter/factory.rb +0 -1
  29. data/lib/asciidoctor/converter/html5.rb +116 -98
  30. data/lib/asciidoctor/converter/manpage.rb +51 -52
  31. data/lib/asciidoctor/converter/template.rb +47 -36
  32. data/lib/asciidoctor/core_ext.rb +8 -2
  33. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +4 -0
  34. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +6 -0
  35. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +5 -0
  36. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +1 -1
  37. data/lib/asciidoctor/core_ext/1.8.7/string/{limit.rb → limit_bytesize.rb} +7 -6
  38. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +6 -0
  39. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +1 -1
  40. data/lib/asciidoctor/core_ext/nil_or_empty.rb +5 -5
  41. data/lib/asciidoctor/core_ext/regexp/is_match.rb +3 -0
  42. data/lib/asciidoctor/core_ext/string/{limit.rb → limit_bytesize.rb} +2 -2
  43. data/lib/asciidoctor/document.rb +216 -213
  44. data/lib/asciidoctor/extensions.rb +318 -185
  45. data/lib/asciidoctor/helpers.rb +35 -35
  46. data/lib/asciidoctor/inline.rb +32 -1
  47. data/lib/asciidoctor/list.rb +22 -6
  48. data/lib/asciidoctor/parser.rb +1008 -1038
  49. data/lib/asciidoctor/path_resolver.rb +46 -50
  50. data/lib/asciidoctor/reader.rb +275 -251
  51. data/lib/asciidoctor/section.rb +86 -58
  52. data/lib/asciidoctor/stylesheets.rb +6 -6
  53. data/lib/asciidoctor/substitutors.rb +567 -649
  54. data/lib/asciidoctor/table.rb +163 -108
  55. data/lib/asciidoctor/version.rb +1 -1
  56. data/man/asciidoctor.1 +18 -16
  57. data/man/asciidoctor.adoc +15 -13
  58. data/test/attributes_test.rb +138 -22
  59. data/test/blocks_test.rb +377 -97
  60. data/test/converter_test.rb +13 -0
  61. data/test/document_test.rb +244 -34
  62. data/test/extensions_test.rb +409 -42
  63. data/test/fixtures/asciidoc_index.txt +521 -0
  64. data/test/fixtures/basic-docinfo-footer.html +6 -0
  65. data/test/fixtures/basic-docinfo-footer.xml +8 -0
  66. data/test/fixtures/basic-docinfo.html +1 -0
  67. data/test/fixtures/basic-docinfo.xml +4 -0
  68. data/test/fixtures/basic.asciidoc +5 -0
  69. data/test/fixtures/chapter-a.adoc +3 -0
  70. data/test/fixtures/child-include.adoc +5 -0
  71. data/test/fixtures/circle.svg +9 -0
  72. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
  73. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +6 -0
  74. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +1 -0
  75. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +3 -0
  76. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +5 -0
  77. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +6 -0
  78. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +3 -0
  79. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +5 -0
  80. data/test/fixtures/custom-docinfodir/basic-docinfo.html +1 -0
  81. data/test/fixtures/custom-docinfodir/docinfo.html +1 -0
  82. data/test/fixtures/docinfo-footer.html +1 -0
  83. data/test/fixtures/docinfo-footer.xml +9 -0
  84. data/test/fixtures/docinfo.html +1 -0
  85. data/test/fixtures/docinfo.xml +3 -0
  86. data/test/fixtures/dot.gif +0 -0
  87. data/test/fixtures/encoding.asciidoc +13 -0
  88. data/test/fixtures/grandchild-include.adoc +3 -0
  89. data/test/fixtures/hello-asciidoctor.pdf +69 -0
  90. data/test/fixtures/include-file.asciidoc +24 -0
  91. data/test/fixtures/include-file.ml +3 -0
  92. data/test/fixtures/include-file.xml +5 -0
  93. data/test/fixtures/master.adoc +5 -0
  94. data/test/fixtures/mismatched-end-tag.adoc +7 -0
  95. data/test/fixtures/parent-include-restricted.adoc +5 -0
  96. data/test/fixtures/parent-include.adoc +5 -0
  97. data/test/fixtures/sample.asciidoc +26 -0
  98. data/test/fixtures/stylesheets/custom.css +3 -0
  99. data/test/fixtures/subs-docinfo.html +2 -0
  100. data/test/fixtures/subs.adoc +7 -0
  101. data/test/fixtures/tagged-class-enclosed.rb +26 -0
  102. data/test/fixtures/tagged-class.rb +23 -0
  103. data/test/fixtures/tip.gif +0 -0
  104. data/test/invoker_test.rb +82 -4
  105. data/test/links_test.rb +312 -37
  106. data/test/lists_test.rb +204 -25
  107. data/test/manpage_test.rb +191 -4
  108. data/test/options_test.rb +18 -1
  109. data/test/paragraphs_test.rb +32 -7
  110. data/test/parser_test.rb +150 -30
  111. data/test/paths_test.rb +47 -13
  112. data/test/preamble_test.rb +1 -1
  113. data/test/reader_test.rb +366 -126
  114. data/test/sections_test.rb +203 -56
  115. data/test/substitutions_test.rb +339 -131
  116. data/test/tables_test.rb +315 -15
  117. data/test/test_helper.rb +400 -0
  118. data/test/text_test.rb +5 -5
  119. metadata +110 -22
@@ -88,7 +88,6 @@ module Asciidoctor
88
88
  # If the custom converter is not found, an attempt will be made to find
89
89
  # and instantiate a built-in converter.
90
90
  #
91
- #
92
91
  # backend - The String backend name
93
92
  # opts - A Hash of options to pass to the converter
94
93
  #
@@ -4,20 +4,20 @@ module Asciidoctor
4
4
  # consistent with the html5 backend from AsciiDoc Python.
5
5
  class Converter::Html5Converter < Converter::BuiltIn
6
6
  (QUOTE_TAGS = {
7
+ :monospaced => ['<code>', '</code>', true],
7
8
  :emphasis => ['<em>', '</em>', true],
8
9
  :strong => ['<strong>', '</strong>', true],
9
- :monospaced => ['<code>', '</code>', true],
10
- :superscript => ['<sup>', '</sup>', true],
11
- :subscript => ['<sub>', '</sub>', true],
12
10
  :double => ['&#8220;', '&#8221;', false],
13
11
  :single => ['&#8216;', '&#8217;', false],
14
12
  :mark => ['<mark>', '</mark>', true],
15
- :asciimath => ['\\$', '\\$', false],
16
- :latexmath => ['\\(', '\\)', false]
13
+ :superscript => ['<sup>', '</sup>', true],
14
+ :subscript => ['<sub>', '</sub>', true],
15
+ :asciimath => ['\$', '\$', false],
16
+ :latexmath => ['\(', '\)', false]
17
17
  # Opal can't resolve these constants when referenced here
18
18
  #:asciimath => INLINE_MATH_DELIMITERS[:asciimath] + [false],
19
19
  #:latexmath => INLINE_MATH_DELIMITERS[:latexmath] + [false]
20
- }).default = [nil, nil, nil]
20
+ }).default = ['', '', false]
21
21
 
22
22
  SvgPreambleRx = /\A.*?(?=<svg\b)/m
23
23
  SvgStartTagRx = /\A<svg[^>]*>/
@@ -30,7 +30,6 @@ module Asciidoctor
30
30
  end
31
31
 
32
32
  def document node
33
- result = []
34
33
  slash = @void_element_slash
35
34
  br = %(<br#{slash}>)
36
35
  unless (asset_uri_scheme = (node.attr 'asset-uri-scheme', 'https')).empty?
@@ -38,7 +37,7 @@ module Asciidoctor
38
37
  end
39
38
  cdn_base = %(#{asset_uri_scheme}//cdnjs.cloudflare.com/ajax/libs)
40
39
  linkcss = node.safe >= SafeMode::SECURE || (node.attr? 'linkcss')
41
- result << '<!DOCTYPE html>'
40
+ result = ['<!DOCTYPE html>']
42
41
  lang_attribute = (node.attr? 'nolang') ? nil : %( lang="#{node.attr 'lang', 'en'}")
43
42
  result << %(<html#{@xml_mode ? ' xmlns="http://www.w3.org/1999/xhtml"' : nil}#{lang_attribute}>)
44
43
  result << %(<head>
@@ -49,8 +48,16 @@ module Asciidoctor
49
48
  result << %(<meta name="application-name" content="#{node.attr 'app-name'}"#{slash}>) if node.attr? 'app-name'
50
49
  result << %(<meta name="description" content="#{node.attr 'description'}"#{slash}>) if node.attr? 'description'
51
50
  result << %(<meta name="keywords" content="#{node.attr 'keywords'}"#{slash}>) if node.attr? 'keywords'
52
- result << %(<meta name="author" content="#{node.attr 'authors'}"#{slash}>) if node.attr? 'authors'
51
+ result << %(<meta name="author" content="#{((authors = node.attr 'authors').include? '<') ? (authors.gsub XmlSanitizeRx, '') : authors}"#{slash}>) if node.attr? 'authors'
53
52
  result << %(<meta name="copyright" content="#{node.attr 'copyright'}"#{slash}>) if node.attr? 'copyright'
53
+ if node.attr? 'favicon'
54
+ if (icon_href = node.attr 'favicon').empty?
55
+ icon_href, icon_type = 'favicon.ico', 'image/x-icon'
56
+ else
57
+ icon_type = (icon_ext = ::File.extname icon_href) == '.ico' ? 'image/x-icon' : %(image/#{icon_ext[1..-1]})
58
+ end
59
+ result << %(<link rel="shortcut icon" type="#{icon_type}" href="#{icon_href}">)
60
+ end
54
61
  result << %(<title>#{node.doctitle :sanitize => true, :use_fallback => true}</title>)
55
62
 
56
63
  if DEFAULT_STYLESHEET_KEYS.include?(node.attr 'stylesheet')
@@ -67,7 +74,7 @@ module Asciidoctor
67
74
  result << %(<link rel="stylesheet" href="#{node.normalize_web_path((node.attr 'stylesheet'), (node.attr 'stylesdir', ''))}"#{slash}>)
68
75
  else
69
76
  result << %(<style>
70
- #{node.read_asset node.normalize_system_path((node.attr 'stylesheet'), (node.attr 'stylesdir', '')), :warn_on_failure => true}
77
+ #{node.read_asset node.normalize_system_path((node.attr 'stylesheet'), (node.attr 'stylesdir', '')), :warn_on_failure => true, :label => 'stylesheet'}
71
78
  </style>)
72
79
  end
73
80
  end
@@ -207,7 +214,7 @@ module Asciidoctor
207
214
  # See http://www.html5rocks.com/en/tutorials/speed/script-loading/
208
215
  case highlighter
209
216
  when 'highlightjs', 'highlight.js'
210
- highlightjs_path = node.attr 'highlightjsdir', %(#{cdn_base}/highlight.js/8.9.1)
217
+ highlightjs_path = node.attr 'highlightjsdir', %(#{cdn_base}/highlight.js/9.12.0)
211
218
  result << %(<link rel="stylesheet" href="#{highlightjs_path}/styles/#{node.attr 'highlightjs-theme', 'github'}.min.css"#{slash}>)
212
219
  result << %(<script src="#{highlightjs_path}/highlight.min.js"></script>
213
220
  <script>hljs.initHighlighting()</script>)
@@ -243,7 +250,7 @@ MathJax.Hub.Config({
243
250
 
244
251
  result << '</body>'
245
252
  result << '</html>'
246
- result * EOL
253
+ result * LF
247
254
  end
248
255
 
249
256
  def embedded node
@@ -286,38 +293,39 @@ MathJax.Hub.Config({
286
293
  result << '</div>'
287
294
  end
288
295
 
289
- result * EOL
296
+ result * LF
290
297
  end
291
298
 
292
299
  def outline node, opts = {}
293
300
  return unless node.sections?
294
301
  sectnumlevels = opts[:sectnumlevels] || (node.document.attr 'sectnumlevels', 3).to_i
295
302
  toclevels = opts[:toclevels] || (node.document.attr 'toclevels', 2).to_i
296
- result = []
297
303
  sections = node.sections
298
- # FIXME the level for special sections should be set correctly in the model
299
- # slevel will only be 0 if we have a book doctype with parts
300
- slevel = (first_section = sections[0]).level
301
- slevel = 1 if slevel == 0 && first_section.special
302
- result << %(<ul class="sectlevel#{slevel}">)
304
+ # FIXME top level is incorrect if a multipart book starts with a special section defined at level 0
305
+ result = [%(<ul class="sectlevel#{sections[0].level}">)]
303
306
  sections.each do |section|
304
- section_num = (section.numbered && !section.caption && section.level <= sectnumlevels) ? %(#{section.sectnum} ) : nil
305
- if section.level < toclevels && (child_toc_level = outline section, :toclevels => toclevels, :secnumlevels => sectnumlevels)
306
- result << %(<li><a href="##{section.id}">#{section_num}#{section.captioned_title}</a>)
307
+ slevel = section.level
308
+ if section.caption
309
+ stitle = section.captioned_title
310
+ elsif section.numbered && slevel <= sectnumlevels
311
+ stitle = %(#{section.sectnum} #{section.title})
312
+ else
313
+ stitle = section.title
314
+ end
315
+ if slevel < toclevels && (child_toc_level = outline section, :toclevels => toclevels, :secnumlevels => sectnumlevels)
316
+ result << %(<li><a href="##{section.id}">#{stitle}</a>)
307
317
  result << child_toc_level
308
318
  result << '</li>'
309
319
  else
310
- result << %(<li><a href="##{section.id}">#{section_num}#{section.captioned_title}</a></li>)
320
+ result << %(<li><a href="##{section.id}">#{stitle}</a></li>)
311
321
  end
312
322
  end
313
323
  result << '</ul>'
314
- result * EOL
324
+ result * LF
315
325
  end
316
326
 
317
327
  def section node
318
328
  slevel = node.level
319
- # QUESTION should the check for slevel be done in section?
320
- slevel = 1 if slevel == 0 && node.special
321
329
  htag = %(h#{slevel + 1})
322
330
  id_attr = anchor = link_start = link_end = nil
323
331
  if node.id
@@ -356,20 +364,20 @@ MathJax.Hub.Config({
356
364
  id_attr = node.id ? %( id="#{node.id}") : nil
357
365
  name = node.attr 'name'
358
366
  title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
359
- caption = if node.document.attr? 'icons'
367
+ if node.document.attr? 'icons'
360
368
  if (node.document.attr? 'icons', 'font') && !(node.attr? 'icon')
361
- %(<i class="fa icon-#{name}" title="#{node.caption}"></i>)
369
+ label = %(<i class="fa icon-#{name}" title="#{node.attr 'textlabel'}"></i>)
362
370
  else
363
- %(<img src="#{node.icon_uri name}" alt="#{node.caption}"#{@void_element_slash}>)
371
+ label = %(<img src="#{node.icon_uri name}" alt="#{node.attr 'textlabel'}"#{@void_element_slash}>)
364
372
  end
365
373
  else
366
- %(<div class="title">#{node.caption}</div>)
374
+ label = %(<div class="title">#{node.attr 'textlabel'}</div>)
367
375
  end
368
376
  %(<div#{id_attr} class="admonitionblock #{name}#{(role = node.role) && " #{role}"}">
369
377
  <table>
370
378
  <tr>
371
379
  <td class="icon">
372
- #{caption}
380
+ #{label}
373
381
  </td>
374
382
  <td class="content">
375
383
  #{title_element}#{node.content}
@@ -384,10 +392,13 @@ MathJax.Hub.Config({
384
392
  id_attribute = node.id ? %( id="#{node.id}") : nil
385
393
  classes = ['audioblock', node.role].compact
386
394
  class_attribute = %( class="#{classes * ' '}")
387
- title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : nil
395
+ title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
396
+ start_t = node.attr 'start', nil, false
397
+ end_t = node.attr 'end', nil, false
398
+ time_anchor = (start_t || end_t) ? %(#t=#{start_t}#{end_t ? ',' : nil}#{end_t}) : nil
388
399
  %(<div#{id_attribute}#{class_attribute}>
389
400
  #{title_element}<div class="content">
390
- <audio src="#{node.media_uri(node.attr 'target')}"#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : nil}#{(node.option? 'nocontrols') ? nil : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : nil}>
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}>
391
402
  Your browser does not support the audio tag.
392
403
  </audio>
393
404
  </div>
@@ -405,34 +416,32 @@ Your browser does not support the audio tag.
405
416
 
406
417
  if node.document.attr? 'icons'
407
418
  result << '<table>'
408
-
409
- font_icons = node.document.attr? 'icons', 'font'
410
- node.items.each_with_index do |item, i|
411
- num = i + 1
412
- num_element = if font_icons
413
- %(<i class="conum" data-value="#{num}"></i><b>#{num}</b>)
419
+ font_icons, num = (node.document.attr? 'icons', 'font'), 0
420
+ node.items.each do |item|
421
+ num += 1
422
+ if font_icons
423
+ num_label = %(<i class="conum" data-value="#{num}"></i><b>#{num}</b>)
414
424
  else
415
- %(<img src="#{node.icon_uri "callouts/#{num}"}" alt="#{num}"#{@void_element_slash}>)
425
+ num_label = %(<img src="#{node.icon_uri "callouts/#{num}"}" alt="#{num}"#{@void_element_slash}>)
416
426
  end
417
427
  result << %(<tr>
418
- <td>#{num_element}</td>
419
- <td>#{item.text}</td>
428
+ <td>#{num_label}</td>
429
+ <td>#{item.text}#{item.blocks? ? LF + item.content : ''}</td>
420
430
  </tr>)
421
431
  end
422
-
423
432
  result << '</table>'
424
433
  else
425
434
  result << '<ol>'
426
435
  node.items.each do |item|
427
436
  result << %(<li>
428
- <p>#{item.text}</p>
437
+ <p>#{item.text}</p>#{item.blocks? ? LF + item.content : ''}
429
438
  </li>)
430
439
  end
431
440
  result << '</ol>'
432
441
  end
433
442
 
434
443
  result << '</div>'
435
- result * EOL
444
+ result * LF
436
445
  end
437
446
 
438
447
  def dlist node
@@ -515,14 +524,14 @@ Your browser does not support the audio tag.
515
524
  end
516
525
 
517
526
  result << '</div>'
518
- result * EOL
527
+ result * LF
519
528
  end
520
529
 
521
530
  def example node
522
531
  id_attribute = node.id ? %( id="#{node.id}") : nil
523
532
  title_element = node.title? ? %(<div class="title">#{node.captioned_title}</div>\n) : nil
524
533
 
525
- %(<div#{id_attribute} class="#{(role = node.role) ? ['exampleblock', role] * ' ' : 'exampleblock'}">
534
+ %(<div#{id_attribute} class="exampleblock#{(role = node.role) && " #{role}"}">
526
535
  #{title_element}<div class="content">
527
536
  #{node.content}
528
537
  </div>
@@ -543,15 +552,16 @@ Your browser does not support the audio tag.
543
552
  if ((node.attr? 'format', 'svg', false) || (target.include? '.svg')) && node.document.safe < SafeMode::SECURE &&
544
553
  ((svg = (node.option? 'inline')) || (obj = (node.option? 'interactive')))
545
554
  if svg
546
- img = (read_svg_contents node, target) || %(<span class="alt">#{node.attr 'alt'}</span>)
555
+ img = (read_svg_contents node, target) || %(<span class="alt">#{node.alt}</span>)
547
556
  elsif obj
548
- fallback = (node.attr? 'fallback') ? %(<img src="#{node.image_uri(node.attr 'fallback')}" alt="#{node.attr 'alt'}"#{width_attr}#{height_attr}#{@void_element_slash}>) : %(<span class="alt">#{node.attr 'alt'}</span>)
557
+ 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>)
549
558
  img = %(<object type="image/svg+xml" data="#{node.image_uri target}"#{width_attr}#{height_attr}>#{fallback}</object>)
550
559
  end
551
560
  end
552
- img ||= %(<img src="#{node.image_uri target}" alt="#{node.attr 'alt'}"#{width_attr}#{height_attr}#{@void_element_slash}>)
553
- if (link = node.attr 'link')
554
- img = %(<a class="image" href="#{link}">#{img}</a>)
561
+ 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>)
555
565
  end
556
566
  id_attr = node.id ? %( id="#{node.id}") : nil
557
567
  classes = ['imageblock', node.role].compact
@@ -583,9 +593,9 @@ Your browser does not support the audio tag.
583
593
  pre_class = %( class="pygments highlight#{nowrap ? ' nowrap' : nil}")
584
594
  when 'highlightjs', 'highlight.js'
585
595
  pre_class = %( class="highlightjs highlight#{nowrap ? ' nowrap' : nil}")
586
- code_attrs = %( class="language-#{language}"#{code_attrs}) if language
596
+ code_attrs = %( class="language-#{language} hljs"#{code_attrs}) if language
587
597
  when 'prettify'
588
- pre_class = %( class="prettyprint highlight#{nowrap ? ' nowrap' : nil}#{(node.attr? 'linenums') ? ' linenums' : nil}")
598
+ pre_class = %( class="prettyprint highlight#{nowrap ? ' nowrap' : nil}#{(node.attr? 'linenums', nil, false) ? ' linenums' : nil}")
589
599
  code_attrs = %( class="language-#{language}"#{code_attrs}) if language
590
600
  when 'html-pipeline'
591
601
  pre_class = language ? %( lang="#{language}") : nil
@@ -630,7 +640,7 @@ Your browser does not support the audio tag.
630
640
  equation = %(#{open}#{equation}#{close})
631
641
  end
632
642
 
633
- %(<div#{id_attribute} class="#{(role = node.role) ? ['stemblock', role] * ' ' : 'stemblock'}">
643
+ %(<div#{id_attribute} class="stemblock#{(role = node.role) && " #{role}"}">
634
644
  #{title_element}<div class="content">
635
645
  #{equation}
636
646
  </div>
@@ -660,7 +670,7 @@ Your browser does not support the audio tag.
660
670
 
661
671
  result << '</ol>'
662
672
  result << '</div>'
663
- result * EOL
673
+ result * LF
664
674
  end
665
675
 
666
676
  def open node
@@ -758,7 +768,7 @@ Your browser does not support the audio tag.
758
768
  def sidebar node
759
769
  id_attribute = node.id ? %( id="#{node.id}") : nil
760
770
  title_element = node.title? ? %(<div class="title">#{node.title}</div>\n) : nil
761
- %(<div#{id_attribute} class="#{(role = node.role) ? ['sidebarblock', role] * ' ' : 'sidebarblock'}">
771
+ %(<div#{id_attribute} class="sidebarblock#{(role = node.role) && " #{role}"}">
762
772
  <div class="content">
763
773
  #{title_element}#{node.content}
764
774
  </div>
@@ -800,9 +810,10 @@ Your browser does not support the audio tag.
800
810
  end
801
811
  end
802
812
  result << '</colgroup>'
803
- [:head, :foot, :body].select {|tsec| !node.rows[tsec].empty? }.each do |tsec|
813
+ node.rows.by_section.each do |tsec, rows|
814
+ next if rows.empty?
804
815
  result << %(<t#{tsec}>)
805
- node.rows[tsec].each do |row|
816
+ rows.each do |row|
806
817
  result << '<tr>'
807
818
  row.each do |cell|
808
819
  if tsec == :head
@@ -816,10 +827,8 @@ Your browser does not support the audio tag.
816
827
  when :literal
817
828
  cell_content = %(<div class="literal"><pre>#{cell.text}</pre></div>)
818
829
  else
819
- cell_content = ''
820
- cell.content.each do |text|
821
- cell_content = %(#{cell_content}<p class="tableblock">#{text}</p>)
822
- end
830
+ cell_content = (cell_content = cell.content).empty? ? '' : %(<p class="tableblock">#{cell_content * '</p>
831
+ <p class="tableblock">'}</p>)
823
832
  end
824
833
  end
825
834
 
@@ -836,7 +845,7 @@ Your browser does not support the audio tag.
836
845
  end
837
846
  end
838
847
  result << '</table>'
839
- result * EOL
848
+ result * LF
840
849
  end
841
850
 
842
851
  def toc node
@@ -868,7 +877,7 @@ Your browser does not support the audio tag.
868
877
  marker_checked = nil
869
878
  marker_unchecked = nil
870
879
  if (checklist = node.option? 'checklist')
871
- div_classes.insert 1, 'checklist'
880
+ div_classes.unshift div_classes.shift, 'checklist'
872
881
  ul_class_attribute = ' class="checklist"'
873
882
  if node.option? 'interactive'
874
883
  if @xml_mode
@@ -907,7 +916,7 @@ Your browser does not support the audio tag.
907
916
 
908
917
  result << '</ul>'
909
918
  result << '</div>'
910
- result * EOL
919
+ result * LF
911
920
  end
912
921
 
913
922
  def verse node
@@ -935,7 +944,7 @@ Your browser does not support the audio tag.
935
944
  id_attribute = node.id ? %( id="#{node.id}") : nil
936
945
  classes = ['videoblock', node.role].compact
937
946
  class_attribute = %( class="#{classes * ' '}")
938
- title_element = node.title? ? %(\n<div class="title">#{node.captioned_title}</div>) : nil
947
+ title_element = node.title? ? %(\n<div class="title">#{node.title}</div>) : nil
939
948
  width_attribute = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : nil
940
949
  height_attribute = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : nil
941
950
  case node.attr 'poster'
@@ -998,13 +1007,14 @@ Your browser does not support the audio tag.
998
1007
  </div>
999
1008
  </div>)
1000
1009
  else
1001
- poster_attribute = %(#{poster = node.attr 'poster'}).empty? ? nil : %( poster="#{node.media_uri poster}")
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}")
1002
1012
  start_t = node.attr 'start', nil, false
1003
1013
  end_t = node.attr 'end', nil, false
1004
1014
  time_anchor = (start_t || end_t) ? %(#t=#{start_t}#{end_t ? ',' : nil}#{end_t}) : nil
1005
1015
  %(<div#{id_attribute}#{class_attribute}>#{title_element}
1006
1016
  <div class="content">
1007
- <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}>
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}>
1008
1018
  Your browser does not support the video tag.
1009
1019
  </video>
1010
1020
  </div>
@@ -1013,27 +1023,29 @@ Your browser does not support the video tag.
1013
1023
  end
1014
1024
 
1015
1025
  def inline_anchor node
1016
- target = node.target
1017
1026
  case node.type
1018
1027
  when :xref
1019
- refid = node.attributes['refid'] || target
1020
- # NOTE we lookup text in converter because DocBook doesn't need this logic
1021
- text = node.text || (node.document.references[:ids][refid] || %([#{refid}]))
1022
- # FIXME shouldn't target be refid? logic seems confused here
1023
- %(<a href="#{target}">#{text}</a>)
1028
+ unless (text = node.text)
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}])
1033
+ end
1034
+ end
1035
+ %(<a href="#{node.target}">#{text}</a>)
1024
1036
  when :ref
1025
- %(<a id="#{target}"></a>)
1037
+ %(<a id="#{node.id}"></a>)
1026
1038
  when :link
1027
- attrs = []
1028
- attrs << %( id="#{node.id}") if node.id
1039
+ attrs = node.id ? [%( id="#{node.id}")] : []
1029
1040
  if (role = node.role)
1030
1041
  attrs << %( class="#{role}")
1031
1042
  end
1032
1043
  attrs << %( title="#{node.attr 'title'}") if node.attr? 'title', nil, false
1033
- attrs << %( target="#{node.attr 'window'}") if node.attr? 'window', nil, false
1034
- %(<a href="#{target}"#{attrs.join}>#{node.text}</a>)
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>)
1035
1046
  when :bibref
1036
- %(<a id="#{target}"></a>[#{target}])
1047
+ # NOTE technically node.text should be node.reftext, but subs have already been applied to text
1048
+ %(<a id="#{node.id}"></a>#{node.text})
1037
1049
  else
1038
1050
  warn %(asciidoctor: WARNING: unknown anchor type: #{node.type.inspect})
1039
1051
  end
@@ -1059,7 +1071,7 @@ Your browser does not support the video tag.
1059
1071
  end
1060
1072
 
1061
1073
  def inline_footnote node
1062
- if (index = node.attr 'index')
1074
+ if (index = node.attr 'index', nil, false)
1063
1075
  if node.type == :xref
1064
1076
  %(<sup class="footnoteref">[<a class="footnote" href="#_footnote_#{index}" title="View footnote.">#{index}</a>]</sup>)
1065
1077
  else
@@ -1080,23 +1092,23 @@ Your browser does not support the video tag.
1080
1092
  title_attr = (node.attr? 'title') ? %( title="#{node.attr 'title'}") : nil
1081
1093
  img = %(<i class="#{class_attr_val}"#{title_attr}></i>)
1082
1094
  elsif type == 'icon' && !(node.document.attr? 'icons')
1083
- img = %([#{node.attr 'alt'}])
1095
+ img = %([#{node.alt}])
1084
1096
  else
1085
1097
  target = node.target
1086
1098
  attrs = ['width', 'height', 'title'].map {|name| (node.attr? name) ? %( #{name}="#{node.attr name}") : nil }.join
1087
1099
  if type != 'icon' && ((node.attr? 'format', 'svg', false) || (target.include? '.svg')) &&
1088
1100
  node.document.safe < SafeMode::SECURE && ((svg = (node.option? 'inline')) || (obj = (node.option? 'interactive')))
1089
1101
  if svg
1090
- img = (read_svg_contents node, target) || %(<span class="alt">#{node.attr 'alt'}</span>)
1102
+ img = (read_svg_contents node, target) || %(<span class="alt">#{node.alt}</span>)
1091
1103
  elsif obj
1092
- fallback = (node.attr? 'fallback') ? %(<img src="#{node.image_uri(node.attr 'fallback')}" alt="#{node.attr 'alt'}"#{attrs}#{@void_element_slash}>) : %(<span class="alt">#{node.attr 'alt'}</span>)
1104
+ 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>)
1093
1105
  img = %(<object type="image/svg+xml" data="#{node.image_uri target}"#{attrs}>#{fallback}</object>)
1094
1106
  end
1095
1107
  end
1096
- img ||= %(<img src="#{type == 'icon' ? (node.icon_uri target) : (node.image_uri target)}" alt="#{node.attr 'alt'}"#{attrs}#{@void_element_slash}>)
1108
+ img ||= %(<img src="#{type == 'icon' ? (node.icon_uri target) : (node.image_uri target)}" alt="#{encode_quotes node.alt}"#{attrs}#{@void_element_slash}>)
1097
1109
  end
1098
1110
  if node.attr? 'link'
1099
- window_attr = (node.attr? 'window') ? %( target="#{node.attr 'window'}") : nil
1111
+ window_attr = %( target="#{window = node.attr 'window'}"#{window == '_blank' || (node.option? 'noopener') ? ' rel="noopener"' : ''}) if node.attr? 'window'
1100
1112
  img = %(<a class="image" href="#{node.attr 'link'}"#{window_attr}>#{img}</a>)
1101
1113
  end
1102
1114
  class_attr_val = (role = node.role) ? %(#{type} #{role}) : type
@@ -1112,30 +1124,32 @@ Your browser does not support the video tag.
1112
1124
  if (keys = node.attr 'keys').size == 1
1113
1125
  %(<kbd>#{keys[0]}</kbd>)
1114
1126
  else
1115
- key_combo = keys.map {|key| %(<kbd>#{key}</kbd>+) }.join.chop
1116
- %(<span class="keyseq">#{key_combo}</span>)
1127
+ %(<span class="keyseq"><kbd>#{keys * '</kbd>+<kbd>'}</kbd></span>)
1117
1128
  end
1118
1129
  end
1119
1130
 
1120
1131
  def inline_menu node
1132
+ caret = (node.document.attr? 'icons', 'font') ? '&#160;<i class="fa fa-angle-right caret"></i> ' : '&#160;<b class="caret">&#8250;</b> '
1133
+ submenu_joiner = %(</b>#{caret}<b class="submenu">)
1121
1134
  menu = node.attr 'menu'
1122
- if !(submenus = node.attr 'submenus').empty?
1123
- submenu_path = submenus.map {|submenu| %(<span class="submenu">#{submenu}</span>&#160;&#9656; ) }.join.chop
1124
- %(<span class="menuseq"><span class="menu">#{menu}</span>&#160;&#9656; #{submenu_path} <span class="menuitem">#{node.attr 'menuitem'}</span></span>)
1125
- elsif (menuitem = node.attr 'menuitem')
1126
- %(<span class="menuseq"><span class="menu">#{menu}</span>&#160;&#9656; <span class="menuitem">#{menuitem}</span></span>)
1135
+ if (submenus = node.attr 'submenus').empty?
1136
+ if (menuitem = node.attr 'menuitem', nil, false)
1137
+ %(<span class="menuseq"><b class="menu">#{menu}</b>#{caret}<b class="menuitem">#{menuitem}</b></span>)
1138
+ else
1139
+ %(<b class="menuref">#{menu}</b>)
1140
+ end
1127
1141
  else
1128
- %(<span class="menu">#{menu}</span>)
1142
+ %(<span class="menuseq"><b class="menu">#{menu}</b>#{caret}<b class="submenu">#{submenus * submenu_joiner}</b>#{caret}<b class="menuitem">#{node.attr 'menuitem'}</b></span>)
1129
1143
  end
1130
1144
  end
1131
1145
 
1132
1146
  def inline_quoted node
1133
1147
  open, close, is_tag = QUOTE_TAGS[node.type]
1134
- if (role = node.role)
1148
+ if node.role
1135
1149
  if is_tag
1136
- quoted_text = %(#{open.chop} class="#{role}">#{node.text}#{close})
1150
+ quoted_text = %(#{open.chop} class="#{node.role}">#{node.text}#{close})
1137
1151
  else
1138
- quoted_text = %(<span class="#{role}">#{open}#{node.text}#{close}</span>)
1152
+ quoted_text = %(<span class="#{node.role}">#{open}#{node.text}#{close}</span>)
1139
1153
  end
1140
1154
  else
1141
1155
  quoted_text = %(#{open}#{node.text}#{close})
@@ -1148,6 +1162,10 @@ Your browser does not support the video tag.
1148
1162
  xml ? %( #{name}="#{name}") : %( #{name})
1149
1163
  end
1150
1164
 
1165
+ def encode_quotes val
1166
+ (val.include? '"') ? (val.gsub '"', '&quot;') : val
1167
+ end
1168
+
1151
1169
  def read_svg_contents node, target
1152
1170
  if (svg = node.read_contents target, :start => (node.document.attr 'imagesdir'), :normalize => true, :label => 'SVG')
1153
1171
  svg = svg.sub SvgPreambleRx, '' unless svg.start_with? '<svg'