def self.next_block(reader, parent, attributes = {}, options = {})
return unless (skipped = reader.skip_blank_lines)
if (text_only = options[:text]) && skipped > 0
options.delete :text
text_only = false
end
document = parent.document
if options.fetch :parse_metadata, true
while parse_block_metadata_line reader, document, attributes, options
reader.shift
reader.skip_blank_lines || return
end
end
if (extensions = document.extensions)
block_extensions, block_macro_extensions = extensions.blocks?, extensions.block_macros?
end
source_location = reader.cursor if document.sourcemap
this_path, this_lineno, this_line, in_list = reader.path, reader.lineno, reader.read_line, ListItem === parent
block = block_context = cloaked_context = terminator = nil
style = attributes[1] ? (parse_style_attribute attributes, reader) : nil
if (delimited_block = is_delimited_block? this_line, true)
block_context = cloaked_context = delimited_block.context
terminator = delimited_block.terminator
if !style
style = attributes['style'] = block_context.to_s
elsif style != block_context.to_s
if delimited_block.masq.include? style
block_context = style.to_sym
elsif delimited_block.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: #{this_path}: line #{this_lineno}: invalid style for #{block_context} block: #{style})
style = block_context.to_s
end
end
end
while true
if style && Compliance.strict_verbatim_paragraphs && VERBATIM_STYLES.include?(style)
block_context = style.to_sym
reader.unshift_line this_line
break
end
if text_only
indented = this_line.start_with? ' ', TAB
else
md_syntax = Compliance.markdown_syntax
if this_line.start_with? ' '
indented, ch0 = true, ' '
if md_syntax && this_line.lstrip.start_with?(*MARKDOWN_THEMATIC_BREAK_CHARS.keys) &&
(MarkdownThematicBreakRx.match? this_line)
block = Block.new(parent, :thematic_break, :content_model => :empty)
break
end
elsif this_line.start_with? TAB
indented, ch0 = true, TAB
else
indented, ch0 = false, this_line.chr
layout_break_chars = md_syntax ? HYBRID_LAYOUT_BREAK_CHARS : LAYOUT_BREAK_CHARS
if (layout_break_chars.key? ch0) && (md_syntax ? (ExtLayoutBreakRx.match? this_line) :
(this_line == ch0 * (ll = this_line.length) && ll > 2))
block = Block.new(parent, layout_break_chars[ch0], :content_model => :empty)
break
elsif (this_line.end_with? ']') && (this_line.include? '::')
if (ch0 == 'i' || (this_line.start_with? 'video:', 'audio:')) && (match = BlockMediaMacroRx.match(this_line))
blk_ctx, target = match[1].to_sym, match[2]
block = Block.new(parent, blk_ctx, :content_model => :empty)
case blk_ctx
when :video
posattrs = ['poster', 'width', 'height']
when :audio
posattrs = []
else
posattrs = ['alt', 'width', 'height']
end
block.parse_attributes(match[3], posattrs, :sub_input => true, :sub_result => false, :into => attributes)
attributes.delete 'style' if attributes.key? 'style'
if (target.include? ATTR_REF_HEAD) && (target = block.sub_attributes target, :attribute_missing => 'drop-line').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
if blk_ctx == :image
block.document.register :images, target
attributes['alt'] ||= style || (attributes['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
unless (scaledwidth = attributes.delete 'scaledwidth').nil_or_empty?
attributes['scaledwidth'] = (TrailingDigitsRx.match? scaledwidth) ? %(#{scaledwidth}%) : scaledwidth
end
block.title = attributes.delete 'title'
block.assign_caption((attributes.delete 'caption'), 'figure')
end
attributes['target'] = target
break
elsif ch0 == 't' && (this_line.start_with? 'toc:') && (match = BlockTocMacroRx.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 = CustomBlockMacroRx.match(this_line)) &&
(extension = extensions.registered_for_block_macro?(match[1]))
target = match[2]
content = match[3]
if extension.config[:content_model] == :attributes
unless content.empty?
document.parse_attributes(content, extension.config[:pos_attrs] || [],
:sub_input => true, :sub_result => false, :into => attributes)
end
else
attributes['text'] = content
end
if (default_attrs = extension.config[:default_attrs])
attributes.update(default_attrs) {|_, old_v| old_v }
end
if (block = extension.process_method[parent, target, attributes])
attributes.replace block.attributes
break
else
attributes.clear
return
end
end
end
end
end
if !indented && CALLOUT_LIST_HEADS.include?(ch0 ||= this_line.chr) &&
(CalloutListSniffRx.match? this_line) && (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)))
list_item_lineno = reader.lineno
unless match[1] == expected_index.to_s
warn %(asciidoctor: WARNING: #{reader.path}: line #{list_item_lineno}: callout list item index: expected #{expected_index} got #{match[1]})
end
if (list_item = next_list_item reader, block, match)
block << list_item
if (coids = document.callouts.callout_ids block.items.size).empty?
warn %(asciidoctor: WARNING: #{reader.path}: line #{list_item_lineno}: no callouts refer to list item #{block.items.size})
else
list_item.attributes['coids'] = coids
end
end
expected_index += 1
match = nil
end
document.callouts.next_list
break
elsif UnorderedListRx.match? this_line
reader.unshift_line this_line
block = next_item_list(reader, :ulist, parent)
if (style || (Section === parent && parent.sectname)) == 'bibliography'
attributes['style'] = 'bibliography' unless style
block.items.each {|item| catalog_inline_biblio_anchor item.instance_variable_get(:@text), item, document }
end
break
elsif (match = OrderedListRx.match(this_line))
reader.unshift_line this_line
block = next_item_list(reader, :olist, parent)
unless style
marker = block.items[0].marker
if marker.start_with? '.'
attributes['style'] = (ORDERED_LIST_STYLES[marker.length - 1] || 'arabic').to_s
else
attributes['style'] = (ORDERED_LIST_STYLES.find {|s| OrderedListMarkerRxMap[s].match? marker } || 'arabic').to_s
end
end
break
elsif (match = DescriptionListRx.match(this_line))
reader.unshift_line this_line
block = next_description_list(reader, match, parent)
break
elsif (style == 'float' || style == 'discrete') && (Compliance.underline_style_section_titles ?
(is_section_title? this_line, reader.peek_line) : !indented && (atx_section_title? this_line))
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
block = Block.new(parent, :floating_title, :content_model => :empty)
block.title = float_title
attributes.delete 'title'
block.id = float_id || attributes['id'] ||
((document.attributes.key? 'sectids') ? (Section.generate_id block.title, document) : nil)
block.level = float_level
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: #{this_path}: line #{this_lineno}: invalid style for paragraph: #{style})
style = nil
end
end
break_at_list = (skipped == 0 && in_list)
reader.unshift_line this_line
if indented && !style
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
lines = read_paragraph_lines reader, break_at_list, :skip_line_comments => true
if text_only
adjust_indentation! lines if indented && style == 'normal'
block = Block.new(parent, :paragraph, :content_model => :simple, :source => lines, :attributes => attributes)
elsif (ADMONITION_STYLE_HEADS.include? ch0) && (this_line.include? ':') && (AdmonitionParagraphRx =~ this_line)
lines[0] = $'
attributes['name'] = admonition_name = (attributes['style'] = $1).downcase
attributes['textlabel'] = (attributes.delete 'caption') || document.attributes[%(#{admonition_name}-caption)]
block = Block.new(parent, :admonition, :content_model => :simple, :source => lines, :attributes => attributes)
elsif md_syntax && ch0 == '>' && this_line.start_with?('> ')
lines.map! {|line| line == '>' ? line[1..-1] : ((line.start_with? '> ') ? line[2..-1] : line) }
if lines[-1].start_with? '-- '
attribution, citetitle = lines.pop[3..-1].split(', ', 2)
attributes['attribution'] = attribution if attribution
attributes['citetitle'] = citetitle if citetitle
lines.pop while lines[-1].empty?
end
attributes['style'] = 'quote'
block = build_block(:quote, :compound, false, parent, Reader.new(lines), attributes)
elsif ch0 == '"' && lines.size > 1 && (lines[-1].start_with? '-- ') && (lines[-2].end_with? '"')
lines[0] = this_line[1..-1]
attribution, citetitle = lines.pop[3..-1].split(', ', 2)
attributes['attribution'] = attribution if attribution
attributes['citetitle'] = citetitle if citetitle
lines.pop while lines[-1].empty?
lines[-1] = lines[-1].chop
attributes['style'] = 'quote'
block = Block.new(parent, :quote, :content_model => :simple, :source => lines, :attributes => attributes)
else
adjust_indentation! lines if indented && style == 'normal'
block = Block.new(parent, :paragraph, :content_model => :simple, :source => lines, :attributes => attributes)
end
catalog_inline_anchors lines * LF, block, document
end
break
end unless delimited_block
unless block
block_context = :open if block_context == :abstract || block_context == :partintro
case block_context
when :admonition
attributes['name'] = admonition_name = style.downcase
attributes['textlabel'] = (attributes.delete '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)
attributes.clear
return
when :example
block = build_block(block_context, :compound, terminator, parent, reader, attributes)
when :listing, :literal
block = build_block(block_context, :verbatim, terminator, parent, reader, attributes)
when :source
AttributeList.rekey attributes, [nil, 'language', 'linenums']
if document.attributes.key? 'source-language'
attributes['language'] = document.attributes['source-language'] || 'text'
end unless attributes.key? 'language'
if (attributes.key? 'linenums-option') || (document.attributes.key? 'source-linenums-option')
attributes['linenums'] = ''
end unless attributes.key? 'linenums'
if document.attributes.key? 'source-indent'
attributes['indent'] = document.attributes['source-indent']
end unless attributes.key? 'indent'
block = build_block(:listing, :verbatim, terminator, parent, reader, attributes)
when :fenced_code
attributes['style'] = 'source'
if (ll = this_line.length) == 3
language = nil
elsif (comma_idx = (language = this_line.slice 3, ll).index ',')
if comma_idx > 0
language = (language.slice 0, comma_idx).strip
attributes['linenums'] = '' if comma_idx < ll - 4
else
language = nil
attributes['linenums'] = '' if ll > 4
end
else
language = language.lstrip
end
if language.nil_or_empty?
if document.attributes.key? 'source-language'
attributes['language'] = document.attributes['source-language'] || 'text'
end
else
attributes['language'] = language
end
if (attributes.key? 'linenums-option') || (document.attributes.key? 'source-linenums-option')
attributes['linenums'] = ''
end unless attributes.key? 'linenums'
if document.attributes.key? 'source-indent'
attributes['indent'] = document.attributes['source-indent']
end unless attributes.key? 'indent'
terminator = terminator.slice 0, 3
block = build_block(:listing, :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
block_reader = Reader.new reader.read_lines_until(:terminator => terminator, :skip_line_comments => true), reader.cursor
unless terminator.start_with? '|', '!'
attributes['format'] ||= (terminator.start_with? ',') ? 'csv' : '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
attributes['cloaked-context'] = cloaked_context
end
block = build_block block_context, content_model, terminator, parent, reader, attributes, :extension => extension
unless block
attributes.clear
return
end
else
raise %(Unsupported block type #{block_context} at #{reader.line_info})
end
end
end
block.source_location = source_location if source_location
block.title = attributes.delete 'title' if attributes.key? 'title'
block.style = attributes['style']
if (block_id = (block.id ||= attributes['id']))
unless document.register :refs, [block_id, block, attributes['reftext'] || (block.title? ? block.title : nil)]
warn %(asciidoctor: WARNING: #{this_path}: line #{this_lineno}: id assigned to block already in use: #{block_id})
end
end
block.attributes.update(attributes) unless attributes.empty?
block.lock_in_subs
if block.sub? :callouts
block.remove_sub :callouts unless catalog_callouts block.source, document
end
block
end