asciidoctor 2.0.10 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +294 -30
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +16 -20
  5. data/README-fr.adoc +15 -22
  6. data/README-jp.adoc +15 -26
  7. data/README-zh_CN.adoc +21 -25
  8. data/README.adoc +161 -138
  9. data/asciidoctor.gemspec +6 -13
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-be.adoc +23 -0
  12. data/data/locale/attributes-bg.adoc +4 -3
  13. data/data/locale/attributes-ca.adoc +6 -5
  14. data/data/locale/attributes-cs.adoc +4 -3
  15. data/data/locale/attributes-da.adoc +6 -5
  16. data/data/locale/attributes-de.adoc +4 -4
  17. data/data/locale/attributes-en.adoc +4 -4
  18. data/data/locale/attributes-es.adoc +6 -5
  19. data/data/locale/attributes-fa.adoc +4 -3
  20. data/data/locale/attributes-fi.adoc +4 -3
  21. data/data/locale/attributes-fr.adoc +8 -7
  22. data/data/locale/attributes-hu.adoc +4 -3
  23. data/data/locale/attributes-id.adoc +4 -3
  24. data/data/locale/attributes-it.adoc +6 -5
  25. data/data/locale/attributes-ja.adoc +4 -3
  26. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  27. data/data/locale/attributes-nb.adoc +4 -3
  28. data/data/locale/attributes-nl.adoc +6 -5
  29. data/data/locale/attributes-nn.adoc +4 -3
  30. data/data/locale/attributes-pl.adoc +8 -7
  31. data/data/locale/attributes-pt.adoc +6 -5
  32. data/data/locale/attributes-pt_BR.adoc +6 -5
  33. data/data/locale/attributes-ro.adoc +4 -3
  34. data/data/locale/attributes-ru.adoc +6 -5
  35. data/data/locale/attributes-sr.adoc +4 -4
  36. data/data/locale/attributes-sr_Latn.adoc +4 -4
  37. data/data/locale/attributes-sv.adoc +4 -4
  38. data/data/locale/attributes-th.adoc +23 -0
  39. data/data/locale/attributes-tr.adoc +4 -3
  40. data/data/locale/attributes-uk.adoc +6 -5
  41. data/data/locale/attributes-vi.adoc +23 -0
  42. data/data/locale/attributes-zh_CN.adoc +4 -3
  43. data/data/locale/attributes-zh_TW.adoc +4 -3
  44. data/data/reference/syntax.adoc +14 -7
  45. data/data/stylesheets/asciidoctor-default.css +76 -76
  46. data/data/stylesheets/coderay-asciidoctor.css +9 -9
  47. data/lib/asciidoctor/abstract_block.rb +20 -13
  48. data/lib/asciidoctor/abstract_node.rb +23 -12
  49. data/lib/asciidoctor/attribute_list.rb +64 -72
  50. data/lib/asciidoctor/block.rb +6 -6
  51. data/lib/asciidoctor/cli/invoker.rb +3 -2
  52. data/lib/asciidoctor/cli/options.rb +32 -31
  53. data/lib/asciidoctor/convert.rb +168 -162
  54. data/lib/asciidoctor/converter/docbook5.rb +49 -34
  55. data/lib/asciidoctor/converter/html5.rb +180 -139
  56. data/lib/asciidoctor/converter/manpage.rb +118 -90
  57. data/lib/asciidoctor/converter/template.rb +15 -13
  58. data/lib/asciidoctor/converter.rb +19 -16
  59. data/lib/asciidoctor/core_ext/hash/merge.rb +1 -1
  60. data/lib/asciidoctor/document.rb +77 -86
  61. data/lib/asciidoctor/extensions.rb +22 -16
  62. data/lib/asciidoctor/helpers.rb +20 -15
  63. data/lib/asciidoctor/list.rb +2 -6
  64. data/lib/asciidoctor/load.rb +103 -101
  65. data/lib/asciidoctor/logging.rb +10 -8
  66. data/lib/asciidoctor/parser.rb +211 -220
  67. data/lib/asciidoctor/path_resolver.rb +17 -15
  68. data/lib/asciidoctor/reader.rb +87 -79
  69. data/lib/asciidoctor/rx.rb +9 -7
  70. data/lib/asciidoctor/section.rb +7 -0
  71. data/lib/asciidoctor/substitutors.rb +167 -148
  72. data/lib/asciidoctor/syntax_highlighter/coderay.rb +3 -2
  73. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +13 -5
  74. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  75. data/lib/asciidoctor/syntax_highlighter/pygments.rb +19 -11
  76. data/lib/asciidoctor/syntax_highlighter/rouge.rb +35 -20
  77. data/lib/asciidoctor/syntax_highlighter.rb +16 -16
  78. data/lib/asciidoctor/table.rb +70 -43
  79. data/lib/asciidoctor/timings.rb +3 -3
  80. data/lib/asciidoctor/version.rb +1 -1
  81. data/lib/asciidoctor.rb +45 -19
  82. data/man/asciidoctor.1 +29 -31
  83. data/man/asciidoctor.adoc +35 -29
  84. metadata +17 -70
@@ -1,193 +1,199 @@
1
+ # frozen_string_literal: true
1
2
  module Asciidoctor
2
- module_function
3
+ class << self
4
+ # Public: Parse the AsciiDoc source input into an Asciidoctor::Document and
5
+ # convert it to the specified backend format.
6
+ #
7
+ # Accepts input as an IO (or StringIO), String or String Array object. If the
8
+ # input is a File, the object is expected to be opened for reading and is not
9
+ # closed afterwards by this method. Information about the file (filename,
10
+ # directory name, etc) gets assigned to attributes on the Document object.
11
+ #
12
+ # If the :to_file option is true, and the input is a File, the output is
13
+ # written to a file adjacent to the input file, having an extension that
14
+ # corresponds to the backend format. Otherwise, if the :to_file option is
15
+ # specified, the file is written to that file. If :to_file is not an absolute
16
+ # path, it is resolved relative to :to_dir, if given, otherwise the
17
+ # Document#base_dir. If the target directory does not exist, it will not be
18
+ # created unless the :mkdirs option is set to true. If the file cannot be
19
+ # written because the target directory does not exist, or because it falls
20
+ # outside of the Document#base_dir in safe mode, an IOError is raised.
21
+ #
22
+ # If the output is going to be written to a file, the header and footer are
23
+ # included unless specified otherwise (writing to a file implies creating a
24
+ # standalone document). Otherwise, the header and footer are not included by
25
+ # default and the converted result is returned.
26
+ #
27
+ # input - the String AsciiDoc source filename
28
+ # options - a String, Array or Hash of options to control processing (default: {})
29
+ # String and Array values are converted into a Hash.
30
+ # See Asciidoctor::Document#initialize for details about options.
31
+ #
32
+ # Returns the Document object if the converted String is written to a
33
+ # file, otherwise the converted String
34
+ def convert input, options = {}
35
+ (options = options.merge).delete :parse
36
+ to_dir = options.delete :to_dir
37
+ mkdirs = options.delete :mkdirs
3
38
 
4
- # Public: Parse the AsciiDoc source input into an Asciidoctor::Document and
5
- # convert it to the specified backend format.
6
- #
7
- # Accepts input as an IO (or StringIO), String or String Array object. If the
8
- # input is a File, the object is expected to be opened for reading and is not
9
- # closed afterwards by this method. Information about the file (filename,
10
- # directory name, etc) gets assigned to attributes on the Document object.
11
- #
12
- # If the :to_file option is true, and the input is a File, the output is
13
- # written to a file adjacent to the input file, having an extension that
14
- # corresponds to the backend format. Otherwise, if the :to_file option is
15
- # specified, the file is written to that file. If :to_file is not an absolute
16
- # path, it is resolved relative to :to_dir, if given, otherwise the
17
- # Document#base_dir. If the target directory does not exist, it will not be
18
- # created unless the :mkdirs option is set to true. If the file cannot be
19
- # written because the target directory does not exist, or because it falls
20
- # outside of the Document#base_dir in safe mode, an IOError is raised.
21
- #
22
- # If the output is going to be written to a file, the header and footer are
23
- # included unless specified otherwise (writing to a file implies creating a
24
- # standalone document). Otherwise, the header and footer are not included by
25
- # default and the converted result is returned.
26
- #
27
- # input - the String AsciiDoc source filename
28
- # options - a String, Array or Hash of options to control processing (default: {})
29
- # String and Array values are converted into a Hash.
30
- # See Asciidoctor::Document#initialize for details about options.
31
- #
32
- # Returns the Document object if the converted String is written to a
33
- # file, otherwise the converted String
34
- def convert input, options = {}
35
- (options = options.merge).delete :parse
36
- to_dir = options.delete :to_dir
37
- mkdirs = options.delete :mkdirs
38
-
39
- case (to_file = options.delete :to_file)
40
- when true, nil
41
- unless (write_to_target = to_dir)
42
- sibling_path = ::File.absolute_path input.path if ::File === input
39
+ case (to_file = options.delete :to_file)
40
+ when true, nil
41
+ unless (write_to_target = to_dir)
42
+ sibling_path = ::File.absolute_path input.path if ::File === input
43
+ end
44
+ to_file = nil
45
+ when false
46
+ to_file = nil
47
+ when '/dev/null'
48
+ return load input, options
49
+ else
50
+ options[:to_file] = write_to_target = to_file unless (stream_output = to_file.respond_to? :write)
43
51
  end
44
- to_file = nil
45
- when false
46
- to_file = nil
47
- when '/dev/null'
48
- return load input, options
49
- else
50
- options[:to_file] = write_to_target = to_file unless (stream_output = to_file.respond_to? :write)
51
- end
52
52
 
53
- unless options.key? :standalone
54
- if sibling_path || write_to_target
55
- options[:standalone] = options.fetch :header_footer, true
56
- elsif options.key? :header_footer
57
- options[:standalone] = options[:header_footer]
53
+ unless options.key? :standalone
54
+ if sibling_path || write_to_target
55
+ options[:standalone] = options.fetch :header_footer, true
56
+ elsif options.key? :header_footer
57
+ options[:standalone] = options[:header_footer]
58
+ end
58
59
  end
59
- end
60
60
 
61
- # NOTE outfile may be controlled by document attributes, so resolve outfile after loading
62
- if sibling_path
63
- options[:to_dir] = outdir = ::File.dirname sibling_path
64
- elsif write_to_target
65
- if to_dir
66
- if to_file
67
- options[:to_dir] = ::File.dirname ::File.expand_path ::File.join to_dir, to_file
68
- else
69
- options[:to_dir] = ::File.expand_path to_dir
61
+ # NOTE outfile may be controlled by document attributes, so resolve outfile after loading
62
+ if sibling_path
63
+ options[:to_dir] = outdir = ::File.dirname sibling_path
64
+ elsif write_to_target
65
+ if to_dir
66
+ if to_file
67
+ options[:to_dir] = ::File.dirname ::File.expand_path to_file, to_dir
68
+ else
69
+ options[:to_dir] = ::File.expand_path to_dir
70
+ end
71
+ elsif to_file
72
+ options[:to_dir] = ::File.dirname ::File.expand_path to_file
70
73
  end
71
- elsif to_file
72
- options[:to_dir] = ::File.dirname ::File.expand_path to_file
73
74
  end
74
- end
75
75
 
76
- # NOTE :to_dir is always set when outputting to a file
77
- # NOTE :to_file option only passed if assigned an explicit path
78
- doc = load input, options
76
+ # NOTE :to_dir is always set when outputting to a file
77
+ # NOTE :to_file option only passed if assigned an explicit path
78
+ doc = load input, options
79
79
 
80
- if sibling_path # write to file in same directory
81
- outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
82
- raise ::IOError, %(input file and output file cannot be the same: #{outfile}) if outfile == sibling_path
83
- elsif write_to_target # write to explicit file or directory
84
- working_dir = (options.key? :base_dir) ? (::File.expand_path options[:base_dir]) : ::Dir.pwd
85
- # QUESTION should the jail be the working_dir or doc.base_dir???
86
- jail = doc.safe >= SafeMode::SAFE ? working_dir : nil
87
- if to_dir
88
- outdir = doc.normalize_system_path(to_dir, working_dir, jail, target_name: 'to_dir', recover: false)
89
- if to_file
90
- outfile = doc.normalize_system_path(to_file, outdir, nil, target_name: 'to_dir', recover: false)
91
- # reestablish outdir as the final target directory (in the case to_file had directory segments)
80
+ if sibling_path # write to file in same directory
81
+ outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
82
+ raise ::IOError, %(input file and output file cannot be the same: #{outfile}) if outfile == sibling_path
83
+ elsif write_to_target # write to explicit file or directory
84
+ working_dir = (options.key? :base_dir) ? (::File.expand_path options[:base_dir]) : ::Dir.pwd
85
+ # QUESTION should the jail be the working_dir or doc.base_dir???
86
+ jail = doc.safe >= SafeMode::SAFE ? working_dir : nil
87
+ if to_dir
88
+ outdir = doc.normalize_system_path(to_dir, working_dir, jail, target_name: 'to_dir', recover: false)
89
+ if to_file
90
+ outfile = doc.normalize_system_path(to_file, outdir, nil, target_name: 'to_dir', recover: false)
91
+ # reestablish outdir as the final target directory (in the case to_file had directory segments)
92
+ outdir = ::File.dirname outfile
93
+ else
94
+ outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
95
+ end
96
+ elsif to_file
97
+ outfile = doc.normalize_system_path(to_file, working_dir, jail, target_name: 'to_dir', recover: false)
98
+ # establish outdir as the final target directory (in the case to_file had directory segments)
92
99
  outdir = ::File.dirname outfile
93
- else
94
- outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
95
100
  end
96
- elsif to_file
97
- outfile = doc.normalize_system_path(to_file, working_dir, jail, target_name: 'to_dir', recover: false)
98
- # establish outdir as the final target directory (in the case to_file had directory segments)
99
- outdir = ::File.dirname outfile
100
- end
101
101
 
102
- if ::File === input && outfile == (::File.absolute_path input.path)
103
- raise ::IOError, %(input file and output file cannot be the same: #{outfile})
102
+ if ::File === input && outfile == (::File.absolute_path input.path)
103
+ raise ::IOError, %(input file and output file cannot be the same: #{outfile})
104
+ end
105
+
106
+ if mkdirs
107
+ Helpers.mkdir_p outdir
108
+ else
109
+ # NOTE we intentionally refer to the directory as it was passed to the API
110
+ raise ::IOError, %(target directory does not exist: #{to_dir} (hint: set :mkdirs option)) unless ::File.directory? outdir
111
+ end
112
+ else # write to stream
113
+ outfile = to_file
114
+ outdir = nil
104
115
  end
105
116
 
106
- if mkdirs
107
- Helpers.mkdir_p outdir
117
+ if outfile && !stream_output
118
+ output = doc.convert 'outfile' => outfile, 'outdir' => outdir
108
119
  else
109
- # NOTE we intentionally refer to the directory as it was passed to the API
110
- raise ::IOError, %(target directory does not exist: #{to_dir} (hint: set :mkdirs option)) unless ::File.directory? outdir
120
+ output = doc.convert
111
121
  end
112
- else # write to stream
113
- outfile = to_file
114
- outdir = nil
115
- end
116
-
117
- if outfile && !stream_output
118
- output = doc.convert 'outfile' => outfile, 'outdir' => outdir
119
- else
120
- output = doc.convert
121
- end
122
122
 
123
- if outfile
124
- doc.write output, outfile
123
+ if outfile
124
+ doc.write output, outfile
125
125
 
126
- # NOTE document cannot control this behavior if safe >= SafeMode::SERVER
127
- # NOTE skip if stylesdir is a URI
128
- if !stream_output && doc.safe < SafeMode::SECURE && (doc.attr? 'linkcss') && (doc.attr? 'copycss') &&
129
- (doc.basebackend? 'html') && !((stylesdir = (doc.attr 'stylesdir')) && (Helpers.uriish? stylesdir))
130
- if (stylesheet = doc.attr 'stylesheet')
131
- if DEFAULT_STYLESHEET_KEYS.include? stylesheet
132
- copy_asciidoctor_stylesheet = true
133
- elsif !(Helpers.uriish? stylesheet)
134
- copy_user_stylesheet = true
135
- end
136
- end
137
- copy_syntax_hl_stylesheet = (syntax_hl = doc.syntax_highlighter) && (syntax_hl.write_stylesheet? doc)
138
- if copy_asciidoctor_stylesheet || copy_user_stylesheet || copy_syntax_hl_stylesheet
139
- stylesoutdir = doc.normalize_system_path(stylesdir, outdir, doc.safe >= SafeMode::SAFE ? outdir : nil)
140
- if mkdirs
141
- Helpers.mkdir_p stylesoutdir
142
- else
143
- raise ::IOError, %(target stylesheet directory does not exist: #{stylesoutdir} (hint: set :mkdirs option)) unless ::File.directory? stylesoutdir
126
+ # NOTE document cannot control this behavior if safe >= SafeMode::SERVER
127
+ # NOTE skip if stylesdir is a URI
128
+ if !stream_output && doc.safe < SafeMode::SECURE && (doc.attr? 'linkcss') && (doc.attr? 'copycss') &&
129
+ (doc.basebackend? 'html') && !((stylesdir = (doc.attr 'stylesdir')) && (Helpers.uriish? stylesdir))
130
+ if (stylesheet = doc.attr 'stylesheet')
131
+ if DEFAULT_STYLESHEET_KEYS.include? stylesheet
132
+ copy_asciidoctor_stylesheet = true
133
+ elsif !(Helpers.uriish? stylesheet)
134
+ copy_user_stylesheet = true
135
+ end
144
136
  end
145
-
146
- if copy_asciidoctor_stylesheet
147
- Stylesheets.instance.write_primary_stylesheet stylesoutdir
148
- # FIXME should Stylesheets also handle the user stylesheet?
149
- elsif copy_user_stylesheet
150
- if (stylesheet_src = doc.attr 'copycss').empty?
151
- stylesheet_src = doc.normalize_system_path stylesheet
137
+ copy_syntax_hl_stylesheet = (syntax_hl = doc.syntax_highlighter) && (syntax_hl.write_stylesheet? doc)
138
+ if copy_asciidoctor_stylesheet || copy_user_stylesheet || copy_syntax_hl_stylesheet
139
+ stylesoutdir = doc.normalize_system_path(stylesdir, outdir, doc.safe >= SafeMode::SAFE ? outdir : nil)
140
+ if mkdirs
141
+ Helpers.mkdir_p stylesoutdir
152
142
  else
153
- # NOTE in this case, copycss is a source location (but cannot be a URI)
154
- stylesheet_src = doc.normalize_system_path stylesheet_src
143
+ raise ::IOError, %(target stylesheet directory does not exist: #{stylesoutdir} (hint: set :mkdirs option)) unless ::File.directory? stylesoutdir
155
144
  end
156
- stylesheet_dest = doc.normalize_system_path stylesheet, stylesoutdir, (doc.safe >= SafeMode::SAFE ? outdir : nil)
157
- # NOTE don't warn if src can't be read and dest already exists (see #2323)
158
- if stylesheet_src != stylesheet_dest && (stylesheet_data = doc.read_asset stylesheet_src,
159
- warn_on_failure: !(::File.file? stylesheet_dest), label: 'stylesheet')
160
- ::File.write stylesheet_dest, stylesheet_data, mode: FILE_WRITE_MODE
145
+
146
+ if copy_asciidoctor_stylesheet
147
+ Stylesheets.instance.write_primary_stylesheet stylesoutdir
148
+ # FIXME should Stylesheets also handle the user stylesheet?
149
+ elsif copy_user_stylesheet
150
+ if (stylesheet_src = doc.attr 'copycss') == '' || stylesheet_src == true
151
+ stylesheet_src = doc.normalize_system_path stylesheet
152
+ else
153
+ # NOTE in this case, copycss is a source location (but cannot be a URI)
154
+ stylesheet_src = doc.normalize_system_path stylesheet_src.to_s
155
+ end
156
+ stylesheet_dest = doc.normalize_system_path stylesheet, stylesoutdir, (doc.safe >= SafeMode::SAFE ? outdir : nil)
157
+ # NOTE don't warn if src can't be read and dest already exists (see #2323)
158
+ if stylesheet_src != stylesheet_dest && (stylesheet_data = doc.read_asset stylesheet_src,
159
+ warn_on_failure: !(::File.file? stylesheet_dest), label: 'stylesheet')
160
+ if (stylesheet_outdir = ::File.dirname stylesheet_dest) != stylesoutdir && !(::File.directory? stylesheet_outdir)
161
+ if mkdirs
162
+ Helpers.mkdir_p stylesheet_outdir
163
+ else
164
+ raise ::IOError, %(target stylesheet directory does not exist: #{stylesheet_outdir} (hint: set :mkdirs option))
165
+ end
166
+ end
167
+ ::File.write stylesheet_dest, stylesheet_data, mode: FILE_WRITE_MODE
168
+ end
161
169
  end
170
+ syntax_hl.write_stylesheet doc, stylesoutdir if copy_syntax_hl_stylesheet
162
171
  end
163
- syntax_hl.write_stylesheet doc, stylesoutdir if copy_syntax_hl_stylesheet
164
172
  end
173
+ doc
174
+ else
175
+ output
165
176
  end
166
- doc
167
- else
168
- output
169
177
  end
170
- end
171
178
 
172
- # Public: Parse the contents of the AsciiDoc source file into an
173
- # Asciidoctor::Document and convert it to the specified backend format.
174
- #
175
- # input - the String AsciiDoc source filename
176
- # options - a String, Array or Hash of options to control processing (default: {})
177
- # String and Array values are converted into a Hash.
178
- # See Asciidoctor::Document#initialize for details about options.
179
- #
180
- # Returns the Document object if the converted String is written to a
181
- # file, otherwise the converted String
182
- def convert_file filename, options = {}
183
- ::File.open(filename, FILE_READ_MODE) {|file| convert file, options }
184
- end
179
+ # Public: Parse the contents of the AsciiDoc source file into an
180
+ # Asciidoctor::Document and convert it to the specified backend format.
181
+ #
182
+ # input - the String AsciiDoc source filename
183
+ # options - a String, Array or Hash of options to control processing (default: {})
184
+ # String and Array values are converted into a Hash.
185
+ # See Asciidoctor::Document#initialize for details about options.
186
+ #
187
+ # Returns the Document object if the converted String is written to a
188
+ # file, otherwise the converted String
189
+ def convert_file filename, options = {}
190
+ ::File.open(filename, FILE_READ_MODE) {|file| convert file, options }
191
+ end
185
192
 
186
- # Deprecated: Use {Asciidoctor.convert} instead.
187
- alias render convert
188
- module_function :render
193
+ # Deprecated: Use {Asciidoctor.convert} instead.
194
+ alias render convert
189
195
 
190
- # Deprecated: Use {Asciidoctor.convert_file} instead.
191
- alias render_file convert_file
192
- module_function :render_file
196
+ # Deprecated: Use {Asciidoctor.convert_file} instead.
197
+ alias render_file convert_file
198
+ end
193
199
  end
@@ -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)
@@ -371,24 +375,22 @@ class Converter::DocBook5Converter < Converter::Base
371
375
  has_body = false
372
376
  result = []
373
377
  pgwide_attribute = (node.option? 'pgwide') ? ' pgwide="1"' : ''
374
- if (frame = node.attr 'frame', 'all', 'table-frame') == 'ends'
375
- frame = 'topbot'
376
- end
378
+ frame = 'topbot' if (frame = node.attr 'frame', 'all', 'table-frame') == 'ends'
377
379
  grid = node.attr 'grid', nil, 'table-grid'
378
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"' : ''}>)
379
- if (node.option? 'unbreakable')
381
+ if node.option? 'unbreakable'
380
382
  result << '<?dbfo keep-together="always"?>'
381
- elsif (node.option? 'breakable')
383
+ elsif node.option? 'breakable'
382
384
  result << '<?dbfo keep-together="auto"?>'
383
385
  end
384
386
  result << %(<title>#{node.title}</title>) if tag_name == 'table'
385
- col_width_key = if (width = (node.attr? 'width') ? (node.attr 'width') : nil)
387
+ if (width = (node.attr? 'width') ? (node.attr 'width') : nil)
386
388
  TABLE_PI_NAMES.each do |pi_name|
387
389
  result << %(<?#{pi_name} table-width="#{width}"?>)
388
390
  end
389
- 'colabswidth'
391
+ col_width_key = 'colabswidth'
390
392
  else
391
- 'colpcwidth'
393
+ col_width_key = 'colpcwidth'
392
394
  end
393
395
  result << %(<tgroup cols="#{node.attr 'colcount'}">)
394
396
  node.columns.each do |col|
@@ -401,12 +403,10 @@ class Converter::DocBook5Converter < Converter::Base
401
403
  rows.each do |row|
402
404
  result << '<row>'
403
405
  row.each do |cell|
404
- halign_attribute = (cell.attr? 'halign') ? %( align="#{cell.attr 'halign'}") : ''
405
- valign_attribute = (cell.attr? 'valign') ? %( valign="#{cell.attr 'valign'}") : ''
406
406
  colspan_attribute = cell.colspan ? %( namest="col_#{colnum = cell.column.attr 'colnumber'}" nameend="col_#{colnum + cell.colspan - 1}") : ''
407
407
  rowspan_attribute = cell.rowspan ? %( morerows="#{cell.rowspan - 1}") : ''
408
408
  # NOTE <entry> may not have whitespace (e.g., line breaks) as a direct descendant according to DocBook rules
409
- entry_start = %(<entry#{halign_attribute}#{valign_attribute}#{colspan_attribute}#{rowspan_attribute}>)
409
+ entry_start = %(<entry align="#{cell.attr 'halign'}" valign="#{cell.attr 'valign'}"#{colspan_attribute}#{rowspan_attribute}>)
410
410
  if tsec == :head
411
411
  cell_content = cell.text
412
412
  else
@@ -478,16 +478,22 @@ class Converter::DocBook5Converter < Converter::Base
478
478
  %(<anchor#{common_attributes((id = node.id), nil, node.reftext || %([#{id}]))}/>)
479
479
  when :xref
480
480
  if (path = node.attributes['path'])
481
- # QUESTION should we use refid as fallback text instead? (like the html5 backend?)
482
481
  %(<link xl:href="#{node.target}">#{node.text || path}</link>)
483
482
  else
484
- 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."
485
491
  (text = node.text) ? %(<link linkend="#{linkend}">#{text}</link>) : %(<xref linkend="#{linkend}"/>)
486
492
  end
487
493
  when :link
488
494
  %(<link xl:href="#{node.target}">#{node.text}</link>)
489
495
  when :bibref
490
- %(<anchor#{common_attributes node.id, nil, "[#{node.reftext || node.id}]"}/>#{text})
496
+ %(<anchor#{common_attributes node.id, nil, (text = "[#{node.reftext || node.id}]")}/>#{text})
491
497
  else
492
498
  logger.warn %(unknown anchor type: #{node.type.inspect})
493
499
  nil
@@ -517,7 +523,7 @@ class Converter::DocBook5Converter < Converter::Base
517
523
  def convert_inline_image node
518
524
  width_attribute = (node.attr? 'width') ? %( contentwidth="#{node.attr 'width'}") : ''
519
525
  depth_attribute = (node.attr? 'height') ? %( contentdepth="#{node.attr 'height'}") : ''
520
- %(<inlinemediaobject>
526
+ %(<inlinemediaobject#{common_attributes nil, node.role}>
521
527
  <imageobject>
522
528
  <imagedata fileref="#{node.type == 'icon' ? (node.icon_uri node.target) : (node.image_uri node.target)}"#{width_attribute}#{depth_attribute}/>
523
529
  </imageobject>
@@ -537,9 +543,8 @@ class Converter::DocBook5Converter < Converter::Base
537
543
  %(<indexterm>
538
544
  <primary>#{node.text}</primary>#{rel}
539
545
  </indexterm>#{node.text})
540
- else
541
- if (numterms = (terms = node.attr 'terms').size) > 2
542
- %(<indexterm>
546
+ elsif (numterms = (terms = node.attr 'terms').size) > 2
547
+ %(<indexterm>
543
548
  <primary>#{terms[0]}</primary><secondary>#{terms[1]}</secondary><tertiary>#{terms[2]}</tertiary>#{rel}
544
549
  </indexterm>#{(node.document.option? 'indexterm-promotion') ? %[
545
550
  <indexterm>
@@ -548,18 +553,17 @@ class Converter::DocBook5Converter < Converter::Base
548
553
  <indexterm>
549
554
  <primary>#{terms[2]}</primary>
550
555
  </indexterm>] : ''})
551
- elsif numterms > 1
552
- %(<indexterm>
556
+ elsif numterms > 1
557
+ %(<indexterm>
553
558
  <primary>#{terms[0]}</primary><secondary>#{terms[1]}</secondary>#{rel}
554
- </indexterm>#{(node.document.option? 'indexterm-promotion') ? %[
559
+ </indexterm>#{(node.document.option? 'indexterm-promotion') ? %[
555
560
  <indexterm>
556
561
  <primary>#{terms[1]}</primary>
557
562
  </indexterm>] : ''})
558
- else
559
- %(<indexterm>
563
+ else
564
+ %(<indexterm>
560
565
  <primary>#{terms[0]}</primary>#{rel}
561
566
  </indexterm>)
562
- end
563
567
  end
564
568
  end
565
569
 
@@ -714,6 +718,17 @@ class Converter::DocBook5Converter < Converter::Base
714
718
  result.join LF
715
719
  end
716
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
+
717
732
  # FIXME this should be handled through a template mechanism
718
733
  def enclose_content node
719
734
  node.content_model == :compound ? node.content : %(<simpara>#{node.content}</simpara>)