asciidoctor 2.0.13 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +151 -30
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +9 -12
  5. data/README-fr.adoc +9 -12
  6. data/README-jp.adoc +10 -13
  7. data/README-zh_CN.adoc +9 -12
  8. data/README.adoc +40 -19
  9. data/asciidoctor.gemspec +2 -9
  10. data/data/locale/attributes-fr.adoc +2 -2
  11. data/data/locale/attributes-th.adoc +23 -0
  12. data/data/locale/attributes-vi.adoc +23 -0
  13. data/data/stylesheets/asciidoctor-default.css +54 -53
  14. data/data/stylesheets/coderay-asciidoctor.css +9 -9
  15. data/lib/asciidoctor/abstract_block.rb +11 -9
  16. data/lib/asciidoctor/abstract_node.rb +9 -8
  17. data/lib/asciidoctor/attribute_list.rb +1 -1
  18. data/lib/asciidoctor/block.rb +6 -6
  19. data/lib/asciidoctor/cli/invoker.rb +1 -2
  20. data/lib/asciidoctor/cli/options.rb +25 -25
  21. data/lib/asciidoctor/convert.rb +1 -0
  22. data/lib/asciidoctor/converter/docbook5.rb +45 -26
  23. data/lib/asciidoctor/converter/html5.rb +130 -102
  24. data/lib/asciidoctor/converter/manpage.rb +69 -64
  25. data/lib/asciidoctor/converter/template.rb +12 -13
  26. data/lib/asciidoctor/converter.rb +6 -4
  27. data/lib/asciidoctor/core_ext/hash/merge.rb +1 -1
  28. data/lib/asciidoctor/document.rb +61 -57
  29. data/lib/asciidoctor/extensions.rb +20 -12
  30. data/lib/asciidoctor/list.rb +2 -6
  31. data/lib/asciidoctor/load.rb +11 -9
  32. data/lib/asciidoctor/logging.rb +10 -8
  33. data/lib/asciidoctor/parser.rb +177 -193
  34. data/lib/asciidoctor/path_resolver.rb +3 -3
  35. data/lib/asciidoctor/reader.rb +73 -72
  36. data/lib/asciidoctor/rx.rb +5 -4
  37. data/lib/asciidoctor/section.rb +7 -0
  38. data/lib/asciidoctor/substitutors.rb +121 -121
  39. data/lib/asciidoctor/syntax_highlighter/coderay.rb +2 -1
  40. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +1 -1
  41. data/lib/asciidoctor/syntax_highlighter/pygments.rb +16 -7
  42. data/lib/asciidoctor/syntax_highlighter/rouge.rb +2 -1
  43. data/lib/asciidoctor/syntax_highlighter.rb +8 -11
  44. data/lib/asciidoctor/table.rb +18 -20
  45. data/lib/asciidoctor/timings.rb +3 -3
  46. data/lib/asciidoctor/version.rb +1 -1
  47. data/lib/asciidoctor.rb +10 -10
  48. data/man/asciidoctor.1 +26 -28
  49. data/man/asciidoctor.adoc +33 -27
  50. metadata +8 -62
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Asciidoctor
3
3
  # A built-in {Converter} implementation that generates HTML 5 output
4
- # consistent with the html5 backend from AsciiDoc Python.
4
+ # consistent with the html5 backend from AsciiDoc.py.
5
5
  class Converter::Html5Converter < Converter::Base
6
6
  register_for 'html5'
7
7
 
@@ -21,7 +21,7 @@ class Converter::Html5Converter < Converter::Base
21
21
  #latexmath: INLINE_MATH_DELIMITERS[:latexmath] + [false],
22
22
  }).default = ['', '']
23
23
 
24
- DropAnchorRx = /<(?:a\b[^>]*|\/a)>/
24
+ DropAnchorRx = %r(<(?:a\b[^>]*|/a)>)
25
25
  StemBreakRx = / *\\\n(?:\\?\n)*|\n\n+/
26
26
  if RUBY_ENGINE == 'opal'
27
27
  # NOTE In JavaScript, ^ matches the start of the string when the m flag is not set
@@ -48,44 +48,45 @@ class Converter::Html5Converter < Converter::Base
48
48
  end
49
49
 
50
50
  def convert node, transform = node.node_name, opts = nil
51
- if transform == 'inline_quoted'; return convert_inline_quoted node
52
- elsif transform == 'paragraph'; return convert_paragraph node
53
- elsif transform == 'inline_anchor'; return convert_inline_anchor node
54
- elsif transform == 'section'; return convert_section node
55
- elsif transform == 'listing'; return convert_listing node
56
- elsif transform == 'literal'; return convert_literal node
57
- elsif transform == 'ulist'; return convert_ulist node
58
- elsif transform == 'olist'; return convert_olist node
59
- elsif transform == 'dlist'; return convert_dlist node
60
- elsif transform == 'admonition'; return convert_admonition node
61
- elsif transform == 'colist'; return convert_colist node
62
- elsif transform == 'embedded'; return convert_embedded node
63
- elsif transform == 'example'; return convert_example node
64
- elsif transform == 'floating_title'; return convert_floating_title node
65
- elsif transform == 'image'; return convert_image node
66
- elsif transform == 'inline_break'; return convert_inline_break node
67
- elsif transform == 'inline_button'; return convert_inline_button node
68
- elsif transform == 'inline_callout'; return convert_inline_callout node
69
- elsif transform == 'inline_footnote'; return convert_inline_footnote node
70
- elsif transform == 'inline_image'; return convert_inline_image node
71
- elsif transform == 'inline_indexterm'; return convert_inline_indexterm node
72
- elsif transform == 'inline_kbd'; return convert_inline_kbd node
73
- elsif transform == 'inline_menu'; return convert_inline_menu node
74
- elsif transform == 'open'; return convert_open node
75
- elsif transform == 'page_break'; return convert_page_break node
76
- elsif transform == 'preamble'; return convert_preamble node
77
- elsif transform == 'quote'; return convert_quote node
78
- elsif transform == 'sidebar'; return convert_sidebar node
79
- elsif transform == 'stem'; return convert_stem node
80
- elsif transform == 'table'; return convert_table node
81
- elsif transform == 'thematic_break'; return convert_thematic_break node
82
- elsif transform == 'verse'; return convert_verse node
83
- elsif transform == 'video'; return convert_video node
84
- elsif transform == 'document'; return convert_document node
85
- elsif transform == 'toc'; return convert_toc node
86
- elsif transform == 'pass'; return convert_pass node
87
- elsif transform == 'audio'; return convert_audio node
88
- else; return super
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
89
90
  end
90
91
  end
91
92
 
@@ -350,9 +351,10 @@ MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
350
351
  stitle = section.captioned_title
351
352
  elsif section.numbered && slevel <= sectnumlevels
352
353
  if slevel < 2 && node.document.doctype == 'book'
353
- if section.sectname == 'chapter'
354
+ case section.sectname
355
+ when 'chapter'
354
356
  stitle = %(#{(signifier = node.document.attributes['chapter-signifier']) ? "#{signifier} " : ''}#{section.sectnum} #{section.title})
355
- elsif section.sectname == 'part'
357
+ when 'part'
356
358
  stitle = %(#{(signifier = node.document.attributes['part-signifier']) ? "#{signifier} " : ''}#{section.sectnum nil, ':'} #{section.title})
357
359
  else
358
360
  stitle = %(#{section.sectnum} #{section.title})
@@ -383,9 +385,10 @@ MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
383
385
  title = node.captioned_title
384
386
  elsif node.numbered && level <= (doc_attrs['sectnumlevels'] || 3).to_i
385
387
  if level < 2 && node.document.doctype == 'book'
386
- if node.sectname == 'chapter'
388
+ case node.sectname
389
+ when 'chapter'
387
390
  title = %(#{(signifier = doc_attrs['chapter-signifier']) ? "#{signifier} " : ''}#{node.sectnum} #{node.title})
388
- elsif node.sectname == 'part'
391
+ when 'part'
389
392
  title = %(#{(signifier = doc_attrs['part-signifier']) ? "#{signifier} " : ''}#{node.sectnum nil, ':'} #{node.title})
390
393
  else
391
394
  title = %(#{node.sectnum} #{node.title})
@@ -513,16 +516,16 @@ Your browser does not support the audio tag.
513
516
  result = []
514
517
  id_attribute = node.id ? %( id="#{node.id}") : ''
515
518
 
516
- classes = case node.style
519
+ case node.style
517
520
  when 'qanda'
518
- ['qlist', 'qanda', node.role]
521
+ classes = ['qlist', 'qanda', node.role]
519
522
  when 'horizontal'
520
- ['hdlist', node.role]
523
+ classes = ['hdlist', node.role]
521
524
  else
522
- ['dlist', node.style, node.role]
523
- end.compact
525
+ classes = ['dlist', node.style, node.role]
526
+ end
524
527
 
525
- class_attribute = %( class="#{classes.join ' '}")
528
+ class_attribute = %( class="#{classes.compact.join ' '}")
526
529
 
527
530
  result << %(<div#{id_attribute}#{class_attribute}>)
528
531
  result << %(<div class="title">#{node.title}</div>) if node.title?
@@ -578,12 +581,11 @@ Your browser does not support the audio tag.
578
581
  terms.each do |dt|
579
582
  result << %(<dt#{dt_style_attribute}>#{dt.text}</dt>)
580
583
  end
581
- if dd
582
- result << '<dd>'
583
- result << %(<p>#{dd.text}</p>) if dd.text?
584
- result << dd.content if dd.blocks?
585
- result << '</dd>'
586
- 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>'
587
589
  end
588
590
  result << '</dl>'
589
591
  end
@@ -624,16 +626,18 @@ Your browser does not support the audio tag.
624
626
  target = node.attr 'target'
625
627
  width_attr = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : ''
626
628
  height_attr = (node.attr? 'height') ? %( height="#{node.attr 'height'}") : ''
627
- if ((node.attr? 'format', 'svg') || (target.include? '.svg')) && node.document.safe < SafeMode::SECURE &&
628
- ((svg = (node.option? 'inline')) || (obj = (node.option? 'interactive')))
629
- if svg
629
+ if ((node.attr? 'format', 'svg') || (target.include? '.svg')) && node.document.safe < SafeMode::SECURE
630
+ if node.option? 'inline'
630
631
  img = (read_svg_contents node, target) || %(<span class="alt">#{node.alt}</span>)
631
- elsif obj
632
- 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>)
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>)
633
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}>)
634
637
  end
638
+ else
639
+ img = %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>)
635
640
  end
636
- img ||= %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>)
637
641
  img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>) if node.attr? 'link'
638
642
  id_attr = node.id ? %( id="#{node.id}") : ''
639
643
  classes = ['imageblock']
@@ -759,8 +763,8 @@ Your browser does not support the audio tag.
759
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.'
760
764
  ''
761
765
  else
762
- id_attr = node.id ? %( id="#{node.id}") : ''
763
- title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
766
+ id_attr = node.id ? %( id="#{node.id}") : ''
767
+ title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
764
768
  %(<div#{id_attr} class="openblock#{style && style != 'open' ? " #{style}" : ''}#{(role = node.role) ? " #{role}" : ''}">
765
769
  #{title_el}<div class="content">
766
770
  #{node.content}
@@ -857,20 +861,19 @@ Your browser does not support the audio tag.
857
861
  if (stripes = node.attr 'stripes', nil, 'table-stripes')
858
862
  classes << %(stripes-#{stripes})
859
863
  end
860
- styles = []
864
+ style_attribute = ''
861
865
  if (autowidth = node.option? 'autowidth') && !(node.attr? 'width')
862
866
  classes << 'fit-content'
863
867
  elsif (tablewidth = node.attr 'tablepcwidth') == 100
864
868
  classes << 'stretch'
865
869
  else
866
- styles << %(width: #{tablewidth}%;)
870
+ style_attribute = %( style="width: #{tablewidth}%;")
867
871
  end
868
872
  classes << (node.attr 'float') if node.attr? 'float'
869
873
  if (role = node.role)
870
874
  classes << role
871
875
  end
872
876
  class_attribute = %( class="#{classes.join ' '}")
873
- style_attribute = styles.empty? ? '' : %( style="#{styles.join ' '}")
874
877
 
875
878
  result << %(<table#{id_attribute}#{class_attribute}#{style_attribute}>)
876
879
  result << %(<caption class="title">#{node.captioned_title}</caption>) if node.title?
@@ -1033,12 +1036,14 @@ Your browser does not support the audio tag.
1033
1036
  end
1034
1037
  start_anchor = (node.attr? 'start') ? %(#at=#{node.attr 'start'}) : ''
1035
1038
  delimiter = ['?']
1039
+ target, hash = (node.attr 'target').split '/', 2
1040
+ hash_param = (hash ||= node.attr 'hash') ? %(#{delimiter.pop || '&amp;'}h=#{hash}) : ''
1036
1041
  autoplay_param = (node.option? 'autoplay') ? %(#{delimiter.pop || '&amp;'}autoplay=1) : ''
1037
1042
  loop_param = (node.option? 'loop') ? %(#{delimiter.pop || '&amp;'}loop=1) : ''
1038
1043
  muted_param = (node.option? 'muted') ? %(#{delimiter.pop || '&amp;'}muted=1) : ''
1039
1044
  %(<div#{id_attribute}#{class_attribute}>#{title_element}
1040
1045
  <div class="content">
1041
- <iframe#{width_attribute}#{height_attribute} src="#{asset_uri_scheme}//player.vimeo.com/video/#{node.attr 'target'}#{autoplay_param}#{loop_param}#{muted_param}#{start_anchor}" 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>
1042
1047
  </div>
1043
1048
  </div>)
1044
1049
  when 'youtube'
@@ -1074,7 +1079,7 @@ Your browser does not support the audio tag.
1074
1079
  target, playlist = target.split ',', 2
1075
1080
  if (playlist ||= (node.attr 'playlist'))
1076
1081
  # INFO playlist bar doesn't appear in Firefox unless showinfo=1 and modestbranding=1
1077
- list_param = %(&amp;playlist=#{playlist})
1082
+ list_param = %(&amp;playlist=#{target},#{playlist})
1078
1083
  else
1079
1084
  # NOTE for loop to work, playlist must be specified; use VIDEO_ID if there's no explicit playlist
1080
1085
  list_param = has_loop_param ? %(&amp;playlist=#{target}) : ''
@@ -1111,14 +1116,17 @@ Your browser does not support the video tag.
1111
1116
  else
1112
1117
  attrs = node.role ? %( class="#{node.role}") : ''
1113
1118
  unless (text = node.text)
1114
- refid = node.attributes['refid']
1115
- if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid]) && (@resolving_xref ||= (outer = true)) && outer
1116
- if !(text = ref.xreftext node.attr 'xrefstyle', nil, true)
1117
- text = %([#{refid}])
1118
- elsif text.include? '<a'
1119
- text = text.gsub DropAnchorRx, ''
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
1127
+ else
1128
+ text = top ? '[^top]' : %([#{refid}])
1120
1129
  end
1121
- @resolving_xref = nil
1122
1130
  else
1123
1131
  text = %([#{refid}])
1124
1132
  end
@@ -1175,40 +1183,49 @@ Your browser does not support the video tag.
1175
1183
  end
1176
1184
 
1177
1185
  def convert_inline_image node
1178
- if (type = node.type || 'image') == 'icon' && (node.document.attr? 'icons', 'font')
1179
- class_attr_val = %(fa fa-#{node.target})
1180
- { 'size' => 'fa-', 'rotate' => 'fa-rotate-', 'flip' => 'fa-flip-' }.each do |key, prefix|
1181
- class_attr_val = %(#{class_attr_val} #{prefix}#{node.attr key}) if node.attr? key
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'})
1195
+ end
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}>)
1203
+ else
1204
+ img = %([#{node.alt}&#93;)
1182
1205
  end
1183
- title_attr = (node.attr? 'title') ? %( title="#{node.attr 'title'}") : ''
1184
- img = %(<i class="#{class_attr_val}"#{title_attr}></i>)
1185
- elsif type == 'icon' && !(node.document.attr? 'icons')
1186
- img = %([#{node.alt}])
1187
1206
  else
1188
- target = node.target
1189
- attrs = ['width', 'height', 'title'].map {|name| (node.attr? name) ? %( #{name}="#{node.attr name}") : '' }.join
1190
- if type != 'icon' && ((node.attr? 'format', 'svg') || (target.include? '.svg')) &&
1191
- node.document.safe < SafeMode::SECURE && ((svg = (node.option? 'inline')) || (obj = (node.option? 'interactive')))
1192
- if svg
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'
1193
1212
  img = (read_svg_contents node, target) || %(<span class="alt">#{node.alt}</span>)
1194
- elsif obj
1195
- 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>)
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>)
1196
1215
  img = %(<object type="image/svg+xml" data="#{node.image_uri target}"#{attrs}>#{fallback}</object>)
1216
+ else
1217
+ img = %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{attrs}#{@void_element_slash}>)
1197
1218
  end
1219
+ else
1220
+ img = %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{attrs}#{@void_element_slash}>)
1198
1221
  end
1199
- img ||= %(<img src="#{type == 'icon' ? (node.icon_uri target) : (node.image_uri target)}" alt="#{encode_attribute_value node.alt}"#{attrs}#{@void_element_slash}>)
1200
1222
  end
1201
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
1202
1225
  if (role = node.role)
1203
- if node.attr? 'float'
1204
- class_attr_val = %(#{type} #{node.attr 'float'} #{role})
1205
- else
1206
- class_attr_val = %(#{type} #{role})
1207
- end
1226
+ class_attr_val = (node.attr? 'float') ? %(#{class_attr_val} #{node.attr 'float'} #{role}) : %(#{class_attr_val} #{role})
1208
1227
  elsif node.attr? 'float'
1209
- class_attr_val = %(#{type} #{node.attr 'float'})
1210
- else
1211
- class_attr_val = type
1228
+ class_attr_val = %(#{class_attr_val} #{node.attr 'float'})
1212
1229
  end
1213
1230
  %(<span class="#{class_attr_val}">#{img}</span>)
1214
1231
  end
@@ -1314,9 +1331,20 @@ Your browser does not support the video tag.
1314
1331
  </div>)
1315
1332
  end
1316
1333
 
1334
+ def get_root_document node
1335
+ while (node = node.document).nested?
1336
+ node = node.parent_document
1337
+ end
1338
+ node
1339
+ end
1340
+
1317
1341
  # NOTE adapt to older converters that relied on unprefixed method names
1318
- def method_missing id, *params
1319
- !((name = id.to_s).start_with? 'convert_') && (handles? name) ? (send %(convert_#{name}), *params) : super
1342
+ def method_missing id, *args
1343
+ !((name = id.to_s).start_with? 'convert_') && (handles? name) ? (send %(convert_#{name}), *args) : super
1344
+ end
1345
+
1346
+ def respond_to_missing? id, *options
1347
+ !((name = id.to_s).start_with? 'convert_') && (handles? name)
1320
1348
  end
1321
1349
  end
1322
1350
  end
@@ -5,7 +5,7 @@ module Asciidoctor
5
5
  # The output of this converter adheres to the man definition as defined by
6
6
  # groff and uses the manpage output of the DocBook toolchain as a foundation.
7
7
  # That means if you've previously been generating man pages using the a2x tool
8
- # from AsciiDoc Python, you should be able to achieve a very similar result
8
+ # from AsciiDoc.py, you should be able to achieve a very similar result
9
9
  # using this converter. Though you'll also get to enjoy some notable
10
10
  # enhancements that have been added since, such as the customizable linkstyle.
11
11
  #
@@ -23,7 +23,7 @@ class Converter::ManPageConverter < Converter::Base
23
23
  LeadingPeriodRx = /^\./
24
24
  EscapedMacroRx = /^(?:#{ESC}\\c\n)?#{ESC}\.((?:URL|MTO) "#{CC_ANY}*?" "#{CC_ANY}*?" )( |[^\s]*)(#{CC_ANY}*?)(?: *#{ESC}\\c)?$/
25
25
  MalformedEscapedMacroRx = /(#{ESC}\\c) (#{ESC}\.(?:URL|MTO) )/
26
- MockMacroRx = /<\/?(#{ESC}\\[^>]+)>/
26
+ MockMacroRx = %r(</?(#{ESC}\\[^>]+)>)
27
27
  EmDashCharRefRx = /&#8212;(?:&#8203;)?/
28
28
  EllipsisCharRefRx = /&#8230;(?:&#8203;)?/
29
29
  WrappedIndentRx = /#{CG_BLANK}*#{LF}#{CG_BLANK}*/
@@ -419,15 +419,7 @@ allbox tab(:);'
419
419
  end
420
420
  row_text[row_index] << %(T{#{LF}.sp#{LF})
421
421
  cell_halign = (cell.attr 'halign', 'left').chr
422
- if tsec == :head
423
- if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
424
- row_header[row_index][cell_index] << %(#{cell_halign}tB)
425
- else
426
- row_header[row_index][cell_index + 1] ||= []
427
- row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
428
- end
429
- row_text[row_index] << %(#{manify cell.text, whitespace: :normalize}#{LF})
430
- elsif tsec == :body
422
+ if tsec == :body
431
423
  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
432
424
  row_header[row_index][cell_index] << %(#{cell_halign}t)
433
425
  else
@@ -443,7 +435,7 @@ allbox tab(:);'
443
435
  cell_content = manify cell.content.join, whitespace: :normalize
444
436
  end
445
437
  row_text[row_index] << %(#{cell_content}#{LF})
446
- elsif tsec == :foot
438
+ else # tsec == :head || tsec == :foot
447
439
  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
448
440
  row_header[row_index][cell_index] << %(#{cell_halign}tB)
449
441
  else
@@ -587,8 +579,16 @@ allbox tab(:);'
587
579
  %(#{ESC_BS}c#{LF}#{ESC_FS}#{macro} "#{target}" "#{text}" )
588
580
  when :xref
589
581
  unless (text = node.text)
590
- refid = node.attributes['refid']
591
- text = %([#{refid}]) unless AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid]) && (@resolving_xref ||= outer = true) && outer && (text = ref.xreftext node.attr 'xrefstyle', nil, true)
582
+ if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid = node.attributes['refid']] || (refid.nil_or_empty? ? (top = get_root_document node) : nil))
583
+ if (@resolving_xref ||= (outer = true)) && outer && (text = ref.xreftext node.attr 'xrefstyle', nil, true)
584
+ text = uppercase_pcdata text if ref.context === :section && ref.level < 2 && text == ref.title
585
+ else
586
+ text = top ? '[^top]' : %([#{refid}])
587
+ end
588
+ @resolving_xref = nil if outer
589
+ else
590
+ text = %([#{refid}])
591
+ end
592
592
  end
593
593
  text
594
594
  when :ref, :bibref
@@ -605,11 +605,11 @@ allbox tab(:);'
605
605
  end
606
606
 
607
607
  def convert_inline_button node
608
- %(#{ESC_BS}fB[#{ESC_BS}0#{node.text}#{ESC_BS}0]#{ESC_BS}fP)
608
+ %(<#{ESC_BS}fB>[#{ESC_BS}0#{node.text}#{ESC_BS}0]</#{ESC_BS}fP>)
609
609
  end
610
610
 
611
611
  def convert_inline_callout node
612
- %(#{ESC_BS}fB(#{node.text})#{ESC_BS}fP)
612
+ %(<#{ESC_BS}fB>(#{node.text})<#{ESC_BS}fP>)
613
613
  end
614
614
 
615
615
  def convert_inline_footnote node
@@ -629,23 +629,19 @@ allbox tab(:);'
629
629
  end
630
630
 
631
631
  def convert_inline_kbd node
632
- if (keys = node.attr 'keys').size == 1
633
- keys[0]
634
- else
635
- keys.join %(#{ESC_BS}0+#{ESC_BS}0)
636
- end
632
+ %[<#{ESC_BS}f(CR>#{(keys = node.attr 'keys').size == 1 ? keys[0] : (keys.join "#{ESC_BS}0+#{ESC_BS}0")}</#{ESC_BS}fP>]
637
633
  end
638
634
 
639
635
  def convert_inline_menu node
640
636
  caret = %[#{ESC_BS}0#{ESC_BS}(fc#{ESC_BS}0]
641
637
  menu = node.attr 'menu'
642
638
  if !(submenus = node.attr 'submenus').empty?
643
- submenu_path = submenus.map {|item| %(#{ESC_BS}fI#{item}#{ESC_BS}fP) }.join caret
644
- %(#{ESC_BS}fI#{menu}#{ESC_BS}fP#{caret}#{submenu_path}#{caret}#{ESC_BS}fI#{node.attr 'menuitem'}#{ESC_BS}fP)
639
+ submenu_path = submenus.map {|item| %(<#{ESC_BS}fI>#{item}</#{ESC_BS}fP>) }.join caret
640
+ %(<#{ESC_BS}fI>#{menu}</#{ESC_BS}fP>#{caret}#{submenu_path}#{caret}<#{ESC_BS}fI>#{node.attr 'menuitem'}</#{ESC_BS}fP>)
645
641
  elsif (menuitem = node.attr 'menuitem')
646
- %(#{ESC_BS}fI#{menu}#{caret}#{menuitem}#{ESC_BS}fP)
642
+ %(<#{ESC_BS}fI>#{menu}#{caret}#{menuitem}</#{ESC_BS}fP>)
647
643
  else
648
- %(#{ESC_BS}fI#{menu}#{ESC_BS}fP)
644
+ %(<#{ESC_BS}fI>#{menu}</#{ESC_BS}fP>)
649
645
  end
650
646
  end
651
647
 
@@ -668,13 +664,12 @@ allbox tab(:);'
668
664
  end
669
665
 
670
666
  def self.write_alternate_pages mannames, manvolnum, target
671
- if mannames && mannames.size > 1
672
- mannames.shift
673
- manvolext = %(.#{manvolnum})
674
- dir, basename = ::File.split target
675
- mannames.each do |manname|
676
- ::File.write ::File.join(dir, %(#{manname}#{manvolext})), %(.so #{basename}), mode: FILE_WRITE_MODE
677
- end
667
+ return unless mannames && mannames.size > 1
668
+ mannames.shift
669
+ manvolext = %(.#{manvolnum})
670
+ dir, basename = ::File.split target
671
+ mannames.each do |manname|
672
+ ::File.write ::File.join(dir, %(#{manname}#{manvolext})), %(.so #{basename}), mode: FILE_WRITE_MODE
678
673
  end
679
674
  end
680
675
 
@@ -683,7 +678,7 @@ allbox tab(:);'
683
678
  def append_footnotes result, node
684
679
  if node.footnotes? && !(node.attr? 'nofootnotes')
685
680
  result << '.SH "NOTES"'
686
- node.footnotes.each_with_index do |fn, idx|
681
+ node.footnotes.each do |fn|
687
682
  result << %(.IP [#{fn.index}])
688
683
  # NOTE restore newline in escaped macro that gets removed by normalize_text in substitutor
689
684
  if (text = fn.text).include? %(#{ESC}\\c #{ESC}.)
@@ -717,37 +712,40 @@ allbox tab(:);'
717
712
  else
718
713
  str = str.tr_s WHITESPACE, ' '
719
714
  end
720
- str = str.
721
- gsub(LiteralBackslashRx) { $1 ? $& : '\\(rs' }. # literal backslash (not a troff escape sequence)
722
- gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
723
- gsub(LeadingPeriodRx, '\\\&.'). # leading . is used in troff for macro call or other formatting; replace with \&.
724
- # drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
725
- gsub(EscapedMacroRx) { (rest = $3.lstrip).empty? ? %(.#$1"#$2") : %(.#$1"#{$2.rstrip}"#{LF}#{rest}) }.
726
- gsub('-', '\-').
727
- gsub('&lt;', '<').
728
- gsub('&gt;', '>').
729
- gsub('&#160;', '\~'). # non-breaking space
730
- gsub('&#169;', '\(co'). # copyright sign
731
- gsub('&#174;', '\(rg'). # registered sign
732
- gsub('&#8482;', '\(tm'). # trademark sign
733
- gsub('&#8201;', ' '). # thin space
734
- gsub('&#8211;', '\(en'). # en dash
735
- gsub(EmDashCharRefRx, '\(em'). # em dash
736
- gsub('&#8216;', '\(oq'). # left single quotation mark
737
- gsub('&#8217;', '\(cq'). # right single quotation mark
738
- gsub('&#8220;', '\(lq'). # left double quotation mark
739
- gsub('&#8221;', '\(rq'). # right double quotation mark
740
- gsub('&#8592;', '\(<-'). # leftwards arrow
741
- gsub('&#8594;', '\(->'). # rightwards arrow
742
- gsub('&#8656;', '\(lA'). # leftwards double arrow
743
- gsub('&#8658;', '\(rA'). # rightwards double arrow
744
- gsub('&#8203;', '\:'). # zero width space
745
- gsub('&amp;', '&'). # literal ampersand (NOTE must take place after any other replacement that includes &)
746
- gsub('\'', '\(aq'). # apostrophe-quote
747
- gsub(MockMacroRx, '\1'). # mock boundary
748
- gsub(ESC_BS, '\\'). # unescape troff backslash (NOTE update if more escapes are added)
749
- gsub(ESC_FS, '.'). # unescape full stop in troff commands (NOTE must take place after gsub(LeadingPeriodRx))
750
- rstrip # strip trailing space
715
+ str = str
716
+ .gsub(LiteralBackslashRx) { $1 ? $& : '\\(rs' } # literal backslash (not a troff escape sequence)
717
+ .gsub(EllipsisCharRefRx, '...') # horizontal ellipsis
718
+ .gsub(LeadingPeriodRx, '\\\&.') # leading . is used in troff for macro call or other formatting; replace with \&.
719
+ .gsub(EscapedMacroRx) do # drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
720
+ (rest = $3.lstrip).empty? ? %(.#{$1}"#{$2}") : %(.#{$1}"#{$2.rstrip}"#{LF}#{rest})
721
+ end
722
+ .gsub('-', '\-')
723
+ .gsub('&lt;', '<')
724
+ .gsub('&gt;', '>')
725
+ .gsub('&#43;', '+') # plus sign; alternately could use \c(pl
726
+ .gsub('&#160;', '\~') # non-breaking space
727
+ .gsub('&#169;', '\(co') # copyright sign
728
+ .gsub('&#174;', '\(rg') # registered sign
729
+ .gsub('&#8482;', '\(tm') # trademark sign
730
+ .gsub('&#176;', '\(de') # degree sign
731
+ .gsub('&#8201;', ' ') # thin space
732
+ .gsub('&#8211;', '\(en') # en dash
733
+ .gsub(EmDashCharRefRx, '\(em') # em dash
734
+ .gsub('&#8216;', '\(oq') # left single quotation mark
735
+ .gsub('&#8217;', '\(cq') # right single quotation mark
736
+ .gsub('&#8220;', '\(lq') # left double quotation mark
737
+ .gsub('&#8221;', '\(rq') # right double quotation mark
738
+ .gsub('&#8592;', '\(<-') # leftwards arrow
739
+ .gsub('&#8594;', '\(->') # rightwards arrow
740
+ .gsub('&#8656;', '\(lA') # leftwards double arrow
741
+ .gsub('&#8658;', '\(rA') # rightwards double arrow
742
+ .gsub('&#8203;', '\:') # zero width space
743
+ .gsub('&amp;', '&') # literal ampersand (NOTE must take place after any other replacement that includes &)
744
+ .gsub('\'', '\*(Aq') # apostrophe / neutral single quote
745
+ .gsub(MockMacroRx, '\1') # mock boundary
746
+ .gsub(ESC_BS, '\\') # unescape troff backslash (NOTE update if more escapes are added)
747
+ .gsub(ESC_FS, '.') # unescape full stop in troff commands (NOTE must take place after gsub(LeadingPeriodRx))
748
+ .rstrip # strip trailing space
751
749
  opts[:append_newline] ? %(#{str}#{LF}) : str
752
750
  end
753
751
 
@@ -758,5 +756,12 @@ allbox tab(:);'
758
756
  def enclose_content node
759
757
  node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content, whitespace: :normalize})
760
758
  end
759
+
760
+ def get_root_document node
761
+ while (node = node.document).nested?
762
+ node = node.parent_document
763
+ end
764
+ node
765
+ end
761
766
  end
762
767
  end