asciidoctor 2.0.13 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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