asciidoctor 0.1.0 → 0.1.1

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 (40) hide show
  1. data/README.asciidoc +11 -2
  2. data/asciidoctor.gemspec +3 -2
  3. data/lib/asciidoctor.rb +95 -62
  4. data/lib/asciidoctor/abstract_block.rb +7 -5
  5. data/lib/asciidoctor/abstract_node.rb +63 -15
  6. data/lib/asciidoctor/attribute_list.rb +3 -1
  7. data/lib/asciidoctor/backends/base_template.rb +17 -7
  8. data/lib/asciidoctor/backends/docbook45.rb +182 -150
  9. data/lib/asciidoctor/backends/html5.rb +138 -110
  10. data/lib/asciidoctor/block.rb +21 -18
  11. data/lib/asciidoctor/callouts.rb +3 -1
  12. data/lib/asciidoctor/cli/invoker.rb +3 -3
  13. data/lib/asciidoctor/cli/options.rb +6 -6
  14. data/lib/asciidoctor/debug.rb +7 -6
  15. data/lib/asciidoctor/document.rb +197 -25
  16. data/lib/asciidoctor/errors.rb +1 -1
  17. data/lib/asciidoctor/helpers.rb +29 -0
  18. data/lib/asciidoctor/inline.rb +11 -4
  19. data/lib/asciidoctor/lexer.rb +338 -182
  20. data/lib/asciidoctor/list_item.rb +14 -12
  21. data/lib/asciidoctor/reader.rb +423 -206
  22. data/lib/asciidoctor/renderer.rb +59 -15
  23. data/lib/asciidoctor/section.rb +7 -4
  24. data/lib/asciidoctor/substituters.rb +536 -511
  25. data/lib/asciidoctor/table.rb +473 -472
  26. data/lib/asciidoctor/version.rb +1 -1
  27. data/man/asciidoctor.1 +23 -14
  28. data/man/asciidoctor.ad +13 -7
  29. data/test/attributes_test.rb +42 -8
  30. data/test/blocks_test.rb +161 -1
  31. data/test/document_test.rb +134 -16
  32. data/test/invoker_test.rb +14 -6
  33. data/test/lexer_test.rb +45 -18
  34. data/test/lists_test.rb +79 -0
  35. data/test/paragraphs_test.rb +9 -1
  36. data/test/reader_test.rb +456 -19
  37. data/test/sections_test.rb +19 -0
  38. data/test/substitutions_test.rb +14 -12
  39. data/test/tables_test.rb +10 -10
  40. metadata +3 -5
@@ -1,6 +1,7 @@
1
+ module Asciidoctor
1
2
  # Public: Methods for rendering Asciidoc Documents, Sections, and Blocks
2
3
  # using eRuby templates.
3
- class Asciidoctor::Renderer
4
+ class Renderer
4
5
  attr_reader :compact
5
6
 
6
7
  # Public: Initialize an Asciidoctor::Renderer object.
@@ -15,10 +16,10 @@ class Asciidoctor::Renderer
15
16
  case backend
16
17
  when 'html5', 'docbook45'
17
18
  eruby = load_eruby options[:eruby]
18
- #Asciidoctor.require_library 'asciidoctor/backends/' + backend
19
+ #Helpers.require_library 'asciidoctor/backends/' + backend
19
20
  require 'asciidoctor/backends/' + backend
20
21
  # Load up all the template classes that we know how to render for this backend
21
- Asciidoctor::BaseTemplate.template_classes.each do |tc|
22
+ BaseTemplate.template_classes.each do |tc|
22
23
  if tc.to_s.downcase.include?('::' + backend + '::') # optimization
23
24
  view_name, view_backend = self.class.extract_view_mapping(tc)
24
25
  if view_backend == backend
@@ -27,29 +28,71 @@ class Asciidoctor::Renderer
27
28
  end
28
29
  end
29
30
  else
30
- Asciidoctor.debug { "No built-in templates for backend: #{backend}" }
31
+ Debug.debug { "No built-in templates for backend: #{backend}" }
31
32
  end
32
33
 
33
34
  # If user passed in a template dir, let them override our base templates
34
35
  if template_dir = options.delete(:template_dir)
35
- Asciidoctor.require_library 'tilt'
36
+ Helpers.require_library 'tilt'
37
+
38
+ template_glob = '*'
39
+ if (engine = options[:template_engine])
40
+ template_glob = "*.#{engine}"
41
+ # example: templates/haml
42
+ if File.directory? File.join(template_dir, engine)
43
+ template_dir = File.join template_dir, engine
44
+ end
45
+ end
46
+
47
+ # example: templates/html5 or templates/haml/html5
48
+ if File.directory? File.join(template_dir, options[:backend])
49
+ template_dir = File.join template_dir, options[:backend]
50
+ end
36
51
 
37
- Asciidoctor.debug {
52
+ view_opts = {
53
+ :erb => { :trim => '<>' },
54
+ :haml => { :attr_wrapper => '"', :ugly => true, :escape_attrs => false },
55
+ :slim => { :disable_escape => true, :sort_attrs => false, :pretty => false }
56
+ }
57
+
58
+ if backend == 'html5'
59
+ view_opts[:haml][:format] = view_opts[:slim][:format] = :html5
60
+ end
61
+
62
+ Debug.debug {
38
63
  msg = []
39
64
  msg << "Views going in are like so:"
40
65
  msg << @views.map {|k, v| "#{k}: #{v}"}
41
66
  msg << '=' * 60
42
67
  msg * "\n"
43
68
  }
69
+
70
+ slim_loaded = false
71
+ helpers = nil
44
72
 
45
73
  # Grab the files in the top level of the directory (we're not traversing)
46
- files = Dir.glob(File.join(template_dir, '*')).select{|f| File.stat(f).file?}
47
- files.inject(@views) do |view_hash, view|
48
- name = File.basename(view).split('.').first
49
- view_hash.merge!(name => Tilt.new(view, nil, :trim => '<>', :attr_wrapper => '"'))
74
+ Dir.glob(File.join(template_dir, template_glob)).
75
+ select{|f| File.file? f }.each do |template|
76
+ basename = File.basename(template)
77
+ if basename == 'helpers.rb'
78
+ helpers = template
79
+ next
80
+ end
81
+ name_parts = basename.split('.')
82
+ next if name_parts.size < 2
83
+ view_name = name_parts.first
84
+ ext_name = name_parts.last
85
+ if ext_name == 'slim' && !slim_loaded
86
+ # slim doesn't get loaded by Tilt
87
+ Helpers.require_library 'slim'
88
+ end
89
+ next unless Tilt.registered? ext_name
90
+ @views[view_name] = Tilt.new(template, nil, view_opts[ext_name.to_sym])
50
91
  end
51
92
 
52
- Asciidoctor.debug {
93
+ require helpers unless helpers.nil?
94
+
95
+ Debug.debug {
53
96
  msg = []
54
97
  msg << "Views going in are like so:"
55
98
  msg << @views.map {|k, v| "#{k}: #{v}"}
@@ -72,7 +115,7 @@ class Asciidoctor::Renderer
72
115
  if !@views.has_key? view
73
116
  raise "Couldn't find a view in @views for #{view}"
74
117
  else
75
- Asciidoctor.debug { "View for #{view} is #{@views[view]}, object is #{object}" }
118
+ Debug.debug { "View for #{view} is #{@views[view]}, object is #{object}" }
76
119
  end
77
120
 
78
121
  ret = @views[view].render(object, locals)
@@ -83,8 +126,8 @@ class Asciidoctor::Renderer
83
126
  STDERR.puts "Rendering:"
84
127
  @render_stack.each do |stack_view, stack_obj|
85
128
  obj_info = case stack_obj
86
- when Asciidoctor::Section; "SECTION #{stack_obj.title}"
87
- when Asciidoctor::Block;
129
+ when Section; "SECTION #{stack_obj.title}"
130
+ when Block;
88
131
  if stack_obj.context == :dlist
89
132
  dt_list = stack_obj.buffer.map{|dt,dd| dt.content.strip}.join(', ')
90
133
  "BLOCK :dlist (#{dt_list})"
@@ -122,7 +165,7 @@ class Asciidoctor::Renderer
122
165
  name = 'erb'
123
166
  end
124
167
 
125
- Asciidoctor.require_library name
168
+ Helpers.require_library name
126
169
 
127
170
  if name == 'erb'
128
171
  ::ERB
@@ -176,3 +219,4 @@ class Asciidoctor::Renderer
176
219
  end
177
220
 
178
221
  end
222
+ end
@@ -1,3 +1,4 @@
1
+ module Asciidoctor
1
2
  # Public: Methods for managing sections of AsciiDoc content in a document.
2
3
  # The section responds as an Array of content blocks by delegating
3
4
  # block-related methods to its @blocks Array.
@@ -17,7 +18,7 @@
17
18
  # section << new_block
18
19
  # section.size
19
20
  # => 1
20
- class Asciidoctor::Section < Asciidoctor::AbstractBlock
21
+ class Section < AbstractBlock
21
22
 
22
23
  # Public: Get/Set the Integer index of this section within the parent block
23
24
  attr_accessor :index
@@ -36,7 +37,7 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
36
37
  if level.nil? && !parent.nil?
37
38
  @level = parent.level + 1
38
39
  end
39
- if parent.is_a?(::Asciidoctor::Section) && parent.special
40
+ if parent.is_a?(Section) && parent.special
40
41
  @special = true
41
42
  else
42
43
  @special = false
@@ -89,7 +90,8 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
89
90
  # Public: Get the rendered String content for this Section and all its child
90
91
  # Blocks.
91
92
  def render
92
- Asciidoctor.debug { "Now rendering section for #{self}" }
93
+ Debug.debug { "Now rendering section for #{self}" }
94
+ @document.playback_attributes @attributes
93
95
  renderer.render('section', self)
94
96
  end
95
97
 
@@ -152,7 +154,7 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
152
154
  # Returns the section number as a String
153
155
  def sectnum(delimiter = '.', append = nil)
154
156
  append ||= (append == false ? '' : delimiter)
155
- if !@level.nil? && @level > 1 && @parent.is_a?(::Asciidoctor::Section)
157
+ if !@level.nil? && @level > 1 && @parent.is_a?(Section)
156
158
  "#{@parent.sectnum(delimiter)}#{@index + 1}#{append}"
157
159
  else
158
160
  "#{@index + 1}#{append}"
@@ -171,3 +173,4 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
171
173
  end
172
174
  end
173
175
  end
176
+ end
@@ -1,587 +1,612 @@
1
+ module Asciidoctor
1
2
  # Public: Methods to perform substitutions on lines of AsciiDoc text. This module
2
3
  # is intented to be mixed-in to Section and Block to provide operations for performing
3
4
  # the necessary substitutions.
4
- module Asciidoctor
5
- module Substituters
5
+ module Substituters
6
+
7
+ COMPOSITE_SUBS = {
8
+ :none => [],
9
+ :normal => [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements],
10
+ :verbatim => [:specialcharacters, :callouts]
11
+ }
12
+
13
+ SUB_OPTIONS = COMPOSITE_SUBS.keys + COMPOSITE_SUBS[:normal]
14
+
15
+ # Internal: A String Array of passthough (unprocessed) text captured from this block
16
+ attr_reader :passthroughs
17
+
18
+ # Public: Apply the specified substitutions to the lines of text
19
+ #
20
+ # lines - The lines of text to process. Can be a String or a String Array
21
+ # subs - The substitutions to perform. Can be a Symbol or a Symbol Array (default: COMPOSITE_SUBS[:normal])
22
+ #
23
+ # returns Either a String or String Array, whichever matches the type of the first argument
24
+ def apply_subs(lines, subs = COMPOSITE_SUBS[:normal])
25
+ if subs.nil?
26
+ subs = []
27
+ elsif subs.is_a? Symbol
28
+ subs = [subs]
29
+ end
6
30
 
7
- COMPOSITE_SUBS = {
8
- :none => [],
9
- :normal => [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements],
10
- :verbatim => [:specialcharacters, :callouts]
31
+ if !subs.empty?
32
+ # QUESTION is this most efficient operation?
33
+ subs = subs.map {|key|
34
+ COMPOSITE_SUBS.has_key?(key) ? COMPOSITE_SUBS[key] : key
35
+ }.flatten
36
+ end
37
+
38
+ return lines if subs.empty?
39
+
40
+ multiline = lines.is_a?(Array)
41
+ text = multiline ? lines.join : lines
42
+
43
+ passthroughs = subs.include?(:macros)
44
+ text = extract_passthroughs(text) if passthroughs
45
+
46
+ subs.each {|type|
47
+ case type
48
+ when :specialcharacters
49
+ text = sub_specialcharacters(text)
50
+ when :quotes
51
+ text = sub_quotes(text)
52
+ when :attributes
53
+ text = sub_attributes(text.lines.entries).join
54
+ when :replacements
55
+ text = sub_replacements(text)
56
+ when :macros
57
+ text = sub_macros(text)
58
+ when :callouts
59
+ text = sub_callouts(text)
60
+ when :post_replacements
61
+ text = sub_post_replacements(text)
62
+ else
63
+ puts "asciidoctor: WARNING: unknown substitution type #{type}"
64
+ end
11
65
  }
66
+ text = restore_passthroughs(text) if passthroughs
67
+
68
+ multiline ? text.lines.entries : text
69
+ end
12
70
 
13
- SUB_OPTIONS = COMPOSITE_SUBS.keys + COMPOSITE_SUBS[:normal]
71
+ # Public: Apply normal substitutions.
72
+ #
73
+ # lines - The lines of text to process. Can be a String or a String Array
74
+ #
75
+ # returns - A String with normal substitutions performed
76
+ def apply_normal_subs(lines)
77
+ apply_subs(lines.is_a?(Array) ? lines.join : lines)
78
+ end
14
79
 
15
- # Internal: A String Array of passthough (unprocessed) text captured from this block
16
- attr_reader :passthroughs
80
+ # Public: Apply substitutions for titles.
81
+ #
82
+ # title - The String title to process
83
+ #
84
+ # returns - A String with title substitutions performed
85
+ def apply_title_subs(title)
86
+ apply_subs(title, [:specialcharacters, :quotes, :replacements, :macros, :attributes, :post_replacements])
87
+ end
17
88
 
18
- # Public: Apply the specified substitutions to the lines of text
19
- #
20
- # lines - The lines of text to process. Can be a String or a String Array
21
- # subs - The substitutions to perform. Can be a Symbol or a Symbol Array (default: COMPOSITE_SUBS[:normal])
22
- #
23
- # returns Either a String or String Array, whichever matches the type of the first argument
24
- def apply_subs(lines, subs = COMPOSITE_SUBS[:normal])
25
- if subs.nil?
26
- subs = []
27
- elsif subs.is_a? Symbol
28
- subs = [subs]
89
+ # Public: Apply substitutions for titles
90
+ #
91
+ # lines - A String Array containing the lines of text process
92
+ #
93
+ # returns - A String with literal (verbatim) substitutions performed
94
+ def apply_literal_subs(lines)
95
+ if @document.attributes['basebackend'] == 'html' && attr('style') == 'source' &&
96
+ @document.attributes['source-highlighter'] == 'coderay' && attr?('language')
97
+ sub_callouts(highlight_source(lines.join))
98
+ else
99
+ apply_subs(lines.join, COMPOSITE_SUBS[:verbatim])
100
+ end
101
+ end
102
+
103
+ # Public: Apply substitutions for header metadata and attribute assignments
104
+ #
105
+ # text - String containing the text process
106
+ #
107
+ # returns - A String with header substitutions performed
108
+ def apply_header_subs(text)
109
+ apply_subs(text, [:specialcharacters, :attributes])
110
+ end
111
+
112
+ # Public: Apply substitutions for passthrough text
113
+ #
114
+ # lines - A String Array containing the lines of text process
115
+ #
116
+ # returns - A String Array with passthrough substitutions performed
117
+ def apply_passthrough_subs(lines)
118
+ if attr? 'subs'
119
+ subs = resolve_subs(attr('subs'))
120
+ else
121
+ subs = [:attributes, :macros]
122
+ end
123
+ apply_subs(lines.join, subs)
124
+ end
125
+
126
+ # Internal: Extract the passthrough text from the document for reinsertion after processing.
127
+ #
128
+ # text - The String from which to extract passthrough fragements
129
+ #
130
+ # returns - The text with the passthrough region substituted with placeholders
131
+ def extract_passthroughs(text)
132
+ result = text.dup
133
+
134
+ result.gsub!(REGEXP[:pass_macro]) {
135
+ # alias match for Ruby 1.8.7 compat
136
+ m = $~
137
+ # honor the escape
138
+ if m[0].start_with? '\\'
139
+ next m[0][1..-1]
29
140
  end
30
141
 
31
- if !subs.empty?
32
- # QUESTION is this most efficient operation?
33
- subs = subs.map {|key|
34
- COMPOSITE_SUBS.has_key?(key) ? COMPOSITE_SUBS[key] : key
35
- }.flatten
142
+ if m[1] == '$$'
143
+ subs = [:specialcharacters]
144
+ elsif !m[3].nil? && !m[3].empty?
145
+ subs = resolve_subs(m[3])
146
+ else
147
+ subs = []
36
148
  end
37
149
 
38
- return lines if subs.empty?
150
+ # TODO move unescaping closing square bracket to an operation
151
+ @passthroughs << {:text => m[2] || m[4].gsub('\]', ']'), :subs => subs}
152
+ index = @passthroughs.size - 1
153
+ "\x0#{index}\x0"
154
+ } unless !(result.include?('+++') || result.include?('$$') || result.include?('pass:'))
39
155
 
40
- multiline = lines.is_a?(Array)
41
- text = multiline ? lines.join : lines
156
+ result.gsub!(REGEXP[:pass_lit]) {
157
+ # alias match for Ruby 1.8.7 compat
158
+ m = $~
42
159
 
43
- passthroughs = subs.include?(:macros)
44
- text = extract_passthroughs(text) if passthroughs
160
+ # honor the escape
161
+ if m[2].start_with? '\\'
162
+ next "#{m[1]}#{m[2][1..-1]}"
163
+ end
45
164
 
46
- subs.each {|type|
47
- case type
48
- when :specialcharacters
49
- text = sub_specialcharacters(text)
50
- when :quotes
51
- text = sub_quotes(text)
52
- when :attributes
53
- text = sub_attributes(text.lines.entries).join
54
- when :replacements
55
- text = sub_replacements(text)
56
- when :macros
57
- text = sub_macros(text)
58
- when :callouts
59
- text = sub_callouts(text)
60
- when :post_replacements
61
- text = sub_post_replacements(text)
62
- else
63
- puts "asciidoctor: WARNING: unknown substitution type #{type}"
64
- end
65
- }
66
- text = restore_passthroughs(text) if passthroughs
165
+ @passthroughs << {:text => m[3], :subs => [:specialcharacters], :literal => true}
166
+ index = @passthroughs.size - 1
167
+ "#{m[1]}\x0#{index}\x0"
168
+ } unless !result.include?('`')
67
169
 
68
- multiline ? text.lines.entries : text
69
- end
170
+ result
171
+ end
70
172
 
71
- # Public: Apply normal substitutions.
72
- #
73
- # lines - The lines of text to process. Can be a String or a String Array
74
- #
75
- # returns - A String with normal substitutions performed
76
- def apply_normal_subs(lines)
77
- apply_subs(lines.is_a?(Array) ? lines.join : lines)
78
- end
173
+ # Internal: Restore the passthrough text by reinserting into the placeholder positions
174
+ #
175
+ # text - The String text into which to restore the passthrough text
176
+ #
177
+ # returns The String text with the passthrough text restored
178
+ def restore_passthroughs(text)
179
+ return text if @passthroughs.nil? || @passthroughs.empty? || !text.include?("\x0")
180
+
181
+ text.gsub(REGEXP[:pass_placeholder]) {
182
+ pass = @passthroughs[$1.to_i];
183
+ text = apply_subs(pass[:text], pass.fetch(:subs, []))
184
+ pass[:literal] ? Inline.new(self, :quoted, text, :type => :monospaced).render : text
185
+ }
186
+ end
79
187
 
80
- # Public: Apply substitutions for titles.
81
- #
82
- # title - The String title to process
83
- #
84
- # returns - A String with title substitutions performed
85
- def apply_title_subs(title)
86
- apply_subs(title, [:specialcharacters, :quotes, :replacements, :macros, :attributes, :post_replacements])
87
- end
188
+ # Public: Substitute special characters (i.e., encode XML)
189
+ #
190
+ # Special characters are defined in the Asciidoctor::SPECIAL_CHARS Array constant
191
+ #
192
+ # text - The String text to process
193
+ #
194
+ # returns The String text with special characters replaced
195
+ def sub_specialcharacters(text)
196
+ # this syntax only available in Ruby 1.9
197
+ #text.gsub(SPECIAL_CHARS_PATTERN, SPECIAL_CHARS)
198
+
199
+ text.gsub(SPECIAL_CHARS_PATTERN) { SPECIAL_CHARS[$&] }
200
+ end
88
201
 
89
- # Public: Apply substitutions for titles
90
- #
91
- # lines - A String Array containing the lines of text process
92
- #
93
- # returns - A String with literal (verbatim) substitutions performed
94
- def apply_literal_subs(lines)
95
- if @document.attr('basebackend') == 'html' && attr('style') == 'source' &&
96
- @document.attr('source-highlighter') == 'coderay' && attr?('language')
97
- sub_callouts(highlight_source(lines.join))
98
- else
99
- apply_subs(lines.join, COMPOSITE_SUBS[:verbatim])
100
- end
101
- end
202
+ # Public: Substitute quoted text (includes emphasis, strong, monospaced, etc)
203
+ #
204
+ # text - The String text to process
205
+ #
206
+ # returns The String text with quoted text rendered using the backend templates
207
+ def sub_quotes(text)
208
+ result = text.dup
102
209
 
103
- # Public: Apply substitutions for header metadata and attribute assignments
104
- #
105
- # text - String containing the text process
106
- #
107
- # returns - A String with header substitutions performed
108
- def apply_header_subs(text)
109
- apply_subs(text, [:specialcharacters, :attributes])
110
- end
210
+ QUOTE_SUBS.each {|type, scope, pattern|
211
+ result.gsub!(pattern) { transform_quoted_text($~, type, scope) }
212
+ }
213
+
214
+ result
215
+ end
111
216
 
112
- # Public: Apply substitutions for passthrough text
113
- #
114
- # lines - A String Array containing the lines of text process
115
- #
116
- # returns - A String Array with passthrough substitutions performed
117
- def apply_passthrough_subs(lines)
118
- if attr? 'subs'
119
- subs = resolve_subs(attr('subs'))
120
- else
121
- subs = [:attributes, :macros]
122
- end
123
- apply_subs(lines.join, subs)
124
- end
217
+ # Public: Substitute replacement characters (e.g., copyright, trademark, etc)
218
+ #
219
+ # text - The String text to process
220
+ #
221
+ # returns The String text with the replacement characters substituted
222
+ def sub_replacements(text)
223
+ result = text.dup
125
224
 
126
- # Internal: Extract the passthrough text from the document for reinsertion after processing.
127
- #
128
- # text - The String from which to extract passthrough fragements
129
- #
130
- # returns - The text with the passthrough region substituted with placeholders
131
- def extract_passthroughs(text)
132
- result = text.dup
225
+ REPLACEMENTS.each {|pattern, replacement|
226
+ result.gsub!(pattern, replacement)
227
+ }
228
+
229
+ result
230
+ end
133
231
 
134
- result.gsub!(REGEXP[:pass_macro]) {
232
+ # Public: Substitute attribute references
233
+ #
234
+ # Attribute references are in the format {name}.
235
+ #
236
+ # If an attribute referenced in the line is missing, the line is dropped.
237
+ #
238
+ # text - The String text to process
239
+ #
240
+ # returns The String text with the attribute references replaced with attribute values
241
+ #--
242
+ # NOTE it's necessary to perform this substitution line-by-line
243
+ # so that a missing key doesn't wipe out the whole block of data
244
+ def sub_attributes(data)
245
+ return data if data.nil? || data.empty?
246
+
247
+ # normalizes data type to an array (string becomes single-element array)
248
+ lines = Array(data)
249
+
250
+ result = lines.map {|line|
251
+ reject = false
252
+ subject = line.dup
253
+ subject.gsub!(REGEXP[:attr_ref]) {
135
254
  # alias match for Ruby 1.8.7 compat
136
255
  m = $~
137
- # honor the escape
138
- if m[0].start_with? '\\'
139
- next m[0][1..-1]
140
- end
141
-
142
- if m[1] == '$$'
143
- subs = [:specialcharacters]
144
- elsif !m[3].nil? && !m[3].empty?
145
- subs = resolve_subs(m[3])
256
+ key = m[2].downcase
257
+ # escaped attribute
258
+ if !$1.empty? || !$3.empty?
259
+ "{#$2}"
260
+ elsif m[2].start_with?('counter:')
261
+ args = m[2].split(':')
262
+ @document.counter(args[1], args[2])
263
+ elsif m[2].start_with?('counter2:')
264
+ args = m[2].split(':')
265
+ @document.counter(args[1], args[2])
266
+ ''
267
+ elsif document.attributes.has_key? key
268
+ @document.attributes[key]
269
+ elsif INTRINSICS.has_key? key
270
+ INTRINSICS[key]
146
271
  else
147
- subs = []
272
+ Debug.debug { "Missing attribute: #{m[2]}, line marked for removal" }
273
+ reject = true
274
+ break '{undefined}'
148
275
  end
276
+ } if subject.include?('{')
149
277
 
150
- @passthroughs << {:text => m[2] || m[4].gsub('\]', ']'), :subs => subs}
151
- index = @passthroughs.size - 1
152
- "\x0#{index}\x0"
153
- } unless !(result.include?('+++') || result.include?('$$') || result.include?('pass:'))
278
+ !reject ? subject : nil
279
+ }.compact
154
280
 
155
- result.gsub!(REGEXP[:pass_lit]) {
281
+ data.is_a?(String) ? result.join : result
282
+ end
283
+
284
+ # Public: Substitute inline macros (e.g., links, images, etc)
285
+ #
286
+ # Replace inline macros, which may span multiple lines, in the provided text
287
+ #
288
+ # text - The String text to process
289
+ #
290
+ # returns The String with the inline macros rendered using the backend templates
291
+ def sub_macros(text)
292
+ return text if text.nil? || text.empty?
293
+
294
+ result = text.dup
295
+
296
+ # some look ahead assertions to cut unnecessary regex calls
297
+ found = {}
298
+ found[:square_bracket] = result.include?('[')
299
+ found[:round_bracket] = result.include?('(')
300
+ found[:colon] = result.include?(':')
301
+ found[:macroish] = (found[:square_bracket] && found[:colon])
302
+ found[:macroish_short_form] = (found[:square_bracket] && found[:colon] && result.include?(':['))
303
+ found[:uri] = (found[:colon] && result.include?('://'))
304
+ link_attrs = @document.attributes.has_key?('linkattrs')
305
+
306
+ if found[:macroish] && result.include?('image:')
307
+ # image:filename.png[Alt Text]
308
+ result.gsub!(REGEXP[:image_macro]) {
156
309
  # alias match for Ruby 1.8.7 compat
157
310
  m = $~
158
-
159
311
  # honor the escape
160
- if m[2].start_with? '\\'
161
- next "#{m[1]}#{m[2][1..-1]}"
312
+ if m[0].start_with? '\\'
313
+ next m[0][1..-1]
162
314
  end
163
-
164
- @passthroughs << {:text => m[3], :subs => [:specialcharacters], :literal => true}
165
- index = @passthroughs.size - 1
166
- "#{m[1]}\x0#{index}\x0"
167
- } unless !result.include?('`')
168
-
169
- result
170
- end
171
-
172
- # Internal: Restore the passthrough text by reinserting into the placeholder positions
173
- #
174
- # text - The String text into which to restore the passthrough text
175
- #
176
- # returns The String text with the passthrough text restored
177
- def restore_passthroughs(text)
178
- return text if @passthroughs.nil? || @passthroughs.empty? || !text.include?("\x0")
179
-
180
- text.gsub(REGEXP[:pass_placeholder]) {
181
- pass = @passthroughs[$1.to_i];
182
- text = apply_subs(pass[:text], pass.fetch(:subs, []))
183
- pass[:literal] ? Inline.new(self, :quoted, text, :type => :monospaced).render : text
315
+ target = sub_attributes(m[1])
316
+ @document.register(:images, target)
317
+ attrs = parse_attributes(m[2], ['alt', 'width', 'height'])
318
+ if !attrs.has_key?('alt') || attrs['alt'].empty?
319
+ attrs['alt'] = File.basename(target, File.extname(target))
320
+ end
321
+ Inline.new(self, :image, nil, :target => target, :attributes => attrs).render
184
322
  }
185
323
  end
186
324
 
187
- # Public: Substitute special characters (i.e., encode XML)
188
- #
189
- # Special characters are defined in the Asciidoctor::SPECIAL_CHARS Array constant
190
- #
191
- # text - The String text to process
192
- #
193
- # returns The String text with special characters replaced
194
- def sub_specialcharacters(text)
195
- # this syntax only available in Ruby 1.9
196
- #text.gsub(SPECIAL_CHARS_PATTERN, SPECIAL_CHARS)
197
-
198
- text.gsub(SPECIAL_CHARS_PATTERN) { SPECIAL_CHARS[$&] }
199
- end
200
-
201
- # Public: Substitute quoted text (includes emphasis, strong, monospaced, etc)
202
- #
203
- # text - The String text to process
204
- #
205
- # returns The String text with quoted text rendered using the backend templates
206
- def sub_quotes(text)
207
- result = text.dup
325
+ if found[:macroish_short_form] || found[:round_bracket]
326
+ # indexterm:[Tigers,Big cats]
327
+ # (((Tigers,Big cats)))
328
+ result.gsub!(REGEXP[:indexterm_macro]) {
329
+ # alias match for Ruby 1.8.7 compat
330
+ m = $~
331
+ # honor the escape
332
+ if m[0].start_with? '\\'
333
+ next m[0][1..-1]
334
+ end
208
335
 
209
- QUOTE_SUBS.each {|type, scope, pattern|
210
- result.gsub!(pattern) { transform_quoted_text($~, type, scope) }
336
+ terms = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']').split(REGEXP[:csv_delimiter])
337
+ document.register(:indexterms, [*terms])
338
+ Inline.new(self, :indexterm, text, :attributes => {'terms' => terms}).render
211
339
  }
212
-
213
- result
214
- end
215
-
216
- # Public: Substitute replacement characters (e.g., copyright, trademark, etc)
217
- #
218
- # text - The String text to process
219
- #
220
- # returns The String text with the replacement characters substituted
221
- def sub_replacements(text)
222
- result = text.dup
340
+
341
+ # indexterm2:[Tigers]
342
+ # ((Tigers))
343
+ result.gsub!(REGEXP[:indexterm2_macro]) {
344
+ # alias match for Ruby 1.8.7 compat
345
+ m = $~
346
+ # honor the escape
347
+ if m[0].start_with? '\\'
348
+ next m[0][1..-1]
349
+ end
223
350
 
224
- REPLACEMENTS.each {|pattern, replacement|
225
- result.gsub!(pattern, replacement)
351
+ text = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']')
352
+ document.register(:indexterms, [text])
353
+ Inline.new(self, :indexterm, text, :type => :visible).render
226
354
  }
227
-
228
- result
229
355
  end
230
356
 
231
- # Public: Substitute attribute references
232
- #
233
- # Attribute references are in the format {name}.
234
- #
235
- # If an attribute referenced in the line is missing, the line is dropped.
236
- #
237
- # text - The String text to process
238
- #
239
- # returns The String text with the attribute references replaced with attribute values
240
- #--
241
- # NOTE it's necessary to perform this substitution line-by-line
242
- # so that a missing key doesn't wipe out the whole block of data
243
- def sub_attributes(data)
244
- return data if data.nil? || data.empty?
245
-
246
- # normalizes data type to an array (string becomes single-element array)
247
- lines = Array(data)
248
-
249
- result = lines.map {|line|
250
- reject = false
251
- subject = line.dup
252
- subject.gsub!(REGEXP[:attr_ref]) {
253
- # alias match for Ruby 1.8.7 compat
254
- m = $~
255
- key = m[2].downcase
256
- # escaped attribute
257
- if !$1.empty? || !$3.empty?
258
- "{#$2}"
259
- elsif m[2].start_with?('counter:')
260
- args = m[2].split(':')
261
- @document.counter(args[1], args[2])
262
- elsif m[2].start_with?('counter2:')
263
- args = m[2].split(':')
264
- @document.counter(args[1], args[2])
265
- ''
266
- elsif document.attributes.has_key? key
267
- @document.attributes[key]
268
- elsif INTRINSICS.has_key? key
269
- INTRINSICS[key]
357
+ if found[:uri]
358
+ # inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
359
+ result.gsub!(REGEXP[:link_inline]) {
360
+ # alias match for Ruby 1.8.7 compat
361
+ m = $~
362
+ # honor the escape
363
+ if m[2].start_with? '\\'
364
+ next "#{m[1]}#{m[2][1..-1]}#{m[3]}"
365
+ # not a valid macro syntax w/o trailing square brackets
366
+ # we probably shouldn't even get here...our regex is doing too much
367
+ elsif m[1] == 'link:' && m[3].nil?
368
+ next m[0]
369
+ end
370
+ prefix = (m[1] != 'link:' ? m[1] : '')
371
+ target = m[2]
372
+ # strip the <> around the link
373
+ if prefix.end_with? '&lt;'
374
+ prefix = prefix[0..-5]
375
+ end
376
+ if target.end_with? '&gt;'
377
+ target = target[0..-5]
378
+ end
379
+ @document.register(:links, target)
380
+
381
+ attrs = nil
382
+ #text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
383
+ if !m[3].to_s.empty?
384
+ if link_attrs && (m[3].start_with?('"') || m[3].include?(','))
385
+ attrs = parse_attributes(sub_attributes(m[3].gsub('\]', ']')))
386
+ text = attrs[1]
270
387
  else
271
- Asciidoctor.debug { "Missing attribute: #{m[2]}, line marked for removal" }
272
- reject = true
273
- break '{undefined}'
388
+ text = sub_attributes(m[3].gsub('\]', ']'))
274
389
  end
275
- } if subject.include?('{')
276
-
277
- !reject ? subject : nil
278
- }.compact
390
+ else
391
+ text = ''
392
+ end
279
393
 
280
- data.is_a?(String) ? result.join : result
394
+ "#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target, :attributes => attrs).render}"
395
+ }
281
396
  end
282
397
 
283
- # Public: Substitute inline macros (e.g., links, images, etc)
284
- #
285
- # Replace inline macros, which may span multiple lines, in the provided text
286
- #
287
- # text - The String text to process
288
- #
289
- # returns The String with the inline macros rendered using the backend templates
290
- def sub_macros(text)
291
- return text if text.nil? || text.empty?
292
-
293
- result = text.dup
294
-
295
- # some look ahead assertions to cut unnecessary regex calls
296
- found = {}
297
- found[:square_bracket] = result.include?('[')
298
- found[:round_bracket] = result.include?('(')
299
- found[:macroish] = (found[:square_bracket] && result.include?(':'))
300
- found[:macroish_short_form] = (found[:square_bracket] && result.include?(':['))
301
- found[:uri] = result.include?('://')
302
-
303
- if found[:macroish] && result.include?('image:')
304
- # image:filename.png[Alt Text]
305
- result.gsub!(REGEXP[:image_macro]) {
306
- # alias match for Ruby 1.8.7 compat
307
- m = $~
308
- # honor the escape
309
- if m[0].start_with? '\\'
310
- next m[0][1..-1]
311
- end
312
- target = sub_attributes(m[1])
313
- @document.register(:images, target)
314
- attrs = parse_attributes(m[2], ['alt', 'width', 'height'])
315
- if !attrs.has_key?('alt') || attrs['alt'].empty?
316
- attrs['alt'] = File.basename(target, File.extname(target))
317
- end
318
- Inline.new(self, :image, nil, :target => target, :attributes => attrs).render
319
- }
320
- end
321
-
322
- if found[:macroish_short_form] || found[:round_bracket]
323
- # indexterm:[Tigers,Big cats]
324
- # (((Tigers,Big cats)))
325
- result.gsub!(REGEXP[:indexterm_macro]) {
326
- # alias match for Ruby 1.8.7 compat
327
- m = $~
328
- # honor the escape
329
- if m[0].start_with? '\\'
330
- next m[0][1..-1]
331
- end
332
-
333
- terms = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']').split(REGEXP[:csv_delimiter])
334
- document.register(:indexterms, [*terms])
335
- Inline.new(self, :indexterm, text, :attributes => {'terms' => terms}).render
336
- }
337
-
338
- # indexterm2:[Tigers]
339
- # ((Tigers))
340
- result.gsub!(REGEXP[:indexterm2_macro]) {
341
- # alias match for Ruby 1.8.7 compat
342
- m = $~
343
- # honor the escape
344
- if m[0].start_with? '\\'
345
- next m[0][1..-1]
346
- end
347
-
348
- text = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']')
349
- document.register(:indexterms, [text])
350
- Inline.new(self, :indexterm, text, :type => :visible).render
351
- }
352
- end
353
-
354
- if found[:uri]
355
- # inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
356
- result.gsub!(REGEXP[:link_inline]) {
357
- # alias match for Ruby 1.8.7 compat
358
- m = $~
359
- # honor the escape
360
- if m[2].start_with? '\\'
361
- next "#{m[1]}#{m[2][1..-1]}#{m[3]}"
362
- # not a valid macro syntax w/o trailing square brackets
363
- # we probably shouldn't even get here...our regex is doing too much
364
- elsif m[1] == 'link:' && m[3].nil?
365
- next m[0]
366
- end
367
- prefix = (m[1] != 'link:' ? m[1] : '')
368
- target = m[2]
369
- # strip the <> around the link
370
- if prefix.end_with? '&lt;'
371
- prefix = prefix[0..-5]
372
- end
373
- if target.end_with? '&gt;'
374
- target = target[0..-5]
375
- end
376
- @document.register(:links, target)
377
- text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
378
- "#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target).render}"
379
- }
380
- end
381
-
382
- if found[:macroish] && result.include?('link:')
383
- # inline link macros, link:target[text]
384
- result.gsub!(REGEXP[:link_macro]) {
385
- # alias match for Ruby 1.8.7 compat
386
- m = $~
387
- # honor the escape
388
- if m[0].start_with? '\\'
389
- next m[0][1..-1]
390
- end
391
- target = m[1]
392
- @document.register(:links, target)
398
+ if found[:macroish] && result.include?('link:')
399
+ # inline link macros, link:target[text]
400
+ result.gsub!(REGEXP[:link_macro]) {
401
+ # alias match for Ruby 1.8.7 compat
402
+ m = $~
403
+ # honor the escape
404
+ if m[0].start_with? '\\'
405
+ next m[0][1..-1]
406
+ end
407
+ target = m[1]
408
+ @document.register(:links, target)
409
+
410
+ attrs = nil
411
+ #text = sub_attributes(m[2].gsub('\]', ']'))
412
+ if link_attrs && (m[2].start_with?('"') || m[2].include?(','))
413
+ attrs = parse_attributes(sub_attributes(m[2].gsub('\]', ']')))
414
+ text = attrs[1]
415
+ else
393
416
  text = sub_attributes(m[2].gsub('\]', ']'))
394
- Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target).render
395
- }
396
- end
417
+ end
397
418
 
398
- if found[:macroish_short_form] && result.include?('footnote')
399
- result.gsub!(REGEXP[:footnote_macro]) {
400
- # alias match for Ruby 1.8.7 compat
401
- m = $~
402
- # honor the escape
403
- if m[0].start_with? '\\'
404
- next m[0][1..-1]
405
- end
406
- if m[1] == 'footnote'
419
+ Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target, :attributes => attrs).render
420
+ }
421
+ end
422
+
423
+ if found[:macroish_short_form] && result.include?('footnote')
424
+ result.gsub!(REGEXP[:footnote_macro]) {
425
+ # alias match for Ruby 1.8.7 compat
426
+ m = $~
427
+ # honor the escape
428
+ if m[0].start_with? '\\'
429
+ next m[0][1..-1]
430
+ end
431
+ if m[1] == 'footnote'
432
+ # hmmmm
433
+ text = restore_passthroughs(m[2])
434
+ id = nil
435
+ index = @document.counter('footnote-number')
436
+ @document.register(:footnotes, Document::Footnote.new(index, id, text))
437
+ type = nil
438
+ target = nil
439
+ else
440
+ id, text = m[2].split(REGEXP[:csv_delimiter], 2)
441
+ if !text.nil?
407
442
  # hmmmm
408
- text = restore_passthroughs(m[2])
409
- id = nil
443
+ text = restore_passthroughs(text)
410
444
  index = @document.counter('footnote-number')
411
445
  @document.register(:footnotes, Document::Footnote.new(index, id, text))
412
- type = nil
446
+ type = :ref
413
447
  target = nil
414
448
  else
415
- id, text = m[2].split(/ *, */, 2)
416
- if !text.nil?
417
- # hmmmm
418
- text = restore_passthroughs(text)
419
- index = @document.counter('footnote-number')
420
- @document.register(:footnotes, Document::Footnote.new(index, id, text))
421
- type = :ref
422
- target = nil
423
- else
424
- fn = @document.references[:footnotes].find {|fn| fn.id == id }
425
- target = id
426
- id = nil
427
- index = fn.index
428
- text = fn.text
429
- type = :xref
430
- end
431
- end
432
- Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).render
433
- }
434
- end
435
-
436
- if found[:macroish] || result.include?('&lt;&lt;')
437
- result.gsub!(REGEXP[:xref_macro]) {
438
- # alias match for Ruby 1.8.7 compat
439
- m = $~
440
- # honor the escape
441
- if m[0].start_with? '\\'
442
- next m[0][1..-1]
443
- end
444
- if !m[1].nil?
445
- id, reftext = m[1].split(REGEXP[:csv_delimiter], 2)
446
- id.sub!(/^("|)(.*)\1$/, '\2')
447
- reftext.sub!(/^("|)(.*)\1$/m, '\2') unless reftext.nil?
448
- else
449
- id = m[2]
450
- reftext = !m[3].empty? ? m[3] : nil
451
- end
452
- Inline.new(self, :anchor, reftext, :type => :xref, :target => id).render
453
- }
454
- end
455
-
456
- if found[:square_bracket] && result.include?('[[[')
457
- result.gsub!(REGEXP[:biblio_macro]) {
458
- # alias match for Ruby 1.8.7 compat
459
- m = $~
460
- # honor the escape
461
- if m[0].start_with? '\\'
462
- next m[0][1..-1]
463
- end
464
- id = reftext = m[1]
465
- Inline.new(self, :anchor, reftext, :type => :bibref, :target => id).render
466
- }
467
- end
468
-
469
- if found[:square_bracket] && result.include?('[[')
470
- result.gsub!(REGEXP[:anchor_macro]) {
471
- # alias match for Ruby 1.8.7 compat
472
- m = $~
473
- # honor the escape
474
- if m[0].start_with? '\\'
475
- next m[0][1..-1]
476
- end
477
- id, reftext = m[1].split(REGEXP[:csv_delimiter])
478
- id.sub!(/^("|)(.*)\1$/, '\2')
479
- if reftext.nil?
480
- reftext = "[#{id}]"
481
- else
482
- reftext.sub!(/^("|)(.*)\1$/m, '\2')
483
- end
484
- # NOTE the reftext should also match what's in our references dic
485
- if !@document.references[:ids].has_key? id
486
- Asciidoctor.debug { "Missing reference for anchor #{id}" }
449
+ footnote = @document.references[:footnotes].find {|fn| fn.id == id }
450
+ target = id
451
+ id = nil
452
+ index = footnote.index
453
+ text = footnote.text
454
+ type = :xref
487
455
  end
488
- Inline.new(self, :anchor, reftext, :type => :ref, :target => id).render
489
- }
490
- end
456
+ end
457
+ Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).render
458
+ }
459
+ end
491
460
 
492
- result
461
+ if found[:macroish] || result.include?('&lt;&lt;')
462
+ result.gsub!(REGEXP[:xref_macro]) {
463
+ # alias match for Ruby 1.8.7 compat
464
+ m = $~
465
+ # honor the escape
466
+ if m[0].start_with? '\\'
467
+ next m[0][1..-1]
468
+ end
469
+ if !m[1].nil?
470
+ id, reftext = m[1].split(REGEXP[:csv_delimiter], 2)
471
+ id.sub!(REGEXP[:dbl_quoted], '\2')
472
+ reftext.sub!(REGEXP[:m_dbl_quoted], '\2') unless reftext.nil?
473
+ else
474
+ id = m[2]
475
+ reftext = !m[3].empty? ? m[3] : nil
476
+ end
477
+ Inline.new(self, :anchor, reftext, :type => :xref, :target => id).render
478
+ }
493
479
  end
494
480
 
495
- # Public: Substitute callout references
496
- #
497
- # text - The String text to process
498
- #
499
- # returns The String with the callout references rendered using the backend templates
500
- def sub_callouts(text)
501
- text.gsub(REGEXP[:callout_render]) {
481
+ if found[:square_bracket] && result.include?('[[[')
482
+ result.gsub!(REGEXP[:biblio_macro]) {
502
483
  # alias match for Ruby 1.8.7 compat
503
484
  m = $~
504
485
  # honor the escape
505
486
  if m[0].start_with? '\\'
506
- next "&lt;#{m[1]}&gt;"
487
+ next m[0][1..-1]
507
488
  end
508
- Inline.new(self, :callout, m[1], :id => document.callouts.read_next_id).render
489
+ id = reftext = m[1]
490
+ Inline.new(self, :anchor, reftext, :type => :bibref, :target => id).render
509
491
  }
510
492
  end
511
493
 
512
- # Public: Substitute post replacements
513
- #
514
- # text - The String text to process
515
- #
516
- # returns The String with the post replacements rendered using the backend templates
517
- def sub_post_replacements(text)
518
- if @document.attr? 'hardbreaks'
519
- lines = text.lines.entries
520
- return text if lines.size == 1
521
- last = lines.pop
522
- "#{lines.map {|line| Inline.new(self, :break, line.rstrip.chomp(' +'), :type => :line).render } * "\n"}\n#{last}"
523
- else
524
- text.gsub(REGEXP[:line_break]) { Inline.new(self, :break, $1, :type => :line).render }
525
- end
494
+ if found[:square_bracket] && result.include?('[[')
495
+ result.gsub!(REGEXP[:anchor_macro]) {
496
+ # alias match for Ruby 1.8.7 compat
497
+ m = $~
498
+ # honor the escape
499
+ if m[0].start_with? '\\'
500
+ next m[0][1..-1]
501
+ end
502
+ id, reftext = m[1].split(REGEXP[:csv_delimiter])
503
+ id.sub!(REGEXP[:dbl_quoted], '\2')
504
+ if reftext.nil?
505
+ reftext = "[#{id}]"
506
+ else
507
+ reftext.sub!(REGEXP[:m_dbl_quoted], '\2')
508
+ end
509
+ # NOTE the reftext should also match what's in our references dic
510
+ if !@document.references[:ids].has_key? id
511
+ Debug.debug { "Missing reference for anchor #{id}" }
512
+ end
513
+ Inline.new(self, :anchor, reftext, :type => :ref, :target => id).render
514
+ }
526
515
  end
527
516
 
528
- # Internal: Transform (render) a quoted text region
529
- #
530
- # match - The MatchData for the quoted text region
531
- # type - The quoting type (single, double, strong, emphasis, monospaced, etc)
532
- # scope - The scope of the quoting (constrained or unconstrained)
533
- #
534
- # returns The rendered text for the quoted text region
535
- def transform_quoted_text(match, type, scope)
536
- if match[0].start_with? '\\'
537
- match[0][1..-1]
538
- elsif scope == :constrained
539
- "#{match[1]}#{Inline.new(self, :quoted, match[3], :type => type, :attributes => parse_attributes(match[2])).render}"
540
- else
541
- Inline.new(self, :quoted, match[2], :type => type, :attributes => parse_attributes(match[1])).render
517
+ result
518
+ end
519
+
520
+ # Public: Substitute callout references
521
+ #
522
+ # text - The String text to process
523
+ #
524
+ # returns The String with the callout references rendered using the backend templates
525
+ def sub_callouts(text)
526
+ text.gsub(REGEXP[:callout_render]) {
527
+ # alias match for Ruby 1.8.7 compat
528
+ m = $~
529
+ # honor the escape
530
+ if m[0].start_with? '\\'
531
+ next "&lt;#{m[1]}&gt;"
542
532
  end
543
- end
533
+ Inline.new(self, :callout, m[1], :id => document.callouts.read_next_id).render
534
+ }
535
+ end
544
536
 
545
- # Internal: Parse the attributes in the attribute line
546
- #
547
- # attrline - A String of unprocessed attributes (key/value pairs)
548
- # posattrs - The keys for positional attributes
549
- #
550
- # returns nil if attrline is nil, an empty Hash if attrline is empty, otherwise a Hash of parsed attributes
551
- def parse_attributes(attrline, posattrs = ['role'])
552
- return nil if attrline.nil?
553
- return {} if attrline.empty?
554
-
555
- AttributeList.new(attrline, self).parse(posattrs)
537
+ # Public: Substitute post replacements
538
+ #
539
+ # text - The String text to process
540
+ #
541
+ # returns The String with the post replacements rendered using the backend templates
542
+ def sub_post_replacements(text)
543
+ if @document.attr? 'hardbreaks'
544
+ lines = text.lines.entries
545
+ return text if lines.size == 1
546
+ last = lines.pop
547
+ "#{lines.map {|line| Inline.new(self, :break, line.rstrip.chomp(LINE_BREAK), :type => :line).render } * "\n"}\n#{last}"
548
+ else
549
+ text.gsub(REGEXP[:line_break]) { Inline.new(self, :break, $1, :type => :line).render }
556
550
  end
551
+ end
557
552
 
558
- # Internal: Resolve the list of comma-delimited subs against the possible options.
559
- #
560
- # subs - A comma-delimited String of substitution aliases
561
- #
562
- # returns An Array of Symbols representing the substitution operation
563
- def resolve_subs(subs)
564
- candidates = subs.split(',').map {|sub| sub.strip.to_sym}
565
- resolved = candidates & SUB_OPTIONS
566
- if (invalid = candidates - resolved).size > 0
567
- puts "asciidoctor: WARNING: invalid passthrough macro substitution operation#{invalid.size > 1 ? 's' : ''}: #{invalid * ', '}"
568
- end
569
- resolved
553
+ # Internal: Transform (render) a quoted text region
554
+ #
555
+ # match - The MatchData for the quoted text region
556
+ # type - The quoting type (single, double, strong, emphasis, monospaced, etc)
557
+ # scope - The scope of the quoting (constrained or unconstrained)
558
+ #
559
+ # returns The rendered text for the quoted text region
560
+ def transform_quoted_text(match, type, scope)
561
+ if match[0].start_with? '\\'
562
+ match[0][1..-1]
563
+ elsif scope == :constrained
564
+ "#{match[1]}#{Inline.new(self, :quoted, match[3], :type => type, :attributes => parse_attributes(match[2])).render}"
565
+ else
566
+ Inline.new(self, :quoted, match[2], :type => type, :attributes => parse_attributes(match[1])).render
570
567
  end
568
+ end
571
569
 
572
- # Public: Highlight the source code if a source highlighter is defined
573
- # on the document, otherwise return the text unprocessed
574
- #
575
- # source - the source code String to highlight
576
- #
577
- # returns the highlighted source code, if a source highlighter is defined
578
- # on the document, otherwise the unprocessed text
579
- def highlight_source(source)
580
- Asciidoctor.require_library 'coderay'
581
- ::CodeRay::Duo[attr('language', 'text').to_sym, :html, {
582
- :css => @document.attr('coderay-css', 'class').to_sym,
583
- :line_numbers => (attr?('linenums') ? @document.attr('coderay-linenums-mode', 'table').to_sym : nil),
584
- :line_number_anchors => false}].highlight(source).chomp
585
- end
570
+ # Internal: Parse the attributes in the attribute line
571
+ #
572
+ # attrline - A String of unprocessed attributes (key/value pairs)
573
+ # posattrs - The keys for positional attributes
574
+ #
575
+ # returns nil if attrline is nil, an empty Hash if attrline is empty, otherwise a Hash of parsed attributes
576
+ def parse_attributes(attrline, posattrs = ['role'])
577
+ return nil if attrline.nil?
578
+ return {} if attrline.empty?
579
+
580
+ AttributeList.new(attrline, self).parse(posattrs)
586
581
  end
582
+
583
+ # Internal: Resolve the list of comma-delimited subs against the possible options.
584
+ #
585
+ # subs - A comma-delimited String of substitution aliases
586
+ #
587
+ # returns An Array of Symbols representing the substitution operation
588
+ def resolve_subs(subs)
589
+ candidates = subs.split(',').map {|sub| sub.strip.to_sym}
590
+ resolved = candidates & SUB_OPTIONS
591
+ if (invalid = candidates - resolved).size > 0
592
+ puts "asciidoctor: WARNING: invalid passthrough macro substitution operation#{invalid.size > 1 ? 's' : ''}: #{invalid * ', '}"
593
+ end
594
+ resolved
595
+ end
596
+
597
+ # Public: Highlight the source code if a source highlighter is defined
598
+ # on the document, otherwise return the text unprocessed
599
+ #
600
+ # source - the source code String to highlight
601
+ #
602
+ # returns the highlighted source code, if a source highlighter is defined
603
+ # on the document, otherwise the unprocessed text
604
+ def highlight_source(source)
605
+ Helpers.require_library 'coderay'
606
+ ::CodeRay::Duo[attr('language', 'text').to_sym, :html, {
607
+ :css => @document.attributes.fetch('coderay-css', 'class').to_sym,
608
+ :line_numbers => (attr?('linenums') ? @document.attributes.fetch('coderay-linenums-mode', 'table').to_sym : nil),
609
+ :line_number_anchors => false}].highlight(source).chomp
610
+ end
611
+ end
587
612
  end