asciidoctor 0.0.7 → 0.0.9

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 (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