lib/asciidoctor/parser.rb in asciidoctor-1.5.7.1 vs lib/asciidoctor/parser.rb in asciidoctor-1.5.8
- old
+ new
@@ -90,17 +90,20 @@
#
# returns the Document object
def self.parse(reader, document, options = {})
block_attributes = parse_document_header(reader, document)
- while reader.has_more_lines?
- new_section, block_attributes = next_section(reader, document, block_attributes)
- if new_section
- document.assign_numeral new_section
- document.blocks << new_section
+ # NOTE don't use a postfix conditional here as it's known to confuse JRuby in certain circumstances
+ unless options[:header_only]
+ while reader.has_more_lines?
+ new_section, block_attributes = next_section(reader, document, block_attributes)
+ if new_section
+ document.assign_numeral new_section
+ document.blocks << new_section
+ end
end
- end unless options[:header_only]
+ end
document
end
# Public: Parses the document header of the AsciiDoc source read from the Reader
@@ -167,61 +170,84 @@
# restore doctitle attribute to original assignment
doc_attrs['doctitle'] = assigned_doctitle if assigned_doctitle
# parse title and consume name section of manpage document
- parse_manpage_header(reader, document) if document.doctype == 'manpage'
+ parse_manpage_header(reader, document, block_attrs) if document.doctype == 'manpage'
# NOTE block_attrs are the block-level attributes (not document attributes) that
# precede the first line of content (document title, first section or first block)
document.finalize_header block_attrs
end
# Public: Parses the manpage header of the AsciiDoc source read from the Reader
#
# returns Nothing
- def self.parse_manpage_header(reader, document)
- if ManpageTitleVolnumRx =~ document.attributes['doctitle']
- document.attributes['mantitle'] = (($1.include? ATTR_REF_HEAD) ? (document.sub_attributes $1) : $1).downcase
- document.attributes['manvolnum'] = $2
+ def self.parse_manpage_header(reader, document, block_attributes)
+ if ManpageTitleVolnumRx =~ (doc_attrs = document.attributes)['doctitle']
+ doc_attrs['manvolnum'] = manvolnum = $2
+ doc_attrs['mantitle'] = (((mantitle = $1).include? ATTR_REF_HEAD) ? (document.sub_attributes mantitle) : mantitle).downcase
else
- logger.error message_with_context 'malformed manpage title', :source_location => reader.cursor_at_prev_line
+ logger.error message_with_context 'non-conforming manpage title', :source_location => (reader.cursor_at_line 1)
# provide sensible fallbacks
- document.attributes['mantitle'] = document.attributes['doctitle']
- document.attributes['manvolnum'] = '1'
+ doc_attrs['mantitle'] = doc_attrs['doctitle'] || doc_attrs['docname'] || 'command'
+ doc_attrs['manvolnum'] = manvolnum = '1'
end
-
- reader.skip_blank_lines
-
- if is_next_line_section? reader, {}
- name_section = initialize_section reader, document, {}
- if name_section.level == 1
- name_section_buffer = (reader.read_lines_until :break_on_blank_lines => true, :skip_line_comments => true) * ' '
- if ManpageNamePurposeRx =~ name_section_buffer
- document.attributes['manname-title'] ||= name_section.title
- document.attributes['manname-id'] = name_section.id if name_section.id
- document.attributes['manpurpose'] = $2
- if (manname = ($1.include? ATTR_REF_HEAD) ? (document.sub_attributes $1) : $1).include? ','
- manname = (mannames = (manname.split ',').map {|n| n.lstrip })[0]
+ if (manname = doc_attrs['manname']) && doc_attrs['manpurpose']
+ doc_attrs['manname-title'] ||= 'Name'
+ doc_attrs['mannames'] = [manname]
+ if document.backend == 'manpage'
+ doc_attrs['docname'] = manname
+ doc_attrs['outfilesuffix'] = %(.#{manvolnum})
+ end
+ else
+ reader.skip_blank_lines
+ reader.save
+ block_attributes.update parse_block_metadata_lines reader, document
+ if (name_section_level = is_next_line_section? reader, {})
+ if name_section_level == 1
+ name_section = initialize_section reader, document, {}
+ name_section_buffer = (reader.read_lines_until :break_on_blank_lines => true, :skip_line_comments => true).map(&:lstrip).join ' '
+ if ManpageNamePurposeRx =~ name_section_buffer
+ doc_attrs['manname-title'] ||= name_section.title
+ doc_attrs['manname-id'] = name_section.id if name_section.id
+ doc_attrs['manpurpose'] = $2
+ if (manname = $1).include? ATTR_REF_HEAD
+ manname = document.sub_attributes manname
+ end
+ if manname.include? ','
+ manname = (mannames = (manname.split ',').map {|n| n.lstrip })[0]
+ else
+ mannames = [manname]
+ end
+ doc_attrs['manname'] = manname
+ doc_attrs['mannames'] = mannames
+ if document.backend == 'manpage'
+ doc_attrs['docname'] = manname
+ doc_attrs['outfilesuffix'] = %(.#{manvolnum})
+ end
else
- mannames = [manname]
+ error_msg = 'non-conforming name section body'
end
- document.attributes['manname'] = manname
- document.attributes['mannames'] = mannames
-
- if document.backend == 'manpage'
- document.attributes['docname'] = manname
- document.attributes['outfilesuffix'] = %(.#{document.attributes['manvolnum']})
- end
else
- logger.error message_with_context 'malformed name section body', :source_location => reader.cursor_at_prev_line
+ error_msg = 'name section must be at level 1'
end
else
- logger.error message_with_context 'name section title must be at level 1', :source_location => reader.cursor_at_prev_line
+ error_msg = 'name section expected'
end
- else
- logger.error message_with_context 'name section expected', :source_location => reader.cursor_at_prev_line
+ if error_msg
+ reader.restore_save
+ logger.error message_with_context error_msg, :source_location => reader.cursor
+ doc_attrs['manname'] = (manname = doc_attrs['docname'] || 'command')
+ doc_attrs['mannames'] = [manname]
+ if document.backend == 'manpage'
+ doc_attrs['docname'] = manname
+ doc_attrs['outfilesuffix'] = %(.#{manvolnum})
+ end
+ else
+ reader.discard_save
+ end
end
nil
end
# Public: Return the next section from the Reader.
@@ -487,11 +513,11 @@
style = block_context.to_s
end
end
end
- # this loop is used for flow control; it only executes once, and only when delimited_block is set
+ # this loop is used for flow control; it only executes once, and only when delimited_block is not set
# break once a block is found or at end of loop
# returns nil if the line should be dropped
while true
# process lines verbatim
if style && Compliance.strict_verbatim_paragraphs && VERBATIM_STYLES.include?(style)
@@ -540,11 +566,11 @@
when :audio
posattrs = []
else # :image
posattrs = ['alt', 'width', 'height']
end
- block.parse_attributes blk_attrs, posattrs, :sub_input => true, :sub_result => false, :into => attributes
+ block.parse_attributes blk_attrs, posattrs, :sub_input => true, :into => attributes
end
# style doesn't have special meaning for media macros
attributes.delete 'style' if attributes.key? 'style'
if (target.include? ATTR_REF_HEAD) && (target = block.sub_attributes target, :attribute_missing => 'drop-line').empty?
# retain as unparsed if attribute-missing is skip
@@ -555,35 +581,40 @@
attributes.clear
return
end
end
if blk_ctx == :image
- document.register :images, target
+ document.register :images, [target, (attributes['imagesdir'] = doc_attrs['imagesdir'])]
# NOTE style is the value of the first positional attribute in the block attribute line
attributes['alt'] ||= style || (attributes['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
unless (scaledwidth = attributes.delete 'scaledwidth').nil_or_empty?
# NOTE assume % units if not specified
attributes['scaledwidth'] = (TrailingDigitsRx.match? scaledwidth) ? %(#{scaledwidth}%) : scaledwidth
end
- block.title = attributes.delete 'title'
- block.assign_caption((attributes.delete 'caption'), 'figure')
+ if attributes.key? 'title'
+ block.title = attributes.delete 'title'
+ block.assign_caption((attributes.delete 'caption'), 'figure')
+ end
end
attributes['target'] = target
break
elsif ch0 == 't' && (this_line.start_with? 'toc:') && BlockTocMacroRx =~ this_line
block = Block.new parent, :toc, :content_model => :empty
- block.parse_attributes($1, [], :sub_result => false, :into => attributes) if $1
+ block.parse_attributes $1, [], :into => attributes if $1
break
elsif block_macro_extensions && CustomBlockMacroRx =~ this_line &&
(extension = extensions.registered_for_block_macro? $1)
target, content = $2, $3
+ if (target.include? ATTR_REF_HEAD) && (target = parent.sub_attributes target).empty? &&
+ (doc_attrs['attribute-missing'] || Compliance.attribute_missing) == 'drop-line'
+ attributes.clear
+ return
+ end
if extension.config[:content_model] == :attributes
- if content
- document.parse_attributes content, extension.config[:pos_attrs] || [], :sub_input => true, :sub_result => false, :into => attributes
- end
+ document.parse_attributes content, extension.config[:pos_attrs] || [], :sub_input => true, :into => attributes if content
else
attributes['text'] = content || ''
end
if (default_attrs = extension.config[:default_attrs])
attributes.update(default_attrs) {|_, old_v| old_v }
@@ -599,65 +630,31 @@
end
end
end
# haven't found anything yet, continue
- 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'
+ if !indented && (ch0 ||= this_line.chr) == '<' && CalloutListRx =~ this_line
reader.unshift_line this_line
- next_index = 1
- # NOTE skip the match on the first time through as we've already done it (emulates begin...while)
- while match || ((match = CalloutListRx.match reader.peek_line) && reader.mark)
- # might want to move this check to a validate method
- unless match[1] == next_index.to_s
- logger.warn message_with_context %(callout list item index: expected #{next_index}, got #{match[1]}), :source_location => reader.cursor_at_mark
- end
- if (list_item = next_list_item reader, block, match)
- block.items << list_item
- if (coids = document.callouts.callout_ids block.items.size).empty?
- logger.warn message_with_context %(no callout found for <#{block.items.size}>), :source_location => reader.cursor_at_mark
- else
- list_item.attributes['coids'] = coids
- end
- end
- next_index += 1
- match = nil
- end
-
- document.callouts.next_list
+ block = parse_callout_list(reader, $~, parent, document.callouts)
+ attributes['style'] = 'arabic'
break
elsif UnorderedListRx.match? this_line
reader.unshift_line this_line
- if Section === parent && parent.sectname == 'bibliography'
- style = attributes['style'] = 'bibliography'
- end unless style
- block = next_list(reader, :ulist, parent, style)
+ attributes['style'] = (style = 'bibliography') if !style && Section === parent && parent.sectname == 'bibliography'
+ block = parse_list(reader, :ulist, parent, style)
break
elsif (match = OrderedListRx.match(this_line))
reader.unshift_line this_line
- block = next_list(reader, :olist, parent)
- # FIXME move this logic into next_list
- unless style
- marker = block.items[0].marker
- if marker.start_with? '.'
- # first one makes more sense, but second one is AsciiDoc-compliant
- # TODO control behavior using a compliance setting
- #attributes['style'] = (ORDERED_LIST_STYLES[block.level - 1] || 'arabic').to_s
- 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
+ block = parse_list(reader, :olist, parent, style)
+ attributes['style'] = block.style if block.style
break
elsif (match = DescriptionListRx.match(this_line))
reader.unshift_line this_line
- block = next_description_list(reader, match, parent)
+ block = parse_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
@@ -723,13 +720,13 @@
lines[0] = $' # string after match
attributes['name'] = admonition_name = (attributes['style'] = $1).downcase
attributes['textlabel'] = (attributes.delete 'caption') || doc_attrs[%(#{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) }
+ lines.map! {|line| line == '>' ? (line.slice 1, line.length) : ((line.start_with? '> ') ? (line.slice 2, line.length) : line) }
if lines[-1].start_with? '-- '
- credit_line = (credit_line = lines.pop[3..-1])
+ credit_line = (credit_line = lines.pop).slice 3, credit_line.length
lines.pop while lines[-1].empty?
end
attributes['style'] = 'quote'
# NOTE will only detect discrete (aka free-floating) headings
# TODO could assume a discrete heading when inside a block context
@@ -739,14 +736,14 @@
attribution, citetitle = (block.apply_subs credit_line).split ', ', 2
attributes['attribution'] = attribution if attribution
attributes['citetitle'] = citetitle if citetitle
end
elsif ch0 == '"' && lines.size > 1 && (lines[-1].start_with? '-- ') && (lines[-2].end_with? '"')
- lines[0] = this_line[1..-1] # strip leading quote
- credit_line = (credit_line = lines.pop).slice(3, credit_line.length)
+ lines[0] = this_line.slice 1, this_line.length # strip leading quote
+ credit_line = (credit_line = lines.pop).slice 3, credit_line.length
lines.pop while lines[-1].empty?
- lines[-1] = lines[-1].chop # strip trailing quote
+ lines << lines.pop.chop # strip trailing quote
attributes['style'] = 'quote'
block = Block.new(parent, :quote, :content_model => :simple, :source => lines, :attributes => attributes)
attribution, citetitle = (block.apply_subs credit_line).split ', ', 2
attributes['attribution'] = attribution if attribution
attributes['citetitle'] = citetitle if citetitle
@@ -755,11 +752,11 @@
# QUESTION do we even need to shift since whitespace is normalized by XML in this case?
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
+ catalog_inline_anchors((lines.join LF), block, document, reader)
end
break # forbid loop from executing more than once
end unless delimited_block
@@ -846,11 +843,11 @@
# NOTE it's very rare that format is set when using a format hint char, so short-circuit
unless terminator.start_with? '|', '!'
# NOTE infer dsv once all other format hint chars are ruled out
attributes['format'] ||= (terminator.start_with? ',') ? 'csv' : 'dsv'
end
- block = next_table(block_reader, parent, attributes)
+ block = parse_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)
@@ -964,11 +961,11 @@
context, masq = DELIMITED_BLOCKS[tip]
BlockMatchData.new(context, masq, tip, tip)
else
true
end
- elsif %(#{tip}#{tip[-1..-1] * (line_len - tl)}) == line
+ elsif %(#{tip}#{tip.slice(-1, 1) * (line_len - tl)}) == line
if return_match_data
context, masq = DELIMITED_BLOCKS[tip]
BlockMatchData.new(context, masq, tip, line)
else
true
@@ -1089,49 +1086,19 @@
# list_type - A Symbol representing the list type (:olist for ordered, :ulist for unordered)
# parent - The parent Block to which this list belongs
# style - The block style assigned to this list (optional, default: nil)
#
# Returns the Block encapsulating the parsed unordered or ordered list
- def self.next_list(reader, list_type, parent, style = nil)
+ def self.parse_list(reader, list_type, parent, style)
list_block = List.new(parent, list_type)
- list_block.level = parent.context == list_type ? (parent.level + 1) : 1
- while reader.has_more_lines? && ListRxMap[list_type] =~ reader.peek_line
- match, marker = $~, resolve_list_marker(list_type, $1)
-
- # if we are moving to the next item, and the marker is different
- # determine if we are moving up or down in nesting
- if list_block.items? && marker != list_block.items[0].marker
- # assume list is nested by default, but then check to see if we are
- # popping out of a nested list by matching an ancestor's list marker
- this_item_level = list_block.level + 1
- ancestor = parent
- while ancestor.context == list_type
- if marker == ancestor.items[0].marker
- this_item_level = ancestor.level
- break
- end
- ancestor = ancestor.parent
- end
- else
- this_item_level = list_block.level
+ while reader.has_more_lines? && (list_rx ||= ListRxMap[list_type]) =~ reader.peek_line
+ # NOTE parse_list_item will stop at sibling item or end of list; never sees ancestor items
+ if (list_item = parse_list_item reader, list_block, $~, $1, style)
+ list_block.items << list_item
end
- if !list_block.items? || this_item_level == list_block.level
- list_item = next_list_item(reader, list_block, match, nil, style)
- elsif this_item_level < list_block.level
- # leave this block
- break
- elsif this_item_level > list_block.level
- # If this next list level is down one from the
- # current Block's, append it to content of the current list item
- list_block.items[-1] << next_block(reader, list_block)
- end
-
- list_block.items << list_item if list_item
- list_item = nil
-
reader.skip_blank_lines || break
end
list_block
end
@@ -1142,14 +1109,15 @@
# document - The current document in which the callouts are stored
#
# Returns A Boolean indicating whether callouts were found
def self.catalog_callouts(text, document)
found = false
+ autonum = 0
text.scan(CalloutScanRx) {
# lead with assignments for Ruby 1.8.7 compat
captured, num = $&, $2
- document.callouts.register num unless captured.start_with? '\\'
+ document.callouts.register num == '.' ? (autonum += 1).to_s : num unless captured.start_with? '\\'
# we have to mark as found even if it's escaped so it can be unescaped
found = true
} if text.include? '<'
found
end
@@ -1178,12 +1146,14 @@
# text - The String text in which to look for inline anchors
# block - The block in which the references should be searched
# document - The current Document on which the references are stored
#
# Returns nothing
- def self.catalog_inline_anchors text, block, document
+ def self.catalog_inline_anchors text, block, document, reader
text.scan(InlineAnchorScanRx) do
+ # alias match for Ruby 1.8.7 compat
+ m = $~
if (id = $1)
if (reftext = $2)
next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
end
else
@@ -1192,11 +1162,15 @@
reftext = reftext.gsub '\]', ']' if reftext.include? ']'
next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
end
end
unless document.register :refs, [id, (Inline.new block, :anchor, reftext, :type => :ref, :id => id), reftext]
- logger.warn message_with_context %(id assigned to anchor already in use: #{id}), :source_location => document.reader.cursor_at_prev_line
+ location = reader.cursor_at_mark
+ if (offset = (m.pre_match.count LF) + ((m[0].start_with? LF) ? 1 : 0)) > 0
+ (location = location.dup).advance offset
+ end
+ logger.warn message_with_context %(id assigned to anchor already in use: #{id}), :source_location => location
end
end if (text.include? '[[') || (text.include? 'or:')
nil
end
@@ -1221,20 +1195,20 @@
# reader - The Reader from which to retrieve the description list
# match - The Regexp match for the head of the list
# parent - The parent Block to which this description list belongs
#
# Returns the Block encapsulating the parsed description list
- def self.next_description_list(reader, match, parent)
+ def self.parse_description_list(reader, match, parent)
list_block = List.new(parent, :dlist)
previous_pair = nil
# allows us to capture until we find a description item
# that uses the same delimiter (::, :::, :::: or ;;)
sibling_pattern = DescriptionListSiblingRx[match[2]]
# NOTE skip the match on the first time through as we've already done it (emulates begin...while)
while match || (reader.has_more_lines? && (match = sibling_pattern.match(reader.peek_line)))
- term, item = next_list_item(reader, list_block, match, sibling_pattern)
+ term, item = parse_list_item(reader, list_block, match, sibling_pattern)
if previous_pair && !previous_pair[1]
previous_pair[0] << term
previous_pair[1] = item
else
list_block.items << (previous_pair = [[term], item])
@@ -1243,29 +1217,67 @@
end
list_block
end
- # Internal: Parse and construct the next ListItem for the current list Block
- # (unordered, ordered, or callout list) or the term ListItem and description
- # ListItem pair for the description list Block.
+ # Internal: Parse and construct a callout list Block from the current position of the Reader and
+ # advance the document callouts catalog to the next list.
#
- # First collect and process all the lines that constitute the next list
- # item for the parent list (according to its type). Next, parse those lines
- # into blocks and associate them with the ListItem (in the case of a
- # description list, the description ListItem). Finally, fold the first block
- # into the item's text attribute according to rules described in ListItem.
+ # reader - The Reader from which to retrieve the callout list.
+ # match - The Regexp match containing the head of the list.
+ # parent - The parent Block to which this callout list belongs.
+ # callouts - The document callouts catalog.
#
+ # Returns the Block that represents the parsed callout list.
+ def self.parse_callout_list reader, match, parent, callouts
+ list_block = List.new(parent, :colist)
+ next_index = 1
+ autonum = 0
+ # NOTE skip the match on the first time through as we've already done it (emulates begin...while)
+ while match || ((match = CalloutListRx.match reader.peek_line) && reader.mark)
+ if (num = match[1]) == '.'
+ num = (autonum += 1).to_s
+ end
+ # might want to move this check to a validate method
+ unless num == next_index.to_s
+ logger.warn message_with_context %(callout list item index: expected #{next_index}, got #{num}), :source_location => reader.cursor_at_mark
+ end
+ if (list_item = parse_list_item reader, list_block, match, '<1>')
+ list_block.items << list_item
+ if (coids = callouts.callout_ids list_block.items.size).empty?
+ logger.warn message_with_context %(no callout found for <#{list_block.items.size}>), :source_location => reader.cursor_at_mark
+ else
+ list_item.attributes['coids'] = coids
+ end
+ end
+ next_index += 1
+ match = nil
+ end
+
+ callouts.next_list
+ list_block
+ end
+
+ # Internal: Parse and construct the next ListItem (unordered, ordered, or callout list) or next
+ # term ListItem and description ListItem pair (description list) for the specified list Block.
+ #
+ # First, collect and process all the lines that constitute the next list item for the specified
+ # list (according to its type). Next, create a ListItem (in the case of a description list, a
+ # description ListItem), parse the lines into blocks, and associate those blocks with that
+ # ListItem. Finally, fold the first block into the item's text attribute according to rules
+ # described in ListItem.
+ #
# reader - The Reader from which to retrieve the next list item
- # list_block - The parent list Block of this ListItem. Also provides access to the list type.
- # match - The match Array which contains the marker and text (first-line) of the ListItem
- # sibling_trait - The list marker or the Regexp to match a sibling item (optional, default: nil)
+ # list_block - The parent list Block for this ListItem. Also provides access to the list type.
+ # match - The MatchData that contains the list item marker and first line text of the ListItem
+ # sibling_trait - The trait to match a sibling list item. For ordered and unordered lists, this is
+ # a String marker (e.g., '**' or 'ii)'). For description lists, this is a Regexp
+ # marker pattern.
# style - The block style assigned to this list (optional, default: nil)
#
- # Returns the next ListItem or ListItem pair (depending on the list type)
- # for the parent list Block.
- def self.next_list_item(reader, list_block, match, sibling_trait = nil, style = nil)
+ # Returns the next ListItem or ListItem pair (description list) for the parent list Block.
+ def self.parse_list_item(reader, list_block, match, sibling_trait, style = nil)
if (list_type = list_block.context) == :dlist
dlist = true
list_term = ListItem.new(list_block, (term_text = match[1]))
if term_text.start_with?('[[') && LeadingInlineAnchorRx =~ term_text
catalog_inline_anchor $1, ($2 || $'.lstrip), list_term, reader
@@ -1283,11 +1295,11 @@
else
has_text = true
list_item = ListItem.new(list_block, (item_text = match[2]))
list_item.source_location = reader.cursor if list_block.document.sourcemap
if list_type == :ulist
- list_item.marker = (sibling_trait ||= match[1])
+ list_item.marker = sibling_trait
if item_text.start_with?('[')
if style && style == 'bibliography'
if InlineBiblioAnchorRx =~ item_text
catalog_inline_biblio_anchor $1, $2, list_item, reader
end
@@ -1303,13 +1315,22 @@
list_item.attributes['checked'] = '' unless item_text.start_with? '[ '
list_item.text = item_text.slice(4, item_text.length)
end
end
elsif list_type == :olist
- list_item.marker = (sibling_trait ||= resolve_ordered_list_marker(match[1], list_block.items.size, true, reader))
+ sibling_trait, implicit_style = resolve_ordered_list_marker(sibling_trait, (ordinal = list_block.items.size), true, reader)
+ list_item.marker = sibling_trait
+ if ordinal == 0 && !style
+ # using list level makes more sense, but we don't track it
+ # basing style on marker level is compliant with AsciiDoc Python
+ list_block.style = implicit_style || ((ORDERED_LIST_STYLES[sibling_trait.length - 1] || 'arabic').to_s)
+ end
+ if item_text.start_with?('[[') && LeadingInlineAnchorRx =~ item_text
+ catalog_inline_anchor $1, $2, list_item, reader
+ end
else # :colist
- list_item.marker = (sibling_trait ||= '<1>')
+ list_item.marker = sibling_trait
end
end
# first skip the line with the marker / term (it gets put back onto the reader by next_block)
reader.shift
@@ -1538,11 +1559,11 @@
# We do need to replace the optional trailing continuation
# a blank line would have served the same purpose in the document
buffer.pop if !buffer.empty? && buffer[-1] == LIST_CONTINUATION
- #warn "BUFFER[#{list_type},#{sibling_trait}]>#{buffer * LF}<BUFFER"
+ #warn "BUFFER[#{list_type},#{sibling_trait}]>#{buffer.join LF}<BUFFER"
#warn "BUFFER[#{list_type},#{sibling_trait}]>#{buffer.inspect}<BUFFER"
buffer
end
@@ -1782,27 +1803,29 @@
# data = ["Author Name <author@example.org>\n", "v1.0, 2012-12-21: Coincide w/ end of world.\n"]
# parse_header_metadata(Reader.new data, nil, :normalize => true)
# # => {'author' => 'Author Name', 'firstname' => 'Author', 'lastname' => 'Name', 'email' => 'author@example.org',
# # 'revnumber' => '1.0', 'revdate' => '2012-12-21', 'revremark' => 'Coincide w/ end of world.'}
def self.parse_header_metadata(reader, document = nil)
+ doc_attrs = document && document.attributes
# NOTE this will discard any comment lines, but not skip blank lines
process_attribute_entries reader, document
- metadata, implicit_author, implicit_authors = {}, nil, nil
+ metadata, implicit_author, implicit_authorinitials = implicit_authors = {}, nil, nil
if reader.has_more_lines? && !reader.next_line_empty?
unless (author_metadata = process_authors reader.read_line).empty?
if document
# apply header subs and assign to document
author_metadata.each do |key, val|
- unless document.attributes.key? key
- document.attributes[key] = ::String === val ? (document.apply_header_subs val) : val
+ unless doc_attrs.key? key
+ doc_attrs[key] = ::String === val ? (document.apply_header_subs val) : val
end
end
- implicit_author = document.attributes['author']
- implicit_authors = document.attributes['authors']
+ implicit_author = doc_attrs['author']
+ implicit_authorinitials = doc_attrs['authorinitials']
+ implicit_authors = doc_attrs['authors']
end
metadata = author_metadata
end
@@ -1816,11 +1839,11 @@
if (match = RevisionInfoLineRx.match(rev_line))
rev_metadata['revnumber'] = match[1].rstrip if match[1]
unless (component = match[2].strip).empty?
# version must begin with 'v' if date is absent
if !match[1] && (component.start_with? 'v')
- rev_metadata['revnumber'] = component[1..-1]
+ rev_metadata['revnumber'] = component.slice 1, component.length
else
rev_metadata['revdate'] = component
end
end
rev_metadata['revremark'] = match[3].rstrip if match[3]
@@ -1832,12 +1855,12 @@
unless rev_metadata.empty?
if document
# apply header subs and assign to document
rev_metadata.each do |key, val|
- unless document.attributes.key? key
- document.attributes[key] = document.apply_header_subs(val)
+ unless doc_attrs.key? key
+ doc_attrs[key] = document.apply_header_subs val
end
end
end
metadata.update rev_metadata
@@ -1851,22 +1874,23 @@
author_metadata = {}
end
# process author attribute entries that override (or stand in for) the implicit author line
if document
- if document.attributes.key?('author') && (author_line = document.attributes['author']) != implicit_author
+ if doc_attrs.key?('author') && (author_line = doc_attrs['author']) != implicit_author
# do not allow multiple, process as names only
author_metadata = process_authors author_line, true, false
- elsif document.attributes.key?('authors') && (author_line = document.attributes['authors']) != implicit_authors
+ author_metadata.delete 'authorinitials' if doc_attrs['authorinitials'] != implicit_authorinitials
+ elsif doc_attrs.key?('authors') && (author_line = doc_attrs['authors']) != implicit_authors
# allow multiple, process as names only
author_metadata = process_authors author_line, true
else
authors, author_idx, author_key, explicit, sparse = [], 1, 'author_1', false, false
- while document.attributes.key? author_key
+ while doc_attrs.key? author_key
# only use indexed author attribute if value is different
# leaves corner case if line matches with underscores converted to spaces; use double space to force
- if (author_override = document.attributes[author_key]) == author_metadata[author_key]
+ if (author_override = doc_attrs[author_key]) == author_metadata[author_key]
authors << nil
sparse = true
else
authors << author_override
explicit = true
@@ -1879,28 +1903,28 @@
unless author
authors[idx] = [
author_metadata[%(firstname_#{name_idx = idx + 1})],
author_metadata[%(middlename_#{name_idx})],
author_metadata[%(lastname_#{name_idx})]
- ].compact.map {|it| it.tr ' ', '_' } * ' '
+ ].compact.map {|it| it.tr ' ', '_' }.join ' '
end
end if sparse
# process as names only
author_metadata = process_authors authors, true, false
else
author_metadata = {}
end
end
if author_metadata.empty?
- metadata['authorcount'] ||= (document.attributes['authorcount'] = 0)
+ metadata['authorcount'] ||= (doc_attrs['authorcount'] = 0)
else
- document.attributes.update author_metadata
+ doc_attrs.update author_metadata
# special case
- if !document.attributes.key?('email') && document.attributes.key?('email_1')
- document.attributes['email'] = document.attributes['email_1']
+ if !doc_attrs.key?('email') && doc_attrs.key?('email_1')
+ doc_attrs['email'] = doc_attrs['email_1']
end
end
end
metadata
@@ -2043,11 +2067,11 @@
return true
end
elsif (next_line.end_with? ']') && BlockAttributeListRx =~ next_line
current_style = attributes[1]
# extract id, role, and options from first positional attribute and remove, if present
- if (document.parse_attributes $1, [], :sub_input => true, :into => attributes)[1]
+ if (document.parse_attributes $1, [], :sub_input => true, :sub_result => true, :into => attributes)[1]
attributes[1] = (parse_style_attribute attributes, reader) || current_style
end
return true
end
elsif normal && (next_line.start_with? '.')
@@ -2091,15 +2115,14 @@
def self.process_attribute_entry reader, document, attributes = nil, match = nil
if (match ||= (reader.has_more_lines? ? (AttributeEntryRx.match reader.peek_line) : nil))
if (value = match[2]).nil_or_empty?
value = ''
elsif value.end_with? LINE_CONTINUATION, LINE_CONTINUATION_LEGACY
- con, value = value.slice(-2, 2), value.slice(0, value.length - 2).rstrip
- while reader.advance && !(next_line = reader.peek_line.lstrip).empty?
- if (keep_open = next_line.end_with? con)
- next_line = (next_line.slice 0, next_line.length - 2).rstrip
- end
+ con, value = value.slice(-2, 2), (value.slice 0, value.length - 2).rstrip
+ while reader.advance && !(next_line = reader.peek_line || '').empty?
+ next_line = next_line.lstrip
+ next_line = (next_line.slice 0, next_line.length - 2).rstrip if (keep_open = next_line.end_with? con)
value = %(#{value}#{(value.end_with? HARD_LINE_BREAK) ? LF : ' '}#{next_line})
break unless keep_open
end
end
@@ -2134,13 +2157,13 @@
if doc
if value
if name == 'leveloffset'
# support relative leveloffset values
if value.start_with? '+'
- value = ((doc.attr 'leveloffset', 0).to_i + (value[1..-1] || 0).to_i).to_s
+ value = ((doc.attr 'leveloffset', 0).to_i + (value.slice 1, value.length).to_i).to_s
elsif value.start_with? '-'
- value = ((doc.attr 'leveloffset', 0).to_i - (value[1..-1] || 0).to_i).to_s
+ value = ((doc.attr 'leveloffset', 0).to_i - (value.slice 1, value.length).to_i).to_s
end
end
# QUESTION should we set value to locked value if set_attribute returns false?
if (resolved_value = doc.set_attribute name, value)
value = resolved_value
@@ -2174,11 +2197,11 @@
# Returns the String 0-index marker for this list item
def self.resolve_list_marker(list_type, marker, ordinal = 0, validate = false, reader = nil)
if list_type == :ulist
marker
elsif list_type == :olist
- resolve_ordered_list_marker(marker, ordinal, validate, reader)
+ resolve_ordered_list_marker(marker, ordinal, validate, reader)[0]
else # :colist
'<1>'
end
end
@@ -2197,18 +2220,23 @@
# marker in the sequence (default: false)
#
# Examples
#
# marker = 'B.'
- # Parser.resolve_ordered_list_marker(marker, 1, true)
- # # => 'A.'
+ # Parser.resolve_ordered_list_marker(marker, 1, true, reader)
+ # # => ['A.', :upperalpha]
#
- # Returns the String of the first marker in this number series
+ # marker = '.'
+ # Parser.resolve_ordered_list_marker(marker, 1, true, reader)
+ # # => ['.']
+ #
+ # Returns a tuple that contains the String of the first marker in this number
+ # series and the implicit list style, if applicable
def self.resolve_ordered_list_marker(marker, ordinal = 0, validate = false, reader = nil)
- return marker if marker.start_with? '.'
- expected = actual = nil
- case ORDERED_LIST_STYLES.find {|s| OrderedListMarkerRxMap[s].match? marker }
+ return [marker] if marker.start_with? '.'
+ # NOTE case statement is guaranteed to match one of the conditions
+ case (style = ORDERED_LIST_STYLES.find {|s| OrderedListMarkerRxMap[s].match? marker })
when :arabic
if validate
expected = ordinal + 1
actual = marker.to_i # remove trailing . and coerce to int
end
@@ -2241,11 +2269,11 @@
if validate && expected != actual
logger.warn message_with_context %(list item index: expected #{expected}, got #{actual}), :source_location => reader.cursor
end
- marker
+ [marker, style]
end
# Internal: Determine whether the this line is a sibling list item
# according to the list type and trait (marker) provided.
#
@@ -2275,11 +2303,11 @@
# table_reader - a Reader containing the source lines of an AsciiDoc table
# parent - the parent Block of this Asciidoctor::Table
# attributes - attributes captured from above this Block
#
# returns an instance of Asciidoctor::Table parsed from the provided reader
- def self.next_table(table_reader, parent, attributes)
+ def self.parse_table(table_reader, parent, attributes)
table = Table.new(parent, attributes)
if attributes.key? 'title'
table.title = attributes.delete 'title'
table.assign_caption(attributes.delete 'caption')
end
@@ -2430,11 +2458,11 @@
return ::Array.new(records.to_i) { { 'width' => 1 } }
end
specs = []
# NOTE -1 argument ensures we don't drop empty records
- records.split(',', -1).each {|record|
+ ((records.include? ',') ? (records.split ',', -1) : (records.split ';', -1)).each do |record|
if record.empty?
specs << { 'width' => 1 }
# TODO might want to use scan rather than this mega-regexp
elsif (m = ColumnSpecRx.match(record))
spec = {}
@@ -2467,11 +2495,11 @@
}
else
specs << spec
end
end
- }
+ end
specs
end
# Internal: Parse the cell specs for the current cell.
#
@@ -2656,11 +2684,11 @@
# EOS
#
# source.split "\n"
# # => [" def names", " @names.split", " end"]
#
- # puts Parser.adjust_indentation!(source.split "\n") * "\n"
+ # puts Parser.adjust_indentation!(source.split "\n").join "\n"
# # => def names
# # => @names.split
# # => end
#
# returns Nothing
@@ -2721,15 +2749,15 @@
# remove gutter then apply new indent if specified
# NOTE gutter_width is > 0 if not nil
if indent == 0
if gutter_width
- lines.map! {|line| line.empty? ? line : line[gutter_width..-1] }
+ lines.map! {|line| line.empty? ? line : (line.slice gutter_width, line.length) }
end
else
padding = ' ' * indent
if gutter_width
- lines.map! {|line| line.empty? ? line : padding + line[gutter_width..-1] }
+ lines.map! {|line| line.empty? ? line : padding + (line.slice gutter_width, line.length) }
else
lines.map! {|line| line.empty? ? line : padding + line }
end
end