asciidoctor 2.0.12 → 2.0.13

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.
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