def self.next_block(reader, parent, attributes = {}, options = {})
skipped = reader.skip_blank_lines
return nil unless reader.has_more_lines?
text_only = options[:text]
if text_only && skipped > 0
options.delete(:text)
text_only = false
end
parse_metadata = options.fetch(:parse_metadata, true)
document = parent.document
parent_context = parent.is_a?(Block) ? parent.context : nil
block = nil
style = nil
explicit_style = nil
while reader.has_more_lines? && block.nil?
if parse_metadata && parse_block_metadata_line(reader, document, attributes, options)
reader.advance
next
end
this_line = reader.get_line
delimited_block = false
block_context = nil
terminator = nil
if attributes[1]
style, explicit_style = parse_style_attribute(attributes)
end
if delimited_blk_match = is_delimited_block?(this_line, true)
delimited_block = true
block_context = delimited_blk_match.context
terminator = delimited_blk_match.terminator
if !style
style = attributes['style'] = block_context.to_s
elsif style != block_context.to_s
if delimited_blk_match.masq.include? style
block_context = style.to_sym
elsif delimited_blk_match.masq.include?('admonition') && ADMONITION_STYLES.include?(style)
block_context = :admonition
else
puts "asciidoctor: WARNING: line #{reader.lineno}: invalid style for #{block_context} block: #{style}"
style = block_context.to_s
end
end
end
if !delimited_block
while true
if !style.nil? && COMPLIANCE[:strict_verbatim_paragraphs] && VERBATIM_STYLES.include?(style)
block_context = style.to_sym
reader.unshift_line this_line
break
end
if !text_only
if (match = this_line.match(REGEXP[:break_line]))
block = Block.new(parent, BREAK_LINES[match[0][0..2]])
break
elsif (match = this_line.match(REGEXP[:media_blk_macro]))
blk_ctx = match[1].to_sym
block = Block.new(parent, blk_ctx)
if blk_ctx == :image
posattrs = ['alt', 'width', 'height']
elsif blk_ctx == :video
posattrs = ['poster', 'width', 'height']
else
posattrs = []
end
unless style.nil? || explicit_style
attributes['alt'] = style if blk_ctx == :image
attributes.delete('style')
style = nil
end
block.parse_attributes(match[3], posattrs,
:unescape_input => (blk_ctx == :image),
:sub_input => true,
:sub_result => false,
:into => attributes)
target = block.sub_attributes(match[2])
if target.empty?
return nil
end
attributes['target'] = target
block.title = attributes.delete('title') if attributes.has_key?('title')
if blk_ctx == :image
document.register(:images, target)
attributes['alt'] ||= File.basename(target, File.extname(target))
block.assign_caption attributes.delete('caption'), 'figure'
end
break
elsif (match = this_line.match(REGEXP[:toc]))
block = Block.new(parent, :toc)
block.parse_attributes(match[1], [], :sub_result => false, :into => attributes)
break
end
end
if (match = this_line.match(REGEXP[:colist]))
block = Block.new(parent, :colist)
attributes['style'] = 'arabic'
items = []
block.buffer = items
reader.unshift_line this_line
expected_index = 1
begin
if match[1].to_i != expected_index
puts "asciidoctor: WARNING: line #{reader.lineno + 1}: callout list item index: expected #{expected_index} got #{match[1]}"
end
list_item = next_list_item(reader, block, match)
expected_index += 1
if !list_item.nil?
items << list_item
coids = document.callouts.callout_ids(items.size)
if !coids.empty?
list_item.attributes['coids'] = coids
else
puts "asciidoctor: WARNING: line #{reader.lineno}: no callouts refer to list item #{items.size}"
end
end
end while reader.has_more_lines? && match = reader.peek_line.match(REGEXP[:colist])
document.callouts.next_list
break
elsif (match = this_line.match(REGEXP[:ulist]))
reader.unshift_line this_line
block = next_outline_list(reader, :ulist, parent)
break
elsif (match = this_line.match(REGEXP[:olist]))
reader.unshift_line this_line
block = next_outline_list(reader, :olist, parent)
if !(attributes.has_key? 'style') && !(block.attributes.has_key? 'style')
marker = block.buffer.first.marker
if marker.start_with? '.'
attributes['style'] = (ORDERED_LIST_STYLES[marker.length - 1] || ORDERED_LIST_STYLES.first).to_s
else
style = ORDERED_LIST_STYLES.detect{|s| marker.match(ORDERED_LIST_MARKER_PATTERNS[s]) }
attributes['style'] = (style || ORDERED_LIST_STYLES.first).to_s
end
end
break
elsif (match = this_line.match(REGEXP[:dlist]))
reader.unshift_line this_line
block = next_labeled_list(reader, match, parent)
break
elsif (style == 'float' || style == 'discrete') && is_section_title?(this_line, reader.peek_line)
reader.unshift_line this_line
float_id, float_title, float_level, _ = parse_section_title(reader, document)
float_id ||= attributes['id'] if attributes.has_key?('id')
block = Block.new(parent, :floating_title)
if float_id.nil? || float_id.empty?
tmp_sect = Section.new(parent)
tmp_sect.title = float_title
block.id = tmp_sect.generate_id
else
block.id = float_id
end
document.register(:ids, [block.id, float_title]) if block.id
block.level = float_level
block.title = float_title
break
elsif !style.nil? && style != 'normal'
if PARAGRAPH_STYLES.include?(style)
block_context = style.to_sym
reader.unshift_line this_line
break
elsif ADMONITION_STYLES.include?(style)
block_context = :admonition
reader.unshift_line this_line
break
else
puts "asciidoctor: WARNING: line #{reader.lineno}: invalid style for paragraph: #{style}"
style = nil
end
end
break_at_list = (skipped == 0 && parent_context.to_s.end_with?('list'))
if style != 'normal' && this_line.match(REGEXP[:lit_par])
reader.unshift_line this_line
buffer = reader.grab_lines_until(
:break_on_blank_lines => true,
:break_on_list_continuation => true,
:preserve_last_line => true) {|line|
(break_at_list && line.match(REGEXP[:any_list])) ||
(COMPLIANCE[:block_terminates_paragraph] && (is_delimited_block?(line) || line.match(REGEXP[:attr_line])))
}
reset_block_indent! buffer
block = Block.new(parent, :literal, buffer)
if LIST_CONTEXTS.include?(parent_context)
attributes['options'] ||= []
attributes['options'] << 'listparagraph'
end
else
reader.unshift_line this_line
buffer = reader.grab_lines_until(
:break_on_blank_lines => true,
:break_on_list_continuation => true,
:preserve_last_line => true,
:skip_line_comments => true) {|line|
(break_at_list && line.match(REGEXP[:any_list])) ||
(COMPLIANCE[:block_terminates_paragraph] && (is_delimited_block?(line) || line.match(REGEXP[:attr_line])))
}
if buffer.empty?
reader.get_line
return nil
end
catalog_inline_anchors(buffer.join, document)
first_line = buffer.first
if !text_only && (admonition_match = first_line.match(REGEXP[:admonition_inline]))
buffer[0] = admonition_match.post_match.lstrip
block = Block.new(parent, :admonition, buffer)
attributes['style'] = admonition_match[1]
attributes['name'] = admonition_name = admonition_match[1].downcase
attributes['caption'] ||= document.attributes["#{admonition_name}-caption"]
elsif !text_only && COMPLIANCE[:markdown_syntax] && first_line.start_with?('> ')
buffer.map! {|line|
if line.start_with?('> ')
line[2..-1]
elsif line.chomp == '>'
line[1..-1]
else
line
end
}
if buffer.last.start_with?('-- ')
attribution, citetitle = buffer.pop[3..-1].split(', ')
buffer.pop while buffer.last.chomp.empty?
buffer[-1] = buffer.last.chomp
else
attribution, citetitle = nil
end
attributes['style'] = 'quote'
attributes['attribution'] = attribution unless attribution.nil?
attributes['citetitle'] = citetitle unless citetitle.nil?
block = build_block(:quote, :complex, false, parent, Reader.new(buffer), attributes)
elsif !text_only && buffer.size > 1 && first_line.start_with?('"') &&
buffer.last.start_with?('-- ') && buffer[-2].chomp.end_with?('"')
buffer[0] = first_line[1..-1]
attribution, citetitle = buffer.pop[3..-1].split(', ')
buffer.pop while buffer.last.chomp.empty?
buffer[-1] = buffer.last.chomp.chop
attributes['style'] = 'quote'
attributes['attribution'] = attribution unless attribution.nil?
attributes['citetitle'] = citetitle unless citetitle.nil?
block = Block.new(parent, :quote, buffer)
else
block = Block.new(parent, :paragraph, buffer)
end
end
break
end
end
if block.nil? && !block_context.nil?
block_context = :open if block_context == :abstract || block_context == :partintro
case block_context
when :admonition
attributes['name'] = admonition_name = style.downcase
attributes['caption'] ||= document.attributes["#{admonition_name}-caption"]
block = build_block(block_context, :complex, terminator, parent, reader, attributes)
when :comment
reader.grab_lines_until(:break_on_blank_lines => true, :chomp_last_line => false)
return nil
when :example
block = build_block(block_context, :complex, terminator, parent, reader, attributes, {:supports_caption => true})
when :listing, :fenced_code, :source
if block_context == :fenced_code
style = attributes['style'] = 'source'
lang = this_line[3..-1].strip
attributes['language'] = lang unless lang.empty?
terminator = terminator[0..2] if terminator.length > 3
elsif block_context == :source
AttributeList.rekey(attributes, [nil, 'language', 'linenums'])
end
block = build_block(:listing, :verbatim, terminator, parent, reader, attributes, {:supports_caption => true})
when :literal
block = build_block(block_context, :verbatim, terminator, parent, reader, attributes)
when :pass
block = build_block(block_context, :simple, terminator, parent, reader, attributes)
when :open, :sidebar
block = build_block(block_context, :complex, terminator, parent, reader, attributes)
when :table
block_reader = Reader.new reader.grab_lines_until(:terminator => terminator, :skip_line_comments => true)
case terminator[0..0]
when ','
attributes['format'] = 'csv'
when ':'
attributes['format'] = 'dsv'
end
block = next_table(block_reader, parent, attributes)
when :quote, :verse
AttributeList.rekey(attributes, [nil, 'attribution', 'citetitle'])
block = build_block(block_context, (block_context == :verse ? :verbatim : :complex), terminator, parent, reader, attributes)
else
raise "Unsupported block type #{block_context} at line #{reader.lineno}"
end
end
end
if !block.nil?
block.id ||= attributes['id'] if attributes.has_key?('id')
block.title = attributes['title'] unless block.title?
block.caption ||= attributes.delete('caption')
if block.id && block.title? && !attributes.has_key?('reftext')
document.register(:ids, [block.id, block.title])
end
block.update_attributes(attributes)
if block.context == :listing || block.context == :literal
catalog_callouts(block.buffer.join, document)
end
end
block
end