asciidoctor 0.0.7 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Gemfile +2 -0
  2. data/README.asciidoc +35 -26
  3. data/Rakefile +9 -6
  4. data/asciidoctor.gemspec +27 -8
  5. data/bin/asciidoctor +1 -1
  6. data/lib/asciidoctor.rb +351 -63
  7. data/lib/asciidoctor/abstract_block.rb +218 -0
  8. data/lib/asciidoctor/abstract_node.rb +249 -0
  9. data/lib/asciidoctor/attribute_list.rb +211 -0
  10. data/lib/asciidoctor/backends/base_template.rb +99 -0
  11. data/lib/asciidoctor/backends/docbook45.rb +510 -0
  12. data/lib/asciidoctor/backends/html5.rb +585 -0
  13. data/lib/asciidoctor/block.rb +27 -254
  14. data/lib/asciidoctor/callouts.rb +117 -0
  15. data/lib/asciidoctor/debug.rb +7 -4
  16. data/lib/asciidoctor/document.rb +229 -77
  17. data/lib/asciidoctor/inline.rb +29 -0
  18. data/lib/asciidoctor/lexer.rb +1330 -502
  19. data/lib/asciidoctor/list_item.rb +33 -34
  20. data/lib/asciidoctor/reader.rb +305 -142
  21. data/lib/asciidoctor/renderer.rb +115 -19
  22. data/lib/asciidoctor/section.rb +100 -189
  23. data/lib/asciidoctor/substituters.rb +468 -0
  24. data/lib/asciidoctor/table.rb +499 -0
  25. data/lib/asciidoctor/version.rb +1 -1
  26. data/test/attributes_test.rb +301 -87
  27. data/test/blocks_test.rb +568 -0
  28. data/test/document_test.rb +221 -24
  29. data/test/fixtures/dot.gif +0 -0
  30. data/test/fixtures/encoding.asciidoc +1 -0
  31. data/test/fixtures/include-file.asciidoc +1 -0
  32. data/test/fixtures/tip.gif +0 -0
  33. data/test/headers_test.rb +411 -43
  34. data/test/lexer_test.rb +265 -45
  35. data/test/links_test.rb +144 -3
  36. data/test/lists_test.rb +2252 -74
  37. data/test/paragraphs_test.rb +21 -30
  38. data/test/preamble_test.rb +24 -0
  39. data/test/reader_test.rb +248 -12
  40. data/test/renderer_test.rb +22 -0
  41. data/test/substitutions_test.rb +414 -0
  42. data/test/tables_test.rb +484 -0
  43. data/test/test_helper.rb +70 -6
  44. data/test/text_test.rb +30 -6
  45. metadata +64 -10
  46. data/lib/asciidoctor/render_templates.rb +0 -317
  47. data/lib/asciidoctor/string.rb +0 -12
@@ -1,26 +1,21 @@
1
1
  # Public: Methods for managing items for AsciiDoc olists, ulist, and dlists.
2
- class Asciidoctor::ListItem
3
- # Public: Get the Array of Blocks from the list item's continuation.
4
- attr_reader :blocks
2
+ class Asciidoctor::ListItem < Asciidoctor::AbstractBlock
5
3
 
6
- # Public: Get/Set the String list item anchor name.
7
- attr_accessor :anchor
8
-
9
- # Public: Get/Set the Integer list level (for nesting list elements).
10
- attr_accessor :level
4
+ # Public: Get/Set the String used to mark this list item
5
+ attr_accessor :marker
11
6
 
12
7
  # Public: Initialize an Asciidoctor::ListItem object.
13
8
  #
14
9
  # parent - The parent list block for this list item
15
- # text - the String text (default '')
16
- def initialize(parent, text='')
17
- @parent = parent
10
+ # text - the String text (default nil)
11
+ def initialize(parent, text = nil)
12
+ super(parent, :list_item)
18
13
  @text = text
19
- @blocks = []
14
+ @level = parent.level
20
15
  end
21
16
 
22
- def text=(new_text)
23
- @text = new_text
17
+ def text?
18
+ !@text.to_s.empty?
24
19
  end
25
20
 
26
21
  def text
@@ -28,35 +23,35 @@ class Asciidoctor::ListItem
28
23
  ::Asciidoctor::Block.new(self, nil, [@text]).content
29
24
  end
30
25
 
31
- def document
32
- @parent.document
33
- end
34
-
35
26
  def content
36
- # create method for !blocks.empty?
37
- if !blocks.empty?
38
- blocks.map{|block| block.render}.join
39
- else
40
- nil
41
- end
27
+ blocks? ? blocks.map {|b| b.render }.join : nil
42
28
  end
43
29
 
44
30
  # Public: Fold the first paragraph block into the text
45
- def fold_first
46
- # looking for :literal here allows indentation of paragraph content, then strip indent
31
+ #
32
+ # Here are the rules for when a folding occurs:
33
+ #
34
+ # Given: this list item has at least one block
35
+ # When: the first block is a paragraph that's not connected by a list continuation
36
+ # Or: the first block is an indented paragraph that's adjacent (wrapped line)
37
+ # Or: the first block is an indented paragraph that's not connected by a list continuation
38
+ # Then: then drop the first block and fold it's content (buffer) into the list text
39
+ #
40
+ # Returns nothing
41
+ def fold_first(continuation_connects_first_block = false, content_adjacent = false)
47
42
  if !blocks.empty? && blocks.first.is_a?(Asciidoctor::Block) &&
48
- (blocks.first.context == :paragraph || blocks.first.context == :literal)
43
+ ((blocks.first.context == :paragraph && !continuation_connects_first_block) ||
44
+ ((content_adjacent || !continuation_connects_first_block) && blocks.first.context == :literal &&
45
+ blocks.first.attr('options', []).include?('listparagraph')))
46
+
49
47
  block = blocks.shift
50
- if !@text.nil? && !@text.empty?
48
+ unless @text.to_s.empty?
51
49
  block.buffer.unshift(@text)
52
50
  end
53
51
 
54
- if block.context == :literal
55
- @text = block.buffer.map {|l| l.lstrip}.join("\n")
56
- else
57
- @text = block.buffer.join("\n")
58
- end
52
+ @text = block.buffer.join("\n")
59
53
  end
54
+ nil
60
55
  end
61
56
 
62
57
  def splain(parent_level = 0)
@@ -71,7 +66,7 @@ class Asciidoctor::ListItem
71
66
  @blocks.each_with_index do |block, i|
72
67
  Asciidoctor.puts_indented(parent_level, "v" * (60 - parent_level*2))
73
68
  Asciidoctor.puts_indented(parent_level, "Block ##{i} is a #{block.class}")
74
- Asciidoctor.puts_indented(parent_level, "Name is #{block.name rescue 'n/a'}")
69
+ Asciidoctor.puts_indented(parent_level, "Name is #{block.title rescue 'n/a'}")
75
70
  Asciidoctor.puts_indented(parent_level, "=" * 40)
76
71
  block.splain(parent_level) if block.respond_to? :splain
77
72
  Asciidoctor.puts_indented(parent_level, "^" * (60 - parent_level*2))
@@ -79,4 +74,8 @@ class Asciidoctor::ListItem
79
74
  end
80
75
  nil
81
76
  end
77
+
78
+ def to_s
79
+ "#{super.to_s} - #@context [text:#@text, blocks:#{(@blocks || []).size}]"
80
+ end
82
81
  end
@@ -9,155 +9,55 @@ class Asciidoctor::Reader
9
9
  # Public: Get the String Array of lines parsed from the source
10
10
  attr_reader :lines
11
11
 
12
- # Public: Get the Hash of attributes
13
- attr_reader :attributes
14
-
15
- attr_reader :references
16
-
17
- # Public: Convert a string to a legal attribute name.
18
- #
19
- # name - The String holding the Asciidoc attribute name.
20
- #
21
- # Returns a String with the legal name.
22
- #
23
- # Examples
24
- #
25
- # sanitize_attribute_name('Foo Bar')
26
- # => 'foobar'
27
- #
28
- # sanitize_attribute_name('foo')
29
- # => 'foo'
30
- #
31
- # sanitize_attribute_name('Foo 3 #-Billy')
32
- # => 'foo3-billy'
33
- def sanitize_attribute_name(name)
34
- name.gsub(/[^\w\-_]/, '').downcase
35
- end
36
-
37
12
  # Public: Initialize the Reader object.
38
13
  #
39
- # data - The Array of Strings holding the Asciidoc source document.
40
- # block - A block that can be used to retrieve external Asciidoc
41
- # data to include in this document.
14
+ # data - The Array of Strings holding the Asciidoc source document. The
15
+ # original instance of this Array is not modified
16
+ # document - The document with which this reader is associated. Used to access
17
+ # document attributes
18
+ # overrides - A Hash of attributes that were passed to the Document and should
19
+ # prevent attribute assignments or removals of matching keys found in
20
+ # the document
21
+ # block - A block that can be used to retrieve external Asciidoc
22
+ # data to include in this document.
42
23
  #
43
24
  # Examples
44
25
  #
45
26
  # data = File.readlines(filename)
46
- # reader = Asciidoctor::Reader.new(data)
47
- def initialize(data = [], attributes = {}, &block)
48
- raw_source = []
49
- @attributes = attributes
50
- @references = {}
51
-
52
- data = data.split("\n") if data.is_a? String
53
-
54
- include_regexp = /^include::([^\[]+)\[\]\s*\n?\z/
55
-
56
- data.each do |line|
57
- if inc = line.match(include_regexp)
58
- if block_given?
59
- raw_source << yield(inc[1])
60
- else
61
- raw_source.concat(File.readlines(inc[1]))
62
- end
63
- else
64
- raw_source << line
65
- end
66
- end
67
-
68
- ifdef_regexp = /^(ifdef|ifndef)::([^\[]+)\[\]/
69
- endif_regexp = /^endif::/
70
- defattr_regexp = /^:([^:!]+):\s*(.*)\s*$/
71
- delete_attr_regexp = /^:([^:]+)!:\s*$/
72
- conditional_regexp = /^\s*\{([^\?]+)\?\s*([^\}]+)\s*\}/
73
-
74
- skip_to = nil
75
- continuing_value = nil
76
- continuing_key = nil
77
- @lines = []
78
- raw_source.each do |line|
79
- if skip_to
80
- skip_to = nil if line.match(skip_to)
81
- elsif continuing_value
82
- close_continue = false
83
- # Lines that start with whitespace and end with a '+' are
84
- # a continuation, so gobble them up into `value`
85
- if match = line.match(/\s+(.+)\s+\+\s*$/)
86
- continuing_value += ' ' + match[1]
87
- elsif match = line.match(/\s+(.+)/)
88
- # If this continued line doesn't end with a +, then this
89
- # is the end of the continuation, no matter what the next
90
- # line does.
91
- continuing_value += ' ' + match[1]
92
- close_continue = true
93
- else
94
- # If this line doesn't start with whitespace, then it's
95
- # not a valid continuation line, so push it back for processing
96
- close_continue = true
97
- raw_source.unshift(line)
98
- end
99
- if close_continue
100
- @attributes[continuing_key] = continuing_value
101
- continuing_key = nil
102
- continuing_value = nil
103
- end
104
- elsif match = line.match(ifdef_regexp)
105
- attr = match[2]
106
- skip = case match[1]
107
- when 'ifdef'; !@attributes.has_key?(attr)
108
- when 'ifndef'; @attributes.has_key?(attr)
109
- end
110
- skip_to = /^endif::#{attr}\[\]\s*\n/ if skip
111
- elsif match = line.match(defattr_regexp)
112
- key = sanitize_attribute_name(match[1])
113
- value = match[2]
114
- if match = value.match(Asciidoctor::REGEXP[:attr_continue])
115
- # attribute value continuation line; grab lines until we run out
116
- # of continuation lines
117
- continuing_key = key
118
- continuing_value = match[1] # strip off the spaces and +
119
- Asciidoctor.debug "continuing key: #{continuing_key} with partial value: '#{continuing_value}'"
120
- else
121
- @attributes[key] = value
122
- Asciidoctor.debug "Defines[#{key}] is '#{value}'"
123
- end
124
- elsif match = line.match(delete_attr_regexp)
125
- key = sanitize_attribute_name(match[1])
126
- @attributes.delete(key)
127
- elsif !line.match(endif_regexp)
128
- while match = line.match(conditional_regexp)
129
- value = @attributes.has_key?(match[1]) ? match[2] : ''
130
- line.sub!(conditional_regexp, value)
131
- end
132
- # leave line comments in as they play a role in flow (such as a list divider)
133
- @lines << line
134
- end
27
+ # reader = Asciidoctor::Reader.new data
28
+ def initialize(data = [], document = nil, overrides = nil, &block)
29
+ # if document is nil, we assume this is a preprocessed string
30
+ if document.nil?
31
+ @lines = data.is_a?(String) ? data.lines.entries : data.dup
32
+ elsif !data.empty?
33
+ @overrides = overrides || {}
34
+ @document = document
35
+ process(data.is_a?(String) ? data.lines.entries : data, &block)
36
+ else
37
+ @lines = []
135
38
  end
136
39
 
137
- # Process bibliography references, so they're available when text
138
- # before the reference is being rendered.
139
- @lines.each do |line|
140
- if biblio = line.match(REGEXP[:biblio])
141
- @references[biblio[1]] = "[#{biblio[1]}]"
142
- end
143
- end
40
+ # just in case we got some nils floating at the end of our lines after reading a funky document
41
+ @lines.pop until @lines.empty? || !@lines.last.nil?
144
42
 
145
- #Asciidoctor.debug "About to leave Reader#init, and references is #{@references.inspect}"
146
43
  @source = @lines.join
147
- Asciidoctor.debug "Leaving Reader#init, and I have #{@lines.count} lines"
148
- Asciidoctor.debug "Also, has_lines? is #{self.has_lines?}"
149
44
  end
150
45
 
151
46
  # Public: Check whether there are any lines left to read.
152
47
  #
153
- # Returns true if @lines.any? is true, or false otherwise.
48
+ # Returns true if !@lines.empty? is true, or false otherwise.
154
49
  def has_lines?
155
- @lines.any?
50
+ !@lines.empty?
156
51
  end
157
52
 
158
- # Private: Strip off leading blank lines in the Array of lines.
53
+ # Public: Check whether this reader is empty (contains no lines)
159
54
  #
160
- # Returns nil.
55
+ # Returns true if @lines.empty? is true, otherwise false.
56
+ def empty?
57
+ @lines.empty?
58
+ end
59
+
60
+ # Private: Strip off leading blank lines in the Array of lines.
161
61
  #
162
62
  # Examples
163
63
  #
@@ -165,23 +65,89 @@ class Asciidoctor::Reader
165
65
  # => ["\n", "\t\n", "Foo\n", "Bar\n", "\n"]
166
66
  #
167
67
  # skip_blank
168
- # => nil
68
+ # => 2
169
69
  #
170
70
  # @lines
171
71
  # => ["Foo\n", "Bar\n"]
72
+ #
73
+ # Returns an Integer of the number of lines skipped
172
74
  def skip_blank
173
- while @lines.any? && @lines.first.strip.empty?
75
+ skipped = 0
76
+ while has_lines? && @lines.first.strip.empty?
174
77
  @lines.shift
78
+ skipped += 1
175
79
  end
176
80
 
177
- nil
81
+ skipped
82
+ end
83
+ # Create alias of skip_blank named skip_blank_lines for readability
84
+ # TODO likely want to drop the original method name
85
+ alias :skip_blank_lines :skip_blank
86
+
87
+ # Public: Consume consecutive lines containing line- or block-level comments.
88
+ #
89
+ # Returns the Array of lines that were consumed
90
+ #
91
+ # Examples
92
+ # @lines
93
+ # => ["// foo\n", "////\n", "foo bar\n", "////\n", "actual text\n"]
94
+ #
95
+ # comment_lines = consume_comments
96
+ # => ["// foo\n", "////\n", "foo bar\n", "////\n"]
97
+ #
98
+ # @lines
99
+ # => ["actual text\n"]
100
+ def consume_comments(opts = {})
101
+ comment_lines = []
102
+ while !@lines.empty?
103
+ next_line = peek_line
104
+ if opts[:include_blanks] && next_line.strip.empty?
105
+ comment_lines << get_line
106
+ elsif match = next_line.match(REGEXP[:comment_blk])
107
+ comment_lines << get_line
108
+ comment_lines.push(*(grab_lines_until(:terminator => match[0], :preserve_last_line => true)))
109
+ comment_lines << get_line
110
+ elsif next_line.match(REGEXP[:comment])
111
+ comment_lines << get_line
112
+ else
113
+ break
114
+ end
115
+ end
116
+
117
+ comment_lines
118
+ end
119
+
120
+ # Public: Consume consecutive lines containing line comments.
121
+ #
122
+ # Returns the Array of lines that were consumed
123
+ #
124
+ # Examples
125
+ # @lines
126
+ # => ["// foo\n", "bar\n"]
127
+ #
128
+ # comment_lines = consume_comments
129
+ # => ["// foo\n"]
130
+ #
131
+ # @lines
132
+ # => ["bar\n"]
133
+ def consume_line_comments
134
+ comment_lines = []
135
+ while !@lines.empty?
136
+ if peek_line.match(REGEXP[:comment])
137
+ comment_lines << get_line
138
+ else
139
+ break
140
+ end
141
+ end
142
+
143
+ comment_lines
178
144
  end
179
145
 
180
146
  # Skip the next line if it's a list continuation character
181
147
  #
182
148
  # Returns nil
183
149
  def skip_list_continuation
184
- if !@lines.empty? && @lines.first.chomp == '+'
150
+ if has_lines? && @lines.first.chomp == '+'
185
151
  @lines.shift
186
152
  end
187
153
 
@@ -195,6 +161,9 @@ class Asciidoctor::Reader
195
161
  def get_line
196
162
  @lines.shift
197
163
  end
164
+ # QUESTION what about advance?
165
+ # Create an alias of get_line named next_line for readability
166
+ alias :next_line :get_line
198
167
 
199
168
  # Public: Get the next line of source data. Does not consume the line returned.
200
169
  #
@@ -204,11 +173,21 @@ class Asciidoctor::Reader
204
173
  @lines.first.dup if @lines.first
205
174
  end
206
175
 
207
- # Public: Push String `line` onto queue of source data lines, unless `line` is nil.
176
+ # Public: Push Array of string `lines` onto queue of source data lines, unless `lines` has no non-nil values.
208
177
  #
209
178
  # Returns nil
210
- def unshift(line)
211
- @lines.unshift(line) if line
179
+ def unshift(*new_lines)
180
+ @lines.unshift(*new_lines) if !new_lines.empty?
181
+ nil
182
+ end
183
+
184
+ # Public: Chomp the String on the last line if this reader contains at least one line
185
+ #
186
+ # Delegates to chomp!
187
+ #
188
+ # Returns nil
189
+ def chomp_last!
190
+ @lines.last.chomp! unless @lines.empty?
212
191
  nil
213
192
  end
214
193
 
@@ -219,9 +198,14 @@ class Asciidoctor::Reader
219
198
  # options - an optional Hash of processing options:
220
199
  # * :break_on_blank_lines may be used to specify to break on
221
200
  # blank lines
201
+ # * :skip_first_line may be used to tell the reader to advance
202
+ # beyond the first line before beginning the scan
222
203
  # * :preserve_last_line may be used to specify that the String
223
204
  # causing the method to stop processing lines should be
224
205
  # pushed back onto the `lines` Array.
206
+ # * :grab_last_line may be used to specify that the String
207
+ # causing the method to stop processing lines should be
208
+ # included in the lines being returned
225
209
  #
226
210
  # Returns the Array of lines forming the next segment.
227
211
  #
@@ -236,18 +220,197 @@ class Asciidoctor::Reader
236
220
  def grab_lines_until(options = {}, &block)
237
221
  buffer = []
238
222
 
223
+ finis = false
224
+ get_line if options[:skip_first_line]
225
+ # save options to locals for minor optimization
226
+ terminator = options[:terminator]
227
+ terminator.chomp! if terminator
228
+ break_on_blank_lines = options[:break_on_blank_lines]
229
+ break_on_list_continuation = options[:break_on_list_continuation]
239
230
  while (this_line = self.get_line)
240
- Asciidoctor.debug "Processing line: '#{this_line}'"
241
- finis ||= true if options[:break_on_blank_lines] && this_line.strip.empty?
242
- finis ||= true if block && value = yield(this_line)
231
+ Asciidoctor.debug { "Reader processing line: '#{this_line}'" }
232
+ finis = true if terminator && this_line.chomp == terminator
233
+ finis = true if !finis && break_on_blank_lines && this_line.strip.empty?
234
+ finis = true if !finis && break_on_list_continuation && this_line.chomp == LIST_CONTINUATION
235
+ finis = true if !finis && block && yield(this_line)
243
236
  if finis
244
237
  self.unshift(this_line) if options[:preserve_last_line]
238
+ buffer << this_line if options[:grab_last_line]
245
239
  break
246
240
  end
247
241
 
248
- buffer << this_line
242
+ if options[:skip_line_comments] && this_line.match(REGEXP[:comment])
243
+ # skip it
244
+ else
245
+ buffer << this_line
246
+ end
249
247
  end
248
+
250
249
  buffer
251
250
  end
252
251
 
252
+ # Public: Convert a string to a legal attribute name.
253
+ #
254
+ # name - The String holding the Asciidoc attribute name.
255
+ #
256
+ # Returns a String with the legal name.
257
+ #
258
+ # Examples
259
+ #
260
+ # sanitize_attribute_name('Foo Bar')
261
+ # => 'foobar'
262
+ #
263
+ # sanitize_attribute_name('foo')
264
+ # => 'foo'
265
+ #
266
+ # sanitize_attribute_name('Foo 3 #-Billy')
267
+ # => 'foo3-billy'
268
+ def sanitize_attribute_name(name)
269
+ name.gsub(/[^\w\-]/, '').downcase
270
+ end
271
+
272
+ # Private: Process raw input, used for the outermost reader.
273
+ def process(data, &block)
274
+ raw_source = []
275
+ include_depth = @document.attr('include-depth', 0).to_i
276
+
277
+ data.each do |line|
278
+ if inc = line.match(REGEXP[:include_macro])
279
+ if inc[0].start_with? '\\'
280
+ raw_source << line[1..-1]
281
+ # if running in SafeMode::SECURE or greater, don't process
282
+ # this directive (or should we swallow it?)
283
+ elsif @document.safe >= SafeMode::SECURE
284
+ raw_source << line
285
+ # assume that if a block is given, the developer wants
286
+ # to handle when and how to process the include, even
287
+ # if the include-depth attribute is 0
288
+ elsif block_given?
289
+ raw_source.concat yield(inc[1])
290
+ elsif include_depth > 0
291
+ raw_source.concat File.readlines(@document.normalize_asset_path(inc[1], 'include file'))
292
+ else
293
+ raw_source << line
294
+ end
295
+ else
296
+ raw_source << line
297
+ end
298
+ end
299
+
300
+ skip_to = nil
301
+ continuing_value = nil
302
+ continuing_key = nil
303
+ @lines = []
304
+
305
+ raw_source.each do |line|
306
+ if skip_to
307
+ skip_to = nil if line.match(skip_to)
308
+ elsif continuing_value
309
+ close_continue = false
310
+ # Lines that start with whitespace and end with a '+' are
311
+ # a continuation, so gobble them up into `value`
312
+ if line.match(REGEXP[:attr_continue])
313
+ continuing_value += ' ' + $1
314
+ # An empty line ends a continuation
315
+ elsif line.strip.empty?
316
+ raw_source.unshift(line)
317
+ close_continue = true
318
+ else
319
+ # If this continued line isn't empty and doesn't end with a +, then
320
+ # this is the end of the continuation, no matter what the next line
321
+ # does.
322
+ continuing_value += ' ' + line.strip
323
+ close_continue = true
324
+ end
325
+ if close_continue
326
+ unless attribute_overridden? continuing_key
327
+ @document.attributes[continuing_key] = apply_attribute_value_subs(continuing_value)
328
+ end
329
+ continuing_key = nil
330
+ continuing_value = nil
331
+ end
332
+ elsif line.match(REGEXP[:ifdef_macro])
333
+ attr = $2
334
+ skip = case $1
335
+ when 'ifdef'; !@document.attributes.has_key?(attr)
336
+ when 'ifndef'; @document.attributes.has_key?(attr)
337
+ end
338
+ skip_to = /^endif::#{attr}\[\]\s*\n/ if skip
339
+ elsif line.match(REGEXP[:attr_assign])
340
+ key = sanitize_attribute_name($1)
341
+ value = $2
342
+ if value.match(REGEXP[:attr_continue])
343
+ # attribute value continuation line; grab lines until we run out
344
+ # of continuation lines
345
+ continuing_key = key
346
+ continuing_value = $1 # strip off the spaces and +
347
+ else
348
+ unless attribute_overridden? key
349
+ @document.attributes[key] = apply_attribute_value_subs(value)
350
+ if key == 'backend'
351
+ @document.update_backend_attributes()
352
+ end
353
+ end
354
+ end
355
+ elsif line.match(REGEXP[:attr_delete])
356
+ key = sanitize_attribute_name($1)
357
+ unless attribute_overridden? key
358
+ @document.attributes.delete(key)
359
+ end
360
+ elsif !line.match(REGEXP[:endif_macro])
361
+ while line.match(REGEXP[:attr_conditional])
362
+ value = @document.attributes.has_key?($1) ? $2 : ''
363
+ line.sub!(conditional_regexp, value)
364
+ end
365
+ # leave line comments in as they play a role in flow (such as a list divider)
366
+ @lines << line
367
+ end
368
+ end
369
+
370
+ # Process bibliography references, so they're available when text
371
+ # before the reference is being rendered.
372
+ # FIXME we don't have support for bibliography lists yet, so disable for now
373
+ # plus, this should be done while we are walking lines above
374
+ #@lines.each do |line|
375
+ # if biblio = line.match(REGEXP[:biblio])
376
+ # @document.register(:ids, biblio[1])
377
+ # end
378
+ #end
379
+ end
380
+
381
+ # Internal: Determine if the attribute has been overridden in the document options
382
+ #
383
+ # key - The attribute key to check
384
+ #
385
+ # Returns true if the attribute has been overridden, false otherwise
386
+ def attribute_overridden?(key)
387
+ @overrides.has_key?(key) || @overrides.has_key?(key + '!')
388
+ end
389
+
390
+ # Internal: Apply substitutions to the attribute value
391
+ #
392
+ # If the value is an inline passthrough macro (e.g., pass:[text]), then
393
+ # apply the substitutions defined on the macro to the text. Otherwise,
394
+ # apply the verbatim substitutions to the value.
395
+ #
396
+ # value - The String attribute value on which to perform substitutions
397
+ #
398
+ # Returns The String value with substitutions performed.
399
+ def apply_attribute_value_subs(value)
400
+ if value.match(REGEXP[:pass_macro_basic])
401
+ # copy match for Ruby 1.8.7 compat
402
+ m = $~
403
+ subs = []
404
+ if !m[1].empty?
405
+ subs = @document.resolve_subs(m[1])
406
+ end
407
+ if !subs.empty?
408
+ @document.apply_subs(m[2], subs)
409
+ else
410
+ m[2]
411
+ end
412
+ else
413
+ @document.apply_header_subs(value)
414
+ end
415
+ end
253
416
  end