asciidoctor 2.0.7 → 2.0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +169 -7
  3. data/LICENSE +2 -1
  4. data/README-de.adoc +5 -15
  5. data/README-fr.adoc +4 -14
  6. data/README-jp.adoc +234 -186
  7. data/README-zh_CN.adoc +7 -17
  8. data/README.adoc +18 -18
  9. data/asciidoctor.gemspec +4 -4
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-bg.adoc +4 -3
  12. data/data/locale/attributes-ca.adoc +6 -5
  13. data/data/locale/attributes-cs.adoc +4 -3
  14. data/data/locale/attributes-da.adoc +6 -5
  15. data/data/locale/attributes-de.adoc +4 -4
  16. data/data/locale/attributes-en.adoc +4 -4
  17. data/data/locale/attributes-es.adoc +6 -5
  18. data/data/locale/attributes-fa.adoc +4 -3
  19. data/data/locale/attributes-fi.adoc +4 -3
  20. data/data/locale/attributes-fr.adoc +6 -5
  21. data/data/locale/attributes-hu.adoc +4 -3
  22. data/data/locale/attributes-id.adoc +4 -3
  23. data/data/locale/attributes-it.adoc +4 -3
  24. data/data/locale/attributes-ja.adoc +4 -3
  25. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  26. data/data/locale/attributes-nb.adoc +4 -3
  27. data/data/locale/attributes-nl.adoc +4 -3
  28. data/data/locale/attributes-nn.adoc +4 -3
  29. data/data/locale/attributes-pl.adoc +8 -7
  30. data/data/locale/attributes-pt.adoc +6 -5
  31. data/data/locale/attributes-pt_BR.adoc +6 -5
  32. data/data/locale/attributes-ro.adoc +4 -3
  33. data/data/locale/attributes-ru.adoc +6 -5
  34. data/data/locale/attributes-sr.adoc +4 -4
  35. data/data/locale/attributes-sr_Latn.adoc +4 -4
  36. data/data/locale/attributes-sv.adoc +4 -4
  37. data/data/locale/attributes-tr.adoc +4 -3
  38. data/data/locale/attributes-uk.adoc +6 -5
  39. data/data/locale/attributes-zh_CN.adoc +4 -3
  40. data/data/locale/attributes-zh_TW.adoc +4 -3
  41. data/data/stylesheets/asciidoctor-default.css +33 -30
  42. data/lib/asciidoctor.rb +89 -791
  43. data/lib/asciidoctor/abstract_block.rb +19 -11
  44. data/lib/asciidoctor/abstract_node.rb +21 -15
  45. data/lib/asciidoctor/attribute_list.rb +59 -67
  46. data/lib/asciidoctor/cli/invoker.rb +2 -0
  47. data/lib/asciidoctor/cli/options.rb +3 -3
  48. data/lib/asciidoctor/convert.rb +167 -162
  49. data/lib/asciidoctor/converter.rb +14 -13
  50. data/lib/asciidoctor/converter/docbook5.rb +10 -26
  51. data/lib/asciidoctor/converter/html5.rb +62 -43
  52. data/lib/asciidoctor/converter/manpage.rb +13 -12
  53. data/lib/asciidoctor/converter/template.rb +6 -3
  54. data/lib/asciidoctor/document.rb +25 -41
  55. data/lib/asciidoctor/extensions.rb +3 -3
  56. data/lib/asciidoctor/helpers.rb +38 -39
  57. data/lib/asciidoctor/inline.rb +1 -1
  58. data/lib/asciidoctor/load.rb +101 -101
  59. data/lib/asciidoctor/parser.rb +30 -25
  60. data/lib/asciidoctor/path_resolver.rb +35 -25
  61. data/lib/asciidoctor/reader.rb +14 -7
  62. data/lib/asciidoctor/rx.rb +722 -0
  63. data/lib/asciidoctor/substitutors.rb +61 -39
  64. data/lib/asciidoctor/syntax_highlighter.rb +22 -8
  65. data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
  66. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
  67. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  68. data/lib/asciidoctor/syntax_highlighter/pygments.rb +2 -3
  69. data/lib/asciidoctor/syntax_highlighter/rouge.rb +15 -7
  70. data/lib/asciidoctor/table.rb +52 -23
  71. data/lib/asciidoctor/version.rb +1 -1
  72. data/man/asciidoctor.1 +6 -6
  73. data/man/asciidoctor.adoc +4 -3
  74. metadata +10 -9
@@ -85,23 +85,24 @@ module Converter
85
85
  # Public: Derive backend traits (basebackend, filetype, outfilesuffix, htmlsyntax) from the given backend.
86
86
  #
87
87
  # backend - the String backend from which to derive the traits
88
+ # basebackend - the String basebackend to use in favor of deriving one from the backend (optional, default: nil)
88
89
  #
89
90
  # Returns the backend traits for the given backend as a [Hash].
90
- def self.derive_backend_traits backend
91
+ def self.derive_backend_traits backend, basebackend = nil
91
92
  return {} unless backend
92
- if (t_outfilesuffix = DEFAULT_EXTENSIONS[(t_basebackend = backend.sub TrailingDigitsRx, '')])
93
- t_filetype = t_outfilesuffix.slice 1, t_outfilesuffix.length
93
+ if (outfilesuffix = DEFAULT_EXTENSIONS[(basebackend ||= backend.sub TrailingDigitsRx, '')])
94
+ filetype = outfilesuffix.slice 1, outfilesuffix.length
94
95
  else
95
- t_outfilesuffix = %(.#{t_filetype = t_basebackend})
96
+ outfilesuffix = %(.#{filetype = basebackend})
96
97
  end
97
- t_filetype == 'html' ?
98
- { basebackend: t_basebackend, filetype: t_filetype, htmlsyntax: 'html', outfilesuffix: t_outfilesuffix } :
99
- { basebackend: t_basebackend, filetype: t_filetype, outfilesuffix: t_outfilesuffix }
98
+ filetype == 'html' ?
99
+ { basebackend: basebackend, filetype: filetype, htmlsyntax: 'html', outfilesuffix: outfilesuffix } :
100
+ { basebackend: basebackend, filetype: filetype, outfilesuffix: outfilesuffix }
100
101
  end
101
102
 
102
103
  module BackendTraits
103
104
  def basebackend value = nil
104
- value ? (backend_traits[:basebackend] = value) : backend_traits[:basebackend]
105
+ value ? ((backend_traits value)[:basebackend] = value) : backend_traits[:basebackend]
105
106
  end
106
107
 
107
108
  def filetype value = nil
@@ -128,15 +129,15 @@ module Converter
128
129
  @backend_traits = value || {}
129
130
  end
130
131
 
131
- def backend_traits
132
- @backend_traits ||= Converter.derive_backend_traits @backend
132
+ def backend_traits basebackend = nil
133
+ @backend_traits ||= Converter.derive_backend_traits @backend, basebackend
133
134
  end
134
135
 
135
136
  alias backend_info backend_traits
136
137
 
137
138
  # Deprecated: Use {Converter.derive_backend_traits} instead.
138
- def self.derive_backend_traits backend
139
- Converter.derive_backend_traits backend
139
+ def self.derive_backend_traits backend, basebackend = nil
140
+ Converter.derive_backend_traits backend, basebackend
140
141
  end
141
142
  end
142
143
 
@@ -148,7 +149,7 @@ module Converter
148
149
  #
149
150
  # Returns nothing.
150
151
  def register_for *backends
151
- Converter.register self, *backends
152
+ Converter.register self, *(backends.map {|backend| backend.to_s })
152
153
  end
153
154
  end
154
155
 
@@ -25,8 +25,8 @@ class Converter::DocBook5Converter < Converter::Base
25
25
  MANPAGE_SECTION_TAGS = { 'section' => 'refsection', 'synopsis' => 'refsynopsisdiv' }
26
26
  TABLE_PI_NAMES = ['dbhtml', 'dbfo', 'dblatex']
27
27
 
28
- CopyrightRx = /^(.+?)(?: ((?:\d{4}\-)?\d{4}))?$/
29
- ImageMacroRx = /^image::?(.+?)\[(.*?)\]$/
28
+ CopyrightRx = /^(#{CC_ANY}+?)(?: ((?:\d{4}\-)?\d{4}))?$/
29
+ ImageMacroRx = /^image::?(\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
30
30
 
31
31
  def initialize backend, opts = {}
32
32
  @backend = backend
@@ -35,20 +35,8 @@ class Converter::DocBook5Converter < Converter::Base
35
35
 
36
36
  def convert_document node
37
37
  result = ['<?xml version="1.0" encoding="UTF-8"?>']
38
- if node.attr? 'toc'
39
- if node.attr? 'toclevels'
40
- result << %(<?asciidoc-toc maxdepth="#{node.attr 'toclevels'}"?>)
41
- else
42
- result << '<?asciidoc-toc?>'
43
- end
44
- end
45
- if node.attr? 'sectnums'
46
- if node.attr? 'sectnumlevels'
47
- result << %(<?asciidoc-numbered maxdepth="#{node.attr 'sectnumlevels'}"?>)
48
- else
49
- result << '<?asciidoc-numbered?>'
50
- end
51
- end
38
+ result << ((node.attr? 'toclevels') ? %(<?asciidoc-toc maxdepth="#{node.attr 'toclevels'}"?>) : '<?asciidoc-toc?>') if node.attr? 'toc'
39
+ result << ((node.attr? 'sectnumlevels') ? %(<?asciidoc-numbered maxdepth="#{node.attr 'sectnumlevels'}"?>) : '<?asciidoc-numbered?>') if node.attr? 'sectnums'
52
40
  lang_attribute = (node.attr? 'nolang') ? '' : %( xml:lang="#{node.attr 'lang', 'en'}")
53
41
  if (root_tag_name = node.doctype) == 'manpage'
54
42
  root_tag_name = 'refentry'
@@ -383,9 +371,7 @@ class Converter::DocBook5Converter < Converter::Base
383
371
  has_body = false
384
372
  result = []
385
373
  pgwide_attribute = (node.option? 'pgwide') ? ' pgwide="1"' : ''
386
- if (frame = node.attr 'frame', 'all', 'table-frame') == 'ends'
387
- frame = 'topbot'
388
- end
374
+ frame = 'topbot' if (frame = node.attr 'frame', 'all', 'table-frame') == 'ends'
389
375
  grid = node.attr 'grid', nil, 'table-grid'
390
376
  result << %(<#{tag_name = node.title? ? 'table' : 'informaltable'}#{common_attributes node.id, node.role, node.reftext}#{pgwide_attribute} frame="#{frame}" rowsep="#{['none', 'cols'].include?(grid) ? 0 : 1}" colsep="#{['none', 'rows'].include?(grid) ? 0 : 1}"#{(node.attr? 'orientation', 'landscape', 'table-orientation') ? ' orient="land"' : ''}>)
391
377
  if (node.option? 'unbreakable')
@@ -413,12 +399,10 @@ class Converter::DocBook5Converter < Converter::Base
413
399
  rows.each do |row|
414
400
  result << '<row>'
415
401
  row.each do |cell|
416
- halign_attribute = (cell.attr? 'halign') ? %( align="#{cell.attr 'halign'}") : ''
417
- valign_attribute = (cell.attr? 'valign') ? %( valign="#{cell.attr 'valign'}") : ''
418
402
  colspan_attribute = cell.colspan ? %( namest="col_#{colnum = cell.column.attr 'colnumber'}" nameend="col_#{colnum + cell.colspan - 1}") : ''
419
403
  rowspan_attribute = cell.rowspan ? %( morerows="#{cell.rowspan - 1}") : ''
420
404
  # NOTE <entry> may not have whitespace (e.g., line breaks) as a direct descendant according to DocBook rules
421
- entry_start = %(<entry#{halign_attribute}#{valign_attribute}#{colspan_attribute}#{rowspan_attribute}>)
405
+ entry_start = %(<entry align="#{cell.attr 'halign'}" valign="#{cell.attr 'valign'}"#{colspan_attribute}#{rowspan_attribute}>)
422
406
  if tsec == :head
423
407
  cell_content = cell.text
424
408
  else
@@ -499,7 +483,7 @@ class Converter::DocBook5Converter < Converter::Base
499
483
  when :link
500
484
  %(<link xl:href="#{node.target}">#{node.text}</link>)
501
485
  when :bibref
502
- %(<anchor#{common_attributes node.id, nil, "[#{node.reftext || node.id}]"}/>#{text})
486
+ %(<anchor#{common_attributes node.id, nil, (text = "[#{node.reftext || node.id}]")}/>#{text})
503
487
  else
504
488
  logger.warn %(unknown anchor type: #{node.type.inspect})
505
489
  nil
@@ -529,7 +513,7 @@ class Converter::DocBook5Converter < Converter::Base
529
513
  def convert_inline_image node
530
514
  width_attribute = (node.attr? 'width') ? %( contentwidth="#{node.attr 'width'}") : ''
531
515
  depth_attribute = (node.attr? 'height') ? %( contentdepth="#{node.attr 'height'}") : ''
532
- %(<inlinemediaobject>
516
+ %(<inlinemediaobject#{common_attributes nil, node.role}>
533
517
  <imageobject>
534
518
  <imagedata fileref="#{node.type == 'icon' ? (node.icon_uri node.target) : (node.image_uri node.target)}"#{width_attribute}#{depth_attribute}/>
535
519
  </imageobject>
@@ -634,7 +618,7 @@ class Converter::DocBook5Converter < Converter::Base
634
618
  if (reftext.include? '<') && ((reftext = reftext.gsub XmlSanitizeRx, '').include? ' ')
635
619
  reftext = (reftext.squeeze ' ').strip
636
620
  end
637
- reftext = (reftext.gsub '"', '&quot;') if reftext.include? '"'
621
+ reftext = reftext.gsub '"', '&quot;' if reftext.include? '"'
638
622
  %(#{attrs} xreflabel="#{reftext}")
639
623
  else
640
624
  attrs
@@ -742,7 +726,7 @@ class Converter::DocBook5Converter < Converter::Base
742
726
  if (cover_image.include? ':') && ImageMacroRx =~ cover_image
743
727
  attrlist = $2
744
728
  cover_image = doc.image_uri $1
745
- unless attrlist.empty?
729
+ if attrlist
746
730
  attrs = (AttributeList.new attrlist).parse ['alt', 'width', 'height']
747
731
  if attrs.key? 'scaledwidth'
748
732
  # NOTE scalefit="1" is the default in this case
@@ -21,11 +21,17 @@ class Converter::Html5Converter < Converter::Base
21
21
  #latexmath: INLINE_MATH_DELIMITERS[:latexmath] + [false],
22
22
  }).default = ['', '']
23
23
 
24
- DropAnchorRx = /<(?:a[^>+]+|\/a)>/
24
+ DropAnchorRx = /<(?:a\b[^>]*|\/a)>/
25
25
  StemBreakRx = / *\\\n(?:\\?\n)*|\n\n+/
26
- SvgPreambleRx = /\A.*?(?=<svg\b)/m
27
- SvgStartTagRx = /\A<svg[^>]*>/
28
- DimensionAttributeRx = /\s(?:width|height|style)=(["']).*?\1/
26
+ if RUBY_ENGINE == 'opal'
27
+ # NOTE In JavaScript, ^ matches the start of the string when the m flag is not set
28
+ SvgPreambleRx = /^#{CC_ALL}*?(?=<svg[\s>])/
29
+ SvgStartTagRx = /^<svg(?:\s[^>]*)?>/
30
+ else
31
+ SvgPreambleRx = /\A.*?(?=<svg[\s>])/m
32
+ SvgStartTagRx = /\A<svg(?:\s[^>]*)?>/
33
+ end
34
+ DimensionAttributeRx = /\s(?:width|height|style)=(["'])#{CC_ANY}*?\1/
29
35
 
30
36
  def initialize backend, opts = {}
31
37
  @backend = backend
@@ -90,6 +96,7 @@ class Converter::Html5Converter < Converter::Base
90
96
  end
91
97
  cdn_base_url = %(#{asset_uri_scheme}//cdnjs.cloudflare.com/ajax/libs)
92
98
  linkcss = node.attr? 'linkcss'
99
+ max_width_attr = (node.attr? 'max-width') ? %( style="max-width: #{node.attr 'max-width'};") : ''
93
100
  result = ['<!DOCTYPE html>']
94
101
  lang_attribute = (node.attr? 'nolang') ? '' : %( lang="#{node.attr 'lang', 'en'}")
95
102
  result << %(<html#{@xml_mode ? ' xmlns="http://www.w3.org/1999/xhtml"' : ''}#{lang_attribute}>)
@@ -132,7 +139,7 @@ class Converter::Html5Converter < Converter::Base
132
139
  result << %(<link rel="stylesheet" href="#{node.normalize_web_path((node.attr 'stylesheet'), (node.attr 'stylesdir', ''))}"#{slash}>)
133
140
  else
134
141
  result << %(<style>
135
- #{node.read_asset node.normalize_system_path((node.attr 'stylesheet'), (node.attr 'stylesdir', '')), warn_on_failure: true, label: 'stylesheet'}
142
+ #{node.read_contents (node.attr 'stylesheet'), start: (node.attr 'stylesdir'), warn_on_failure: true, label: 'stylesheet'}
136
143
  </style>)
137
144
  end
138
145
  end
@@ -146,8 +153,8 @@ class Converter::Html5Converter < Converter::Base
146
153
  end
147
154
  end
148
155
 
149
- if (syntax_hl = node.syntax_highlighter) && (syntax_hl.docinfo? :head)
150
- result << (syntax_hl.docinfo :head, node, linkcss: linkcss)
156
+ if (syntax_hl = node.syntax_highlighter)
157
+ result << (syntax_hl_docinfo_head_idx = result.size)
151
158
  end
152
159
 
153
160
  unless (docinfo_content = node.docinfo).empty?
@@ -155,29 +162,27 @@ class Converter::Html5Converter < Converter::Base
155
162
  end
156
163
 
157
164
  result << '</head>'
158
- body_attrs = node.id ? [%(id="#{node.id}")] : []
165
+ id_attr = node.id ? %( id="#{node.id}") : ''
159
166
  if (sectioned = node.sections?) && (node.attr? 'toc-class') && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
160
167
  classes = [node.doctype, (node.attr 'toc-class'), %(toc-#{node.attr 'toc-position', 'header'})]
161
168
  else
162
169
  classes = [node.doctype]
163
170
  end
164
171
  classes << node.role if node.role?
165
- body_attrs << %(class="#{classes.join ' '}")
166
- body_attrs << %(style="max-width: #{node.attr 'max-width'};") if node.attr? 'max-width'
167
- result << %(<body #{body_attrs.join ' '}>)
172
+ result << %(<body#{id_attr} class="#{classes.join ' '}">)
168
173
 
169
174
  unless (docinfo_content = node.docinfo :header).empty?
170
175
  result << docinfo_content
171
176
  end
172
177
 
173
178
  unless node.noheader
174
- result << '<div id="header">'
179
+ result << %(<div id="header"#{max_width_attr}>)
175
180
  if node.doctype == 'manpage'
176
181
  result << %(<h1>#{node.doctitle} Manual Page</h1>)
177
182
  if sectioned && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
178
183
  result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
179
184
  <div id="toctitle">#{node.attr 'toc-title'}</div>
180
- #{convert_outline node}
185
+ #{node.converter.convert node, 'outline'}
181
186
  </div>)
182
187
  end
183
188
  result << (generate_manname_section node) if node.attr? 'manpurpose'
@@ -210,19 +215,19 @@ class Converter::Html5Converter < Converter::Base
210
215
  if sectioned && (node.attr? 'toc') && (node.attr? 'toc-placement', 'auto')
211
216
  result << %(<div id="toc" class="#{node.attr 'toc-class', 'toc'}">
212
217
  <div id="toctitle">#{node.attr 'toc-title'}</div>
213
- #{convert_outline node}
218
+ #{node.converter.convert node, 'outline'}
214
219
  </div>)
215
220
  end
216
221
  end
217
222
  result << '</div>'
218
223
  end
219
224
 
220
- result << %(<div id="content">
225
+ result << %(<div id="content"#{max_width_attr}>
221
226
  #{node.content}
222
227
  </div>)
223
228
 
224
229
  if node.footnotes? && !(node.attr? 'nofootnotes')
225
- result << %(<div id="footnotes">
230
+ result << %(<div id="footnotes"#{max_width_attr}>
226
231
  <hr#{slash}>)
227
232
  node.footnotes.each do |footnote|
228
233
  result << %(<div class="footnote" id="_footnotedef_#{footnote.index}">
@@ -233,7 +238,7 @@ class Converter::Html5Converter < Converter::Base
233
238
  end
234
239
 
235
240
  unless node.nofooter
236
- result << '<div id="footer">'
241
+ result << %(<div id="footer"#{max_width_attr}>)
237
242
  result << '<div id="footer-text">'
238
243
  result << %(#{node.attr 'version-label'} #{node.attr 'revnumber'}#{br}) if node.attr? 'revnumber'
239
244
  result << %(#{node.attr 'last-update-label'} #{node.attr 'docdatetime'}) if (node.attr? 'last-update-label') && !(node.attr? 'reproducible')
@@ -244,8 +249,15 @@ class Converter::Html5Converter < Converter::Base
244
249
  # JavaScript (and auxiliary stylesheets) loaded at the end of body for performance reasons
245
250
  # See http://www.html5rocks.com/en/tutorials/speed/script-loading/
246
251
 
247
- if syntax_hl && (syntax_hl.docinfo? :footer)
248
- result << (syntax_hl.docinfo :footer, node, cdn_base_url: cdn_base_url, linkcss: linkcss, self_closing_tag_slash: slash)
252
+ if syntax_hl
253
+ if syntax_hl.docinfo? :head
254
+ result[syntax_hl_docinfo_head_idx] = syntax_hl.docinfo :head, node, cdn_base_url: cdn_base_url, linkcss: linkcss, self_closing_tag_slash: slash
255
+ else
256
+ result.delete_at syntax_hl_docinfo_head_idx
257
+ end
258
+ if syntax_hl.docinfo? :footer
259
+ result << (syntax_hl.docinfo :footer, node, cdn_base_url: cdn_base_url, linkcss: linkcss, self_closing_tag_slash: slash)
260
+ end
249
261
  end
250
262
 
251
263
  if node.attr? 'stem'
@@ -269,7 +281,7 @@ MathJax.Hub.Config({
269
281
  })
270
282
  MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
271
283
  MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
272
- if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains('stemblock')) {
284
+ if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
273
285
  data.math.root.display = "block"
274
286
  }
275
287
  return data
@@ -305,7 +317,7 @@ MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
305
317
  if node.sections? && (node.attr? 'toc') && (toc_p = node.attr 'toc-placement') != 'macro' && toc_p != 'preamble'
306
318
  result << %(<div id="toc" class="toc">
307
319
  <div id="toctitle">#{node.attr 'toc-title'}</div>
308
- #{convert_outline node}
320
+ #{node.converter.convert node, 'outline'}
309
321
  </div>)
310
322
  end
311
323
 
@@ -622,9 +634,7 @@ Your browser does not support the audio tag.
622
634
  end
623
635
  end
624
636
  img ||= %(<img src="#{node.image_uri target}" alt="#{encode_attribute_value node.alt}"#{width_attr}#{height_attr}#{@void_element_slash}>)
625
- if node.attr? 'link'
626
- img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>)
627
- end
637
+ img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>) if node.attr? 'link'
628
638
  id_attr = node.id ? %( id="#{node.id}") : ''
629
639
  classes = ['imageblock']
630
640
  classes << (node.attr 'float') if node.attr? 'float'
@@ -683,8 +693,8 @@ Your browser does not support the audio tag.
683
693
  open, close = BLOCK_MATH_DELIMITERS[style = node.style.to_sym]
684
694
  if (equation = node.content)
685
695
  if style == :asciimath && (equation.include? LF)
686
- br = %(<br#{@void_element_slash}>#{LF})
687
- equation = equation.gsub(StemBreakRx) { %(#{close}#{br * ($&.count LF)}#{open}) }
696
+ br = %(#{LF}<br#{@void_element_slash}>)
697
+ equation = equation.gsub(StemBreakRx) { %(#{close}#{br * (($&.count LF) - 1)}#{LF}#{open}) }
688
698
  end
689
699
  unless (equation.start_with? open) && (equation.end_with? close)
690
700
  equation = %(#{open}#{equation}#{close})
@@ -790,7 +800,7 @@ Your browser does not support the audio tag.
790
800
  toc = %(
791
801
  <div id="toc" class="#{doc.attr 'toc-class', 'toc'}">
792
802
  <div id="toctitle">#{doc.attr 'toc-title'}</div>
793
- #{convert_outline doc}
803
+ #{doc.converter.convert doc, 'outline'}
794
804
  </div>)
795
805
  else
796
806
  toc = ''
@@ -842,7 +852,8 @@ Your browser does not support the audio tag.
842
852
  def convert_table node
843
853
  result = []
844
854
  id_attribute = node.id ? %( id="#{node.id}") : ''
845
- classes = ['tableblock', %(frame-#{node.attr 'frame', 'all', 'table-frame'}), %(grid-#{node.attr 'grid', 'all', 'table-grid'})]
855
+ frame = 'ends' if (frame = node.attr 'frame', 'all', 'table-frame') == 'topbot'
856
+ classes = ['tableblock', %(frame-#{frame}), %(grid-#{node.attr 'grid', 'all', 'table-grid'})]
846
857
  if (stripes = node.attr 'stripes', nil, 'table-stripes')
847
858
  classes << %(stripes-#{stripes})
848
859
  end
@@ -928,7 +939,7 @@ Your browser does not support the audio tag.
928
939
 
929
940
  %(<div#{id_attr} class="#{role}">
930
941
  <div#{title_id_attr} class="title">#{title}</div>
931
- #{convert_outline doc, toclevels: levels}
942
+ #{doc.converter.convert doc, 'outline', toclevels: levels}
932
943
  </div>)
933
944
  end
934
945
 
@@ -1083,7 +1094,7 @@ Your browser does not support the audio tag.
1083
1094
  time_anchor = (start_t || end_t) ? %(#t=#{start_t || ''}#{end_t ? ",#{end_t}" : ''}) : ''
1084
1095
  %(<div#{id_attribute}#{class_attribute}>#{title_element}
1085
1096
  <div class="content">
1086
- <video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : ''}#{(node.option? 'nocontrols') ? '' : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : ''}#{preload_attribute}>
1097
+ <video src="#{node.media_uri(node.attr 'target')}#{time_anchor}"#{width_attribute}#{height_attribute}#{poster_attribute}#{(node.option? 'autoplay') ? (append_boolean_attribute 'autoplay', xml) : ''}#{(node.option? 'muted') ? (append_boolean_attribute 'muted', xml) : ''}#{(node.option? 'nocontrols') ? '' : (append_boolean_attribute 'controls', xml)}#{(node.option? 'loop') ? (append_boolean_attribute 'loop', xml) : ''}#{preload_attribute}>
1087
1098
  Your browser does not support the video tag.
1088
1099
  </video>
1089
1100
  </div>
@@ -1101,8 +1112,13 @@ Your browser does not support the video tag.
1101
1112
  attrs = node.role ? %( class="#{node.role}") : ''
1102
1113
  unless (text = node.text)
1103
1114
  refid = node.attributes['refid']
1104
- if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid])
1105
- text = (ref.xreftext node.attr('xrefstyle', nil, true)) || %([#{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, ''
1120
+ end
1121
+ @resolving_xref = nil
1106
1122
  else
1107
1123
  text = %([#{refid}])
1108
1124
  end
@@ -1138,8 +1154,10 @@ Your browser does not support the video tag.
1138
1154
  elsif node.document.attr? 'icons'
1139
1155
  src = node.icon_uri("callouts/#{node.text}")
1140
1156
  %(<img src="#{src}" alt="#{node.text}"#{@void_element_slash}>)
1157
+ elsif ::Array === (guard = node.attributes['guard'])
1158
+ %(&lt;!--<b class="conum">(#{node.text})</b>--&gt;)
1141
1159
  else
1142
- %(#{node.attributes['guard']}<b class="conum">(#{node.text})</b>)
1160
+ %(#{guard}<b class="conum">(#{node.text})</b>)
1143
1161
  end
1144
1162
  end
1145
1163
 
@@ -1157,7 +1175,7 @@ Your browser does not support the video tag.
1157
1175
  end
1158
1176
 
1159
1177
  def convert_inline_image node
1160
- if (type = node.type) == 'icon' && (node.document.attr? 'icons', 'font')
1178
+ if (type = node.type || 'image') == 'icon' && (node.document.attr? 'icons', 'font')
1161
1179
  class_attr_val = %(fa fa-#{node.target})
1162
1180
  { 'size' => 'fa-', 'rotate' => 'fa-rotate-', 'flip' => 'fa-flip-' }.each do |key, prefix|
1163
1181
  class_attr_val = %(#{class_attr_val} #{prefix}#{node.attr key}) if node.attr? key
@@ -1180,9 +1198,7 @@ Your browser does not support the video tag.
1180
1198
  end
1181
1199
  img ||= %(<img src="#{type == 'icon' ? (node.icon_uri target) : (node.image_uri target)}" alt="#{encode_attribute_value node.alt}"#{attrs}#{@void_element_slash}>)
1182
1200
  end
1183
- if node.attr? 'link'
1184
- img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>)
1185
- end
1201
+ img = %(<a class="image" href="#{node.attr 'link'}"#{(append_link_constraint_attrs node).join}>#{img}</a>) if node.attr? 'link'
1186
1202
  if (role = node.role)
1187
1203
  if node.attr? 'float'
1188
1204
  class_attr_val = %(#{type} #{node.attr 'float'} #{role})
@@ -1246,16 +1262,19 @@ Your browser does not support the video tag.
1246
1262
 
1247
1263
  # NOTE expose read_svg_contents for Bespoke converter
1248
1264
  def read_svg_contents node, target
1249
- if (svg = node.read_contents target, start: (node.document.attr 'imagesdir'), normalize: true, label: 'SVG')
1265
+ if (svg = node.read_contents target, start: (node.document.attr 'imagesdir'), normalize: true, label: 'SVG', warn_if_empty: true)
1266
+ return if svg.empty?
1250
1267
  svg = svg.sub SvgPreambleRx, '' unless svg.start_with? '<svg'
1251
- old_start_tag = new_start_tag = nil
1268
+ old_start_tag = new_start_tag = start_tag_match = nil
1252
1269
  # NOTE width, height and style attributes are removed if either width or height is specified
1253
1270
  ['width', 'height'].each do |dim|
1254
- if node.attr? dim
1255
- new_start_tag = (old_start_tag = (svg.match SvgStartTagRx)[0]).gsub DimensionAttributeRx, '' unless new_start_tag
1256
- # QUESTION should we add px since it's already the default?
1257
- new_start_tag = %(#{new_start_tag.chop} #{dim}="#{node.attr dim}px">)
1271
+ next unless node.attr? dim
1272
+ unless new_start_tag
1273
+ next if (start_tag_match ||= (svg.match SvgStartTagRx) || :no_match) == :no_match
1274
+ new_start_tag = (old_start_tag = start_tag_match[0]).gsub DimensionAttributeRx, ''
1258
1275
  end
1276
+ # NOTE a unitless value in HTML is assumed to be px, so we can pass the value straight through
1277
+ new_start_tag = %(#{new_start_tag.chop} #{dim}="#{node.attr dim}">)
1259
1278
  end
1260
1279
  svg = %(#{new_start_tag}#{svg[old_start_tag.length..-1]}) if new_start_tag
1261
1280
  end
@@ -15,9 +15,9 @@ class Converter::ManPageConverter < Converter::Base
15
15
  ESC_BS = %(#{ESC}\\) # escaped backslash (indicates troff formatting sequence)
16
16
  ESC_FS = %(#{ESC}.) # escaped full stop (indicates troff macro)
17
17
 
18
- LiteralBackslashRx = /(?:\A|[^#{ESC}])\\/
18
+ LiteralBackslashRx = /\A\\|(#{ESC})?\\/
19
19
  LeadingPeriodRx = /^\./
20
- EscapedMacroRx = /^(?:#{ESC}\\c\n)?#{ESC}\.((?:URL|MTO) ".*?" ".*?" )( |[^\s]*)(.*?)(?: *#{ESC}\\c)?$/
20
+ EscapedMacroRx = /^(?:#{ESC}\\c\n)?#{ESC}\.((?:URL|MTO) "#{CC_ANY}*?" "#{CC_ANY}*?" )( |[^\s]*)(#{CC_ANY}*?)(?: *#{ESC}\\c)?$/
21
21
  MockBoundaryRx = /<\/?BOUNDARY>/
22
22
  EmDashCharRefRx = /&#8212;(?:&#8203;)?/
23
23
  EllipsisCharRefRx = /&#8230;(?:&#8203;)?/
@@ -247,7 +247,9 @@ r lw(\n(.lu*75u/100u).'
247
247
  result << %(.sp
248
248
  .if n .RS 4
249
249
  .nf
250
+ .fam C
250
251
  #{manify node.content, whitespace: :preserve}
252
+ .fam
251
253
  .fi
252
254
  .if n .RE)
253
255
  result.join LF
@@ -261,7 +263,9 @@ r lw(\n(.lu*75u/100u).'
261
263
  result << %(.sp
262
264
  .if n .RS 4
263
265
  .nf
266
+ .fam C
264
267
  #{manify node.content, whitespace: :preserve}
268
+ .fam
265
269
  .fi
266
270
  .if n .RE)
267
271
  result.join LF
@@ -284,15 +288,16 @@ r lw(\n(.lu*75u/100u).'
284
288
  .B #{manify node.title}
285
289
  .br) if node.title?
286
290
 
291
+ start = (node.attr 'start', 1).to_i
287
292
  node.items.each_with_index do |item, idx|
288
293
  result << %(.sp
289
294
  .RS 4
290
295
  .ie n \\{\\
291
- \\h'-04' #{idx + 1}.\\h'+01'\\c
296
+ \\h'-04' #{numeral = idx + start}.\\h'+01'\\c
292
297
  .\\}
293
298
  .el \\{\\
294
299
  . sp -1
295
- . IP " #{idx + 1}." 4.2
300
+ . IP " #{numeral}." 4.2
296
301
  .\\}
297
302
  #{manify item.text, whitespace: :normalize})
298
303
  result << item.content if item.blocks?
@@ -580,11 +585,7 @@ allbox tab(:);'
580
585
  when :xref
581
586
  unless (text = node.text)
582
587
  refid = node.attributes['refid']
583
- if AbstractNode === (ref = (@refs ||= node.document.catalog[:refs])[refid])
584
- text = (ref.xreftext node.attr('xrefstyle', nil, true)) || %([#{refid}])
585
- else
586
- text = %([#{refid}])
587
- end
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)
588
589
  end
589
590
  text
590
591
  when :ref, :bibref
@@ -699,7 +700,8 @@ allbox tab(:);'
699
700
  str = str.tr_s WHITESPACE, ' '
700
701
  end
701
702
  str = str.
702
- gsub(LiteralBackslashRx, '\&(rs'). # literal backslash (not a troff escape sequence)
703
+ gsub(LiteralBackslashRx) { $1 ? $& : '\\(rs' }. # literal backslash (not a troff escape sequence)
704
+ gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
703
705
  gsub(LeadingPeriodRx, '\\\&.'). # leading . is used in troff for macro call or other formatting; replace with \&.
704
706
  # drop orphaned \c escape lines, unescape troff macro, quote adjacent character, isolate macro line
705
707
  gsub(EscapedMacroRx) { (rest = $3.lstrip).empty? ? %(.#$1"#$2") : %(.#$1"#$2"#{LF}#{rest}) }.
@@ -717,13 +719,12 @@ allbox tab(:);'
717
719
  gsub('&#8217;', '\(cq'). # right single quotation mark
718
720
  gsub('&#8220;', '\(lq'). # left double quotation mark
719
721
  gsub('&#8221;', '\(rq'). # right double quotation mark
720
- gsub(EllipsisCharRefRx, '...'). # horizontal ellipsis
721
722
  gsub('&#8592;', '\(<-'). # leftwards arrow
722
723
  gsub('&#8594;', '\(->'). # rightwards arrow
723
724
  gsub('&#8656;', '\(lA'). # leftwards double arrow
724
725
  gsub('&#8658;', '\(rA'). # rightwards double arrow
725
726
  gsub('&#8203;', '\:'). # zero width space
726
- gsub('&amp;','&'). # literal ampersand (NOTE must take place after any other replacement that includes &)
727
+ gsub('&amp;', '&'). # literal ampersand (NOTE must take place after any other replacement that includes &)
727
728
  gsub('\'', '\(aq'). # apostrophe-quote
728
729
  gsub(MockBoundaryRx, ''). # mock boundary
729
730
  gsub(ESC_BS, '\\'). # unescape troff backslash (NOTE update if more escapes are added)