asciidoctor 2.0.12 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +142 -22
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +15 -6
  5. data/README-fr.adoc +14 -8
  6. data/README-jp.adoc +15 -6
  7. data/README-zh_CN.adoc +14 -5
  8. data/README.adoc +135 -125
  9. data/asciidoctor.gemspec +4 -11
  10. data/data/locale/attributes-be.adoc +23 -0
  11. data/data/locale/attributes-it.adoc +4 -4
  12. data/data/locale/attributes-nl.adoc +6 -6
  13. data/data/locale/attributes-th.adoc +23 -0
  14. data/data/locale/attributes-vi.adoc +23 -0
  15. data/data/reference/syntax.adoc +14 -7
  16. data/data/stylesheets/asciidoctor-default.css +51 -52
  17. data/lib/asciidoctor.rb +12 -12
  18. data/lib/asciidoctor/abstract_block.rb +4 -4
  19. data/lib/asciidoctor/abstract_node.rb +10 -9
  20. data/lib/asciidoctor/attribute_list.rb +6 -6
  21. data/lib/asciidoctor/block.rb +6 -6
  22. data/lib/asciidoctor/cli/invoker.rb +0 -1
  23. data/lib/asciidoctor/cli/options.rb +27 -26
  24. data/lib/asciidoctor/convert.rb +1 -0
  25. data/lib/asciidoctor/converter.rb +5 -3
  26. data/lib/asciidoctor/converter/docbook5.rb +45 -26
  27. data/lib/asciidoctor/converter/html5.rb +89 -69
  28. data/lib/asciidoctor/converter/manpage.rb +113 -86
  29. data/lib/asciidoctor/converter/template.rb +11 -12
  30. data/lib/asciidoctor/document.rb +44 -51
  31. data/lib/asciidoctor/extensions.rb +10 -12
  32. data/lib/asciidoctor/helpers.rb +3 -6
  33. data/lib/asciidoctor/list.rb +2 -6
  34. data/lib/asciidoctor/load.rb +13 -11
  35. data/lib/asciidoctor/logging.rb +10 -8
  36. data/lib/asciidoctor/parser.rb +135 -150
  37. data/lib/asciidoctor/path_resolver.rb +3 -3
  38. data/lib/asciidoctor/reader.rb +72 -71
  39. data/lib/asciidoctor/rx.rb +4 -3
  40. data/lib/asciidoctor/substitutors.rb +117 -117
  41. data/lib/asciidoctor/syntax_highlighter.rb +8 -11
  42. data/lib/asciidoctor/syntax_highlighter/coderay.rb +2 -1
  43. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +1 -1
  44. data/lib/asciidoctor/syntax_highlighter/pygments.rb +6 -5
  45. data/lib/asciidoctor/syntax_highlighter/rouge.rb +33 -26
  46. data/lib/asciidoctor/table.rb +17 -19
  47. data/lib/asciidoctor/timings.rb +3 -3
  48. data/lib/asciidoctor/version.rb +1 -1
  49. data/man/asciidoctor.1 +10 -11
  50. data/man/asciidoctor.adoc +8 -7
  51. metadata +14 -67
data/lib/asciidoctor.rb CHANGED
@@ -53,12 +53,12 @@ module Asciidoctor
53
53
  module SafeMode
54
54
  # A safe mode level that disables any of the security features enforced
55
55
  # by Asciidoctor (Ruby is still subject to its own restrictions).
56
- UNSAFE = 0;
56
+ UNSAFE = 0
57
57
 
58
58
  # A safe mode level that closely parallels safe mode in AsciiDoc. This value
59
59
  # prevents access to files which reside outside of the parent directory of
60
60
  # the source file and disables any macro other than the include::[] directive.
61
- SAFE = 1;
61
+ SAFE = 1
62
62
 
63
63
  # A safe mode level that disallows the document from setting attributes
64
64
  # that would affect the conversion of the document, in addition to all the
@@ -66,19 +66,19 @@ module Asciidoctor
66
66
  # changing the backend or source-highlighter using an attribute defined
67
67
  # in the source document header. This is the most fundamental level of
68
68
  # security for server deployments (hence the name).
69
- SERVER = 10;
69
+ SERVER = 10
70
70
 
71
71
  # A safe mode level that disallows the document from attempting to read
72
72
  # files from the file system and including the contents of them into the
73
73
  # document, in additional to all the security features of SafeMode::SERVER.
74
74
  # For instance, this level disallows use of the include::[] directive and the
75
75
  # embedding of binary content (data uri), stylesheets and JavaScripts
76
- # referenced by the document.(Asciidoctor and trusted extensions may still
76
+ # referenced by the document. (Asciidoctor and trusted extensions may still
77
77
  # be allowed to embed trusted content into the document).
78
78
  #
79
79
  # Since Asciidoctor is aiming for wide adoption, this level is the default
80
80
  # and is recommended for server deployments.
81
- SECURE = 20;
81
+ SECURE = 20
82
82
 
83
83
  # A planned safe mode level that disallows the use of passthrough macros and
84
84
  # prevents the document from setting any known attributes, in addition to all
@@ -86,9 +86,9 @@ module Asciidoctor
86
86
  #
87
87
  # Please note that this level is not currently implemented (and therefore not
88
88
  # enforced)!
89
- #PARANOID = 100;
89
+ #PARANOID = 100
90
90
 
91
- @names_by_value = {}.tap {|accum| (constants false).each {|sym| accum[const_get sym, false] = sym.to_s.downcase } }
91
+ @names_by_value = (constants false).map {|sym| [(const_get sym), sym.to_s.downcase] }.sort {|(a), (b)| a <=> b }.to_h
92
92
 
93
93
  def self.value_for_name name
94
94
  const_get name.upcase, false
@@ -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
 
@@ -332,7 +332,7 @@ module Asciidoctor
332
332
 
333
333
  LIST_CONTINUATION = '+'
334
334
 
335
- # NOTE AsciiDoc Python allows + to be preceded by TAB; Asciidoctor does not
335
+ # NOTE AsciiDoc.py allows + to be preceded by TAB; Asciidoctor does not
336
336
  HARD_LINE_BREAK = ' +'
337
337
 
338
338
  LINE_CONTINUATION = ' \\'
@@ -459,9 +459,9 @@ module Asciidoctor
459
459
  [:emphasis, :unconstrained, /\\?(?:\[([^\]]+)\])?__(#{CC_ALL}+?)__/m],
460
460
  # _emphasis_
461
461
  [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?_(\S|\S#{CC_ALL}*?\S)_(?!#{CG_WORD})/m],
462
- # ##mark## (referred to in AsciiDoc Python as unquoted)
462
+ # ##mark## (referred to in AsciiDoc.py as unquoted)
463
463
  [:mark, :unconstrained, /\\?(?:\[([^\]]+)\])?##(#{CC_ALL}+?)##/m],
464
- # #mark# (referred to in AsciiDoc Python as unquoted)
464
+ # #mark# (referred to in AsciiDoc.py as unquoted)
465
465
  [:mark, :constrained, /(^|[^#{CC_WORD}&;:}])(?:\[([^\]]+)\])?#(\S|\S#{CC_ALL}*?\S)#(?!#{CG_WORD})/m],
466
466
  # ^superscript^
467
467
  [:superscript, :unconstrained, /\\?(?:\[([^\]]+)\])?\^(\S+?)\^/],
@@ -90,7 +90,7 @@ class AbstractBlock < AbstractNode
90
90
  #
91
91
  # context - the context Symbol context to assign to this block
92
92
  #
93
- # Returns the new context Symbol assigned to this block
93
+ # Returns the specified Symbol context
94
94
  def context= context
95
95
  @node_name = (@context = context).to_s
96
96
  end
@@ -296,7 +296,7 @@ class AbstractBlock < AbstractNode
296
296
 
297
297
  # Public: Set the String block title.
298
298
  #
299
- # Returns the new String title assigned to this Block
299
+ # Returns the specified String title
300
300
  def title= val
301
301
  @converted_title = nil
302
302
  @title = val
@@ -480,7 +480,7 @@ class AbstractBlock < AbstractNode
480
480
  @header.find_by_internal selector, result, &block
481
481
  end
482
482
  @blocks.each do |b|
483
- next if (context_selector == :section && b.context != :section) # optimization
483
+ next if context_selector == :section && b.context != :section # optimization
484
484
  b.find_by_internal selector, result, &block
485
485
  end
486
486
  end
@@ -505,7 +505,7 @@ class AbstractBlock < AbstractNode
505
505
  end
506
506
  else
507
507
  @blocks.each do |b|
508
- next if (context_selector == :section && b.context != :section) # optimization
508
+ next if context_selector == :section && b.context != :section # optimization
509
509
  b.find_by_internal selector, result, &block
510
510
  end
511
511
  end
@@ -4,7 +4,8 @@ module Asciidoctor
4
4
  # node of AsciiDoc content. The state and methods on this class are common to
5
5
  # all content segments in an AsciiDoc document.
6
6
  class AbstractNode
7
- include Substitutors, Logging
7
+ include Logging
8
+ include Substitutors
8
9
 
9
10
  # Public: Get the Hash of attributes for this node
10
11
  attr_reader :attributes
@@ -65,7 +66,7 @@ class AbstractNode
65
66
  #
66
67
  # parent - The Block to set as the parent of this Block
67
68
  #
68
- # Returns the value of the parent argument
69
+ # Returns the the specified Block parent
69
70
  def parent= parent
70
71
  @parent, @document = parent, parent.document
71
72
  end
@@ -155,7 +156,7 @@ class AbstractNode
155
156
  #
156
157
  # name - the String name of the option
157
158
  #
158
- # Returns Nothing
159
+ # Returns nothing
159
160
  def set_option name
160
161
  @attributes[%(#{name}-option)] = ''
161
162
  nil
@@ -216,11 +217,11 @@ class AbstractNode
216
217
  (val = @attributes['role']) ? (%( #{val} ).include? %( #{name} )) : false
217
218
  end
218
219
 
219
- # Public: Sets the value of the role attribute on this ndoe.
220
+ # Public: Sets the value of the role attribute on this node.
220
221
  #
221
222
  # names - A single role name, a space-separated String of role names, or an Array of role names
222
223
  #
223
- # Returns the value of the names argument
224
+ # Returns the specified String role name or Array of role names
224
225
  def role= names
225
226
  @attributes['role'] = (::Array === names) ? (names.join ' ') : names
226
227
  end
@@ -462,8 +463,8 @@ class AbstractNode
462
463
  start = doc.base_dir
463
464
  end
464
465
  else
465
- start = doc.base_dir unless start
466
- jail = doc.base_dir unless jail
466
+ start ||= doc.base_dir
467
+ jail ||= doc.base_dir
467
468
  end
468
469
  doc.path_resolver.system_path target, start, jail, opts
469
470
  end
@@ -542,8 +543,8 @@ class AbstractNode
542
543
  rescue
543
544
  logger.warn %(could not retrieve contents of #{opts[:label] || 'asset'} at URI: #{target}) if opts.fetch :warn_on_failure, true
544
545
  end
545
- else
546
- logger.warn %(cannot retrieve contents of #{opts[:label] || 'asset'} at URI: #{target} (allow-uri-read attribute not enabled)) if opts.fetch :warn_on_failure, true
546
+ elsif opts.fetch :warn_on_failure, true
547
+ logger.warn %(cannot retrieve contents of #{opts[:label] || 'asset'} at URI: #{target} (allow-uri-read attribute not enabled))
547
548
  end
548
549
  else
549
550
  target = normalize_system_path target, opts[:start], nil, target_name: (opts[:label] || 'asset')
@@ -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
 
@@ -173,7 +173,7 @@ class AttributeList
173
173
  end
174
174
  else
175
175
  name = @block.apply_subs name if single_quoted && @block
176
- if (positional_attr_name = positional_attrs[index])
176
+ if (positional_attr_name = positional_attrs[index]) && name
177
177
  @attributes[positional_attr_name] = name
178
178
  end
179
179
  # QUESTION should we assign the positional key even when it's claimed by a positional attribute?
@@ -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
@@ -54,21 +54,21 @@ class Block < AbstractBlock
54
54
  # FIXME feels funky; we have to be defensive to get commit_subs to honor override
55
55
  # FIXME does not resolve substitution groups inside Array (e.g., [:normal])
56
56
  if (subs = opts[:subs])
57
- # e.g., subs: :defult
57
+ case subs
58
+ # e.g., subs: :default
58
59
  # subs attribute is honored; falls back to opts[:default_subs], then built-in defaults based on context
59
- if subs == :default
60
+ when :default
60
61
  @default_subs = opts[:default_subs]
61
62
  # e.g., subs: [:quotes]
62
63
  # subs attribute is not honored
63
- elsif ::Array === subs
64
+ when ::Array
64
65
  @default_subs = subs.drop 0
65
66
  @attributes.delete 'subs'
66
67
  # e.g., subs: :normal or subs: 'normal'
67
68
  # subs attribute is not honored
68
69
  else
69
70
  @default_subs = nil
70
- # interpolation is the fastest way to dup subs as a string
71
- @attributes['subs'] = %(#{subs})
71
+ @attributes['subs'] = subs.to_s
72
72
  end
73
73
  # resolve the subs eagerly only if subs option is specified
74
74
  # QUESTION should we skip subsequent calls to commit_subs?
@@ -123,7 +123,7 @@ class Block < AbstractBlock
123
123
  result.join LF
124
124
  end
125
125
  else
126
- logger.warn %(Unknown content model '#{@content_model}' for block: #{to_s}) unless @content_model == :empty
126
+ logger.warn %(Unknown content model '#{@content_model}' for block: #{self}) unless @content_model == :empty
127
127
  nil
128
128
  end
129
129
  end
@@ -88,7 +88,6 @@ module Asciidoctor
88
88
  end
89
89
 
90
90
  if outfile == '-'
91
- # NOTE set_encoding returns nil on JRuby 9.1
92
91
  (tofile = @out) || ((tofile = $stdout).set_encoding UTF_8)
93
92
  elsif outfile
94
93
  opts[:mkdirs] = true
@@ -39,19 +39,20 @@ 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'],
53
- 'document type to use when converting document: [article, book, manpage, inline] (default: article)') do |doc_type|
54
- self[:attributes]['doctype'] = doc_type
54
+ 'document type to use when converting document: [article, book, manpage, inline] (default: article)') do |doctype|
55
+ self[:attributes]['doctype'] = doctype
55
56
  end
56
57
  opts.on('-e', '--embedded', 'suppress enclosing document structure and output an embedded document (default: false)') do
57
58
  self[:standalone] = false
@@ -60,14 +61,14 @@ module Asciidoctor
60
61
  self[:output_file] = output_file
61
62
  end
62
63
  opts.on('--safe',
63
- 'set safe mode level to safe (default: unsafe)',
64
- 'enables include directives, but prevents access to ancestor paths of source file',
65
- 'provided for compatibility with the asciidoc command') do
64
+ 'set safe mode level to safe (default: unsafe)',
65
+ 'enables include directives, but prevents access to ancestor paths of source file',
66
+ 'provided for compatibility with the asciidoc command') do
66
67
  self[:safe] = SafeMode::SAFE
67
68
  end
68
69
  opts.on('-S', '--safe-mode SAFE_MODE', (safe_mode_names = SafeMode.names),
69
- %(set safe mode level explicitly: [#{safe_mode_names.join ', '}] (default: unsafe)),
70
- 'disables potentially dangerous macros in source files, such as include::[]') do |name|
70
+ %(set safe mode level explicitly: [#{safe_mode_names.join ', '}] (default: unsafe)),
71
+ 'disables potentially dangerous macros in source files, such as include::[]') do |name|
71
72
  self[:safe] = SafeMode.value_for_name name
72
73
  end
73
74
  opts.on('-s', '--no-header-footer', 'suppress enclosing document structure and output an embedded document (default: false)') do
@@ -77,19 +78,19 @@ module Asciidoctor
77
78
  self[:attributes]['sectnums'] = ''
78
79
  end
79
80
  opts.on('--eruby ERUBY', ['erb', 'erubi', 'erubis'],
80
- 'specify eRuby implementation to use when rendering custom ERB templates: [erb, erubi, erubis] (default: erb)') do |eruby|
81
+ 'specify eRuby implementation to use when rendering custom ERB templates: [erb, erubi, erubis] (default: erb)') do |eruby|
81
82
  self[:eruby] = eruby
82
83
  end
83
84
  opts.on('-a', '--attribute name[=value]', 'a document attribute to set in the form of name, name!, or name=value pair',
84
- 'this attribute takes precedence over the same attribute defined in the source document',
85
- 'unless either the name or value ends in @ (i.e., name@=value or name=value@)') do |attr|
85
+ 'this attribute takes precedence over the same attribute defined in the source document',
86
+ 'unless either the name or value ends in @ (i.e., name@=value or name=value@)') do |attr|
86
87
  next if (attr = attr.rstrip).empty? || attr == '='
87
88
  attr = attr.encode UTF_8 unless attr.encoding == UTF_8
88
89
  name, _, val = attr.partition '='
89
90
  self[:attributes][name] = val
90
91
  end
91
92
  opts.on('-T', '--template-dir DIR', 'a directory containing custom converter templates that override the built-in converter (requires tilt gem)',
92
- 'may be specified multiple times') do |template_dir|
93
+ 'may be specified multiple times') do |template_dir|
93
94
  if self[:template_dirs].nil?
94
95
  self[:template_dirs] = [template_dir]
95
96
  elsif ::Array === self[:template_dirs]
@@ -120,21 +121,21 @@ module Asciidoctor
120
121
  end
121
122
  opts.on('--failure-level LEVEL', %w(warning WARNING error ERROR info INFO), 'set minimum logging level that triggers non-zero exit code: [WARN, ERROR, INFO] (default: FATAL)') do |level|
122
123
  level = 'WARN' if (level = level.upcase) == 'WARNING'
123
- self[:failure_level] = ::Logger::Severity.const_get level, false
124
+ self[:failure_level] = ::Logger::Severity.const_get level
124
125
  end
125
- opts.on('-q', '--quiet', 'silence application log messages and script warnings (default: false)') do |verbose|
126
+ opts.on('-q', '--quiet', 'silence application log messages and script warnings (default: false)') do
126
127
  self[:verbose] = 0
127
128
  end
128
- opts.on('--trace', 'include backtrace information when reporting errors (default: false)') do |trace|
129
+ opts.on('--trace', 'include backtrace information when reporting errors (default: false)') do
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
132
133
  self[:verbose] = 2
133
134
  end
134
- opts.on('-w', '--warnings', 'turn on script warnings (default: false)') do |warnings|
135
+ opts.on('-w', '--warnings', 'turn on script warnings (default: false)') do
135
136
  self[:warnings] = true
136
137
  end
137
- opts.on('-t', '--timings', 'print timings report (default: false)') do |timing|
138
+ opts.on('-t', '--timings', 'print timings report (default: false)') do
138
139
  self[:timings] = true
139
140
  end
140
141
  opts.on_tail('-h', '--help [TOPIC]', 'print a help message',
@@ -160,7 +161,7 @@ module Asciidoctor
160
161
  elsif ::File.exist? (manpage_path = (::File.join ROOT_DIR, 'man', 'asciidoctor.1'))
161
162
  $stdout.puts ::File.read manpage_path
162
163
  else
163
- manpage_path = `man -w asciidoctor`.chop rescue ''
164
+ manpage_path = %x(man -w asciidoctor).chop rescue ''
164
165
  if manpage_path.empty?
165
166
  $stderr.puts 'asciidoctor: FAILED: manual page not found; try `man asciidoctor`'
166
167
  return 1
@@ -248,7 +249,7 @@ module Asciidoctor
248
249
 
249
250
  self[:input_files] = infiles
250
251
 
251
- self.delete :attributes if self[:attributes].empty?
252
+ delete :attributes if self[:attributes].empty?
252
253
 
253
254
  if self[:template_dirs]
254
255
  begin
@@ -289,11 +290,11 @@ module Asciidoctor
289
290
  rescue ::OptionParser::MissingArgument
290
291
  $stderr.puts %(asciidoctor: option #{$!.message})
291
292
  $stdout.puts opts_parser
292
- return 1
293
+ 1
293
294
  rescue ::OptionParser::InvalidOption, ::OptionParser::InvalidArgument
294
295
  $stderr.puts %(asciidoctor: #{$!.message})
295
296
  $stdout.puts opts_parser
296
- return 1
297
+ 1
297
298
  ensure
298
299
  $VERBOSE = old_verbose
299
300
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Asciidoctor
2
3
  class << self
3
4
  # Public: Parse the AsciiDoc source input into an Asciidoctor::Document and
@@ -366,15 +366,17 @@ module Converter
366
366
  # into - The Class into which the {Converter} module is being included.
367
367
  #
368
368
  # Returns nothing.
369
- private_class_method def self.included into
369
+ def self.included into
370
370
  into.send :include, BackendTraits
371
371
  into.extend Config
372
- end || :included
372
+ end
373
+ private_class_method :included # use separate declaration for Ruby 2.0.x
373
374
 
374
375
  # An abstract base class for defining converters that can be used to convert {AbstractNode} objects in a parsed
375
376
  # AsciiDoc document to a backend format such as HTML or DocBook.
376
377
  class Base
377
- include Converter, Logging
378
+ include Logging
379
+ include Converter
378
380
 
379
381
  # Public: Converts an {AbstractNode} by delegating to a method that matches the transform value.
380
382
  #
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
  module Asciidoctor
3
3
  # A built-in {Converter} implementation that generates DocBook 5 output. The output is inspired by the output produced
4
- # by the docbook45 backend from AsciiDoc Python, except it has been migrated to the DocBook 5 specification.
4
+ # by the docbook45 backend from AsciiDoc.py, except it has been migrated to the DocBook 5 specification.
5
5
  class Converter::DocBook5Converter < Converter::Base
6
6
  register_for 'docbook5'
7
7
 
8
8
  # default represents variablelist
9
9
  (DLIST_TAGS = {
10
- 'qanda' => { list: 'qandaset', entry: 'qandaentry', label: 'question', term: 'simpara', item: 'answer' },
11
- 'glossary' => { list: nil, entry: 'glossentry', term: 'glossterm', item: 'glossdef' },
12
- }).default = { list: 'variablelist', entry: 'varlistentry', term: 'term', item: 'listitem' }
10
+ 'qanda' => { list: 'qandaset', entry: 'qandaentry', label: 'question', term: 'simpara', item: 'answer' },
11
+ 'glossary' => { list: nil, entry: 'glossentry', term: 'glossterm', item: 'glossdef' },
12
+ }).default = { list: 'variablelist', entry: 'varlistentry', term: 'term', item: 'listitem' }
13
13
 
14
14
  (QUOTE_TAGS = {
15
15
  monospaced: ['<literal>', '</literal>'],
@@ -25,7 +25,7 @@ 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 = /^(#{CC_ANY}+?)(?: ((?:\d{4}\-)?\d{4}))?$/
28
+ CopyrightRx = /^(#{CC_ANY}+?)(?: ((?:\d{4}-)?\d{4}))?$/
29
29
  ImageMacroRx = /^image::?(\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
30
30
 
31
31
  def initialize backend, opts = {}
@@ -41,7 +41,8 @@ class Converter::DocBook5Converter < Converter::Base
41
41
  if (root_tag_name = node.doctype) == 'manpage'
42
42
  root_tag_name = 'refentry'
43
43
  end
44
- result << %(<#{root_tag_name} xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"#{lang_attribute}#{common_attributes node.id}>)
44
+ root_tag_idx = result.size
45
+ id = node.id
45
46
  result << (document_info_tag node) unless node.noheader
46
47
  unless (docinfo_content = node.docinfo :header).empty?
47
48
  result << docinfo_content
@@ -50,6 +51,9 @@ class Converter::DocBook5Converter < Converter::Base
50
51
  unless (docinfo_content = node.docinfo :footer).empty?
51
52
  result << docinfo_content
52
53
  end
54
+ id, node.id = node.id, nil unless id
55
+ # defer adding root tag in case document ID is auto-generated on demand
56
+ result.insert root_tag_idx, %(<#{root_tag_name} xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"#{lang_attribute}#{common_attributes id}>)
53
57
  result << %(</#{root_tag_name}>)
54
58
  result.join LF
55
59
  end
@@ -298,13 +302,13 @@ class Converter::DocBook5Converter < Converter::Base
298
302
  </abstract>)
299
303
  end
300
304
  when 'partintro'
301
- unless node.level == 0 && node.parent.context == :section && node.document.doctype == 'book'
302
- logger.error 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
303
- ''
304
- else
305
+ if node.level == 0 && node.parent.context == :section && node.document.doctype == 'book'
305
306
  %(<partintro#{common_attributes node.id, node.role, node.reftext}>
306
307
  #{title_tag node}#{enclose_content node}
307
308
  </partintro>)
309
+ else
310
+ logger.error 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
311
+ ''
308
312
  end
309
313
  else
310
314
  reftext = node.reftext if (id = node.id)
@@ -374,19 +378,19 @@ class Converter::DocBook5Converter < Converter::Base
374
378
  frame = 'topbot' if (frame = node.attr 'frame', 'all', 'table-frame') == 'ends'
375
379
  grid = node.attr 'grid', nil, 'table-grid'
376
380
  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"' : ''}>)
377
- if (node.option? 'unbreakable')
381
+ if node.option? 'unbreakable'
378
382
  result << '<?dbfo keep-together="always"?>'
379
- elsif (node.option? 'breakable')
383
+ elsif node.option? 'breakable'
380
384
  result << '<?dbfo keep-together="auto"?>'
381
385
  end
382
386
  result << %(<title>#{node.title}</title>) if tag_name == 'table'
383
- col_width_key = if (width = (node.attr? 'width') ? (node.attr 'width') : nil)
387
+ if (width = (node.attr? 'width') ? (node.attr 'width') : nil)
384
388
  TABLE_PI_NAMES.each do |pi_name|
385
389
  result << %(<?#{pi_name} table-width="#{width}"?>)
386
390
  end
387
- 'colabswidth'
391
+ col_width_key = 'colabswidth'
388
392
  else
389
- 'colpcwidth'
393
+ col_width_key = 'colpcwidth'
390
394
  end
391
395
  result << %(<tgroup cols="#{node.attr 'colcount'}">)
392
396
  node.columns.each do |col|
@@ -474,10 +478,16 @@ class Converter::DocBook5Converter < Converter::Base
474
478
  %(<anchor#{common_attributes((id = node.id), nil, node.reftext || %([#{id}]))}/>)
475
479
  when :xref
476
480
  if (path = node.attributes['path'])
477
- # QUESTION should we use refid as fallback text instead? (like the html5 backend?)
478
481
  %(<link xl:href="#{node.target}">#{node.text || path}</link>)
479
482
  else
480
- linkend = node.attributes['fragment'] || node.target
483
+ if (linkend = node.attributes['refid']).nil_or_empty?
484
+ root_doc = get_root_document node
485
+ # Q: should we warn instead of generating a document ID on demand?
486
+ linkend = (root_doc.id ||= generate_document_id root_doc)
487
+ end
488
+ # NOTE the xref tag in DocBook does not support explicit link text, so the link tag must be used instead
489
+ # The section at http://www.sagehill.net/docbookxsl/CrossRefs.html#IdrefLinks gives an explanation for this choice
490
+ # "link - a cross reference where you supply the text of the reference as the content of the link element."
481
491
  (text = node.text) ? %(<link linkend="#{linkend}">#{text}</link>) : %(<xref linkend="#{linkend}"/>)
482
492
  end
483
493
  when :link
@@ -533,9 +543,8 @@ class Converter::DocBook5Converter < Converter::Base
533
543
  %(<indexterm>
534
544
  <primary>#{node.text}</primary>#{rel}
535
545
  </indexterm>#{node.text})
536
- else
537
- if (numterms = (terms = node.attr 'terms').size) > 2
538
- %(<indexterm>
546
+ elsif (numterms = (terms = node.attr 'terms').size) > 2
547
+ %(<indexterm>
539
548
  <primary>#{terms[0]}</primary><secondary>#{terms[1]}</secondary><tertiary>#{terms[2]}</tertiary>#{rel}
540
549
  </indexterm>#{(node.document.option? 'indexterm-promotion') ? %[
541
550
  <indexterm>
@@ -544,18 +553,17 @@ class Converter::DocBook5Converter < Converter::Base
544
553
  <indexterm>
545
554
  <primary>#{terms[2]}</primary>
546
555
  </indexterm>] : ''})
547
- elsif numterms > 1
548
- %(<indexterm>
556
+ elsif numterms > 1
557
+ %(<indexterm>
549
558
  <primary>#{terms[0]}</primary><secondary>#{terms[1]}</secondary>#{rel}
550
- </indexterm>#{(node.document.option? 'indexterm-promotion') ? %[
559
+ </indexterm>#{(node.document.option? 'indexterm-promotion') ? %[
551
560
  <indexterm>
552
561
  <primary>#{terms[1]}</primary>
553
562
  </indexterm>] : ''})
554
- else
555
- %(<indexterm>
563
+ else
564
+ %(<indexterm>
556
565
  <primary>#{terms[0]}</primary>#{rel}
557
566
  </indexterm>)
558
- end
559
567
  end
560
568
  end
561
569
 
@@ -710,6 +718,17 @@ class Converter::DocBook5Converter < Converter::Base
710
718
  result.join LF
711
719
  end
712
720
 
721
+ def get_root_document node
722
+ while (node = node.document).nested?
723
+ node = node.parent_document
724
+ end
725
+ node
726
+ end
727
+
728
+ def generate_document_id doc
729
+ %(__#{doc.doctype}-root__)
730
+ end
731
+
713
732
  # FIXME this should be handled through a template mechanism
714
733
  def enclose_content node
715
734
  node.content_model == :compound ? node.content : %(<simpara>#{node.content}</simpara>)