asciidoctor 2.0.12 → 2.0.13

Sign up to get free protection for your applications and to get access to all the features.
data/asciidoctor.gemspec CHANGED
@@ -34,21 +34,21 @@ Gem::Specification.new do |s|
34
34
  #s.test_files = files.grep %r/^(?:features|test)\/.+$/
35
35
 
36
36
  # asciimath is needed for testing AsciiMath in DocBook backend
37
- s.add_development_dependency 'asciimath', '~> 1.0.0'
37
+ s.add_development_dependency 'asciimath', '~> 2.0.0'
38
38
  # coderay is needed for testing syntax highlighting
39
39
  s.add_development_dependency 'coderay', '~> 1.1.0'
40
40
  # concurrent-ruby, haml, slim, and tilt are needed for testing custom templates
41
41
  s.add_development_dependency 'concurrent-ruby', '~> 1.1.0'
42
42
  s.add_development_dependency 'cucumber', '~> 3.1.0'
43
43
  # erubi is needed for testing alternate eRuby impls
44
- s.add_development_dependency 'erubi', '~> 1.9.0'
45
- s.add_development_dependency 'haml', '~> 5.0.0'
44
+ s.add_development_dependency 'erubi', '~> 1.10.0'
45
+ s.add_development_dependency 'haml', '~> 5.2.0'
46
46
  s.add_development_dependency 'minitest', '~> 5.11.0'
47
47
  s.add_development_dependency 'nokogiri', '~> 1.10.0'
48
48
  s.add_development_dependency 'rake', '~> 12.3.0'
49
49
  # Asciidoctor supports Rouge >= 2
50
- s.add_development_dependency 'rouge', '~> 3.15.0'
50
+ s.add_development_dependency 'rouge', '~> 3.26.0'
51
51
  s.add_development_dependency 'rspec-expectations', '~> 3.8.0'
52
- s.add_development_dependency 'slim', '~> 4.0.0'
52
+ s.add_development_dependency 'slim', '~> 4.1.0'
53
53
  s.add_development_dependency 'tilt', '~> 2.0.0'
54
54
  end
@@ -0,0 +1,23 @@
1
+ // Belarusian translation, courtesy of Dexter Morganov <dexter.morganov@gmail.com>
2
+ :appendix-caption: Дадатак
3
+ :appendix-refsig: {appendix-caption}
4
+ :caution-caption: Увага
5
+ :chapter-signifier: Глава
6
+ :chapter-refsig: {chapter-signifier}
7
+ :example-caption: Прыклад
8
+ :figure-caption: Малюнак
9
+ :important-caption: Важна
10
+ :last-update-label: Апошняе абнаўленне
11
+ ifdef::listing-caption[:listing-caption: Лістынг]
12
+ ifdef::manname-title[:manname-title: Назва]
13
+ :note-caption: Заўвага
14
+ :part-signifier: Частка
15
+ :part-refsig: {part-signifier}
16
+ ifdef::preface-title[:preface-title: Прадмова]
17
+ :section-refsig: Раздзел
18
+ :table-caption: Табліца
19
+ :tip-caption: Падказка
20
+ :toc-title: Змест
21
+ :untitled-label: Без назвы
22
+ :version-label: Версія
23
+ :warning-caption: Папярэджанне
@@ -1,4 +1,4 @@
1
- // Italian translation, courtesy of Marco Ciampa <ciampix@libero.it>
1
+ // Italian translation, courtesy of Marco Ciampa <ciampix@posteo.net>
2
2
  :appendix-caption: Appendice
3
3
  :appendix-refsig: {appendix-caption}
4
4
  :caution-caption: Attenzione
@@ -11,10 +11,10 @@
11
11
  ifdef::listing-caption[:listing-caption: Elenco]
12
12
  ifdef::manname-title[:manname-title: Nome]
13
13
  :note-caption: Nota
14
- //:part-signifier: ???
15
- //:part-refsig: {part-signifier}
14
+ :part-signifier: Parte
15
+ :part-refsig: {part-signifier}
16
16
  ifdef::preface-title[:preface-title: Prefazione]
17
- //:section-refsig: ???
17
+ :section-refsig: Sezione
18
18
  :table-caption: Tabella
19
19
  :tip-caption: Suggerimento
20
20
  :toc-title: Indice
@@ -2,8 +2,8 @@
2
2
  :appendix-caption: Bijlage
3
3
  :appendix-refsig: {appendix-caption}
4
4
  :caution-caption: Opgelet
5
- //:chapter-signifier: Hoofdstuk
6
- //:chapter-refsig: {chapter-signifier}
5
+ :chapter-signifier: Hoofdstuk
6
+ :chapter-refsig: {chapter-signifier}
7
7
  :example-caption: Voorbeeld
8
8
  :figure-caption: Figuur
9
9
  :important-caption: Belangrijk
@@ -11,13 +11,13 @@
11
11
  ifdef::listing-caption[:listing-caption: Lijst]
12
12
  ifdef::manname-title[:manname-title: Naam]
13
13
  :note-caption: Noot
14
- //:part-signifier: ???
15
- //:part-refsig: {part-signifier}
14
+ :part-signifier: Deel
15
+ :part-refsig: {part-signifier}
16
16
  ifdef::preface-title[:preface-title: Inleiding]
17
- //:section-refsig: ???
17
+ :section-refsig: Paragraaf
18
18
  :table-caption: Tabel
19
19
  :tip-caption: Tip
20
- :toc-title: Ínhoudsopgave
20
+ :toc-title: Inhoudsopgave
21
21
  :untitled-label: Naamloos
22
22
  :version-label: Versie
23
23
  :warning-caption: Waarschuwing
@@ -17,7 +17,7 @@ Line breaks are not preserved.
17
17
  A blank line separates paragraphs.
18
18
 
19
19
  [%hardbreaks]
20
- This paragraph carries the `hardbreaks` option.
20
+ This paragraph is marked with the `hardbreaks` option.
21
21
  Notice how line breaks are now preserved.
22
22
 
23
23
  An indented (literal) paragraph disables text formatting,
@@ -25,14 +25,14 @@ Notice how line breaks are now preserved.
25
25
  monospaced font.
26
26
 
27
27
  [sidebar#id.role]
28
- A style, ID, and/or role gives a paragraph (or block) special meaning, like this sidebar.
28
+ Adding a style, ID, and/or role gives a paragraph (or block) special meaning, like this sidebar.
29
29
 
30
30
  NOTE: An admonition paragraph, like this note, grabs the reader's attention.
31
31
 
32
32
  TIP: Convert this document using the `asciidoctor` command to see the output produced from it.
33
33
 
34
34
  == Text Formatting
35
- :hardbreaks:
35
+ :hardbreaks-option:
36
36
 
37
37
  .Constrained (applied at word boundaries)
38
38
  *strong importance* (aka bold)
@@ -41,17 +41,24 @@ _stress emphasis_ (aka italic)
41
41
  "`double`" and '`single`' typographic quotes
42
42
  +passthrough text+ (substitutions disabled)
43
43
  `+literal text+` (monospaced with substitutions disabled)
44
+ a #mark# to remember (highlighted for notation)
44
45
 
45
46
  .Unconstrained (applied anywhere)
46
- **C**reate+**R**ead+**U**pdate+**D**elete
47
+ **C**reate, **R**ead, **U**pdate, and **D**elete (CRUD)
47
48
  fan__freakin__tastic
48
49
  ``mono``culture
50
+ ##mark##up your text
49
51
 
50
52
  .Replacements
51
53
  A long time ago in a galaxy far, far away...
52
54
  (C) 1976 Arty Artisan
53
55
  I believe I shall--no, actually I won't.
54
56
 
57
+ .ID and roles for phrases
58
+ [.line-through]#delete me#
59
+ the [.path]_images_ directory
60
+ a [#wibble.term]*wibble* does wobble
61
+
55
62
  .Macros
56
63
  // where c=specialchars, q=quotes, a=attributes, r=replacements, m=macros, p=post_replacements, etc.
57
64
  The European icon:flag[role=blue] is blue & contains pass:[************] arranged in a icon:circle-o[role=yellow].
@@ -60,7 +67,7 @@ Since `pass:[++]` has strong priority in AsciiDoc, you can rewrite pass:c,a,r[C+
60
67
  // activate stem support by adding `:stem:` to the document header
61
68
  stem:[sqrt(4) = 2]
62
69
 
63
- :!hardbreaks:
70
+ :!hardbreaks-option:
64
71
  == Attributes
65
72
 
66
73
  // define attributes in the document header; must be flush with left margin
@@ -242,7 +249,7 @@ comment - content which is not included in the output document
242
249
  == Tables
243
250
 
244
251
  .Table Attributes
245
- [cols=>1h;2d,width=50%,frame=topbot]
252
+ [cols=>1h;2d,width=50%,frame=ends]
246
253
  |===
247
254
  | Attribute Name | Values
248
255
 
@@ -256,7 +263,7 @@ comment - content which is not included in the output document
256
263
  | all \| cols \| rows \| none
257
264
 
258
265
  | frame
259
- | all \| sides \| topbot \| none
266
+ | all \| sides \| ends \| none
260
267
 
261
268
  | stripes
262
269
  | all \| even \| odd \| none
@@ -84,9 +84,6 @@ dl dd{margin-bottom:1.25em}
84
84
  abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
85
85
  abbr{text-transform:none}
86
86
  blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
87
- blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
88
- blockquote cite::before{content:"\2014 \0020"}
89
- blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
90
87
  blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
91
88
  @media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
92
89
  h1{font-size:2.75em}
@@ -251,7 +248,7 @@ pre.pygments .lineno::before{content:"";margin-right:-.125em}
251
248
  .quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
252
249
  .quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
253
250
  .quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
254
- .quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;text-align:left;margin-right:0}
251
+ .quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
255
252
  p.tableblock:last-child{margin-bottom:0}
256
253
  td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
257
254
  td.tableblock>.content>:last-child{margin-bottom:-1.25em}
data/lib/asciidoctor.rb CHANGED
@@ -136,8 +136,8 @@ module Asciidoctor
136
136
  # Compliance value: true
137
137
  define :underline_style_section_titles, true
138
138
 
139
- # Asciidoctor will unwrap the content in a preamble
140
- # if the document has a title and no sections.
139
+ # Asciidoctor will unwrap the content in a preamble if the document has a
140
+ # title and no sections, then discard the empty preamble.
141
141
  # Compliance value: false
142
142
  define :unwrap_standalone_preamble, true
143
143
 
@@ -216,7 +216,7 @@ class AbstractNode
216
216
  (val = @attributes['role']) ? (%( #{val} ).include? %( #{name} )) : false
217
217
  end
218
218
 
219
- # Public: Sets the value of the role attribute on this ndoe.
219
+ # Public: Sets the value of the role attribute on this node.
220
220
  #
221
221
  # names - A single role name, a space-separated String of role names, or an Array of role names
222
222
  #
@@ -27,7 +27,7 @@ class AttributeList
27
27
  QUOT = '"'
28
28
 
29
29
  # Public: Regular expressions for detecting the boundary of a value
30
- BoundaryRxs = {
30
+ BoundaryRx = {
31
31
  QUOT => /.*?[^\\](?=")/,
32
32
  APOS => /.*?[^\\](?=')/,
33
33
  ',' => /.*?(?=[ \t]*(,|$))/
@@ -46,7 +46,7 @@ class AttributeList
46
46
  BlankRx = /[ \t]+/
47
47
 
48
48
  # Public: Regular expressions for skipping delimiters
49
- SkipRxs = {
49
+ SkipRx = {
50
50
  ',' => /[ \t]*(,|$)/
51
51
  }
52
52
 
@@ -54,8 +54,8 @@ class AttributeList
54
54
  @scanner = ::StringScanner.new source
55
55
  @block = block
56
56
  @delimiter = delimiter
57
- @delimiter_skip_pattern = SkipRxs[delimiter]
58
- @delimiter_boundary_pattern = BoundaryRxs[delimiter]
57
+ @delimiter_skip_pattern = SkipRx[delimiter]
58
+ @delimiter_boundary_pattern = BoundaryRx[delimiter]
59
59
  @attributes = nil
60
60
  end
61
61
 
@@ -214,7 +214,7 @@ class AttributeList
214
214
  end
215
215
 
216
216
  def scan_to_quote quote
217
- @scanner.scan BoundaryRxs[quote]
217
+ @scanner.scan BoundaryRx[quote]
218
218
  end
219
219
  end
220
220
  end
@@ -39,14 +39,15 @@ module Asciidoctor
39
39
  # NOTE don't use squiggly heredoc to maintain compatibility with Ruby < 2.3
40
40
  opts.banner = <<-'EOS'.gsub ' ', ''
41
41
  Usage: asciidoctor [OPTION]... FILE...
42
- Translate the AsciiDoc source FILE or FILE(s) into the backend output format (e.g., HTML 5, DocBook 5, etc.)
43
- By default, the output is written to a file with the basename of the source file and the appropriate extension.
44
- Example: asciidoctor -b html5 source.asciidoc
42
+ Convert the AsciiDoc input FILE(s) to the backend output format (e.g., HTML 5, DocBook 5, etc.)
43
+ Unless specified otherwise, the output is written to a file whose name is derived from the input file.
44
+ Application log messages are printed to STDERR.
45
+ Example: asciidoctor input.adoc
45
46
 
46
47
  EOS
47
48
 
48
- opts.on('-b', '--backend BACKEND', 'set output format backend: [html5, xhtml5, docbook5, manpage] (default: html5)',
49
- 'additional backends are supported via extensions (e.g., pdf, latex)') do |backend|
49
+ opts.on('-b', '--backend BACKEND', 'set backend output format: [html5, xhtml5, docbook5, manpage] (default: html5)',
50
+ 'additional backends are supported via extended converters (e.g., pdf, epub3)') do |backend|
50
51
  self[:attributes]['backend'] = backend
51
52
  end
52
53
  opts.on('-d', '--doctype DOCTYPE', ['article', 'book', 'manpage', 'inline'],
@@ -128,7 +129,7 @@ module Asciidoctor
128
129
  opts.on('--trace', 'include backtrace information when reporting errors (default: false)') do |trace|
129
130
  self[:trace] = true
130
131
  end
131
- opts.on('-v', '--verbose', 'enable verbose mode (default: false)') do |verbose|
132
+ opts.on('-v', '--verbose', 'directs application messages logged at DEBUG or INFO level to STDERR (default: false)') do |verbose|
132
133
  self[:verbose] = 2
133
134
  end
134
135
  opts.on('-w', '--warnings', 'turn on script warnings (default: false)') do |warnings|
@@ -1310,7 +1310,7 @@ Your browser does not support the video tag.
1310
1310
  manname_id_attr = (manname_id = node.attr 'manname-id') ? %( id="#{manname_id}") : ''
1311
1311
  %(<h2#{manname_id_attr}>#{manname_title}</h2>
1312
1312
  <div class="sectionbody">
1313
- <p>#{node.attr 'manname'} - #{node.attr 'manpurpose'}</p>
1313
+ <p>#{(node.attr 'mannames').join ', '} - #{node.attr 'manpurpose'}</p>
1314
1314
  </div>)
1315
1315
  end
1316
1316
 
@@ -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 Python, 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 = /<\/?(#{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?
@@ -531,12 +533,13 @@ allbox tab(:);'
531
533
  result.join LF
532
534
  end
533
535
 
534
- # FIXME git uses [verse] for the synopsis; detect this special case
535
536
  def convert_verse node
536
537
  result = []
537
- result << (node.title? ? %(.sp
538
+ if node.title?
539
+ result << %(.sp
538
540
  .B #{manify node.title}
539
- .br) : '.sp')
541
+ .br)
542
+ end
540
543
  attribution_line = (node.attr? 'citetitle') ? %(#{node.attr 'citetitle'} ) : nil
541
544
  attribution_line = (node.attr? 'attribution') ? %[#{attribution_line}\\(em #{node.attr 'attribution'}] : nil
542
545
  result << %(.sp
@@ -609,7 +612,6 @@ allbox tab(:);'
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}])
@@ -647,19 +649,19 @@ allbox tab(:);'
647
649
  end
648
650
  end
649
651
 
650
- # NOTE use fake <BOUNDARY> element to prevent creating artificial word boundaries
652
+ # NOTE use fake XML elements to prevent creating artificial word boundaries
651
653
  def convert_inline_quoted node
652
654
  case node.type
653
655
  when :emphasis
654
- %(#{ESC_BS}fI<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
656
+ %(<#{ESC_BS}fI>#{node.text}</#{ESC_BS}fP>)
655
657
  when :strong
656
- %(#{ESC_BS}fB<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP)
658
+ %(<#{ESC_BS}fB>#{node.text}</#{ESC_BS}fP>)
657
659
  when :monospaced
658
- %[#{ESC_BS}f(CR<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}fP]
660
+ %[<#{ESC_BS}f(CR>#{node.text}</#{ESC_BS}fP>]
659
661
  when :single
660
- %[#{ESC_BS}(oq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(cq]
662
+ %[<#{ESC_BS}(oq>#{node.text}</#{ESC_BS}(cq>]
661
663
  when :double
662
- %[#{ESC_BS}(lq<BOUNDARY>#{node.text}</BOUNDARY>#{ESC_BS}(rq]
664
+ %[<#{ESC_BS}(lq>#{node.text}</#{ESC_BS}(rq>]
663
665
  else
664
666
  node.text
665
667
  end
@@ -678,6 +680,22 @@ allbox tab(:);'
678
680
 
679
681
  private
680
682
 
683
+ def append_footnotes result, node
684
+ if node.footnotes? && !(node.attr? 'nofootnotes')
685
+ result << '.SH "NOTES"'
686
+ node.footnotes.each_with_index do |fn, idx|
687
+ result << %(.IP [#{fn.index}])
688
+ # NOTE restore newline in escaped macro that gets removed by normalize_text in substitutor
689
+ if (text = fn.text).include? %(#{ESC}\\c #{ESC}.)
690
+ text = (manify %(#{text.gsub MalformedEscapedMacroRx, %(\\1#{LF}\\2)} ), whitespace: :normalize).chomp ' '
691
+ else
692
+ text = manify text, whitespace: :normalize
693
+ end
694
+ result << text
695
+ end
696
+ end
697
+ end
698
+
681
699
  # Converts HTML entity references back to their original form, escapes
682
700
  # special man characters and strips trailing whitespace.
683
701
  #
@@ -704,7 +722,7 @@ allbox tab(:);'
704
722
  gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
705
723
  gsub(LeadingPeriodRx, '\\\&.'). # leading . is used in troff for macro call or other formatting; replace with \&.
706
724
  # 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}) }.
725
+ gsub(EscapedMacroRx) { (rest = $3.lstrip).empty? ? %(.#$1"#$2") : %(.#$1"#{$2.rstrip}"#{LF}#{rest}) }.
708
726
  gsub('-', '\-').
709
727
  gsub('&lt;', '<').
710
728
  gsub('&gt;', '>').
@@ -726,13 +744,17 @@ allbox tab(:);'
726
744
  gsub('&#8203;', '\:'). # zero width space
727
745
  gsub('&amp;', '&'). # literal ampersand (NOTE must take place after any other replacement that includes &)
728
746
  gsub('\'', '\(aq'). # apostrophe-quote
729
- gsub(MockBoundaryRx, ''). # mock boundary
747
+ gsub(MockMacroRx, '\1'). # mock boundary
730
748
  gsub(ESC_BS, '\\'). # unescape troff backslash (NOTE update if more escapes are added)
731
749
  gsub(ESC_FS, '.'). # unescape full stop in troff commands (NOTE must take place after gsub(LeadingPeriodRx))
732
750
  rstrip # strip trailing space
733
751
  opts[:append_newline] ? %(#{str}#{LF}) : str
734
752
  end
735
753
 
754
+ def uppercase_pcdata string
755
+ (XMLMarkupRx.match? string) ? string.gsub(PCDATAFilterRx) { $2 ? $2.upcase : $1 } : string.upcase
756
+ end
757
+
736
758
  def enclose_content node
737
759
  node.content_model == :compound ? node.content : %(.sp#{LF}#{manify node.content, whitespace: :normalize})
738
760
  end