asciidoctor 2.0.12 → 2.0.16

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +142 -22
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +15 -6
  5. data/README-fr.adoc +14 -8
  6. data/README-jp.adoc +15 -6
  7. data/README-zh_CN.adoc +14 -5
  8. data/README.adoc +135 -125
  9. data/asciidoctor.gemspec +4 -11
  10. data/data/locale/attributes-be.adoc +23 -0
  11. data/data/locale/attributes-it.adoc +4 -4
  12. data/data/locale/attributes-nl.adoc +6 -6
  13. data/data/locale/attributes-th.adoc +23 -0
  14. data/data/locale/attributes-vi.adoc +23 -0
  15. data/data/reference/syntax.adoc +14 -7
  16. data/data/stylesheets/asciidoctor-default.css +51 -52
  17. data/lib/asciidoctor.rb +12 -12
  18. data/lib/asciidoctor/abstract_block.rb +4 -4
  19. data/lib/asciidoctor/abstract_node.rb +10 -9
  20. data/lib/asciidoctor/attribute_list.rb +6 -6
  21. data/lib/asciidoctor/block.rb +6 -6
  22. data/lib/asciidoctor/cli/invoker.rb +0 -1
  23. data/lib/asciidoctor/cli/options.rb +27 -26
  24. data/lib/asciidoctor/convert.rb +1 -0
  25. data/lib/asciidoctor/converter.rb +5 -3
  26. data/lib/asciidoctor/converter/docbook5.rb +45 -26
  27. data/lib/asciidoctor/converter/html5.rb +89 -69
  28. data/lib/asciidoctor/converter/manpage.rb +113 -86
  29. data/lib/asciidoctor/converter/template.rb +11 -12
  30. data/lib/asciidoctor/document.rb +44 -51
  31. data/lib/asciidoctor/extensions.rb +10 -12
  32. data/lib/asciidoctor/helpers.rb +3 -6
  33. data/lib/asciidoctor/list.rb +2 -6
  34. data/lib/asciidoctor/load.rb +13 -11
  35. data/lib/asciidoctor/logging.rb +10 -8
  36. data/lib/asciidoctor/parser.rb +135 -150
  37. data/lib/asciidoctor/path_resolver.rb +3 -3
  38. data/lib/asciidoctor/reader.rb +72 -71
  39. data/lib/asciidoctor/rx.rb +4 -3
  40. data/lib/asciidoctor/substitutors.rb +117 -117
  41. data/lib/asciidoctor/syntax_highlighter.rb +8 -11
  42. data/lib/asciidoctor/syntax_highlighter/coderay.rb +2 -1
  43. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +1 -1
  44. data/lib/asciidoctor/syntax_highlighter/pygments.rb +6 -5
  45. data/lib/asciidoctor/syntax_highlighter/rouge.rb +33 -26
  46. data/lib/asciidoctor/table.rb +17 -19
  47. data/lib/asciidoctor/timings.rb +3 -3
  48. data/lib/asciidoctor/version.rb +1 -1
  49. data/man/asciidoctor.1 +10 -11
  50. data/man/asciidoctor.adoc +8 -7
  51. metadata +14 -67
@@ -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
@@ -759,8 +761,8 @@ Your browser does not support the audio tag.
759
761
  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
762
  ''
761
763
  else
762
- id_attr = node.id ? %( id="#{node.id}") : ''
763
- title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
764
+ id_attr = node.id ? %( id="#{node.id}") : ''
765
+ title_el = node.title? ? %(<div class="title">#{node.title}</div>\n) : ''
764
766
  %(<div#{id_attr} class="openblock#{style && style != 'open' ? " #{style}" : ''}#{(role = node.role) ? " #{role}" : ''}">
765
767
  #{title_el}<div class="content">
766
768
  #{node.content}
@@ -1111,14 +1113,17 @@ Your browser does not support the video tag.
1111
1113
  else
1112
1114
  attrs = node.role ? %( class="#{node.role}") : ''
1113
1115
  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, ''
1116
+ if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid = node.attributes['refid']] || (refid.nil_or_empty? ? (top = get_root_document node) : nil))
1117
+ if (@resolving_xref ||= (outer = true)) && outer
1118
+ if (text = ref.xreftext node.attr 'xrefstyle', nil, true)
1119
+ text = text.gsub DropAnchorRx, '' if text.include? '<a'
1120
+ else
1121
+ text = top ? '[^top]' : %([#{refid}])
1122
+ end
1123
+ @resolving_xref = nil
1124
+ else
1125
+ text = top ? '[^top]' : %([#{refid}])
1120
1126
  end
1121
- @resolving_xref = nil
1122
1127
  else
1123
1128
  text = %([#{refid}])
1124
1129
  end
@@ -1186,7 +1191,11 @@ Your browser does not support the video tag.
1186
1191
  img = %([#{node.alt}])
1187
1192
  else
1188
1193
  target = node.target
1189
- attrs = ['width', 'height', 'title'].map {|name| (node.attr? name) ? %( #{name}="#{node.attr name}") : '' }.join
1194
+ attrs = []
1195
+ attrs << %( width="#{node.attr 'width'}") if node.attr? 'width'
1196
+ attrs << %( height="#{node.attr 'height'}") if node.attr? 'height'
1197
+ attrs << %( title="#{node.attr 'title'}") if node.attr? 'title'
1198
+ attrs = attrs.empty? ? '' : attrs.join
1190
1199
  if type != 'icon' && ((node.attr? 'format', 'svg') || (target.include? '.svg')) &&
1191
1200
  node.document.safe < SafeMode::SECURE && ((svg = (node.option? 'inline')) || (obj = (node.option? 'interactive')))
1192
1201
  if svg
@@ -1310,13 +1319,24 @@ Your browser does not support the video tag.
1310
1319
  manname_id_attr = (manname_id = node.attr 'manname-id') ? %( id="#{manname_id}") : ''
1311
1320
  %(<h2#{manname_id_attr}>#{manname_title}</h2>
1312
1321
  <div class="sectionbody">
1313
- <p>#{node.attr 'manname'} - #{node.attr 'manpurpose'}</p>
1322
+ <p>#{(node.attr 'mannames').join ', '} - #{node.attr 'manpurpose'}</p>
1314
1323
  </div>)
1315
1324
  end
1316
1325
 
1326
+ def get_root_document node
1327
+ while (node = node.document).nested?
1328
+ node = node.parent_document
1329
+ end
1330
+ node
1331
+ end
1332
+
1317
1333
  # 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
1334
+ def method_missing id, *args
1335
+ !((name = id.to_s).start_with? 'convert_') && (handles? name) ? (send %(convert_#{name}), *args) : super
1336
+ end
1337
+
1338
+ def respond_to_missing? id, *options
1339
+ !((name = id.to_s).start_with? 'convert_') && (handles? name)
1320
1340
  end
1321
1341
  end
1322
1342
  end
@@ -2,8 +2,12 @@
2
2
  module Asciidoctor
3
3
  # A built-in {Converter} implementation that generates the man page (troff) format.
4
4
  #
5
- # The output follows the groff man page definition while also trying to be
6
- # consistent with the output produced by the a2x tool from AsciiDoc Python.
5
+ # The output of this converter adheres to the man definition as defined by
6
+ # groff and uses the manpage output of the DocBook toolchain as a foundation.
7
+ # That means if you've previously been generating man pages using the a2x tool
8
+ # from AsciiDoc.py, you should be able to achieve a very similar result
9
+ # using this converter. Though you'll also get to enjoy some notable
10
+ # enhancements that have been added since, such as the customizable linkstyle.
7
11
  #
8
12
  # See http://www.gnu.org/software/groff/manual/html_node/Man-usage.html#Man-usage
9
13
  class Converter::ManPageConverter < Converter::Base
@@ -18,10 +22,13 @@ class Converter::ManPageConverter < Converter::Base
18
22
  LiteralBackslashRx = /\A\\|(#{ESC})?\\/
19
23
  LeadingPeriodRx = /^\./
20
24
  EscapedMacroRx = /^(?:#{ESC}\\c\n)?#{ESC}\.((?:URL|MTO) "#{CC_ANY}*?" "#{CC_ANY}*?" )( |[^\s]*)(#{CC_ANY}*?)(?: *#{ESC}\\c)?$/
21
- MockBoundaryRx = /<\/?BOUNDARY>/
25
+ MalformedEscapedMacroRx = /(#{ESC}\\c) (#{ESC}\.(?:URL|MTO) )/
26
+ MockMacroRx = %r(</?(#{ESC}\\[^>]+)>)
22
27
  EmDashCharRefRx = /&#8212;(?:&#8203;)?/
23
28
  EllipsisCharRefRx = /&#8230;(?:&#8203;)?/
24
29
  WrappedIndentRx = /#{CG_BLANK}*#{LF}#{CG_BLANK}*/
30
+ XMLMarkupRx = /&#?[a-z\d]+;|</
31
+ PCDATAFilterRx = /(&#?[a-z\d]+;|<[^>]+>)|([^&<]+)/
25
32
 
26
33
  def initialize backend, opts = {}
27
34
  @backend = backend
@@ -91,17 +98,14 @@ class Converter::ManPageConverter < Converter::Base
91
98
  if node.attr? 'manpurpose'
92
99
  mannames = node.attr 'mannames', [manname]
93
100
  result << %(.SH "#{(node.attr 'manname-title', 'NAME').upcase}"
94
- #{mannames.map {|n| manify n }.join ', '} \\- #{manify node.attr('manpurpose'), whitespace: :normalize})
101
+ #{mannames.map {|n| (manify n).gsub '\-', '-' }.join ', '} \\- #{manify node.attr('manpurpose'), whitespace: :normalize})
95
102
  end
96
103
  end
97
104
 
98
105
  result << node.content
99
106
 
100
107
  # QUESTION should NOTES come after AUTHOR(S)?
101
- if node.footnotes? && !(node.attr? 'nofootnotes')
102
- result << '.SH "NOTES"'
103
- result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
104
- end
108
+ append_footnotes result, node
105
109
 
106
110
  unless (authors = node.authors).empty?
107
111
  if authors.size > 1
@@ -124,10 +128,7 @@ class Converter::ManPageConverter < Converter::Base
124
128
  def convert_embedded node
125
129
  result = [node.content]
126
130
 
127
- if node.footnotes? && !(node.attr? 'nofootnotes')
128
- result << '.SH "NOTES"'
129
- result.concat(node.footnotes.map {|fn| %(#{fn.index}. #{fn.text}) })
130
- end
131
+ append_footnotes result, node
131
132
 
132
133
  # QUESTION should we add an AUTHOR(S) section?
133
134
 
@@ -142,7 +143,7 @@ class Converter::ManPageConverter < Converter::Base
142
143
  stitle = node.captioned_title
143
144
  else
144
145
  macro = 'SH'
145
- stitle = node.title.upcase
146
+ stitle = uppercase_pcdata node.title
146
147
  end
147
148
  result << %(.#{macro} "#{manify stitle}"
148
149
  #{node.content})
@@ -315,8 +316,9 @@ r lw(\n(.lu*75u/100u).'
315
316
  end
316
317
  end
317
318
 
318
- # TODO use Page Control https://www.gnu.org/software/groff/manual/html_node/Page-Control.html#Page-Control
319
- alias convert_page_break skip
319
+ def convert_page_break node
320
+ '.bp'
321
+ end
320
322
 
321
323
  def convert_paragraph node
322
324
  if node.title?
@@ -417,15 +419,7 @@ allbox tab(:);'
417
419
  end
418
420
  row_text[row_index] << %(T{#{LF}.sp#{LF})
419
421
  cell_halign = (cell.attr 'halign', 'left').chr
420
- if tsec == :head
421
- if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
422
- row_header[row_index][cell_index] << %(#{cell_halign}tB)
423
- else
424
- row_header[row_index][cell_index + 1] ||= []
425
- row_header[row_index][cell_index + 1] << %(#{cell_halign}tB)
426
- end
427
- row_text[row_index] << %(#{manify cell.text, whitespace: :normalize}#{LF})
428
- elsif tsec == :body
422
+ if tsec == :body
429
423
  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
430
424
  row_header[row_index][cell_index] << %(#{cell_halign}t)
431
425
  else
@@ -441,7 +435,7 @@ allbox tab(:);'
441
435
  cell_content = manify cell.content.join, whitespace: :normalize
442
436
  end
443
437
  row_text[row_index] << %(#{cell_content}#{LF})
444
- elsif tsec == :foot
438
+ else # tsec == :head || tsec == :foot
445
439
  if row_header[row_index].empty? || row_header[row_index][cell_index].empty?
446
440
  row_header[row_index][cell_index] << %(#{cell_halign}tB)
447
441
  else
@@ -531,12 +525,13 @@ allbox tab(:);'
531
525
  result.join LF
532
526
  end
533
527
 
534
- # FIXME git uses [verse] for the synopsis; detect this special case
535
528
  def convert_verse node
536
529
  result = []
537
- result << (node.title? ? %(.sp
530
+ if node.title?
531
+ result << %(.sp
538
532
  .B #{manify node.title}
539
- .br) : '.sp')
533
+ .br)
534
+ end
540
535
  attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
541
536
  attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
542
537
  result << %(.sp
@@ -584,8 +579,16 @@ allbox tab(:);'
584
579
  %(#{ESC_BS}c#{LF}#{ESC_FS}#{macro} "#{target}" "#{text}" )
585
580
  when :xref
586
581
  unless (text = node.text)
587
- refid = node.attributes['refid']
588
- 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
589
592
  end
590
593
  text
591
594
  when :ref, :bibref
@@ -602,14 +605,13 @@ allbox tab(:);'
602
605
  end
603
606
 
604
607
  def convert_inline_button node
605
- %(#{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>)
606
609
  end
607
610
 
608
611
  def convert_inline_callout node
609
- %(#{ESC_BS}fB(#{node.text})#{ESC_BS}fP)
612
+ %(<#{ESC_BS}fB>(#{node.text})<#{ESC_BS}fP>)
610
613
  end
611
614
 
612
- # TODO supposedly groff has footnotes, but we're in search of an example
613
615
  def convert_inline_footnote node
614
616
  if (index = node.attr 'index')
615
617
  %([#{index}])
@@ -627,57 +629,68 @@ allbox tab(:);'
627
629
  end
628
630
 
629
631
  def convert_inline_kbd node
630
- if (keys = node.attr 'keys').size == 1
631
- keys[0]
632
- else
633
- keys.join %(#{ESC_BS}0+#{ESC_BS}0)
634
- 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>]
635
633
  end
636
634
 
637
635
  def convert_inline_menu node
638
636
  caret = %[#{ESC_BS}0#{ESC_BS}(fc#{ESC_BS}0]
639
637
  menu = node.attr 'menu'
640
638
  if !(submenus = node.attr 'submenus').empty?
641
- submenu_path = submenus.map {|item| %(#{ESC_BS}fI#{item}#{ESC_BS}fP) }.join caret
642
- %(#{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>)
643
641
  elsif (menuitem = node.attr 'menuitem')
644
- %(#{ESC_BS}fI#{menu}#{caret}#{menuitem}#{ESC_BS}fP)
642
+ %(<#{ESC_BS}fI>#{menu}#{caret}#{menuitem}</#{ESC_BS}fP>)
645
643
  else
646
- %(#{ESC_BS}fI#{menu}#{ESC_BS}fP)
644
+ %(<#{ESC_BS}fI>#{menu}</#{ESC_BS}fP>)
647
645
  end
648
646
  end
649
647
 
650
- # NOTE use fake <BOUNDARY> element to prevent creating artificial word boundaries
648
+ # NOTE use fake XML elements to prevent creating artificial word boundaries
651
649
  def convert_inline_quoted node
652
650
  case node.type
653
651
  when :emphasis
654
- %(#{ESC_BS}fI<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
652
+ %(<#{ESC_BS}fI>#{node.text}</#{ESC_BS}fP>)
655
653
  when :strong
656
- %(#{ESC_BS}fB<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
654
+ %(<#{ESC_BS}fB>#{node.text}</#{ESC_BS}fP>)
657
655
  when :monospaced
658
- %[#{ESC_BS}f(CR<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP]
656
+ %[<#{ESC_BS}f(CR>#{node.text}</#{ESC_BS}fP>]
659
657
  when :single
660
- %[#{ESC_BS}(oq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(cq]
658
+ %[<#{ESC_BS}(oq>#{node.text}</#{ESC_BS}(cq>]
661
659
  when :double
662
- %[#{ESC_BS}(lq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(rq]
660
+ %[<#{ESC_BS}(lq>#{node.text}</#{ESC_BS}(rq>]
663
661
  else
664
662
  node.text
665
663
  end
666
664
  end
667
665
 
668
666
  def self.write_alternate_pages mannames, manvolnum, target
669
- if mannames && mannames.size > 1
670
- mannames.shift
671
- manvolext = %(.#{manvolnum})
672
- dir, basename = ::File.split target
673
- mannames.each do |manname|
674
- ::File.write ::File.join(dir, %(#{manname}#{manvolext})), %(.so #{basename}), mode: FILE_WRITE_MODE
675
- 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
676
673
  end
677
674
  end
678
675
 
679
676
  private
680
677
 
678
+ def append_footnotes result, node
679
+ if node.footnotes? && !(node.attr? 'nofootnotes')
680
+ result << '.SH "NOTES"'
681
+ node.footnotes.each do |fn|
682
+ result << %(.IP [#{fn.index}])
683
+ # NOTE restore newline in escaped macro that gets removed by normalize_text in substitutor
684
+ if (text = fn.text).include? %(#{ESC}\\c #{ESC}.)
685
+ text = (manify %(#{text.gsub MalformedEscapedMacroRx, %(\\1#{LF}\\2)} ), whitespace: :normalize).chomp ' '
686
+ else
687
+ text = manify text, whitespace: :normalize
688
+ end
689
+ result << text
690
+ end
691
+ end
692
+ end
693
+
681
694
  # Converts HTML entity references back to their original form, escapes
682
695
  # special man characters and strips trailing whitespace.
683
696
  #
@@ -699,42 +712,56 @@ allbox tab(:);'
699
712
  else
700
713
  str = str.tr_s WHITESPACE, ' '
701
714
  end
702
- str = str.
703
- gsub(LiteralBackslashRx) { $1 ? $& : '\\(rs' }. # literal backslash (not a troff escape sequence)
704
- gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
705
- gsub(LeadingPeriodRx, '\\\&.'). # leading . is used in troff for macro call or other formatting; replace with \&.
706
- # drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
707
- gsub(EscapedMacroRx) { (rest = $3.lstrip).empty? ? %(.#$1"#$2") : %(.#$1"#$2"#{LF}#{rest}) }.
708
- gsub('-', '\-').
709
- gsub('&lt;', '<').
710
- gsub('&gt;', '>').
711
- gsub('&#160;', '\~'). # non-breaking space
712
- gsub('&#169;', '\(co'). # copyright sign
713
- gsub('&#174;', '\(rg'). # registered sign
714
- gsub('&#8482;', '\(tm'). # trademark sign
715
- gsub('&#8201;', ' '). # thin space
716
- gsub('&#8211;', '\(en'). # en dash
717
- gsub(EmDashCharRefRx, '\(em'). # em dash
718
- gsub('&#8216;', '\(oq'). # left single quotation mark
719
- gsub('&#8217;', '\(cq'). # right single quotation mark
720
- gsub('&#8220;', '\(lq'). # left double quotation mark
721
- gsub('&#8221;', '\(rq'). # right double quotation mark
722
- gsub('&#8592;', '\(<-'). # leftwards arrow
723
- gsub('&#8594;', '\(->'). # rightwards arrow
724
- gsub('&#8656;', '\(lA'). # leftwards double arrow
725
- gsub('&#8658;', '\(rA'). # rightwards double arrow
726
- gsub('&#8203;', '\:'). # zero width space
727
- gsub('&amp;', '&'). # literal ampersand (NOTE must take place after any other replacement that includes &)
728
- gsub('\'', '\(aq'). # apostrophe-quote
729
- gsub(MockBoundaryRx, ''). # mock boundary
730
- gsub(ESC_BS, '\\'). # unescape troff backslash (NOTE update if more escapes are added)
731
- gsub(ESC_FS, '.'). # unescape full stop in troff commands (NOTE must take place after gsub(LeadingPeriodRx))
732
- 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
733
749
  opts[:append_newline] ? %(#{str}#{LF}) : str
734
750
  end
735
751
 
752
+ def uppercase_pcdata string
753
+ (XMLMarkupRx.match? string) ? string.gsub(PCDATAFilterRx) { $2 ? $2.upcase : $1 } : string.upcase
754
+ end
755
+
736
756
  def enclose_content node
737
757
  node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content, whitespace: :normalize})
738
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
739
766
  end
740
767
  end