# class Kramdown::Parser::Kramdown

Used for parsing a document in kramdown format.

If you want to extend the functionality of the parser, you need to do the following:

- Create a new subclass

- add the needed parser methods

- modify the @block_parsers and @span_parsers variables and add the names of your parser methods

Here is a small example for an extended parser class that parses ERB style tags as raw text if they are used as span-level elements (an equivalent block-level parser should probably also be made to handle the block case):

    require 'kramdown/parser/kramdown'

    class Kramdown::Parser::ERBKramdown < Kramdown::Parser::Kramdown

       def initialize(source, options)
         super
         @span_parsers.unshift(:erb_tags)
       end

       ERB_TAGS_START = /<%.*?%>/

       def parse_erb_tags
         @src.pos += @src.matched_size
         @tree.children << Element.new(:raw, @src.matched)
       end
       define_parser(:erb_tags, ERB_TAGS_START, '<%')

    end

The new parser can be used like this:

    require 'kramdown/document'
    # require the file with the above parser class

    Kramdown::Document.new(input_text, :input => 'ERBKramdown').to_html

### Constants

ABBREV_DEFINITION_START
ACHARS
ALD_ANY_CHARS
ALD_CLASS_NAME
ALD_ID_CHARS
ALD_ID_NAME
ALD_START
ALD_TYPE_ANY
ALD_TYPE_CLASS_NAME
ALD_TYPE_ID_NAME
ALD_TYPE_ID_OR_CLASS
ALD_TYPE_ID_OR_CLASS_MULTI
ALD_TYPE_KEY_VALUE_PAIR
ALD_TYPE_REF
ATX_HEADER_START
AUTOLINK_START
AUTOLINK_START_STR
BLANK_LINE
BLOCKQUOTE_START
BLOCK_BOUNDARY
BLOCK_EXTENSIONS_START
BLOCK_MATH_START
CODEBLOCK_MATCH
CODEBLOCK_START
CODESPAN_DELIMITER
DEFINITION_LIST_START
Data

  Struct class holding all the needed data for one block/span-level parser method.

EMPHASIS_START
EOB_MARKER
ESCAPED_CHARS
EXT_BLOCK_START
EXT_BLOCK_STOP_STR
EXT_SPAN_START
EXT_START_STR
EXT_STOP_STR
FENCED_CODEBLOCK_MATCH
FENCED_CODEBLOCK_START
FOOTNOTE_DEFINITION_START
FOOTNOTE_MARKER_START
HEADER_ID
HR_START
HTML_BLOCK_START
HTML_MARKDOWN_ATTR_MAP

  Mapping of markdown attribute value to content model. I.e. :raw when “0”, :default when “1” (use default content model for the HTML element), :span when “span”, :block when block and for everything else `nil` is returned.

HTML_SPAN_START
IAL_BLOCK
IAL_BLOCK_START
IAL_CLASS_ATTR
IAL_SPAN_START
INDENT

  Regexp for matching indentation (one tab or four spaces)

INLINE_MATH_START
LAZY_END
LAZY_END_HTML_SPAN_ELEMENTS
LAZY_END_HTML_START
LAZY_END_HTML_STOP
LINE_BREAK
LINK_BRACKET_STOP_RE
LINK_DEFINITION_START
LINK_INLINE_ID_RE
LINK_INLINE_TITLE_RE
LINK_PAREN_STOP_RE
LINK_START
LIST_ITEM_IAL
LIST_ITEM_IAL_CHECK
LIST_START
LIST_START_OL
LIST_START_UL
OPT_SPACE

  Regexp for matching the optional space (zero or up to three spaces)

PARAGRAPH_END
PARAGRAPH_MATCH
PARAGRAPH_START
PARSE_FIRST_LIST_LINE_REGEXP_CACHE
SETEXT_HEADER_START
SMART_QUOTES_RE
SPAN_EXTENSIONS_START
SQ_CLOSE
SQ_PUNCT
SQ_RULES
SQ_SUBSTS
TABLE_FSEP_LINE
TABLE_HSEP_ALIGN
TABLE_LINE
TABLE_PIPE_CHECK
TABLE_ROW_LINE
TABLE_SEP_LINE
TABLE_START
TRAILING_WHITESPACE
TYPOGRAPHIC_SYMS
TYPOGRAPHIC_SYMS_RE
TYPOGRAPHIC_SYMS_SUBST

### Protected Class Methods

define_parser(name, start_re, span_start = nil, meth_name = "parse_ click to toggle source

Add a parser method

- with the given `name`,

- using `start_re` as start regexp

- and, for span parsers, `span_start` as a String that can be used in a regexp and which identifies the starting character(s)

to the registry. The method name is automatically derived from the `name` or can explicitly be set by using the `meth_name` parameter.

        # File lib/kramdown/parser/kramdown.rb
    322 def self.define_parser(name, start_re, span_start = nil, meth_name = "parse_#{name}")
    323   raise "A parser with the name #{name} already exists!" if @@parsers.has_key?(name)
    324   @@parsers[name] = Data.new(name, start_re, span_start, meth_name)
    325 end

has_parser?(name) click to toggle source

Return `true` if there is a parser called `name`.

        # File lib/kramdown/parser/kramdown.rb
    333 def self.has_parser?(name)
    334   @@parsers.has_key?(name)
    335 end

parser(name = nil) click to toggle source

Return the `Data` structure for the parser `name`.

        # File lib/kramdown/parser/kramdown.rb
    328 def self.parser(name = nil)
    329   @@parsers[name]
    330 end

### Private Class Methods

new(source, options) click to toggle source

Create a new `Kramdown` parser object with the given `options`.

Calls superclass method `Kramdown::Parser::Base::new`

       # File lib/kramdown/parser/kramdown.rb
    64 def initialize(source, options)
    65   super
    66
    67   reset_env
    68
    69   @alds = {}
    70   @footnotes = {}
    71   @link_defs = {}
    72   update_link_definitions(@options[:link_defs])
    73
    74   @block_parsers = [:blank_line, :codeblock, :codeblock_fenced, :blockquote, :atx_header,
    75                     :horizontal_rule, :list, :definition_list, :block_html, :setext_header,
    76                     :block_math, :table, :footnote_definition, :link_definition, :abbrev_definition,
    77                     :block_extensions, :eob_marker, :paragraph]
    78   @span_parsers =  [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link, :smart_quotes, :inline_math,
    79                    :span_extensions, :html_entity, :typographic_syms, :line_break, :escaped_chars]
    80
    81 end

new(source, options) click to toggle source

Initialize the parser object with the `source` string and the parsing `options`.

The @root element, the @warnings array and @text_type (specifies the default type for newly created text nodes) are automatically initialized.

       # File lib/kramdown/parser/base.rb
    51 def initialize(source, options)
    52   @source = source
    53   @options = Kramdown::Options.merge(options)
    54   @root = Element.new(:root, nil, nil, :encoding => (source.encoding rescue nil), :location => 1,
    55                       :options => {}, :abbrev_defs => {}, :abbrev_attr => {})
    56   @warnings = []
    57   @text_type = :text
    58 end

### Public Instance Methods

add_link(el, href, title, alt_text = nil, ial = nil) click to toggle source

This helper methods adds the approriate attributes to the element `el` of type `a` or `img` and the element itself to the @tree.

       # File lib/kramdown/parser/kramdown/link.rb
    37 def add_link(el, href, title, alt_text = nil, ial = nil)
    38   el.options[:ial] = ial
    39   update_attr_with_ial(el.attr, ial) if ial
    40   if el.type == :a
    41     el.attr['href'] = href
    42   else
    43     el.attr['src'] = href
    44     el.attr['alt'] = alt_text
    45     el.children.clear
    46   end
    47   el.attr['title'] = title if title
    48   @tree.children << el
    49 end

after_block_boundary?() click to toggle source

Return `true` if we are after a block boundary.

       # File lib/kramdown/parser/kramdown/block_boundary.rb
    20 def after_block_boundary?
    21   !@tree.children.last || @tree.children.last.type == :blank ||
    22     (@tree.children.last.type == :eob && @tree.children.last.value.nil?) || @block_ial
    23 end

before_block_boundary?() click to toggle source

Return `true` if we are before a block boundary.

       # File lib/kramdown/parser/kramdown/block_boundary.rb
    26 def before_block_boundary?
    27   @src.check(self.class::BLOCK_BOUNDARY)
    28 end

correct_abbreviations_attributes() click to toggle source

Correct abbreviation attributes.

       # File lib/kramdown/parser/kramdown/abbreviation.rb
    30 def correct_abbreviations_attributes
    31   @root.options[:abbrev_attr].keys.each do |k|
    32     @root.options[:abbrev_attr][k] = @root.options[:abbrev_attr][k].attr
    33   end
    34 end

handle_extension(name, opts, body, type, line_no = nil) click to toggle source

        # File lib/kramdown/parser/kramdown/extensions.rb
     94 def handle_extension(name, opts, body, type, line_no = nil)
     95   case name
     96   when 'comment'
     97     @tree.children << Element.new(:comment, body, nil, :category => type, :location => line_no) if body.kind_of?(String)
     98     true
     99   when 'nomarkdown'
    100     @tree.children << Element.new(:raw, body, nil, :category => type, :location => line_no, :type => opts['type'].to_s.split(/\s+/)) if body.kind_of?(String)
    101     true
    102   when 'options'
    103     opts.select do |k,v|
    104       k = k.to_sym
    105       if Kramdown::Options.defined?(k)
    106         if @options[:forbidden_inline_options].include?(k) ||
    107             k == :forbidden_inline_options
    108           warning("Option #{k} may not be set inline")
    109           next false
    110         end
    111
    112         begin
    113           val = Kramdown::Options.parse(k, v)
    114           @options[k] = val
    115           (@root.options[:options] ||= {})[k] = val
    116         rescue
    117         end
    118         false
    119       else
    120         true
    121       end
    122     end.each do |k,v|
    123       warning("Unknown kramdown option '#{k}'")
    124     end
    125     @tree.children << new_block_el(:eob, :extension) if type == :block
    126     true
    127   else
    128     false
    129   end
    130 end

handle_kramdown_html_tag(el, closed, handle_body) click to toggle source

       # File lib/kramdown/parser/kramdown/html.rb
    24 def handle_kramdown_html_tag(el, closed, handle_body)
    25   if @block_ial
    26     el.options[:ial] = @block_ial
    27     @block_ial = nil
    28   end
    29
    30   content_model = if @tree.type != :html_element || @tree.options[:content_model] != :raw
    31                     (@options[:parse_block_html] ? HTML_CONTENT_MODEL[el.value] : :raw)
    32                   else
    33                     :raw
    34                   end
    35   if val = HTML_MARKDOWN_ATTR_MAP[el.attr.delete('markdown')]
    36     content_model = (val == :default ? HTML_CONTENT_MODEL[el.value] : val)
    37   end
    38
    39   @src.scan(TRAILING_WHITESPACE) if content_model == :block
    40   el.options[:content_model] = content_model
    41   el.options[:is_closed] = closed
    42
    43   if !closed && handle_body
    44     if content_model == :block
    45       if !parse_blocks(el)
    46         warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
    47       end
    48     elsif content_model == :span
    49       curpos = @src.pos
    50       if @src.scan_until(/(?=<\/#{el.value}\s*>)/mi)
    51         add_text(extract_string(curpos...@src.pos, @src), el)
    52         @src.scan(HTML_TAG_CLOSE_RE)
    53       else
    54         add_text(@src.rest, el)
    55         @src.terminate
    56         warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
    57       end
    58     else
    59       parse_raw_html(el, &method(:handle_kramdown_html_tag))
    60     end
    61     @src.scan(TRAILING_WHITESPACE) unless (@tree.type == :html_element && @tree.options[:content_model] == :raw)
    62   end
    63 end

normalize_link_id(id) click to toggle source

Normalize the link identifier.

       # File lib/kramdown/parser/kramdown/link.rb
    16 def normalize_link_id(id)
    17   id.gsub(/[\s]+/, ' ').downcase
    18 end

paragraph_end() click to toggle source

       # File lib/kramdown/parser/kramdown/paragraph.rb
    55 def paragraph_end
    56   self.class::PARAGRAPH_END
    57 end

parse() click to toggle source

The source string provided on initialization is parsed into the @root element.

        # File lib/kramdown/parser/kramdown.rb
     86 def parse
     87   configure_parser
     88   parse_blocks(@root, adapt_source(source))
     89   update_tree(@root)
     90   correct_abbreviations_attributes
     91   replace_abbreviations(@root)
     92   @footnotes.each do |name,data|
     93     update_tree(data[:content])
     94     replace_abbreviations(data[:content])
     95   end
     96   @footnotes.each do |name, data|
     97     next if data.key?(:marker)
     98     line = data[:content].options[:location]
     99     warning("Footnote definition for '#{name}' on line #{line} is unreferenced - ignoring")
    100   end
    101 end

parse_abbrev_definition() click to toggle source

Parse the link definition at the current location.

       # File lib/kramdown/parser/kramdown/abbreviation.rb
    16 def parse_abbrev_definition
    17   start_line_number = @src.current_line_number
    18   @src.pos += @src.matched_size
    19   abbrev_id, abbrev_text = @src[1], @src[2]
    20   abbrev_text.strip!
    21   warning("Duplicate abbreviation ID '#{abbrev_id}' on line #{start_line_number} - overwriting") if @root.options[:abbrev_defs][abbrev_id]
    22   @tree.children << new_block_el(:eob, :abbrev_def)
    23   @root.options[:abbrev_defs][abbrev_id] = abbrev_text
    24   @root.options[:abbrev_attr][abbrev_id] = @tree.children.last
    25   true
    26 end

parse_attribute_list(str, opts) click to toggle source

Parse the string `str` and extract all attributes and add all found attributes to the hash `opts`.

       # File lib/kramdown/parser/kramdown/extensions.rb
    17 def parse_attribute_list(str, opts)
    18   return if str.strip.empty? || str.strip == ':'
    19   attrs = str.scan(ALD_TYPE_ANY)
    20   attrs.each do |key, sep, val, ref, id_and_or_class, _, _|
    21     if ref
    22       (opts[:refs] ||= []) << ref
    23     elsif id_and_or_class
    24       id_and_or_class.scan(ALD_TYPE_ID_OR_CLASS).each do |id_attr, class_attr|
    25         if class_attr
    26           opts[IAL_CLASS_ATTR] = (opts[IAL_CLASS_ATTR] || '') << " #{class_attr}"
    27           opts[IAL_CLASS_ATTR].lstrip!
    28         else
    29           opts['id'] = id_attr
    30         end
    31       end
    32     else
    33       val.gsub!(/\\(\}|#{sep})/, "\\1")
    34       opts[key] = val
    35     end
    36   end
    37   warning("No or invalid attributes found in IAL/ALD content: #{str}") if attrs.length == 0
    38 end

parse_atx_header() click to toggle source

Parse the Atx header at the current location.

       # File lib/kramdown/parser/kramdown/header.rb
    31 def parse_atx_header
    32   return false if !after_block_boundary?
    33   text, id = parse_header_contents
    34   text.sub!(/[\t ]#+\z/, '') && text.rstrip!
    35   return false if text.empty?
    36   add_header(@src["level"].length, text, id)
    37   true
    38 end

parse_autolink() click to toggle source

Parse the autolink at the current location.

       # File lib/kramdown/parser/kramdown/autolink.rb
    18 def parse_autolink
    19   start_line_number = @src.current_line_number
    20   @src.pos += @src.matched_size
    21   href = (@src[2].nil? ? "mailto:#{@src[1]}" : @src[1])
    22   el = Element.new(:a, nil, {'href' => href}, :location => start_line_number)
    23   add_text(@src[1].sub(/^mailto:/, ''), el)
    24   @tree.children << el
    25 end

parse_blank_line() click to toggle source

Parse the blank line at the current postition.

       # File lib/kramdown/parser/kramdown/blank_line.rb
    16 def parse_blank_line
    17   @src.pos += @src.matched_size
    18   if @tree.children.last && @tree.children.last.type == :blank
    19     @tree.children.last.value << @src.matched
    20   else
    21     @tree.children << new_block_el(:blank, @src.matched)
    22   end
    23   true
    24 end

parse_block_extensions() click to toggle source

Parse one of the block extensions (ALD, block IAL or generic extension) at the current location.

        # File lib/kramdown/parser/kramdown/extensions.rb
    158 def parse_block_extensions
    159   if @src.scan(ALD_START)
    160     parse_attribute_list(@src[2], @alds[@src[1]] ||= Utils::OrderedHash.new)
    161     @tree.children << new_block_el(:eob, :ald)
    162     true
    163   elsif @src.check(EXT_BLOCK_START)
    164     parse_extension_start_tag(:block)
    165   elsif @src.scan(IAL_BLOCK_START)
    166     if @tree.children.last && @tree.children.last.type != :blank &&
    167         (@tree.children.last.type != :eob || [:link_def, :abbrev_def, :footnote_def].include?(@tree.children.last.value))
    168       parse_attribute_list(@src[1], @tree.children.last.options[:ial] ||= Utils::OrderedHash.new)
    169       @tree.children << new_block_el(:eob, :ial) unless @src.check(IAL_BLOCK_START)
    170     else
    171       parse_attribute_list(@src[1], @block_ial ||= Utils::OrderedHash.new)
    172     end
    173     true
    174   else
    175     false
    176   end
    177 end

parse_block_html() click to toggle source

Parse the HTML at the current position as block-level HTML.

       # File lib/kramdown/parser/kramdown/html.rb
    69 def parse_block_html
    70   line = @src.current_line_number
    71   if result = @src.scan(HTML_COMMENT_RE)
    72     @tree.children << Element.new(:xml_comment, result, nil, :category => :block, :location => line)
    73     @src.scan(TRAILING_WHITESPACE)
    74     true
    75   elsif result = @src.scan(HTML_INSTRUCTION_RE)
    76     @tree.children << Element.new(:xml_pi, result, nil, :category => :block, :location => line)
    77     @src.scan(TRAILING_WHITESPACE)
    78     true
    79   else
    80     if result = @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
    81       @src.pos += @src.matched_size
    82       handle_html_start_tag(line, &method(:handle_kramdown_html_tag))
    83       Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native]
    84       true
    85     elsif result = @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
    86       name = @src[1].downcase
    87
    88       if @tree.type == :html_element && @tree.value == name
    89         @src.pos += @src.matched_size
    90         throw :stop_block_parsing, :found
    91       else
    92         false
    93       end
    94     else
    95       false
    96     end
    97   end
    98 end

parse_block_math() click to toggle source

Parse the math block at the current location.

       # File lib/kramdown/parser/kramdown/math.rb
    18 def parse_block_math
    19   start_line_number = @src.current_line_number
    20   if !after_block_boundary?
    21     return false
    22   elsif @src[1]
    23     @src.scan(/^#{OPT_SPACE}\\/) if @src[3]
    24     return false
    25   end
    26
    27   saved_pos = @src.save_pos
    28   @src.pos += @src.matched_size
    29   data = @src[2].strip
    30   if before_block_boundary?
    31     @tree.children << new_block_el(:math, data, nil, :category => :block, :location => start_line_number)
    32     true
    33   else
    34     @src.revert_pos(saved_pos)
    35     false
    36   end
    37 end

parse_blockquote() click to toggle source

Parse the blockquote at the current location.

       # File lib/kramdown/parser/kramdown/blockquote.rb
    20 def parse_blockquote
    21   start_line_number = @src.current_line_number
    22   result = @src.scan(PARAGRAPH_MATCH)
    23   while !@src.match?(self.class::LAZY_END)
    24     result << @src.scan(PARAGRAPH_MATCH)
    25   end
    26   result.gsub!(BLOCKQUOTE_START, '')
    27
    28   el = new_block_el(:blockquote, nil, nil, :location => start_line_number)
    29   @tree.children << el
    30   parse_blocks(el, result)
    31   true
    32 end

parse_codeblock() click to toggle source

Parse the indented codeblock at the current location.

       # File lib/kramdown/parser/kramdown/codeblock.rb
    22 def parse_codeblock
    23   start_line_number = @src.current_line_number
    24   data = @src.scan(self.class::CODEBLOCK_MATCH)
    25   data.gsub!(/\n( {0,3}\S)/, ' \\1')
    26   data.gsub!(INDENT, '')
    27   @tree.children << new_block_el(:codeblock, data, nil, :location => start_line_number)
    28   true
    29 end

parse_codeblock_fenced() click to toggle source

Parse the fenced codeblock at the current location.

       # File lib/kramdown/parser/kramdown/codeblock.rb
    37 def parse_codeblock_fenced
    38   if @src.check(self.class::FENCED_CODEBLOCK_MATCH)
    39     start_line_number = @src.current_line_number
    40     @src.pos += @src.matched_size
    41     el = new_block_el(:codeblock, @src[5], nil, :location => start_line_number, :fenced => true)
    42     lang = @src[3].to_s.strip
    43     unless lang.empty?
    44       el.options[:lang] = lang
    45       el.attr['class'] = "language-#{@src[4]}"
    46     end
    47     @tree.children << el
    48     true
    49   else
    50     false
    51   end
    52 end

parse_codespan() click to toggle source

Parse the codespan at the current scanner location.

       # File lib/kramdown/parser/kramdown/codespan.rb
    16 def parse_codespan
    17   start_line_number = @src.current_line_number
    18   result = @src.scan(CODESPAN_DELIMITER)
    19   simple = (result.length == 1)
    20   saved_pos = @src.save_pos
    21
    22   if simple && @src.pre_match =~ /\s\Z/ && @src.match?(/\s/)
    23     add_text(result)
    24     return
    25   end
    26
    27   if text = @src.scan_until(/#{result}/)
    28     text.sub!(/#{result}\Z/, '')
    29     if !simple
    30       text = text[1..-1] if text[0..0] == ' '
    31       text = text[0..-2] if text[-1..-1] == ' '
    32     end
    33     @tree.children << Element.new(:codespan, text, nil, :location => start_line_number)
    34   else
    35     @src.revert_pos(saved_pos)
    36     add_text(result)
    37   end
    38 end

parse_definition_list() click to toggle source

Parse the ordered or unordered list at the current location.

        # File lib/kramdown/parser/kramdown/list.rb
    153 def parse_definition_list
    154   children = @tree.children
    155   if !children.last || (children.length == 1 && children.last.type != :p ) ||
    156       (children.length >= 2 && children[-1].type != :p && (children[-1].type != :blank || children[-1].value != "\n" || children[-2].type != :p))
    157     return false
    158   end
    159
    160   first_as_para = false
    161   deflist = new_block_el(:dl)
    162   para = @tree.children.pop
    163   if para.type == :blank
    164     para = @tree.children.pop
    165     first_as_para = true
    166   end
    167   deflist.options[:location] = para.options[:location] # take location from preceding para which is the first definition term
    168   para.children.first.value.split(/\n/).each do |term|
    169     el = Element.new(:dt, nil, nil, :location => @src.current_line_number)
    170     term.sub!(self.class::LIST_ITEM_IAL) do
    171       parse_attribute_list($1, el.options[:ial] ||= {})
    172       ''
    173     end
    174     el.options[:raw_text] = term
    175     el.children << Element.new(:raw_text, term)
    176     deflist.children << el
    177   end
    178   deflist.options[:ial] = para.options[:ial]
    179
    180   item = nil
    181   content_re, lazy_re, indent_re = nil
    182   def_start_re = DEFINITION_LIST_START
    183   last_is_blank = false
    184   while !@src.eos?
    185     start_line_number = @src.current_line_number
    186     if @src.scan(def_start_re)
    187       item = Element.new(:dd, nil, nil, :location => start_line_number)
    188       item.options[:first_as_para] = first_as_para
    189       item.value, indentation, content_re, lazy_re, indent_re = parse_first_list_line(@src[1].length, @src[2])
    190       deflist.children << item
    191
    192       item.value.sub!(self.class::LIST_ITEM_IAL) do |match|
    193         parse_attribute_list($1, item.options[:ial] ||= {})
    194         ''
    195       end
    196
    197       def_start_re = /^( {0,#{[3, indentation - 1].min}}:)([\t| ].*?\n)/
    198       first_as_para = false
    199       last_is_blank = false
    200     elsif @src.check(EOB_MARKER)
    201       break
    202     elsif (result = @src.scan(content_re)) || (!last_is_blank && (result = @src.scan(lazy_re)))
    203       result.sub!(/^(\t+)/) { " "*($1 ? 4*$1.length : 0) }
    204       result.sub!(indent_re, '')
    205       item.value << result
    206       first_as_para = false
    207       last_is_blank = false
    208     elsif result = @src.scan(BLANK_LINE)
    209       first_as_para = true
    210       item.value << result
    211       last_is_blank = true
    212     else
    213       break
    214     end
    215   end
    216
    217   last = nil
    218   deflist.children.each do |it|
    219     next if it.type == :dt
    220
    221     parse_blocks(it, it.value)
    222     it.value = nil
    223     next if it.children.size == 0
    224
    225     if it.children.last.type == :blank
    226       last = it.children.pop
    227     else
    228       last = nil
    229     end
    230
    231     if it.children.first && it.children.first.type == :p && !it.options.delete(:first_as_para)
    232       it.children.first.children.first.value << "\n" if it.children.size > 1
    233       it.children.first.options[:transparent] = true
    234     end
    235   end
    236
    237   if @tree.children.length >= 1 && @tree.children.last.type == :dl
    238     @tree.children[-1].children.concat(deflist.children)
    239   elsif @tree.children.length >= 2 && @tree.children[-1].type == :blank && @tree.children[-2].type == :dl
    240     @tree.children.pop
    241     @tree.children[-1].children.concat(deflist.children)
    242   else
    243     @tree.children << deflist
    244   end
    245
    246   @tree.children << last if !last.nil?
    247
    248   true
    249 end

parse_emphasis() click to toggle source

Parse the emphasis at the current location.

       # File lib/kramdown/parser/kramdown/emphasis.rb
    16 def parse_emphasis
    17   start_line_number = @src.current_line_number
    18   saved_pos = @src.save_pos
    19
    20   result = @src.scan(EMPHASIS_START)
    21   element = (result.length == 2 ? :strong : :em)
    22   type = result[0..0]
    23
    24   if (type == '_' && @src.pre_match =~ /[[:alpha:]-]\z/) || @src.check(/\s/) ||
    25       @tree.type == element || @stack.any? {|el, _| el.type == element}
    26     add_text(result)
    27     return
    28   end
    29
    30   sub_parse = lambda do |delim, elem|
    31     el = Element.new(elem, nil, nil, :location => start_line_number)
    32     stop_re = /#{Regexp.escape(delim)}/
    33     found = parse_spans(el, stop_re) do
    34       (@src.pre_match[-1, 1] !~ /\s/) &&
    35         (elem != :em || !@src.match?(/#{Regexp.escape(delim*2)}(?!#{Regexp.escape(delim)})/)) &&
    36         (type != '_' || !@src.match?(/#{Regexp.escape(delim)}[[:alnum:]]/)) && el.children.size > 0
    37     end
    38     [found, el, stop_re]
    39   end
    40
    41   found, el, stop_re = sub_parse.call(result, element)
    42   if !found && element == :strong && @tree.type != :em
    43     @src.revert_pos(saved_pos)
    44     @src.pos += 1
    45     found, el, stop_re = sub_parse.call(type, :em)
    46   end
    47   if found
    48     @src.scan(stop_re)
    49     @tree.children << el
    50   else
    51     @src.revert_pos(saved_pos)
    52     @src.pos += result.length
    53     add_text(result)
    54   end
    55 end

parse_eob_marker() click to toggle source

Parse the EOB marker at the current location.

       # File lib/kramdown/parser/kramdown/eob.rb
    16 def parse_eob_marker
    17   @src.pos += @src.matched_size
    18   @tree.children << new_block_el(:eob)
    19   true
    20 end

parse_escaped_chars() click to toggle source

Parse the backslash-escaped character at the current location.

       # File lib/kramdown/parser/kramdown/escaped_chars.rb
    16 def parse_escaped_chars
    17   @src.pos += @src.matched_size
    18   add_text(@src[1])
    19 end

parse_extension_start_tag(type) click to toggle source

Parse the generic extension at the current point. The parameter `type` can either be :block or :span depending whether we parse a block or span extension tag.

       # File lib/kramdown/parser/kramdown/extensions.rb
    55 def parse_extension_start_tag(type)
    56   saved_pos = @src.save_pos
    57   start_line_number = @src.current_line_number
    58   @src.pos += @src.matched_size
    59
    60   error_block = lambda do |msg|
    61     warning(msg)
    62     @src.revert_pos(saved_pos)
    63     add_text(@src.getch) if type == :span
    64     false
    65   end
    66
    67   if @src[4] || @src.matched == '{:/}'
    68     name = (@src[4] ? "for '#{@src[4]}' " : '')
    69     return error_block.call("Invalid extension stop tag #{name} found on line #{start_line_number} - ignoring it")
    70   end
    71
    72   ext = @src[1]
    73   opts = {}
    74   body = nil
    75   parse_attribute_list(@src[2] || '', opts)
    76
    77   if !@src[3]
    78     stop_re = (type == :block ? /#{EXT_BLOCK_STOP_STR % ext}/ : /#{EXT_STOP_STR % ext}/)
    79     if result = @src.scan_until(stop_re)
    80       body = result.sub!(stop_re, '')
    81       body.chomp! if type == :block
    82     else
    83       return error_block.call("No stop tag for extension '#{ext}' found on line #{start_line_number} - ignoring it")
    84     end
    85   end
    86
    87   if !handle_extension(ext, opts, body, type, start_line_number)
    88     error_block.call("Invalid extension with name '#{ext}' specified on line #{start_line_number} - ignoring it")
    89   else
    90     true
    91   end
    92 end

parse_first_list_line(indentation, content) click to toggle source

Used for parsing the first line of a list item or a definition, i.e. the line with list item marker or the definition marker.

       # File lib/kramdown/parser/kramdown/list.rb
    31 def parse_first_list_line(indentation, content)
    32   if content =~ self.class::LIST_ITEM_IAL_CHECK
    33     indentation = 4
    34   else
    35     while content =~ /^ *\t/
    36       temp = content.scan(/^ */).first.length + indentation
    37       content.sub!(/^( *)(\t+)/) {$1 << " "*(4 - (temp % 4) + ($2.length - 1)*4)}
    38     end
    39     indentation += content[/^ */].length
    40   end
    41   content.sub!(/^\s*/, '')
    42
    43   [content, indentation, *PARSE_FIRST_LIST_LINE_REGEXP_CACHE[indentation]]
    44 end

parse_footnote_definition() click to toggle source

Parse the foot note definition at the current location.

       # File lib/kramdown/parser/kramdown/footnote.rb
    20 def parse_footnote_definition
    21   start_line_number = @src.current_line_number
    22   @src.pos += @src.matched_size
    23
    24   el = Element.new(:footnote_def, nil, nil, :location => start_line_number)
    25   parse_blocks(el, @src[2].gsub(INDENT, ''))
    26   warning("Duplicate footnote name '#{@src[1]}' on line #{start_line_number} - overwriting") if @footnotes[@src[1]]
    27   @tree.children << new_block_el(:eob, :footnote_def)
    28   (@footnotes[@src[1]] = {})[:content] = el
    29   @footnotes[@src[1]][:eob] = @tree.children.last
    30   true
    31 end

parse_footnote_marker() click to toggle source

Parse the footnote marker at the current location.

       # File lib/kramdown/parser/kramdown/footnote.rb
    38 def parse_footnote_marker
    39   start_line_number = @src.current_line_number
    40   @src.pos += @src.matched_size
    41   fn_def = @footnotes[@src[1]]
    42   if fn_def
    43     if fn_def[:eob]
    44       update_attr_with_ial(fn_def[:eob].attr, fn_def[:eob].options[:ial] || {})
    45       fn_def[:attr] = fn_def[:eob].attr
    46       fn_def[:options] = fn_def[:eob].options
    47       fn_def.delete(:eob)
    48     end
    49     fn_def[:marker] ||= []
    50     fn_def[:marker].push(Element.new(:footnote, fn_def[:content], fn_def[:attr],
    51                                      fn_def[:options].merge(:name => @src[1], :location => start_line_number)))
    52     @tree.children << fn_def[:marker].last
    53   else
    54     warning("Footnote definition for '#{@src[1]}' not found on line #{start_line_number}")
    55     add_text(@src.matched)
    56   end
    57 end

parse_horizontal_rule() click to toggle source

Parse the horizontal rule at the current location.

       # File lib/kramdown/parser/kramdown/horizontal_rule.rb
    16 def parse_horizontal_rule
    17   start_line_number = @src.current_line_number
    18   @src.pos += @src.matched_size
    19   @tree.children << new_block_el(:hr, nil, nil, :location => start_line_number)
    20   true
    21 end

parse_html_entity() click to toggle source

Parse the HTML entity at the current location.

       # File lib/kramdown/parser/kramdown/html_entity.rb
    16 def parse_html_entity
    17   start_line_number = @src.current_line_number
    18   @src.pos += @src.matched_size
    19   begin
    20     @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity(@src[1] || (@src[2] && @src[2].to_i) || @src[3].hex),
    21                                   nil, :original => @src.matched, :location => start_line_number)
    22   rescue ::Kramdown::Error
    23     @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('amp'),
    24                                   nil, :location => start_line_number)
    25     add_text(@src.matched[1..-1])
    26   end
    27 end

parse_inline_math() click to toggle source

Parse the inline math at the current location.

       # File lib/kramdown/parser/kramdown/math.rb
    44 def parse_inline_math
    45   start_line_number = @src.current_line_number
    46   @src.pos += @src.matched_size
    47   @tree.children << Element.new(:math, @src[1].strip, nil, :category => :span, :location => start_line_number)
    48 end

parse_line_break() click to toggle source

Parse the line break at the current location.

       # File lib/kramdown/parser/kramdown/line_break.rb
    16 def parse_line_break
    17   @tree.children << Element.new(:br, nil, nil, :location => @src.current_line_number)
    18   @src.pos += @src.matched_size
    19 end

parse_link() click to toggle source

Parse the link at the current scanner position. This method is used to parse normal links as well as image links.

        # File lib/kramdown/parser/kramdown/link.rb
     59 def parse_link
     60   start_line_number = @src.current_line_number
     61   result = @src.scan(LINK_START)
     62   cur_pos = @src.pos
     63   saved_pos = @src.save_pos
     64
     65   link_type = (result =~ /^!/ ? :img : :a)
     66
     67   # no nested links allowed
     68   if link_type == :a && (@tree.type == :img || @tree.type == :a || @stack.any? {|t,s| t && (t.type == :img || t.type == :a)})
     69     add_text(result)
     70     return
     71   end
     72   el = Element.new(link_type, nil, nil, :location => start_line_number)
     73
     74   count = 1
     75   found = parse_spans(el, LINK_BRACKET_STOP_RE) do
     76     count = count + (@src[1] ? -1 : 1)
     77     count - el.children.select {|c| c.type == :img}.size == 0
     78   end
     79   unless found
     80     @src.revert_pos(saved_pos)
     81     add_text(result)
     82     return
     83   end
     84   alt_text = extract_string(cur_pos...@src.pos, @src).gsub(ESCAPED_CHARS, '\1')
     85   @src.scan(LINK_BRACKET_STOP_RE)
     86
     87   # reference style link or no link url
     88   if @src.scan(LINK_INLINE_ID_RE) || !@src.check(/\(/)
     89     emit_warning = !@src[1]
     90     link_id = normalize_link_id(@src[1] || alt_text)
     91     if @link_defs.has_key?(link_id)
     92       add_link(el, @link_defs[link_id][0], @link_defs[link_id][1], alt_text,
     93                @link_defs[link_id][2] && @link_defs[link_id][2].options[:ial])
     94     else
     95       if emit_warning
     96         warning("No link definition for link ID '#{link_id}' found on line #{start_line_number}")
     97       end
     98       @src.revert_pos(saved_pos)
     99       add_text(result)
    100     end
    101     return
    102   end
    103
    104   # link url in parentheses
    105   if @src.scan(/\(<(.*?)>/)
    106     link_url = @src[1]
    107     if @src.scan(/\)/)
    108       add_link(el, link_url, nil, alt_text)
    109       return
    110     end
    111   else
    112     link_url = ''
    113     nr_of_brackets = 0
    114     while temp = @src.scan_until(LINK_PAREN_STOP_RE)
    115       link_url << temp
    116       if @src[2]
    117         nr_of_brackets -= 1
    118         break if nr_of_brackets == 0
    119       elsif @src[1]
    120         nr_of_brackets += 1
    121       else
    122         break
    123       end
    124     end
    125     link_url = link_url[1..-2]
    126     link_url.strip!
    127
    128     if nr_of_brackets == 0
    129       add_link(el, link_url, nil, alt_text)
    130       return
    131     end
    132   end
    133
    134   if @src.scan(LINK_INLINE_TITLE_RE)
    135     add_link(el, link_url, @src[2], alt_text)
    136   else
    137     @src.revert_pos(saved_pos)
    138     add_text(result)
    139   end
    140 end

parse_link_definition() click to toggle source

Parse the link definition at the current location.

       # File lib/kramdown/parser/kramdown/link.rb
    23 def parse_link_definition
    24   return false if @src[3].to_s =~ /[ \t]+["']/
    25   @src.pos += @src.matched_size
    26   link_id, link_url, link_title = normalize_link_id(@src[1]), @src[2] || @src[3], @src[5]
    27   warning("Duplicate link ID '#{link_id}' on line #{@src.current_line_number} - overwriting") if @link_defs[link_id]
    28   @tree.children << new_block_el(:eob, :link_def)
    29   @link_defs[link_id] = [link_url, link_title, @tree.children.last]
    30   true
    31 end

parse_list() click to toggle source

Parse the ordered or unordered list at the current location.

        # File lib/kramdown/parser/kramdown/list.rb
     52 def parse_list
     53   start_line_number = @src.current_line_number
     54   type, list_start_re = (@src.check(LIST_START_UL) ? [:ul, LIST_START_UL] : [:ol, LIST_START_OL])
     55   list = new_block_el(type, nil, nil, :location => start_line_number)
     56
     57   item = nil
     58   content_re, lazy_re, indent_re = nil
     59   eob_found = false
     60   nested_list_found = false
     61   last_is_blank = false
     62   while !@src.eos?
     63     start_line_number = @src.current_line_number
     64     if last_is_blank && @src.check(HR_START)
     65       break
     66     elsif @src.scan(EOB_MARKER)
     67       eob_found = true
     68       break
     69     elsif @src.scan(list_start_re)
     70       item = Element.new(:li, nil, nil, :location => start_line_number)
     71       item.value, indentation, content_re, lazy_re, indent_re = parse_first_list_line(@src[1].length, @src[2])
     72       list.children << item
     73
     74       item.value.sub!(self.class::LIST_ITEM_IAL) do |match|
     75         parse_attribute_list($1, item.options[:ial] ||= {})
     76         ''
     77       end
     78
     79       list_start_re = (type == :ul ? /^( {0,#{[3, indentation - 1].min}}[+*-])([\t| ].*?\n)/ :
     80                        /^( {0,#{[3, indentation - 1].min}}\d+\.)([\t| ].*?\n)/)
     81       nested_list_found = (item.value =~ LIST_START)
     82       last_is_blank = false
     83       item.value = [item.value]
     84     elsif (result = @src.scan(content_re)) || (!last_is_blank && (result = @src.scan(lazy_re)))
     85       result.sub!(/^(\t+)/) { " " * 4 * $1.length }
     86       indentation_found = result.sub!(indent_re, '')
     87       if !nested_list_found && indentation_found && result =~ LIST_START
     88         item.value << ''
     89         nested_list_found = true
     90       elsif nested_list_found && !indentation_found && result =~ LIST_START
     91         result = " " * (indentation + 4) << result
     92       end
     93       item.value.last << result
     94       last_is_blank = false
     95     elsif result = @src.scan(BLANK_LINE)
     96       nested_list_found = true
     97       last_is_blank = true
     98       item.value.last << result
     99     else
    100       break
    101     end
    102   end
    103
    104   @tree.children << list
    105
    106   last = nil
    107   list.children.each do |it|
    108     temp = Element.new(:temp, nil, nil, :location => it.options[:location])
    109
    110     env = save_env
    111     location = it.options[:location]
    112     it.value.each do |val|
    113       @src = ::Kramdown::Utils::StringScanner.new(val, location)
    114       parse_blocks(temp)
    115       location = @src.current_line_number
    116     end
    117     restore_env(env)
    118
    119     it.children = temp.children
    120     it.value = nil
    121     next if it.children.size == 0
    122
    123     # Handle the case where an EOB marker is inserted by a block IAL for the first paragraph
    124     it.children.delete_at(1) if it.children.first.type == :p &&
    125       it.children.length >= 2 && it.children[1].type == :eob && it.children.first.options[:ial]
    126
    127     if it.children.first.type == :p &&
    128         (it.children.length < 2 || it.children[1].type != :blank ||
    129          (it == list.children.last && it.children.length == 2 && !eob_found)) &&
    130         (list.children.last != it || list.children.size == 1 ||
    131          list.children[0..-2].any? {|cit| !cit.children.first || cit.children.first.type != :p || cit.children.first.options[:transparent]})
    132       it.children.first.children.first.value << "\n" if it.children.size > 1 && it.children[1].type != :blank
    133       it.children.first.options[:transparent] = true
    134     end
    135
    136     if it.children.last.type == :blank
    137       last = it.children.pop
    138     else
    139       last = nil
    140     end
    141   end
    142
    143   @tree.children << last if !last.nil? && !eob_found
    144
    145   true
    146 end

parse_paragraph() click to toggle source

Parse the paragraph at the current location.

       # File lib/kramdown/parser/kramdown/paragraph.rb
    30 def parse_paragraph
    31   pos = @src.pos
    32   start_line_number = @src.current_line_number
    33   result = @src.scan(PARAGRAPH_MATCH)
    34   while !@src.match?(paragraph_end)
    35     result << @src.scan(PARAGRAPH_MATCH)
    36   end
    37   result.rstrip!
    38   if @tree.children.last && @tree.children.last.type == :p
    39     last_item_in_para = @tree.children.last.children.last
    40     if last_item_in_para && last_item_in_para.type == @text_type
    41       joiner = (extract_string((pos - 3)...pos, @src) == "  \n" ? "  \n" : "\n")
    42       last_item_in_para.value << joiner << result
    43     else
    44       add_text(result, @tree.children.last)
    45     end
    46   else
    47     @tree.children << new_block_el(:p, nil, nil, :location => start_line_number)
    48     result.lstrip!
    49     add_text(result, @tree.children.last)
    50   end
    51   true
    52 end

parse_setext_header() click to toggle source

Parse the Setext header at the current location.

       # File lib/kramdown/parser/kramdown/header.rb
    18 def parse_setext_header
    19   return false if !after_block_boundary?
    20   text, id = parse_header_contents
    21   return false if text.empty?
    22   add_header(@src["level"] == '-' ? 2 : 1, text, id)
    23   true
    24 end

parse_smart_quotes() click to toggle source

Parse the smart quotes at current location.

        # File lib/kramdown/parser/kramdown/smart_quotes.rb
    157 def parse_smart_quotes
    158   start_line_number = @src.current_line_number
    159   substs = SQ_RULES.find {|reg, subst| @src.scan(reg)}[1]
    160   substs.each do |subst|
    161     if subst.kind_of?(Integer)
    162       add_text(@src[subst])
    163     else
    164       val = SQ_SUBSTS[[subst, @src[subst.to_s[-1,1].to_i]]] || subst
    165       @tree.children << Element.new(:smart_quote, val, nil, :location => start_line_number)
    166     end
    167   end
    168 end

parse_span_extensions() click to toggle source

Parse the extension span at the current location.

        # File lib/kramdown/parser/kramdown/extensions.rb
    186 def parse_span_extensions
    187   if @src.check(EXT_SPAN_START)
    188     parse_extension_start_tag(:span)
    189   elsif @src.check(IAL_SPAN_START)
    190     if @tree.children.last && @tree.children.last.type != :text
    191       @src.pos += @src.matched_size
    192       attr = Utils::OrderedHash.new
    193       parse_attribute_list(@src[1], attr)
    194       update_ial_with_ial(@tree.children.last.options[:ial] ||= Utils::OrderedHash.new, attr)
    195       update_attr_with_ial(@tree.children.last.attr, attr)
    196     else
    197       warning("Found span IAL after text - ignoring it")
    198       add_text(@src.getch)
    199     end
    200   else
    201     add_text(@src.getch)
    202   end
    203 end

parse_span_html() click to toggle source

Parse the HTML at the current position as span-level HTML.

        # File lib/kramdown/parser/kramdown/html.rb
    105 def parse_span_html
    106   line = @src.current_line_number
    107   if result = @src.scan(HTML_COMMENT_RE)
    108     @tree.children << Element.new(:xml_comment, result, nil, :category => :span, :location => line)
    109   elsif result = @src.scan(HTML_INSTRUCTION_RE)
    110     @tree.children << Element.new(:xml_pi, result, nil, :category => :span, :location => line)
    111   elsif result = @src.scan(HTML_TAG_CLOSE_RE)
    112     warning("Found invalidly used HTML closing tag for '#{@src[1]}' on line #{line}")
    113     add_text(result)
    114   elsif result = @src.scan(HTML_TAG_RE)
    115     tag_name = @src[1]
    116     tag_name.downcase! if HTML_ELEMENT[tag_name.downcase]
    117     if HTML_BLOCK_ELEMENTS.include?(tag_name)
    118       warning("Found block HTML tag '#{tag_name}' in span-level text on line #{line}")
    119       add_text(result)
    120       return
    121     end
    122
    123     attrs = parse_html_attributes(@src[2], line, HTML_ELEMENT[tag_name])
    124     attrs.each {|name, value| value.gsub!(/\n+/, ' ')}
    125
    126     do_parsing = (HTML_CONTENT_MODEL[tag_name] == :raw || @tree.options[:content_model] == :raw ? false : @options[:parse_span_html])
    127     if val = HTML_MARKDOWN_ATTR_MAP[attrs.delete('markdown')]
    128       if val == :block
    129         warning("Cannot use block-level parsing in span-level HTML tag (line #{line}) - using default mode")
    130       elsif val == :span
    131         do_parsing = true
    132       elsif val == :default
    133         do_parsing = HTML_CONTENT_MODEL[tag_name] != :raw
    134       elsif val == :raw
    135         do_parsing = false
    136       end
    137     end
    138
    139     el = Element.new(:html_element, tag_name, attrs, :category => :span, :location => line,
    140                      :content_model => (do_parsing ? :span : :raw), :is_closed => !!@src[4])
    141     @tree.children << el
    142     stop_re = /<\/#{Regexp.escape(tag_name)}\s*>/
    143     stop_re = Regexp.new(stop_re.source, Regexp::IGNORECASE) if HTML_ELEMENT[tag_name]
    144     if !@src[4] && !HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
    145       if parse_spans(el, stop_re, (do_parsing ? nil : [:span_html]))
    146         @src.scan(stop_re)
    147       else
    148         warning("Found no end tag for '#{el.value}' (line #{line}) - auto-closing it")
    149         add_text(@src.rest, el)
    150         @src.terminate
    151       end
    152     end
    153     Kramdown::Parser::Html::ElementConverter.convert(@root, el) if @options[:html_to_native]
    154   else
    155     add_text(@src.getch)
    156   end
    157 end

parse_table() click to toggle source

Parse the table at the current location.

        # File lib/kramdown/parser/kramdown/table.rb
     24 def parse_table
     25   return false if !after_block_boundary?
     26
     27   saved_pos = @src.save_pos
     28   orig_pos = @src.pos
     29   table = new_block_el(:table, nil, nil, :alignment => [], :location => @src.current_line_number)
     30   leading_pipe = (@src.check(TABLE_LINE) =~ /^\s*\|/)
     31   @src.scan(TABLE_SEP_LINE)
     32
     33   rows = []
     34   has_footer = false
     35   columns = 0
     36
     37   add_container = lambda do |type, force|
     38     if !has_footer || type != :tbody || force
     39       cont = Element.new(type)
     40       cont.children, rows = rows, []
     41       table.children << cont
     42     end
     43   end
     44
     45   while !@src.eos?
     46     break if !@src.check(TABLE_LINE)
     47     if @src.scan(TABLE_SEP_LINE)
     48       if rows.empty?
     49         # nothing to do, ignoring multiple consecutive separator lines
     50       elsif table.options[:alignment].empty? && !has_footer
     51         add_container.call(:thead, false)
     52         table.options[:alignment] = @src[1].scan(TABLE_HSEP_ALIGN).map do |left, right|
     53           (left.empty? && right.empty? && :default) || (right.empty? && :left) || (left.empty? && :right) || :center
     54         end
     55       else # treat as normal separator line
     56         add_container.call(:tbody, false)
     57       end
     58     elsif @src.scan(TABLE_FSEP_LINE)
     59       add_container.call(:tbody, true) if !rows.empty?
     60       has_footer = true
     61     elsif @src.scan(TABLE_ROW_LINE)
     62       trow = Element.new(:tr)
     63
     64       # parse possible code spans on the line and correctly split the line into cells
     65       env = save_env
     66       cells = []
     67       @src[1].split(/(<code.*?>.*?<\/code>)/).each_with_index do |str, i|
     68         if i % 2 == 1
     69           (cells.empty? ? cells : cells.last) << str
     70         else
     71           reset_env(:src => Kramdown::Utils::StringScanner.new(str, @src.current_line_number))
     72           root = Element.new(:root)
     73           parse_spans(root, nil, [:codespan])
     74
     75           root.children.each do |c|
     76             if c.type == :raw_text
     77               f, *l = c.value.split(/(?<!\\)\|/, -1).map {|t| t.gsub(/\\\|/, '|')}
     78               (cells.empty? ? cells : cells.last) << f
     79               cells.concat(l)
     80             else
     81               delim = (c.value.scan(/`+/).max || '') + '`'
     82               tmp = "#{delim}#{' ' if delim.size > 1}#{c.value}#{' ' if delim.size > 1}#{delim}"
     83               (cells.empty? ? cells : cells.last) << tmp
     84             end
     85           end
     86         end
     87       end
     88       restore_env(env)
     89
     90       cells.shift if leading_pipe && cells.first.strip.empty?
     91       cells.pop if cells.last.strip.empty?
     92       cells.each do |cell_text|
     93         tcell = Element.new(:td)
     94         tcell.children << Element.new(:raw_text, cell_text.strip)
     95         trow.children << tcell
     96       end
     97       columns = [columns, cells.length].max
     98       rows << trow
     99     else
    100       break
    101     end
    102   end
    103
    104   if !before_block_boundary?
    105     @src.revert_pos(saved_pos)
    106     return false
    107   end
    108
    109   # Parse all lines of the table with the code span parser
    110   env = save_env
    111   l_src = ::Kramdown::Utils::StringScanner.new(extract_string(orig_pos...(@src.pos-1), @src),
    112                                                @src.current_line_number)
    113   reset_env(:src => l_src)
    114   root = Element.new(:root)
    115   parse_spans(root, nil, [:codespan, :span_html])
    116   restore_env(env)
    117
    118   # Check if each line has at least one unescaped pipe that is not inside a code span/code
    119   # HTML element
    120   # Note: It doesn't matter that we parse *all* span HTML elements because the row splitting
    121   # algorithm above only takes <code> elements into account!
    122   pipe_on_line = false
    123   while (c = root.children.shift)
    124     next unless (lines = c.value)
    125     lines = lines.split("\n")
    126     if c.type == :codespan
    127       if lines.size > 2 || (lines.size == 2 && !pipe_on_line)
    128         break
    129       elsif lines.size == 2 && pipe_on_line
    130         pipe_on_line = false
    131       end
    132     else
    133       break if lines.size > 1 && !pipe_on_line && lines.first !~ /^#{TABLE_PIPE_CHECK}/
    134       pipe_on_line = (lines.size > 1 ? false : pipe_on_line) || (lines.last =~ /^#{TABLE_PIPE_CHECK}/)
    135     end
    136   end
    137   @src.revert_pos(saved_pos) and return false if !pipe_on_line
    138
    139   add_container.call(has_footer ? :tfoot : :tbody, false) if !rows.empty?
    140
    141   if !table.children.any? {|el| el.type == :tbody}
    142     warning("Found table without body on line #{table.options[:location]} - ignoring it")
    143     @src.revert_pos(saved_pos)
    144     return false
    145   end
    146
    147   # adjust all table rows to have equal number of columns, same for alignment defs
    148   table.children.each do |kind|
    149     kind.children.each do |row|
    150       (columns - row.children.length).times do
    151         row.children << Element.new(:td)
    152       end
    153     end
    154   end
    155   if table.options[:alignment].length > columns
    156     table.options[:alignment] = table.options[:alignment][0...columns]
    157   else
    158     table.options[:alignment] += [:default] * (columns - table.options[:alignment].length)
    159   end
    160
    161   @tree.children << table
    162
    163   true
    164 end

parse_typographic_syms() click to toggle source

Parse the typographic symbols at the current location.

       # File lib/kramdown/parser/kramdown/typographic_symbol.rb
    21 def parse_typographic_syms
    22   start_line_number = @src.current_line_number
    23   @src.pos += @src.matched_size
    24   val = TYPOGRAPHIC_SYMS_SUBST[@src.matched]
    25   if val.kind_of?(Symbol)
    26     @tree.children << Element.new(:typographic_sym, val, nil, :location => start_line_number)
    27   elsif @src.matched == '\\<<'
    28     @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('lt'),
    29                                   nil, :location => start_line_number)
    30     @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('lt'),
    31                                   nil, :location => start_line_number)
    32   else
    33     @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('gt'),
    34                                   nil, :location => start_line_number)
    35     @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('gt'),
    36                                   nil, :location => start_line_number)
    37   end
    38 end

replace_abbreviations(el, regexps = nil) click to toggle source

Replace the abbreviation text with elements.

       # File lib/kramdown/parser/kramdown/abbreviation.rb
    37 def replace_abbreviations(el, regexps = nil)
    38   return if @root.options[:abbrev_defs].empty?
    39   if !regexps
    40     sorted_abbrevs = @root.options[:abbrev_defs].keys.sort {|a,b| b.length <=> a.length}
    41     regexps = [Regexp.union(*sorted_abbrevs.map {|k| /#{Regexp.escape(k)}/})]
    42     regexps << /(?=(?:\W|^)#{regexps.first}(?!\w))/ # regexp should only match on word boundaries
    43   end
    44   el.children.map! do |child|
    45     if child.type == :text
    46       if child.value =~ regexps.first
    47         result = []
    48         strscan = Kramdown::Utils::StringScanner.new(child.value, child.options[:location])
    49         text_lineno = strscan.current_line_number
    50         while temp = strscan.scan_until(regexps.last)
    51           abbr_lineno = strscan.current_line_number
    52           abbr = strscan.scan(regexps.first) # begin of line case of abbr with \W char as first one
    53           if abbr.nil?
    54             temp << strscan.scan(/\W|^/)
    55             abbr = strscan.scan(regexps.first)
    56           end
    57           result << Element.new(:text, temp, nil, :location => text_lineno)
    58           result << Element.new(:abbreviation, abbr, nil, :location => abbr_lineno)
    59           text_lineno = strscan.current_line_number
    60         end
    61         result << Element.new(:text, strscan.rest, nil, :location => text_lineno)
    62       else
    63         child
    64       end
    65     else
    66       replace_abbreviations(child, regexps)
    67       child
    68     end
    69   end.flatten!
    70 end

update_ial_with_ial(ial, opts) click to toggle source

Update the `ial` with the information from the inline attribute list `opts`.

       # File lib/kramdown/parser/kramdown/extensions.rb
    41 def update_ial_with_ial(ial, opts)
    42   (ial[:refs] ||= []) << opts[:refs]
    43   opts.each do |k,v|
    44     if k == IAL_CLASS_ATTR
    45       ial[k] = (ial[k] || '') << " #{v}"
    46       ial[k].lstrip!
    47     elsif k.kind_of?(String)
    48       ial[k] = v
    49     end
    50   end
    51 end

### Protected Instance Methods

add_header(level, text, id) click to toggle source

       # File lib/kramdown/parser/kramdown/header.rb
    58 def add_header(level, text, id)
    59   start_line_number = @src.current_line_number
    60   @src.pos += @src.matched_size
    61   el = new_block_el(:header, nil, nil, :level => level, :raw_text => text, :location => start_line_number)
    62   add_text(text, el)
    63   el.attr['id'] = id if id
    64   @tree.children << el
    65 end

configure_parser() click to toggle source

Adapt the object to allow parsing like specified in the options.

        # File lib/kramdown/parser/kramdown.rb
    119 def configure_parser
    120   @parsers = {}
    121   (@block_parsers + @span_parsers).each do |name|
    122     if self.class.has_parser?(name)
    123       @parsers[name] = self.class.parser(name)
    124     else
    125       raise Kramdown::Error, "Unknown parser: #{name}"
    126     end
    127   end
    128   @span_start, @span_start_re = span_parser_regexps
    129 end

new_block_el(*args) click to toggle source

Create a new block-level element, taking care of applying a preceding block IAL if it exists. This method should always be used for creating a block-level element!

        # File lib/kramdown/parser/kramdown.rb
    299 def new_block_el(*args)
    300   el = Element.new(*args)
    301   if @block_ial
    302     el.options[:ial] = @block_ial
    303     @block_ial = nil
    304   end
    305   el
    306 end

parse_blocks(el, text = nil) click to toggle source

Parse all block-level elements in `text` into the element `el`.

        # File lib/kramdown/parser/kramdown.rb
    138 def parse_blocks(el, text = nil)
    139   @stack.push([@tree, @src, @block_ial])
    140   @tree, @block_ial = el, nil
    141   @src = (text.nil? ? @src : ::Kramdown::Utils::StringScanner.new(text, el.options[:location]))
    142
    143   status = catch(:stop_block_parsing) do
    144     while !@src.eos?
    145       @block_parsers.any? do |name|
    146         if @src.check(@parsers[name].start_re)
    147           send(@parsers[name].method)
    148         else
    149           false
    150         end
    151       end || begin
    152         warning('Warning: this should not occur - no block parser handled the line')
    153         add_text(@src.scan(/.*\n/))
    154       end
    155     end
    156   end
    157
    158   @tree, @src, @block_ial = *@stack.pop
    159   status
    160 end

parse_header_contents() click to toggle source

Returns header text and optional ID.

       # File lib/kramdown/parser/kramdown/header.rb
    46 def parse_header_contents
    47   text = @src["contents"]
    48   text.rstrip!
    49   id_match = HEADER_ID.match(text)
    50   if id_match
    51     id = id_match["id"]
    52     text = text[0...-id_match[0].length]
    53     text.rstrip!
    54   end
    55   [text, id]
    56 end

parse_spans(el, stop_re = nil, parsers = nil, text_type = @text_type) { |: true)| ... } click to toggle source

Parse all span-level elements in the source string of @src into `el`.

If the parameter `stop_re` (a regexp) is used, parsing is immediately stopped if the regexp matches and if no block is given or if a block is given and it returns `true`.

The parameter `parsers` can be used to specify the (span-level) parsing methods that should be used for parsing.

The parameter `text_type` specifies the type which should be used for created text nodes.

        # File lib/kramdown/parser/kramdown.rb
    207 def parse_spans(el, stop_re = nil, parsers = nil, text_type = @text_type)
    208   @stack.push([@tree, @text_type]) unless @tree.nil?
    209   @tree, @text_type = el, text_type
    210
    211   span_start = @span_start
    212   span_start_re = @span_start_re
    213   span_start, span_start_re = span_parser_regexps(parsers) if parsers
    214   parsers = parsers || @span_parsers
    215
    216   used_re = (stop_re.nil? ? span_start_re : /(?=#{Regexp.union(stop_re, span_start)})/)
    217   stop_re_found = false
    218   while !@src.eos? && !stop_re_found
    219     if result = @src.scan_until(used_re)
    220       add_text(result)
    221       if stop_re && @src.check(stop_re)
    222         stop_re_found = (block_given? ? yield : true)
    223       end
    224       processed = parsers.any? do |name|
    225         if @src.check(@parsers[name].start_re)
    226           send(@parsers[name].method)
    227           true
    228         else
    229           false
    230         end
    231       end unless stop_re_found
    232       add_text(@src.getch) if !processed && !stop_re_found
    233     else
    234       (add_text(@src.rest); @src.terminate) unless stop_re
    235       break
    236     end
    237   end
    238
    239   @tree, @text_type = @stack.pop
    240
    241   stop_re_found
    242 end

reset_env(opts = {}) click to toggle source

Reset the current parsing environment. The parameter `env` can be used to set initial values for one or more environment variables.

        # File lib/kramdown/parser/kramdown.rb
    246 def reset_env(opts = {})
    247   opts = {:text_type => :raw_text, :stack => []}.merge(opts)
    248   @src = opts[:src]
    249   @tree = opts[:tree]
    250   @block_ial = opts[:block_ial]
    251   @stack = opts[:stack]
    252   @text_type = opts[:text_type]
    253 end

restore_env(env) click to toggle source

Restore the current parsing environment.

        # File lib/kramdown/parser/kramdown.rb
    261 def restore_env(env)
    262   @src, @tree, @block_ial, @stack,  @text_type = *env
    263 end

save_env() click to toggle source

Return the current parsing environment.

        # File lib/kramdown/parser/kramdown.rb
    256 def save_env
    257   [@src, @tree, @block_ial, @stack,  @text_type]
    258 end

span_parser_regexps(parsers = @span_parsers) click to toggle source

Create the needed span parser regexps.

        # File lib/kramdown/parser/kramdown.rb
    132 def span_parser_regexps(parsers = @span_parsers)
    133   span_start = /#{parsers.map {|name| @parsers[name].span_start}.join('|')}/
    134   [span_start, /(?=#{span_start})/]
    135 end

update_attr_with_ial(attr, ial) click to toggle source

Update the given attributes hash `attr` with the information from the inline attribute list `ial` and all referenced ALDs.

        # File lib/kramdown/parser/kramdown.rb
    267 def update_attr_with_ial(attr, ial)
    268   ial[:refs].each do |ref|
    269     update_attr_with_ial(attr, ref) if ref = @alds[ref]
    270   end if ial[:refs]
    271   ial.each do |k,v|
    272     if k == IAL_CLASS_ATTR
    273       attr[k] = (attr[k] || '') << " #{v}"
    274       attr[k].lstrip!
    275     elsif k.kind_of?(String)
    276       attr[k] = v
    277     end
    278   end
    279 end

update_link_definitions(link_defs) click to toggle source

Update the parser specific link definitions with the data from `link_defs` (the value of the :link_defs option).

The parameter `link_defs` is a hash where the keys are possibly unnormalized link IDs and the values are two element arrays consisting of the link target and a title (can be `nil`).

        # File lib/kramdown/parser/kramdown.rb
    114 def update_link_definitions(link_defs)
    115   link_defs.each {|k,v| @link_defs[normalize_link_id(k)] = v}
    116 end

update_raw_text(item) click to toggle source

Update the raw text for automatic ID generation.

        # File lib/kramdown/parser/kramdown.rb
    282 def update_raw_text(item)
    283   raw_text = ''
    284
    285   append_text = lambda do |child|
    286     if child.type == :text
    287       raw_text << child.value
    288     else
    289       child.children.each {|c| append_text.call(c)}
    290     end
    291   end
    292
    293   append_text.call(item)
    294   item.options[:raw_text] = raw_text
    295 end

update_tree(element) click to toggle source

Update the tree by parsing all :`raw_text` elements with the span-level parser (resets the environment) and by updating the attributes from the IALs.

        # File lib/kramdown/parser/kramdown.rb
    164 def update_tree(element)
    165   last_blank = nil
    166   element.children.map! do |child|
    167     if child.type == :raw_text
    168       last_blank = nil
    169       reset_env(:src => ::Kramdown::Utils::StringScanner.new(child.value, element.options[:location]),
    170                 :text_type => :text)
    171       parse_spans(child)
    172       child.children
    173     elsif child.type == :eob
    174       update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
    175       []
    176     elsif child.type == :blank
    177       if last_blank
    178         last_blank.value << child.value
    179         []
    180       else
    181         last_blank = child
    182         child
    183       end
    184     else
    185       last_blank = nil
    186       update_tree(child)
    187       update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
    188       # DEPRECATED: option auto_id_stripping will be removed in 2.0 because then this will be
    189       # the default behaviour
    190       if child.type == :dt || (child.type == :header && @options[:auto_id_stripping])
    191         update_raw_text(child)
    192       end
    193       child
    194     end
    195   end.flatten!
    196 end