def self.next_block(reader, parent, attributes = {}, options = {})
skipped = reader.skip_blank_lines
return unless reader.has_more_lines?
if (text_only = options[:text]) && skipped > 0
options.delete(:text)
text_only = false
end
parse_metadata = options.fetch(:parse_metadata, true)
document = parent.document
if (extensions = document.extensions)
block_extensions = extensions.blocks?
block_macro_extensions = extensions.block_macros?
else
block_extensions = block_macro_extensions = false
end
in_list = ListItem === parent
block = nil
style = nil
explicit_style = nil
sourcemap = document.sourcemap
source_location = nil
while !block && reader.has_more_lines?
if parse_metadata && parse_block_metadata_line(reader, document, attributes, options)
reader.advance
next
end
source_location = reader.cursor if sourcemap
this_line = reader.read_line
delimited_block = false
block_context = nil
cloaked_context = nil
terminator = nil
if attributes[1]
style, explicit_style = parse_style_attribute(attributes, reader)
end
if (delimited_blk_match = is_delimited_block? this_line, true)
delimited_block = true
block_context = cloaked_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
elsif block_extensions && extensions.registered_for_block?(style, block_context)
block_context = style.to_sym
else
warn %(asciidoctor: WARNING: #{reader.prev_line_info}: invalid style for #{block_context} block: #{style})
style = block_context.to_s
end
end
end
unless delimited_block
while true
if style && Compliance.strict_verbatim_paragraphs && VERBATIM_STYLES.include?(style)
block_context = style.to_sym
reader.unshift_line this_line
break
end
unless text_only
first_char = Compliance.markdown_syntax ? this_line.lstrip.chr : this_line.chr
if (LAYOUT_BREAK_LINES.has_key? first_char) && this_line.length >= 3 &&
(Compliance.markdown_syntax ? LayoutBreakLinePlusRx : LayoutBreakLineRx) =~ this_line
block = Block.new(parent, LAYOUT_BREAK_LINES[first_char], :content_model => :empty)
break
elsif this_line.end_with?(']') && (match = MediaBlockMacroRx.match(this_line))
blk_ctx = match[1].to_sym
block = Block.new(parent, blk_ctx, :content_model => :empty)
if blk_ctx == :image
posattrs = ['alt', 'width', 'height']
elsif blk_ctx == :video
posattrs = ['poster', 'width', 'height']
else
posattrs = []
end
unless !style || 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], :attribute_missing => 'drop-line')
if target.empty?
if document.attributes.fetch('attribute-missing', Compliance.attribute_missing) == 'skip'
return Block.new(parent, :paragraph, :content_model => :simple, :source => [this_line])
else
attributes.clear
return
end
end
attributes['target'] = target
break
elsif first_char == 't' && (match = TocBlockMacroRx.match(this_line))
block = Block.new(parent, :toc, :content_model => :empty)
block.parse_attributes(match[1], [], :sub_result => false, :into => attributes)
break
elsif block_macro_extensions && (match = GenericBlockMacroRx.match(this_line)) &&
(extension = extensions.registered_for_block_macro?(match[1]))
target = match[2]
raw_attributes = match[3]
if extension.config[:content_model] == :attributes
unless raw_attributes.empty?
document.parse_attributes(raw_attributes, (extension.config[:pos_attrs] || []),
:sub_input => true, :sub_result => false, :into => attributes)
end
else
attributes['text'] = raw_attributes
end
if (default_attrs = extension.config[:default_attrs])
default_attrs.each {|k, v| attributes[k] ||= v }
end
if (block = extension.process_method[parent, target, attributes.dup])
attributes.replace block.attributes
else
attributes.clear
return
end
break
end
end
if (match = CalloutListRx.match(this_line))
block = List.new(parent, :colist)
attributes['style'] = 'arabic'
reader.unshift_line this_line
expected_index = 1
while match || (reader.has_more_lines? && (match = CalloutListRx.match(reader.peek_line)))
if match[1].to_i != expected_index
warn %(asciidoctor: WARNING: #{reader.path}: line #{reader.lineno - 2}: 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
block << list_item
coids = document.callouts.callout_ids(block.items.size)
if !coids.empty?
list_item.attributes['coids'] = coids
else
warn %(asciidoctor: WARNING: #{reader.path}: line #{reader.lineno - 2}: no callouts refer to list item #{block.items.size})
end
end
match = nil
end
document.callouts.next_list
break
elsif UnorderedListRx =~ this_line
reader.unshift_line this_line
block = next_outline_list(reader, :ulist, parent)
break
elsif (match = OrderedListRx.match(this_line))
reader.unshift_line this_line
block = next_outline_list(reader, :olist, parent)
if !attributes['style'] && !block.attributes['style']
marker = block.items[0].marker
if marker.start_with? '.'
attributes['style'] = (ORDERED_LIST_STYLES[marker.length - 1] || ORDERED_LIST_STYLES[0]).to_s
else
style = ORDERED_LIST_STYLES.detect{|s| OrderedListMarkerRxMap[s] =~ marker }
attributes['style'] = (style || ORDERED_LIST_STYLES[0]).to_s
end
end
break
elsif (match = DefinitionListRx.match(this_line))
reader.unshift_line this_line
block = next_labeled_list(reader, match, parent)
break
elsif (style == 'float' || style == 'discrete') &&
is_section_title?(this_line, (Compliance.underline_style_section_titles ? reader.peek_line(true) : nil))
reader.unshift_line this_line
float_id, float_reftext, float_title, float_level, _ = parse_section_title(reader, document)
attributes['reftext'] = float_reftext if float_reftext
float_id ||= attributes['id'] if attributes.has_key?('id')
block = Block.new(parent, :floating_title, :content_model => :empty)
if float_id.nil_or_empty?
tmp_sect = Section.new(parent)
tmp_sect.title = float_title
block.id = tmp_sect.generate_id
else
block.id = float_id
end
block.level = float_level
block.title = float_title
break
elsif style && style != 'normal'
if PARAGRAPH_STYLES.include?(style)
block_context = style.to_sym
cloaked_context = :paragraph
reader.unshift_line this_line
break
elsif ADMONITION_STYLES.include?(style)
block_context = :admonition
cloaked_context = :paragraph
reader.unshift_line this_line
break
elsif block_extensions && extensions.registered_for_block?(style, :paragraph)
block_context = style.to_sym
cloaked_context = :paragraph
reader.unshift_line this_line
break
else
warn %(asciidoctor: WARNING: #{reader.prev_line_info}: invalid style for paragraph: #{style})
style = nil
end
end
break_at_list = (skipped == 0 && in_list)
if style != 'normal' && LiteralParagraphRx =~ this_line
reader.unshift_line this_line
lines = read_paragraph_lines reader, break_at_list, :skip_line_comments => text_only
adjust_indentation! lines
block = Block.new(parent, :literal, :content_model => :verbatim, :source => lines, :attributes => attributes)
block.set_option('listparagraph') if in_list
else
reader.unshift_line this_line
lines = read_paragraph_lines reader, break_at_list, :skip_line_comments => true
if lines.empty?
reader.advance
return
end
catalog_inline_anchors(lines.join(EOL), document)
first_line = lines[0]
if !text_only && (admonition_match = AdmonitionParagraphRx.match(first_line))
lines[0] = admonition_match.post_match.lstrip
attributes['style'] = admonition_match[1]
attributes['name'] = admonition_name = admonition_match[1].downcase
attributes['caption'] ||= document.attributes[%(#{admonition_name}-caption)]
block = Block.new(parent, :admonition, :content_model => :simple, :source => lines, :attributes => attributes)
elsif !text_only && Compliance.markdown_syntax && first_line.start_with?('> ')
lines.map! {|line|
if line == '>'
line[1..-1]
elsif line.start_with? '> '
line[2..-1]
else
line
end
}
if lines[-1].start_with? '-- '
attribution, citetitle = lines.pop[3..-1].split(', ', 2)
lines.pop while lines[-1].empty?
else
attribution, citetitle = nil
end
attributes['style'] = 'quote'
attributes['attribution'] = attribution if attribution
attributes['citetitle'] = citetitle if citetitle
block = build_block(:quote, :compound, false, parent, Reader.new(lines), attributes)
elsif !text_only && (blockquote? lines, first_line)
lines[0] = first_line[1..-1]
attribution, citetitle = lines.pop[3..-1].split(', ', 2)
lines.pop while lines[-1].empty?
lines[-1] = lines[-1].chop
attributes['style'] = 'quote'
attributes['attribution'] = attribution if attribution
attributes['citetitle'] = citetitle if citetitle
block = Block.new(parent, :quote, :content_model => :simple, :source => lines, :attributes => attributes)
else
if style == 'normal'
adjust_indentation! lines
end
block = Block.new(parent, :paragraph, :content_model => :simple, :source => lines, :attributes => attributes)
end
end
break
end
end
if !block && block_context
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, :compound, terminator, parent, reader, attributes)
when :comment
build_block(block_context, :skip, terminator, parent, reader, attributes)
return
when :example
block = build_block(block_context, :compound, terminator, parent, reader, attributes)
when :listing, :fenced_code, :source
if block_context == :fenced_code
style = attributes['style'] = 'source'
language, linenums = this_line[3..-1].tr(' ', '').split(',', 2)
if !language.nil_or_empty?
attributes['language'] = language
attributes['linenums'] = '' unless linenums.nil_or_empty?
elsif (default_language = document.attributes['source-language'])
attributes['language'] = default_language
end
if !attributes.key?('indent') && document.attributes.key?('source-indent')
attributes['indent'] = document.attributes['source-indent']
end
terminator = terminator[0..2]
elsif block_context == :source
AttributeList.rekey(attributes, [nil, 'language', 'linenums'])
unless attributes.key? 'language'
if (default_language = document.attributes['source-language'])
attributes['language'] = default_language
end
end
if !attributes.key?('indent') && document.attributes.key?('source-indent')
attributes['indent'] = document.attributes['source-indent']
end
end
block = build_block(:listing, :verbatim, terminator, parent, reader, attributes)
when :literal
block = build_block(block_context, :verbatim, terminator, parent, reader, attributes)
when :pass
block = build_block(block_context, :raw, terminator, parent, reader, attributes)
when :stem, :latexmath, :asciimath
if block_context == :stem
attributes['style'] = if (explicit_stem_syntax = attributes[2])
explicit_stem_syntax.include?('tex') ? 'latexmath' : 'asciimath'
elsif (default_stem_syntax = document.attributes['stem']).nil_or_empty?
'asciimath'
else
default_stem_syntax
end
end
block = build_block(:stem, :raw, terminator, parent, reader, attributes)
when :open, :sidebar
block = build_block(block_context, :compound, terminator, parent, reader, attributes)
when :table
cursor = reader.cursor
block_reader = Reader.new reader.read_lines_until(:terminator => terminator, :skip_line_comments => true), cursor
case terminator.chr
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 : :compound), terminator, parent, reader, attributes)
else
if block_extensions && (extension = extensions.registered_for_block?(block_context, cloaked_context))
if (content_model = extension.config[:content_model]) != :skip
if !(pos_attrs = extension.config[:pos_attrs] || []).empty?
AttributeList.rekey(attributes, [nil].concat(pos_attrs))
end
if (default_attrs = extension.config[:default_attrs])
default_attrs.each {|k, v| attributes[k] ||= v }
end
end
block = build_block block_context, content_model, terminator, parent, reader, attributes, :extension => extension
unless block && content_model != :skip
attributes.clear
return
end
else
raise %(Unsupported block type #{block_context} at #{reader.line_info})
end
end
end
end
if block
block.source_location = source_location if source_location
block.title = attributes['title'] unless block.title?
if block.context == :image
resolved_target = attributes['target']
block.document.register(:images, resolved_target)
attributes['alt'] ||= Helpers.basename(resolved_target, true).tr('_-', ' ')
attributes['alt'] = block.sub_specialchars attributes['alt']
block.assign_caption attributes.delete('caption'), 'figure'
if (scaledwidth = attributes['scaledwidth'])
if (48..57).include?((scaledwidth[-1] || 0).ord)
attributes['scaledwidth'] = %(#{scaledwidth}%)
end
end
else
block.caption ||= attributes.delete('caption')
end
block.style = attributes['style']
if (block_id = (block.id ||= attributes['id']))
document.register(:ids, [block_id, (attributes['reftext'] || (block.title? ? block.title : nil))])
end
block.attributes.update(attributes) unless attributes.empty?
block.lock_in_subs
if block.sub? :callouts
unless (catalog_callouts block.source, document)
block.remove_sub :callouts
end
end
end
block
end