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.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +142 -22
- data/LICENSE +1 -1
- data/README-de.adoc +15 -6
- data/README-fr.adoc +14 -8
- data/README-jp.adoc +15 -6
- data/README-zh_CN.adoc +14 -5
- data/README.adoc +135 -125
- data/asciidoctor.gemspec +4 -11
- data/data/locale/attributes-be.adoc +23 -0
- data/data/locale/attributes-it.adoc +4 -4
- data/data/locale/attributes-nl.adoc +6 -6
- data/data/locale/attributes-th.adoc +23 -0
- data/data/locale/attributes-vi.adoc +23 -0
- data/data/reference/syntax.adoc +14 -7
- data/data/stylesheets/asciidoctor-default.css +51 -52
- data/lib/asciidoctor.rb +12 -12
- data/lib/asciidoctor/abstract_block.rb +4 -4
- data/lib/asciidoctor/abstract_node.rb +10 -9
- data/lib/asciidoctor/attribute_list.rb +6 -6
- data/lib/asciidoctor/block.rb +6 -6
- data/lib/asciidoctor/cli/invoker.rb +0 -1
- data/lib/asciidoctor/cli/options.rb +27 -26
- data/lib/asciidoctor/convert.rb +1 -0
- data/lib/asciidoctor/converter.rb +5 -3
- data/lib/asciidoctor/converter/docbook5.rb +45 -26
- data/lib/asciidoctor/converter/html5.rb +89 -69
- data/lib/asciidoctor/converter/manpage.rb +113 -86
- data/lib/asciidoctor/converter/template.rb +11 -12
- data/lib/asciidoctor/document.rb +44 -51
- data/lib/asciidoctor/extensions.rb +10 -12
- data/lib/asciidoctor/helpers.rb +3 -6
- data/lib/asciidoctor/list.rb +2 -6
- data/lib/asciidoctor/load.rb +13 -11
- data/lib/asciidoctor/logging.rb +10 -8
- data/lib/asciidoctor/parser.rb +135 -150
- data/lib/asciidoctor/path_resolver.rb +3 -3
- data/lib/asciidoctor/reader.rb +72 -71
- data/lib/asciidoctor/rx.rb +4 -3
- data/lib/asciidoctor/substitutors.rb +117 -117
- data/lib/asciidoctor/syntax_highlighter.rb +8 -11
- data/lib/asciidoctor/syntax_highlighter/coderay.rb +2 -1
- data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +1 -1
- data/lib/asciidoctor/syntax_highlighter/pygments.rb +6 -5
- data/lib/asciidoctor/syntax_highlighter/rouge.rb +33 -26
- data/lib/asciidoctor/table.rb +17 -19
- data/lib/asciidoctor/timings.rb +3 -3
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +10 -11
- data/man/asciidoctor.adoc +8 -7
- 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
|
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 =
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
354
|
+
case section.sectname
|
355
|
+
when 'chapter'
|
354
356
|
stitle = %(#{(signifier = node.document.attributes['chapter-signifier']) ? "#{signifier} " : ''}#{section.sectnum} #{section.title})
|
355
|
-
|
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
|
-
|
388
|
+
case node.sectname
|
389
|
+
when 'chapter'
|
387
390
|
title = %(#{(signifier = doc_attrs['chapter-signifier']) ? "#{signifier} " : ''}#{node.sectnum} #{node.title})
|
388
|
-
|
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
|
-
|
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
|
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
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
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
|
-
|
763
|
-
|
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
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
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 = [
|
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 '
|
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, *
|
1319
|
-
!((name = id.to_s).start_with? 'convert_') && (handles? name) ? (send %(convert_#{name}), *
|
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
|
6
|
-
#
|
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
|
-
|
25
|
+
MalformedEscapedMacroRx = /(#{ESC}\\c) (#{ESC}\.(?:URL|MTO) )/
|
26
|
+
MockMacroRx = %r(</?(#{ESC}\\[^>]+)>)
|
22
27
|
EmDashCharRefRx = /—(?:​)?/
|
23
28
|
EllipsisCharRefRx = /…(?:​)?/
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
319
|
-
|
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 == :
|
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
|
-
|
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
|
-
|
530
|
+
if node.title?
|
531
|
+
result << %(.sp
|
538
532
|
.B #{manify node.title}
|
539
|
-
.br)
|
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
|
-
|
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
|
-
%(
|
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
|
-
%(
|
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
|
-
|
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| %(
|
642
|
-
%(
|
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
|
-
%(
|
642
|
+
%(<#{ESC_BS}fI>#{menu}#{caret}#{menuitem}</#{ESC_BS}fP>)
|
645
643
|
else
|
646
|
-
%(
|
644
|
+
%(<#{ESC_BS}fI>#{menu}</#{ESC_BS}fP>)
|
647
645
|
end
|
648
646
|
end
|
649
647
|
|
650
|
-
# NOTE use fake
|
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
|
-
%(
|
652
|
+
%(<#{ESC_BS}fI>#{node.text}</#{ESC_BS}fP>)
|
655
653
|
when :strong
|
656
|
-
%(
|
654
|
+
%(<#{ESC_BS}fB>#{node.text}</#{ESC_BS}fP>)
|
657
655
|
when :monospaced
|
658
|
-
%[
|
656
|
+
%[<#{ESC_BS}f(CR>#{node.text}</#{ESC_BS}fP>]
|
659
657
|
when :single
|
660
|
-
%[
|
658
|
+
%[<#{ESC_BS}(oq>#{node.text}</#{ESC_BS}(cq>]
|
661
659
|
when :double
|
662
|
-
%[
|
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
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
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' }
|
704
|
-
gsub(EllipsisCharRefRx, '...')
|
705
|
-
gsub(LeadingPeriodRx, '\\\&.')
|
706
|
-
# drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
|
707
|
-
|
708
|
-
|
709
|
-
gsub('
|
710
|
-
gsub('&
|
711
|
-
gsub('
|
712
|
-
gsub('&#
|
713
|
-
gsub('&#
|
714
|
-
gsub('&#
|
715
|
-
gsub('&#
|
716
|
-
gsub('&#
|
717
|
-
gsub(
|
718
|
-
gsub('&#
|
719
|
-
gsub('&#
|
720
|
-
gsub(
|
721
|
-
gsub('&#
|
722
|
-
gsub('&#
|
723
|
-
gsub('&#
|
724
|
-
gsub('&#
|
725
|
-
gsub('&#
|
726
|
-
gsub('&#
|
727
|
-
gsub('
|
728
|
-
gsub('
|
729
|
-
gsub(
|
730
|
-
gsub(
|
731
|
-
gsub(
|
732
|
-
|
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('<', '<')
|
724
|
+
.gsub('>', '>')
|
725
|
+
.gsub('+', '+') # plus sign; alternately could use \c(pl
|
726
|
+
.gsub(' ', '\~') # non-breaking space
|
727
|
+
.gsub('©', '\(co') # copyright sign
|
728
|
+
.gsub('®', '\(rg') # registered sign
|
729
|
+
.gsub('™', '\(tm') # trademark sign
|
730
|
+
.gsub('°', '\(de') # degree sign
|
731
|
+
.gsub(' ', ' ') # thin space
|
732
|
+
.gsub('–', '\(en') # en dash
|
733
|
+
.gsub(EmDashCharRefRx, '\(em') # em dash
|
734
|
+
.gsub('‘', '\(oq') # left single quotation mark
|
735
|
+
.gsub('’', '\(cq') # right single quotation mark
|
736
|
+
.gsub('“', '\(lq') # left double quotation mark
|
737
|
+
.gsub('”', '\(rq') # right double quotation mark
|
738
|
+
.gsub('←', '\(<-') # leftwards arrow
|
739
|
+
.gsub('→', '\(->') # rightwards arrow
|
740
|
+
.gsub('⇐', '\(lA') # leftwards double arrow
|
741
|
+
.gsub('⇒', '\(rA') # rightwards double arrow
|
742
|
+
.gsub('​', '\:') # zero width space
|
743
|
+
.gsub('&', '&') # 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
|