asciidoctor 1.5.5 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of asciidoctor might be problematic. Click here for more details.

Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +216 -1
  3. data/CONTRIBUTING.adoc +2 -2
  4. data/Gemfile +20 -1
  5. data/LICENSE.adoc +1 -1
  6. data/README-fr.adoc +4 -3
  7. data/README-jp.adoc +11 -10
  8. data/README-zh_CN.adoc +4 -3
  9. data/README.adoc +17 -202
  10. data/Rakefile +41 -25
  11. data/asciidoctor.gemspec +9 -10
  12. data/data/locale/attributes.adoc +216 -34
  13. data/data/stylesheets/asciidoctor-default.css +23 -16
  14. data/features/step_definitions.rb +15 -19
  15. data/features/xref.feature +584 -20
  16. data/lib/asciidoctor.rb +292 -278
  17. data/lib/asciidoctor/abstract_block.rb +155 -94
  18. data/lib/asciidoctor/abstract_node.rb +108 -94
  19. data/lib/asciidoctor/attribute_list.rb +30 -22
  20. data/lib/asciidoctor/block.rb +7 -7
  21. data/lib/asciidoctor/cli/invoker.rb +47 -34
  22. data/lib/asciidoctor/cli/options.rb +22 -11
  23. data/lib/asciidoctor/converter.rb +3 -3
  24. data/lib/asciidoctor/converter/base.rb +2 -2
  25. data/lib/asciidoctor/converter/composite.rb +1 -1
  26. data/lib/asciidoctor/converter/docbook45.rb +2 -2
  27. data/lib/asciidoctor/converter/docbook5.rb +132 -87
  28. data/lib/asciidoctor/converter/factory.rb +0 -1
  29. data/lib/asciidoctor/converter/html5.rb +116 -98
  30. data/lib/asciidoctor/converter/manpage.rb +51 -52
  31. data/lib/asciidoctor/converter/template.rb +47 -36
  32. data/lib/asciidoctor/core_ext.rb +8 -2
  33. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +4 -0
  34. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +6 -0
  35. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +5 -0
  36. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +1 -1
  37. data/lib/asciidoctor/core_ext/1.8.7/string/{limit.rb → limit_bytesize.rb} +7 -6
  38. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +6 -0
  39. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +1 -1
  40. data/lib/asciidoctor/core_ext/nil_or_empty.rb +5 -5
  41. data/lib/asciidoctor/core_ext/regexp/is_match.rb +3 -0
  42. data/lib/asciidoctor/core_ext/string/{limit.rb → limit_bytesize.rb} +2 -2
  43. data/lib/asciidoctor/document.rb +216 -213
  44. data/lib/asciidoctor/extensions.rb +318 -185
  45. data/lib/asciidoctor/helpers.rb +35 -35
  46. data/lib/asciidoctor/inline.rb +32 -1
  47. data/lib/asciidoctor/list.rb +22 -6
  48. data/lib/asciidoctor/parser.rb +1008 -1038
  49. data/lib/asciidoctor/path_resolver.rb +46 -50
  50. data/lib/asciidoctor/reader.rb +275 -251
  51. data/lib/asciidoctor/section.rb +86 -58
  52. data/lib/asciidoctor/stylesheets.rb +6 -6
  53. data/lib/asciidoctor/substitutors.rb +567 -649
  54. data/lib/asciidoctor/table.rb +163 -108
  55. data/lib/asciidoctor/version.rb +1 -1
  56. data/man/asciidoctor.1 +18 -16
  57. data/man/asciidoctor.adoc +15 -13
  58. data/test/attributes_test.rb +138 -22
  59. data/test/blocks_test.rb +377 -97
  60. data/test/converter_test.rb +13 -0
  61. data/test/document_test.rb +244 -34
  62. data/test/extensions_test.rb +409 -42
  63. data/test/fixtures/asciidoc_index.txt +521 -0
  64. data/test/fixtures/basic-docinfo-footer.html +6 -0
  65. data/test/fixtures/basic-docinfo-footer.xml +8 -0
  66. data/test/fixtures/basic-docinfo.html +1 -0
  67. data/test/fixtures/basic-docinfo.xml +4 -0
  68. data/test/fixtures/basic.asciidoc +5 -0
  69. data/test/fixtures/chapter-a.adoc +3 -0
  70. data/test/fixtures/child-include.adoc +5 -0
  71. data/test/fixtures/circle.svg +9 -0
  72. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
  73. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +6 -0
  74. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +1 -0
  75. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +3 -0
  76. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +5 -0
  77. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +6 -0
  78. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +3 -0
  79. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +5 -0
  80. data/test/fixtures/custom-docinfodir/basic-docinfo.html +1 -0
  81. data/test/fixtures/custom-docinfodir/docinfo.html +1 -0
  82. data/test/fixtures/docinfo-footer.html +1 -0
  83. data/test/fixtures/docinfo-footer.xml +9 -0
  84. data/test/fixtures/docinfo.html +1 -0
  85. data/test/fixtures/docinfo.xml +3 -0
  86. data/test/fixtures/dot.gif +0 -0
  87. data/test/fixtures/encoding.asciidoc +13 -0
  88. data/test/fixtures/grandchild-include.adoc +3 -0
  89. data/test/fixtures/hello-asciidoctor.pdf +69 -0
  90. data/test/fixtures/include-file.asciidoc +24 -0
  91. data/test/fixtures/include-file.ml +3 -0
  92. data/test/fixtures/include-file.xml +5 -0
  93. data/test/fixtures/master.adoc +5 -0
  94. data/test/fixtures/mismatched-end-tag.adoc +7 -0
  95. data/test/fixtures/parent-include-restricted.adoc +5 -0
  96. data/test/fixtures/parent-include.adoc +5 -0
  97. data/test/fixtures/sample.asciidoc +26 -0
  98. data/test/fixtures/stylesheets/custom.css +3 -0
  99. data/test/fixtures/subs-docinfo.html +2 -0
  100. data/test/fixtures/subs.adoc +7 -0
  101. data/test/fixtures/tagged-class-enclosed.rb +26 -0
  102. data/test/fixtures/tagged-class.rb +23 -0
  103. data/test/fixtures/tip.gif +0 -0
  104. data/test/invoker_test.rb +82 -4
  105. data/test/links_test.rb +312 -37
  106. data/test/lists_test.rb +204 -25
  107. data/test/manpage_test.rb +191 -4
  108. data/test/options_test.rb +18 -1
  109. data/test/paragraphs_test.rb +32 -7
  110. data/test/parser_test.rb +150 -30
  111. data/test/paths_test.rb +47 -13
  112. data/test/preamble_test.rb +1 -1
  113. data/test/reader_test.rb +366 -126
  114. data/test/sections_test.rb +203 -56
  115. data/test/substitutions_test.rb +339 -131
  116. data/test/tables_test.rb +315 -15
  117. data/test/test_helper.rb +400 -0
  118. data/test/text_test.rb +5 -5
  119. metadata +110 -22
@@ -22,38 +22,31 @@ module Asciidoctor
22
22
  # => {'style' => 'quote', 'attribution' => 'Famous Person', 'citetitle' => 'Famous Book (2001)'}
23
23
  #
24
24
  class AttributeList
25
-
26
- # FIXME Opal not inheriting constants from parent scope
27
- # NOTE can't use ::RUBY_ENGINE_OPAL here either
28
- if ::RUBY_ENGINE == 'opal'
29
- CG_BLANK = '[ \\t]'
30
- CC_WORD = 'a-zA-Z0-9_'
31
- CG_WORD = '[a-zA-Z0-9_]'
32
- end
25
+ BACKSLASH = '\\'
33
26
 
34
27
  # Public: Regular expressions for detecting the boundary of a value
35
28
  BoundaryRxs = {
36
29
  '"' => /.*?[^\\](?=")/,
37
30
  '\'' => /.*?[^\\](?=')/,
38
- ',' => /.*?(?=#{CG_BLANK}*(,|$))/
31
+ ',' => /.*?(?=[ \t]*(,|$))/
39
32
  }
40
33
 
41
34
  # Public: Regular expressions for unescaping quoted characters
42
- EscapedQuoteRxs = {
43
- '"' => /\\"/,
44
- '\'' => /\\'/
35
+ EscapedQuotes = {
36
+ '"' => '\\"',
37
+ '\'' => '\\\''
45
38
  }
46
39
 
47
40
  # Public: A regular expression for an attribute name (approx. name token from XML)
48
41
  # TODO named attributes cannot contain dash characters
49
42
  NameRx = /#{CG_WORD}[#{CC_WORD}\-.]*/
50
43
 
51
- BlankRx = /#{CG_BLANK}+/
44
+ BlankRx = /[ \t]+/
52
45
 
53
46
  # Public: Regular expressions for skipping blanks and delimiters
54
47
  SkipRxs = {
55
48
  :blank => BlankRx,
56
- ',' => /#{CG_BLANK}*(,|$)/
49
+ ',' => /[ \t]*(,|$)/
57
50
  }
58
51
 
59
52
  def initialize source, block = nil, delimiter = ','
@@ -162,16 +155,27 @@ class AttributeList
162
155
  # opts is an alias for options
163
156
  case name
164
157
  when 'options', 'opts'
165
- name = 'options'
166
- value.tr(' ', '').split(',').each {|opt| @attributes[%(#{opt}-option)] = '' }
167
- @attributes[name] = value
168
- when 'title'
169
- @attributes[name] = value
158
+ if value.include? ','
159
+ value = value.delete ' ' if value.include? ' '
160
+ (value.split ',').each {|opt| @attributes[%(#{opt}-option)] = '' unless opt.empty? }
161
+ else
162
+ @attributes[%(#{value = value.strip}-option)] = ''
163
+ end
164
+ @attributes['options'] = value
170
165
  else
171
- @attributes[name] = single_quoted_value && !value.empty? && @block ? (@block.apply_normal_subs value) : value
166
+ if single_quoted_value && @block
167
+ case name
168
+ when 'title', 'reftext'
169
+ @attributes[name] = value
170
+ else
171
+ @attributes[name] = @block.apply_subs value
172
+ end
173
+ else
174
+ @attributes[name] = value
175
+ end
172
176
  end
173
177
  else
174
- resolved_name = single_quoted_value && !name.empty? && @block ? (@block.apply_normal_subs name) : name
178
+ resolved_name = single_quoted_value && @block ? (@block.apply_subs name) : name
175
179
  if (pos_name = pos_attrs[index])
176
180
  @attributes[pos_name] = resolved_name
177
181
  end
@@ -193,7 +197,11 @@ class AttributeList
193
197
 
194
198
  if (value = scan_to_quote quote)
195
199
  @scanner.get_byte
196
- value.gsub EscapedQuoteRxs[quote], quote
200
+ if value.include? BACKSLASH
201
+ value.gsub EscapedQuotes[quote], quote
202
+ else
203
+ value
204
+ end
197
205
  else
198
206
  %(#{quote}#{scan_to_delimiter})
199
207
  end
@@ -24,7 +24,7 @@ class Block < AbstractBlock
24
24
  }).default = :simple
25
25
 
26
26
  # Public: Create alias for context to be consistent w/ AsciiDoc
27
- alias :blockname :context
27
+ alias blockname context
28
28
 
29
29
  # Public: Get/Set the original Array content for this block, if applicable
30
30
  attr_accessor :lines
@@ -74,14 +74,14 @@ class Block < AbstractBlock
74
74
  lock_in_subs
75
75
  # e.g., :subs => nil
76
76
  else
77
- @subs = []
77
+ # NOTE @subs is initialized as empty array by super constructor
78
78
  # prevent subs from being resolved
79
79
  @default_subs = []
80
80
  @attributes.delete 'subs'
81
81
  end
82
82
  # defer subs resolution; subs attribute is honored
83
83
  else
84
- @subs = []
84
+ # NOTE @subs is initialized as empty array by super constructor
85
85
  # QUESTION should we honor :default_subs option (i.e., @default_subs = opts[:default_subs])?
86
86
  @default_subs = nil
87
87
  end
@@ -109,9 +109,9 @@ class Block < AbstractBlock
109
109
  when :compound
110
110
  super
111
111
  when :simple
112
- apply_subs(@lines * EOL, @subs)
112
+ apply_subs @lines * LF, @subs
113
113
  when :verbatim, :raw
114
- #((apply_subs @lines.join(EOL), @subs).sub StripLineWiseRx, '\1')
114
+ #((apply_subs @lines * LF, @subs).sub StripLineWiseRx, '\1')
115
115
 
116
116
  # QUESTION could we use strip here instead of popping empty lines?
117
117
  # maybe apply_subs can know how to strip whitespace?
@@ -121,7 +121,7 @@ class Block < AbstractBlock
121
121
  else
122
122
  result.shift while (first = result[0]) && first.rstrip.empty?
123
123
  result.pop while (last = result[-1]) && last.rstrip.empty?
124
- result * EOL
124
+ result * LF
125
125
  end
126
126
  else
127
127
  warn %(Unknown content model '#{@content_model}' for block: #{to_s}) unless @content_model == :empty
@@ -134,7 +134,7 @@ class Block < AbstractBlock
134
134
  # Returns the a String containing the lines joined together or nil if there
135
135
  # are no lines
136
136
  def source
137
- @lines * EOL
137
+ @lines * LF
138
138
  end
139
139
 
140
140
  def to_s
@@ -7,7 +7,7 @@ module Asciidoctor
7
7
  attr_reader :documents
8
8
  attr_reader :code
9
9
 
10
- def initialize(*options)
10
+ def initialize *options
11
11
  @documents = []
12
12
  @out = nil
13
13
  @err = nil
@@ -33,19 +33,12 @@ module Asciidoctor
33
33
  return unless @options
34
34
 
35
35
  old_verbose = $VERBOSE
36
- case @options[:verbose]
37
- when 0
38
- $VERBOSE = nil
39
- when 1
40
- $VERBOSE = false
41
- when 2
42
- $VERBOSE = true
43
- end
44
-
45
36
  opts = {}
46
37
  infiles = []
47
38
  outfile = nil
48
- tofile = nil
39
+ err = @err || $stderr
40
+ show_timings = false
41
+
49
42
  @options.map do |key, val|
50
43
  case key
51
44
  when :input_files
@@ -57,55 +50,75 @@ module Asciidoctor
57
50
  when :attributes
58
51
  # NOTE processor will dup attributes internally
59
52
  opts[:attributes] = val
53
+ when :timings
54
+ show_timings = val
60
55
  when :trace
61
- # currently, nothing
56
+ # currently does nothing
57
+ when :verbose
58
+ case val
59
+ when 0
60
+ $VERBOSE = nil
61
+ when 1
62
+ $VERBOSE = false
63
+ when 2
64
+ $VERBOSE = true
65
+ end
62
66
  else
63
67
  opts[key] = val unless val.nil?
64
68
  end
65
69
  end
66
70
 
67
- if infiles.size == 1 && infiles[0] == '-'
68
- # allows use of block to supply stdin, particularly useful for tests
69
- inputs = [block_given? ? yield : STDIN]
70
- else
71
- inputs = infiles.map {|infile| ::File.new infile, 'r'}
71
+ stdin = if infiles.size == 1
72
+ if (infile0 = infiles[0]) == '-'
73
+ outfile ||= infile0
74
+ true
75
+ elsif ::File.pipe? infile0
76
+ outfile ||= '-'
77
+ nil
78
+ end
72
79
  end
73
80
 
74
- # NOTE if infile is stdin, default to outfile as stout
75
- if outfile == '-' || (!outfile && infiles.size == 1 && infiles[0] == '-')
76
- tofile = (@out || $stdout)
81
+ tofile = if outfile == '-'
82
+ @out || $stdout
77
83
  elsif outfile
78
- tofile = outfile
79
84
  opts[:mkdirs] = true
85
+ outfile
80
86
  else
81
- # automatically calculate outfile based on infile unless to_dir is set
82
- tofile = nil
83
87
  opts[:mkdirs] = true
88
+ nil # automatically calculate outfile based on infile
84
89
  end
85
90
 
86
- show_timings = @options[:timings]
87
- inputs.each do |input|
88
- # NOTE processor will dup options and attributes internally
89
- input_opts = tofile.nil? ? opts : opts.merge(:to_file => tofile)
91
+ if stdin
92
+ # allows use of block to supply stdin, particularly useful for tests
93
+ input = block_given? ? yield : STDIN
94
+ input_opts = opts.merge :to_file => tofile
90
95
  if show_timings
91
- timings = Timings.new
92
- @documents << ::Asciidoctor.convert(input, input_opts.merge(:timings => timings))
93
- timings.print_report((@err || $stderr), ((input.respond_to? :path) ? input.path : '-'))
96
+ @documents << (::Asciidoctor.convert input, (input_opts.merge :timings => (timings = Timings.new)))
97
+ timings.print_report err, '-'
94
98
  else
95
- @documents << ::Asciidoctor.convert(input, input_opts)
99
+ @documents << (::Asciidoctor.convert input, input_opts)
100
+ end
101
+ else
102
+ infiles.each do |infile|
103
+ input_opts = opts.merge :to_file => tofile
104
+ if show_timings
105
+ @documents << (::Asciidoctor.convert_file infile, (input_opts.merge :timings => (timings = Timings.new)))
106
+ timings.print_report err, infile
107
+ else
108
+ @documents << (::Asciidoctor.convert_file infile, input_opts)
109
+ end
96
110
  end
97
111
  end
98
112
  rescue ::Exception => e
99
113
  if ::SignalException === e
100
114
  @code = e.signo
101
115
  # add extra endline if Ctrl+C is used
102
- (@err || $stderr).puts if ::Interrupt === e
116
+ err.puts if ::Interrupt === e
103
117
  else
104
118
  @code = (e.respond_to? :status) ? e.status : 1
105
119
  if @options[:trace]
106
120
  raise e
107
121
  else
108
- err = (@err || $stderr)
109
122
  if ::RuntimeError === e
110
123
  err.puts %(#{e.message} (#{e.class}))
111
124
  else
@@ -123,7 +136,7 @@ module Asciidoctor
123
136
  @documents[0]
124
137
  end
125
138
 
126
- def redirect_streams(out, err = nil)
139
+ def redirect_streams out, err = nil
127
140
  @out = out
128
141
  @err = err
129
142
  end
@@ -56,14 +56,14 @@ Example: asciidoctor -b html5 source.asciidoc
56
56
  end
57
57
  opts.on('--safe',
58
58
  'set safe mode level to safe (default: unsafe)',
59
- 'enables include macros, but restricts access to ancestor paths of source file',
59
+ 'enables include directives, but prevents access to ancestor paths of source file',
60
60
  'provided for compatibility with the asciidoc command') do
61
61
  self[:safe] = SafeMode::SAFE
62
62
  end
63
- opts.on('-S', '--safe-mode SAFE_MODE', (safe_mode_names = SafeMode.constants.map(&:to_s).map(&:downcase)),
63
+ opts.on('-S', '--safe-mode SAFE_MODE', (safe_mode_names = SafeMode.names),
64
64
  %(set safe mode level explicitly: [#{safe_mode_names * ', '}] (default: unsafe)),
65
- 'disables potentially dangerous macros in source files, such as include::[]') do |safe_mode|
66
- self[:safe] = SafeMode.const_get safe_mode.upcase
65
+ 'disables potentially dangerous macros in source files, such as include::[]') do |name|
66
+ self[:safe] = SafeMode.value_for_name name
67
67
  end
68
68
  opts.on('-s', '--no-header-footer', 'suppress output of header and footer (default: false)') do
69
69
  self[:header_footer] = false
@@ -84,7 +84,7 @@ Example: asciidoctor -b html5 source.asciidoc
84
84
  val = val ? (FORCE_ENCODING ? (val.force_encoding ::Encoding::UTF_8) : val) : ''
85
85
  # move leading ! to end for internal processing
86
86
  #if !val && key.start_with?('!')
87
- # key = "#{key[1..-1]}!"
87
+ # key = %(#{key[1..-1]}!)
88
88
  #end
89
89
  self[:attributes][key] = val
90
90
  end
@@ -93,7 +93,7 @@ Example: asciidoctor -b html5 source.asciidoc
93
93
  if self[:template_dirs].nil?
94
94
  self[:template_dirs] = [template_dir]
95
95
  elsif ::Array === self[:template_dirs]
96
- self[:template_dirs].push template_dir
96
+ self[:template_dirs] << template_dir
97
97
  else
98
98
  self[:template_dirs] = [self[:template_dirs], template_dir]
99
99
  end
@@ -128,8 +128,19 @@ Example: asciidoctor -b html5 source.asciidoc
128
128
  self[:timings] = true
129
129
  end
130
130
 
131
- opts.on_tail('-h', '--help', 'show this message') do
132
- $stdout.puts opts
131
+ opts.on_tail('-h', '--help [TOPIC]', 'print the help message',
132
+ 'show the command usage if TOPIC is not specified (or not recognized)',
133
+ 'dump the Asciidoctor man page (in troff/groff format) if TOPIC is manpage') do |topic|
134
+ if topic == 'manpage'
135
+ if ::File.exist?(manpage_path = (::File.join ::Asciidoctor::ROOT_PATH, 'man', 'asciidoctor.1'))
136
+ $stdout.puts(::IO.read manpage_path)
137
+ else
138
+ $stderr.puts 'asciidoctor: FAILED: man page not found; try `man asciidoctor`'
139
+ return 1
140
+ end
141
+ else
142
+ $stdout.puts opts
143
+ end
133
144
  return 0
134
145
  end
135
146
 
@@ -153,12 +164,12 @@ Example: asciidoctor -b html5 source.asciidoc
153
164
 
154
165
  # shave off the file to process so that options errors appear correctly
155
166
  if args.size == 1 && args[0] == '-'
156
- infiles.push args.pop
167
+ infiles << args.pop
157
168
  elsif
158
169
  args.each do |file|
159
170
  if file == '-' || (file.start_with? '-')
160
171
  # warn, but don't panic; we may have enough to proceed, so we won't force a failure
161
- $stderr.puts "asciidoctor: WARNING: extra arguments detected (unparsed arguments: #{args.map{|a| "'#{a}'"} * ', '}) or incorrect usage of stdin"
172
+ $stderr.puts %(asciidoctor: WARNING: extra arguments detected (unparsed arguments: '#{args * "', '"}') or incorrect usage of stdin)
162
173
  else
163
174
  if ::File.readable? file
164
175
  matches = [file]
@@ -179,7 +190,7 @@ Example: asciidoctor -b html5 source.asciidoc
179
190
  end
180
191
 
181
192
  infiles.each do |file|
182
- unless file == '-' || (::File.file? file)
193
+ unless file == '-' || (::File.file? file) || (::File.pipe? file)
183
194
  if ::File.readable? file
184
195
  $stderr.puts %(asciidoctor: FAILED: input path #{file} is a #{(::File.stat file).ftype}, not a file)
185
196
  else
@@ -184,7 +184,7 @@ module Asciidoctor
184
184
  end
185
185
 
186
186
  # Alias for backward compatibility.
187
- alias :convert_with_options :convert
187
+ alias convert_with_options convert
188
188
  end
189
189
 
190
190
  # A module that can be used to mix the {#write} method into a {Converter}
@@ -202,9 +202,9 @@ module Asciidoctor
202
202
  if target.respond_to? :write
203
203
  target.write output.chomp
204
204
  # ensure there's a trailing endline to be nice to terminals
205
- target.write EOL
205
+ target.write LF
206
206
  else
207
- ::File.open(target, 'w') {|f| f.write output }
207
+ ::IO.write target, output
208
208
  end
209
209
  nil
210
210
  end
@@ -34,7 +34,7 @@ module Asciidoctor
34
34
  opts.empty? ? (send transform, node) : (send transform, node, opts)
35
35
  end
36
36
 
37
- alias :handles? :respond_to?
37
+ alias handles? respond_to?
38
38
 
39
39
  # Public: Returns the converted content of the {AbstractNode}.
40
40
  #
@@ -43,7 +43,7 @@ module Asciidoctor
43
43
  node.content
44
44
  end
45
45
 
46
- alias :pass :content
46
+ alias pass content
47
47
 
48
48
  # Public: Skips conversion of the {AbstractNode}.
49
49
  #
@@ -32,7 +32,7 @@ module Asciidoctor
32
32
  end
33
33
 
34
34
  # Alias for backward compatibility.
35
- alias :convert_with_options :convert
35
+ alias convert_with_options convert
36
36
 
37
37
  # Public: Retrieve the converter for the specified transform.
38
38
  #
@@ -29,7 +29,7 @@ module Asciidoctor
29
29
  result << '</listitem>'
30
30
  end
31
31
  result << %(</orderedlist>)
32
- result * EOL
32
+ result * LF
33
33
  end
34
34
 
35
35
  def inline_anchor node
@@ -66,7 +66,7 @@ module Asciidoctor
66
66
  result << %(<email>#{doc.attr email_key}</email>) if doc.attr? email_key
67
67
  result << '</author>'
68
68
 
69
- result * EOL
69
+ result * LF
70
70
  end
71
71
 
72
72
  def common_attributes id, role = nil, reftext = nil
@@ -4,6 +4,8 @@ module Asciidoctor
4
4
  # similar to the docbook45 backend from AsciiDoc Python, but migrated to the
5
5
  # DocBook 5 specification.
6
6
  class Converter::DocBook5Converter < Converter::BuiltIn
7
+ ImageMacroRx = /^image::?(.+?)\[(.*?)\]$/
8
+
7
9
  def document node
8
10
  result = []
9
11
  if (root_tag_name = node.doctype) == 'manpage'
@@ -29,34 +31,25 @@ module Asciidoctor
29
31
  end
30
32
  lang_attribute = (node.attr? 'nolang') ? nil : %( #{lang_attribute_name}="#{node.attr 'lang', 'en'}")
31
33
  result << %(<#{root_tag_name}#{document_ns_attributes node}#{lang_attribute}>)
32
- result << (document_info_element node, root_tag_name)
34
+ result << (document_info_element node, root_tag_name) unless node.noheader
33
35
  result << node.content if node.blocks?
34
36
  unless (footer_docinfo = node.docinfo :footer).empty?
35
37
  result << footer_docinfo
36
38
  end
37
39
  result << %(</#{root_tag_name}>)
38
40
 
39
- result * EOL
41
+ result * LF
40
42
  end
41
43
 
42
- alias :embedded :content
44
+ alias embedded content
45
+
46
+ MANPAGE_SECTION_TAGS = { 'section' => 'refsection', 'synopsis' => 'refsynopsisdiv' }
43
47
 
44
48
  def section node
45
- doctype = node.document.doctype
46
- if node.special
47
- if (tag_name = node.sectname).start_with? 'sect'
48
- # a normal child section of a special section
49
- tag_name = 'section'
50
- end
49
+ if node.document.doctype == 'manpage'
50
+ tag_name = MANPAGE_SECTION_TAGS[tag_name = node.sectname] || tag_name
51
51
  else
52
- tag_name = doctype == 'book' && node.level <= 1 ? (node.level == 0 ? 'part' : 'chapter') : 'section'
53
- end
54
- if doctype == 'manpage'
55
- if tag_name == 'section'
56
- tag_name = 'refsection'
57
- elsif tag_name == 'synopsis'
58
- tag_name = 'refsynopsisdiv'
59
- end
52
+ tag_name = node.sectname
60
53
  end
61
54
  %(<#{tag_name}#{common_attributes node.id, node.role, node.reftext}>
62
55
  <title>#{node.title}</title>
@@ -70,7 +63,7 @@ module Asciidoctor
70
63
  </#{tag_name}>)
71
64
  end
72
65
 
73
- alias :audio :skip
66
+ alias audio skip
74
67
 
75
68
  def colist node
76
69
  result = []
@@ -83,16 +76,10 @@ module Asciidoctor
83
76
  result << '</callout>'
84
77
  end
85
78
  result << %(</calloutlist>)
86
- result * EOL
79
+ result * LF
87
80
  end
88
81
 
89
82
  (DLIST_TAGS = {
90
- 'labeled' => {
91
- :list => 'variablelist',
92
- :entry => 'varlistentry',
93
- :term => 'term',
94
- :item => 'listitem'
95
- },
96
83
  'qanda' => {
97
84
  :list => 'qandaset',
98
85
  :entry => 'qandaentry',
@@ -106,7 +93,7 @@ module Asciidoctor
106
93
  :term => 'glossterm',
107
94
  :item => 'glossdef'
108
95
  }
109
- }).default = { # default value == DLIST['labeled'], expanded for Opal
96
+ }).default = { # default is variable
110
97
  :list => 'variablelist',
111
98
  :entry => 'varlistentry',
112
99
  :term => 'term',
@@ -172,7 +159,7 @@ module Asciidoctor
172
159
  result << %(</#{list_tag}>) if list_tag
173
160
  end
174
161
 
175
- result * EOL
162
+ result * LF
176
163
  end
177
164
 
178
165
  def example node
@@ -193,19 +180,29 @@ module Asciidoctor
193
180
  end
194
181
 
195
182
  def image node
196
- width_attribute = (node.attr? 'width') ? %( contentwidth="#{node.attr 'width'}") : nil
197
- depth_attribute = (node.attr? 'height') ? %( contentdepth="#{node.attr 'height'}") : nil
198
- # FIXME if scaledwidth is set, we should remove width & depth
199
- # See http://www.docbook.org/tdg/en/html/imagedata.html#d0e92271 for details
200
- swidth_attribute = (node.attr? 'scaledwidth') ? %( width="#{node.attr 'scaledwidth'}" scalefit="1") : nil
201
- scale_attribute = (node.attr? 'scale') ? %( scale="#{node.attr 'scale'}") : nil
183
+ # NOTE according to the DocBook spec, content area, scaling, and scaling to fit are mutually exclusive
184
+ # See http://tdg.docbook.org/tdg/4.5/imagedata-x.html#d0e79635
185
+ if node.attr? 'scaledwidth'
186
+ width_attribute = %( width="#{node.attr 'scaledwidth'}")
187
+ depth_attribute = nil
188
+ scale_attribute = nil
189
+ elsif node.attr? 'scale'
190
+ # QUESTION should we set the viewport using width and depth? (the scaled image would be contained within this box)
191
+ #width_attribute = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : nil
192
+ #depth_attribute = (node.attr? 'height') ? %( depth="#{node.attr 'height'}") : nil
193
+ scale_attribute = %( scale="#{node.attr 'scale'}")
194
+ else
195
+ width_attribute = (node.attr? 'width') ? %( contentwidth="#{node.attr 'width'}") : nil
196
+ depth_attribute = (node.attr? 'height') ? %( contentdepth="#{node.attr 'height'}") : nil
197
+ scale_attribute = nil
198
+ end
202
199
  align_attribute = (node.attr? 'align') ? %( align="#{node.attr 'align'}") : nil
203
200
 
204
201
  mediaobject = %(<mediaobject>
205
202
  <imageobject>
206
- <imagedata fileref="#{node.image_uri(node.attr 'target')}"#{width_attribute}#{depth_attribute}#{swidth_attribute}#{scale_attribute}#{align_attribute}/>
203
+ <imagedata fileref="#{node.image_uri(node.attr 'target')}"#{width_attribute}#{depth_attribute}#{scale_attribute}#{align_attribute}/>
207
204
  </imageobject>
208
- <textobject><phrase>#{node.attr 'alt'}</phrase></textobject>
205
+ <textobject><phrase>#{node.alt}</phrase></textobject>
209
206
  </mediaobject>)
210
207
 
211
208
  if node.title?
@@ -224,7 +221,7 @@ module Asciidoctor
224
221
  informal = !node.title?
225
222
  listing_attributes = (common_attributes node.id, node.role, node.reftext)
226
223
  if node.style == 'source' && (node.attr? 'language')
227
- numbering = (node.attr? 'linenums') ? 'numbered' : 'unnumbered'
224
+ numbering = (node.attr? 'linenums', nil, false) ? 'numbered' : 'unnumbered'
228
225
  listing_content = %(<programlisting#{informal ? listing_attributes : nil} language="#{node.attr 'language', nil, false}" linenumbering="#{numbering}">#{node.content}</programlisting>)
229
226
  else
230
227
  listing_content = %(<screen#{informal ? listing_attributes : nil}>#{node.content}</screen>)
@@ -256,10 +253,12 @@ module Asciidoctor
256
253
 
257
254
  def stem node
258
255
  if (idx = node.subs.index :specialcharacters)
259
- node.subs.delete :specialcharacters
256
+ node.subs.delete_at idx
257
+ equation = node.content
258
+ idx > 0 ? (node.subs.insert idx, :specialcharacters) : (node.subs.unshift :specialcharacters)
259
+ else
260
+ equation = node.content
260
261
  end
261
- equation = node.content
262
- node.subs.insert idx, :specialcharacters if idx
263
262
  if node.style == 'asciimath'
264
263
  if ((defined? ::AsciiMath) || ((defined? @asciimath_available) ? @asciimath_available :
265
264
  (@asciimath_available = Helpers.require_library 'asciimath', true, :warn)))
@@ -299,13 +298,13 @@ module Asciidoctor
299
298
  result << '</listitem>'
300
299
  end
301
300
  result << %(</orderedlist>)
302
- result * EOL
301
+ result * LF
303
302
  end
304
303
 
305
304
  def open node
306
305
  case node.style
307
306
  when 'abstract'
308
- if node.parent == node.document && node.document.attr?('doctype', 'book')
307
+ if node.parent == node.document && node.document.doctype == 'book'
309
308
  warn 'asciidoctor: WARNING: abstract block cannot be used in a document without a title when doctype is book. Excluding block content.'
310
309
  ''
311
310
  else
@@ -368,7 +367,7 @@ module Asciidoctor
368
367
  end
369
368
  result << (resolve_content node)
370
369
  result << '</blockquote>'
371
- result * EOL
370
+ result * LF
372
371
  end
373
372
 
374
373
  def thematic_break node
@@ -382,7 +381,6 @@ module Asciidoctor
382
381
  end
383
382
 
384
383
  TABLE_PI_NAMES = ['dbhtml', 'dbfo', 'dblatex']
385
- TABLE_SECTIONS = [:head, :foot, :body]
386
384
 
387
385
  def table node
388
386
  has_body = false
@@ -407,10 +405,11 @@ module Asciidoctor
407
405
  node.columns.each do |col|
408
406
  result << %(<colspec colname="col_#{col.attr 'colnumber'}" colwidth="#{col.attr col_width_key}*"/>)
409
407
  end
410
- TABLE_SECTIONS.select {|tblsec| !node.rows[tblsec].empty? }.each do |tblsec|
411
- has_body = true if tblsec == :body
412
- result << %(<t#{tblsec}>)
413
- node.rows[tblsec].each do |row|
408
+ node.rows.by_section.each do |tsec, rows|
409
+ next if rows.empty?
410
+ has_body = true if tsec == :body
411
+ result << %(<t#{tsec}>)
412
+ rows.each do |row|
414
413
  result << '<row>'
415
414
  row.each do |cell|
416
415
  halign_attribute = (cell.attr? 'halign') ? %( align="#{cell.attr 'halign'}") : nil
@@ -419,20 +418,20 @@ module Asciidoctor
419
418
  rowspan_attribute = cell.rowspan ? %( morerows="#{cell.rowspan - 1}") : nil
420
419
  # NOTE <entry> may not have whitespace (e.g., line breaks) as a direct descendant according to DocBook rules
421
420
  entry_start = %(<entry#{halign_attribute}#{valign_attribute}#{colspan_attribute}#{rowspan_attribute}>)
422
- cell_content = if tblsec == :head
423
- cell.text
421
+ if tsec == :head
422
+ cell_content = cell.text
424
423
  else
425
424
  case cell.style
426
425
  when :asciidoc
427
- cell.content
426
+ cell_content = cell.content
428
427
  when :verse
429
- %(<literallayout>#{cell.text}</literallayout>)
428
+ cell_content = %(<literallayout>#{cell.text}</literallayout>)
430
429
  when :literal
431
- %(<literallayout class="monospaced">#{cell.text}</literallayout>)
430
+ cell_content = %(<literallayout class="monospaced">#{cell.text}</literallayout>)
432
431
  when :header
433
- cell.content.map {|text| %(<simpara><emphasis role="strong">#{text}</emphasis></simpara>) }.join
432
+ cell_content = (cell_content = cell.content).empty? ? '' : %(<simpara><emphasis role="strong">#{cell_content * '</emphasis></simpara><simpara><emphasis role="strong">'}</emphasis></simpara>)
434
433
  else
435
- cell.content.map {|text| %(<simpara>#{text}</simpara>) }.join
434
+ cell_content = (cell_content = cell.content).empty? ? '' : %(<simpara>#{cell_content * '</simpara><simpara>'}</simpara>)
436
435
  end
437
436
  end
438
437
  entry_end = (node.document.attr? 'cellbgcolor') ? %(<?dbfo bgcolor="#{node.document.attr 'cellbgcolor'}"?></entry>) : '</entry>'
@@ -440,16 +439,16 @@ module Asciidoctor
440
439
  end
441
440
  result << '</row>'
442
441
  end
443
- result << %(</t#{tblsec}>)
442
+ result << %(</t#{tsec}>)
444
443
  end
445
444
  result << '</tgroup>'
446
445
  result << %(</#{tag_name}>)
447
446
 
448
447
  warn 'asciidoctor: WARNING: tables must have at least one body row' unless has_body
449
- result * EOL
448
+ result * LF
450
449
  end
451
450
 
452
- alias :toc :skip
451
+ alias toc skip
453
452
 
454
453
  def ulist node
455
454
  result = []
@@ -482,7 +481,7 @@ module Asciidoctor
482
481
  result << '</itemizedlist>'
483
482
  end
484
483
 
485
- result * EOL
484
+ result * LF
486
485
  end
487
486
 
488
487
  def verse node
@@ -501,15 +500,15 @@ module Asciidoctor
501
500
  end
502
501
  result << %(<literallayout>#{node.content}</literallayout>)
503
502
  result << '</blockquote>'
504
- result * EOL
503
+ result * LF
505
504
  end
506
505
 
507
- alias :video :skip
506
+ alias video skip
508
507
 
509
508
  def inline_anchor node
510
509
  case node.type
511
510
  when :ref
512
- %(<anchor#{common_attributes node.target, nil, node.text}/>)
511
+ %(<anchor#{common_attributes((id = node.id), nil, node.reftext || %([#{id}]))}/>)
513
512
  when :xref
514
513
  if (path = node.attributes['path'])
515
514
  # QUESTION should we use refid as fallback text instead? (like the html5 backend?)
@@ -521,8 +520,8 @@ module Asciidoctor
521
520
  when :link
522
521
  %(<link xl:href="#{node.target}">#{node.text}</link>)
523
522
  when :bibref
524
- target = node.target
525
- %(<anchor#{common_attributes target, nil, "[#{target}]"}/>[#{target}])
523
+ # NOTE technically node.text should be node.reftext, but subs have already been applied to text
524
+ %(<anchor#{common_attributes node.id, nil, (text = node.text)}/>#{text})
526
525
  else
527
526
  warn %(asciidoctor: WARNING: unknown anchor type: #{node.type.inspect})
528
527
  end
@@ -555,7 +554,7 @@ module Asciidoctor
555
554
  <imageobject>
556
555
  <imagedata fileref="#{node.type == 'icon' ? (node.icon_uri node.target) : (node.image_uri node.target)}"#{width_attribute}#{depth_attribute}/>
557
556
  </imageobject>
558
- <textobject><phrase>#{node.attr 'alt'}</phrase></textobject>
557
+ <textobject><phrase>#{node.alt}</phrase></textobject>
559
558
  </inlinemediaobject>)
560
559
  end
561
560
 
@@ -578,7 +577,7 @@ module Asciidoctor
578
577
  result << %(<indexterm>
579
578
  <primary>#{terms[-1]}</primary>
580
579
  </indexterm>)
581
- result * EOL
580
+ result * LF
582
581
  end
583
582
  end
584
583
 
@@ -586,32 +585,33 @@ module Asciidoctor
586
585
  if (keys = node.attr 'keys').size == 1
587
586
  %(<keycap>#{keys[0]}</keycap>)
588
587
  else
589
- %(<keycombo>#{keys.map {|key| "<keycap>#{key}</keycap>" }.join}</keycombo>)
588
+ %(<keycombo><keycap>#{keys * '</keycap><keycap>'}</keycap></keycombo>)
590
589
  end
591
590
  end
592
591
 
593
592
  def inline_menu node
594
593
  menu = node.attr 'menu'
595
- if !(submenus = node.attr 'submenus').empty?
596
- submenu_path = submenus.map {|submenu| %(<guisubmenu>#{submenu}</guisubmenu> ) }.join.chop
597
- %(<menuchoice><guimenu>#{menu}</guimenu> #{submenu_path} <guimenuitem>#{node.attr 'menuitem'}</guimenuitem></menuchoice>)
598
- elsif (menuitem = node.attr 'menuitem')
599
- %(<menuchoice><guimenu>#{menu}</guimenu> <guimenuitem>#{menuitem}</guimenuitem></menuchoice>)
594
+ if (submenus = node.attr 'submenus').empty?
595
+ if (menuitem = node.attr 'menuitem', nil, false)
596
+ %(<menuchoice><guimenu>#{menu}</guimenu> <guimenuitem>#{menuitem}</guimenuitem></menuchoice>)
597
+ else
598
+ %(<guimenu>#{menu}</guimenu>)
599
+ end
600
600
  else
601
- %(<guimenu>#{menu}</guimenu>)
601
+ %(<menuchoice><guimenu>#{menu}</guimenu> <guisubmenu>#{submenus * '</guisubmenu> <guisubmenu>'}</guisubmenu> <guimenuitem>#{node.attr 'menuitem'}</guimenuitem></menuchoice>)
602
602
  end
603
603
  end
604
604
 
605
605
  (QUOTE_TAGS = {
606
+ :monospaced => ['<literal>', '</literal>', false],
606
607
  :emphasis => ['<emphasis>', '</emphasis>', true],
607
608
  :strong => ['<emphasis role="strong">', '</emphasis>', true],
608
- :monospaced => ['<literal>', '</literal>', false],
609
- :superscript => ['<superscript>', '</superscript>', false],
610
- :subscript => ['<subscript>', '</subscript>', false],
611
609
  :double => ['&#8220;', '&#8221;', true],
612
610
  :single => ['&#8216;', '&#8217;', true],
613
- :mark => ['<emphasis role="marked">', '</emphasis>', false]
614
- }).default = [nil, nil, true]
611
+ :mark => ['<emphasis role="marked">', '</emphasis>', false],
612
+ :superscript => ['<superscript>', '</superscript>', false],
613
+ :subscript => ['<subscript>', '</subscript>', false]
614
+ }).default = ['', '', true]
615
615
 
616
616
  def inline_quoted node
617
617
  if (type = node.type) == :asciimath
@@ -628,11 +628,11 @@ module Asciidoctor
628
628
  else
629
629
  open, close, supports_phrase = QUOTE_TAGS[type]
630
630
  text = node.text
631
- if (role = node.role)
631
+ if node.role
632
632
  if supports_phrase
633
- quoted_text = %(#{open}<phrase role="#{role}">#{text}</phrase>#{close})
633
+ quoted_text = %(#{open}<phrase role="#{node.role}">#{text}</phrase>#{close})
634
634
  else
635
- quoted_text = %(#{open.chop} role="#{role}">#{text}#{close})
635
+ quoted_text = %(#{open.chop} role="#{node.role}">#{text}#{close})
636
636
  end
637
637
  else
638
638
  quoted_text = %(#{open}#{text}#{close})
@@ -658,14 +658,20 @@ module Asciidoctor
658
658
  result << %(<email>#{doc.attr email_key}</email>) if doc.attr? email_key
659
659
  result << '</author>'
660
660
 
661
- result * EOL
661
+ result * LF
662
662
  end
663
663
 
664
664
  def common_attributes id, role = nil, reftext = nil
665
- res = id ? %( xml:id="#{id}") : ''
666
- res = %(#{res} role="#{role}") if role
667
- res = %(#{res} xreflabel="#{reftext}") if reftext
668
- res
665
+ attrs = id ? %( xml:id="#{id}") : ''
666
+ attrs = %(#{attrs} role="#{role}") if role
667
+ if reftext
668
+ if (reftext.include? '<') && ((reftext = reftext.gsub XmlSanitizeRx, '').include? ' ')
669
+ reftext = (reftext.squeeze ' ').strip
670
+ end
671
+ reftext = (reftext.gsub '"', '&quot;') if reftext.include? '"'
672
+ attrs = %(#{attrs} xreflabel="#{reftext}")
673
+ end
674
+ attrs
669
675
  end
670
676
 
671
677
  def doctype_declaration root_tag_name
@@ -673,7 +679,7 @@ module Asciidoctor
673
679
  end
674
680
 
675
681
  def document_info_element doc, info_tag_prefix, use_info_tag_prefix = false
676
- info_tag_prefix = '' unless use_info_tag_prefix
682
+ info_tag_prefix = nil unless use_info_tag_prefix
677
683
  result = []
678
684
  result << %(<#{info_tag_prefix}info>)
679
685
  result << document_title_tags(doc.doctitle :partition => true, :use_fallback => true) unless doc.notitle
@@ -703,6 +709,16 @@ module Asciidoctor
703
709
  result << %(</revision>
704
710
  </revhistory>)
705
711
  end
712
+ unless use_info_tag_prefix
713
+ if (doc.attr? 'front-cover-image') || (doc.attr? 'back-cover-image')
714
+ if (back_cover_tag = cover_tag doc, 'back')
715
+ result << (cover_tag doc, 'front', true)
716
+ result << back_cover_tag
717
+ elsif (front_cover_tag = cover_tag doc, 'front')
718
+ result << front_cover_tag
719
+ end
720
+ end
721
+ end
706
722
  unless (head_docinfo = doc.docinfo).empty?
707
723
  result << head_docinfo
708
724
  end
@@ -721,7 +737,7 @@ module Asciidoctor
721
737
  result << '</refnamediv>'
722
738
  end
723
739
 
724
- result * EOL
740
+ result * LF
725
741
  end
726
742
 
727
743
  def document_ns_attributes doc
@@ -749,5 +765,34 @@ module Asciidoctor
749
765
  def title_tag node, optional = true
750
766
  !optional || node.title? ? %(<title>#{node.title}</title>\n) : nil
751
767
  end
768
+
769
+ def cover_tag doc, face, use_placeholder = false
770
+ if (cover_image = doc.attr %(#{face}-cover-image))
771
+ width_attr = nil
772
+ depth_attr = nil
773
+ if (cover_image.include? ':') && ImageMacroRx =~ cover_image
774
+ cover_image = doc.image_uri $1
775
+ unless $2.empty?
776
+ attrs = (AttributeList.new $2).parse ['alt', 'width', 'height']
777
+ if attrs.key? 'scaledwidth'
778
+ # NOTE scalefit="1" is the default in this case
779
+ width_attr = %( width="#{attrs['scaledwidth']}")
780
+ else
781
+ width_attr = %( contentwidth="#{attrs['width']}") if attrs.key? 'width'
782
+ depth_attr = %( contentdepth="#{attrs['height']}") if attrs.key? 'height'
783
+ end
784
+ end
785
+ end
786
+ %(<cover role="#{face}">
787
+ <mediaobject>
788
+ <imageobject>
789
+ <imagedata fileref="#{cover_image}"#{width_attr}#{depth_attr}/>
790
+ </imageobject>
791
+ </mediaobject>
792
+ </cover>)
793
+ elsif use_placeholder
794
+ %(<cover role="#{face}"/>)
795
+ end
796
+ end
752
797
  end
753
798
  end