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:

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
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
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
PATTERN_TAIL
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
327 def self.define_parser(name, start_re, span_start = nil, meth_name = "parse_#{name}")
328   raise "A parser with the name #{name} already exists!" if @@parsers.key?(name)
329   @@parsers[name] = Data.new(name, start_re, span_start, meth_name)
330 end
has_parser?(name) click to toggle source

Return true if there is a parser called name.

    # File lib/kramdown/parser/kramdown.rb
338 def self.has_parser?(name)
339   @@parsers.key?(name)
340 end
parser(name = nil) click to toggle source

Return the Data structure for the parser name.

    # File lib/kramdown/parser/kramdown.rb
333 def self.parser(name = nil)
334   @@parsers[name]
335 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,
77                     :abbrev_definition, :block_extensions, :eob_marker, :paragraph]
78   @span_parsers =  [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link,
79                     :smart_quotes, :inline_math, :span_extensions, :html_entity,
80                     :typographic_syms, :line_break, :escaped_chars]
81 
82   @span_pattern_cache ||= Hash.new { |h, k| h[k] = {} }
83 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

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   last_child = @tree.children.last
22   !last_child || last_child.type == :blank ||
23     (last_child.type == :eob && last_child.value.nil?) || @block_ial
24 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
27 def before_block_boundary?
28   @src.check(self.class::BLOCK_BOUNDARY)
29 end
correct_abbreviations_attributes() click to toggle source

Correct abbreviation attributes.

   # File lib/kramdown/parser/kramdown/abbreviation.rb
33 def correct_abbreviations_attributes
34   @root.options[:abbrev_attr].keys.each do |k|
35     @root.options[:abbrev_attr][k] = @root.options[:abbrev_attr][k].attr
36   end
37 end
handle_extension(name, opts, body, type, line_no = nil) click to toggle source
    # File lib/kramdown/parser/kramdown/extensions.rb
 95 def handle_extension(name, opts, body, type, line_no = nil)
 96   case name
 97   when 'comment'
 98     if body.kind_of?(String)
 99       @tree.children << Element.new(:comment, body, nil, category: type, location: line_no)
100     end
101     true
102   when 'nomarkdown'
103     if body.kind_of?(String)
104       @tree.children << Element.new(:raw, body, nil, category: type,
105                                     location: line_no, type: opts['type'].to_s.split(/\s+/))
106     end
107     true
108   when 'options'
109     opts.select do |k, v|
110       k = k.to_sym
111       if Kramdown::Options.defined?(k)
112         if @options[:forbidden_inline_options].include?(k) ||
113             k == :forbidden_inline_options
114           warning("Option #{k} may not be set inline")
115           next false
116         end
117 
118         begin
119           val = Kramdown::Options.parse(k, v)
120           @options[k] = val
121           (@root.options[:options] ||= {})[k] = val
122         rescue StandardError
123         end
124         false
125       else
126         true
127       end
128     end.each do |k, _v|
129       warning("Unknown kramdown option '#{k}'")
130     end
131     @tree.children << new_block_el(:eob, :extension) if type == :block
132     true
133   else
134     false
135   end
136 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       unless 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     unless @tree.type == :html_element && @tree.options[:content_model] == :raw
62       @src.scan(TRAILING_WHITESPACE)
63     end
64   end
65 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
 87 def parse
 88   configure_parser
 89   parse_blocks(@root, adapt_source(source))
 90   update_tree(@root)
 91   correct_abbreviations_attributes
 92   replace_abbreviations(@root)
 93   @footnotes.each do |_name, data|
 94     update_tree(data[:content])
 95     replace_abbreviations(data[:content])
 96   end
 97   footnote_count = 0
 98   @footnotes.each do |name, data|
 99     (footnote_count += 1; next) if data.key?(:marker)
100     line = data[:content].options[:location]
101     warning("Footnote definition for '#{name}' on line #{line} is unreferenced - ignoring")
102   end
103   @root.options[:footnote_count] = footnote_count
104 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   if @root.options[:abbrev_defs][abbrev_id]
22     warning("Duplicate abbreviation ID '#{abbrev_id}' on line #{start_line_number} " \
23             "- overwriting")
24   end
25   @tree.children << new_block_el(:eob, :abbrev_def)
26   @root.options[:abbrev_defs][abbrev_id] = abbrev_text
27   @root.options[:abbrev_attr][abbrev_id] = @tree.children.last
28   true
29 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}".lstrip
27         else
28           opts['id'] = id_attr
29         end
30       end
31     else
32       val.gsub!(/\\(\}|#{sep})/, "\\1")
33       opts[key] = val
34     end
35   end
36   warning("No or invalid attributes found in IAL/ALD content: #{str}") if attrs.empty?
37 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 unless after_block_boundary?
33   text, id = parse_header_contents
34   text.sub!(/(?<!\\)#+\z/, '') && text.rstrip!
35   return false if text.empty?
36   add_header(@src["level"].length, text, id)
37   true
38 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 (last_child = @tree.children.last) && last_child.type == :blank
19     last_child.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
163 def parse_block_extensions
164   if @src.scan(ALD_START)
165     parse_attribute_list(@src[2], @alds[@src[1]] ||= {})
166     @tree.children << new_block_el(:eob, :ald)
167     true
168   elsif @src.check(EXT_BLOCK_START)
169     parse_extension_start_tag(:block)
170   elsif @src.scan(IAL_BLOCK_START)
171     if (last_child = @tree.children.last) && last_child.type != :blank &&
172         (last_child.type != :eob ||
173          [:link_def, :abbrev_def, :footnote_def].include?(last_child.value))
174       parse_attribute_list(@src[1], last_child.options[:ial] ||= {})
175       @tree.children << new_block_el(:eob, :ial) unless @src.check(IAL_BLOCK_START)
176     else
177       parse_attribute_list(@src[1], @block_ial ||= {})
178     end
179     true
180   else
181     false
182   end
183 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
70 def parse_block_html
71   line = @src.current_line_number
72   if (result = @src.scan(HTML_COMMENT_RE))
73     @tree.children << Element.new(:xml_comment, result, nil, category: :block, location: line)
74     @src.scan(TRAILING_WHITESPACE)
75     true
76   else
77     if @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
78       @src.pos += @src.matched_size
79       handle_html_start_tag(line, &method(:handle_kramdown_html_tag))
80       Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native]
81       true
82     elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
83       name = @src[1].downcase
84 
85       if @tree.type == :html_element && @tree.value == name
86         @src.pos += @src.matched_size
87         throw :stop_block_parsing, :found
88       else
89         false
90       end
91     else
92       false
93     end
94   end
95 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}\\/o) 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   until @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
36 def parse_codeblock_fenced
37   if @src.check(self.class::FENCED_CODEBLOCK_MATCH)
38     start_line_number = @src.current_line_number
39     @src.pos += @src.matched_size
40     el = new_block_el(:codeblock, @src[5], nil, location: start_line_number, fenced: true)
41     lang = @src[3].to_s.strip
42     unless lang.empty?
43       el.options[:lang] = lang
44       el.attr['class'] = "language-#{@src[4]}"
45     end
46     @tree.children << el
47     true
48   else
49     false
50   end
51 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|\A\Z/ && @src.match?(/\s/)
23     add_text(result)
24     return
25   end
26 
27   # assign static regex to avoid allocating the same on every instance
28   # where +result+ equals a single-backtick. Interpolate otherwise.
29   if result == '`'
30     scan_pattern = /`/
31     str_sub_pattern = /`\Z/
32   else
33     scan_pattern = /#{result}/
34     str_sub_pattern = /#{result}\Z/
35   end
36 
37   if (text = @src.scan_until(scan_pattern))
38     text.sub!(str_sub_pattern, '')
39     unless simple
40       text = text[1..-1] if text[0..0] == ' '
41       text = text[0..-2] if text[-1..-1] == ' '
42     end
43     @tree.children << Element.new(:codespan, text, nil, {
44                                     codespan_delimiter: result,
45                                     location: start_line_number
46                                   })
47 
48   else
49     @src.revert_pos(saved_pos)
50     add_text(result)
51   end
52 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
152 def parse_definition_list
153   children = @tree.children
154   if !children.last || (children.length == 1 && children.last.type != :p) ||
155       (children.length >= 2 && children[-1].type != :p &&
156        (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   # take location from preceding para which is the first definition term
168   deflist.options[:location] = para.options[:location]
169   para.children.first.value.split(/\n/).each do |term|
170     el = Element.new(:dt, nil, nil, location: @src.current_line_number)
171     term.sub!(self.class::LIST_ITEM_IAL) do
172       parse_attribute_list($1, el.options[:ial] ||= {})
173       ''
174     end
175     el.options[:raw_text] = term
176     el.children << Element.new(:raw_text, term)
177     deflist.children << el
178   end
179   deflist.options[:ial] = para.options[:ial]
180 
181   item = nil
182   content_re, lazy_re, indent_re = nil
183   def_start_re = DEFINITION_LIST_START
184   last_is_blank = false
185   until @src.eos?
186     start_line_number = @src.current_line_number
187     if @src.scan(def_start_re)
188       item = Element.new(:dd, nil, nil, location: start_line_number)
189       item.options[:first_as_para] = first_as_para
190       item.value, indentation, content_re, lazy_re, indent_re =
191         parse_first_list_line(@src[1].length, @src[2])
192       deflist.children << item
193 
194       item.value.sub!(self.class::LIST_ITEM_IAL) do |_match|
195         parse_attribute_list($1, item.options[:ial] ||= {})
196         ''
197       end
198 
199       def_start_re = fetch_pattern(:dl, indentation)
200       first_as_para = false
201       last_is_blank = false
202     elsif @src.check(EOB_MARKER)
203       break
204     elsif (result = @src.scan(content_re)) || (!last_is_blank && (result = @src.scan(lazy_re)))
205       result.sub!(/^(\t+)/) { " " * ($1 ? 4 * $1.length : 0) }
206       result.sub!(indent_re, '')
207       item.value << result
208       first_as_para = false
209       last_is_blank = false
210     elsif (result = @src.scan(BLANK_LINE))
211       first_as_para = true
212       item.value << result
213       last_is_blank = true
214     else
215       break
216     end
217   end
218 
219   last = nil
220   deflist.children.each do |it|
221     next if it.type == :dt
222 
223     parse_blocks(it, it.value)
224     it.value = nil
225     it_children = it.children
226     next if it_children.empty?
227 
228     last = (it_children.last.type == :blank ? it_children.pop : nil)
229 
230     if it_children.first && it_children.first.type == :p && !it.options.delete(:first_as_para)
231       it_children.first.children.first.value << "\n" if it_children.size > 1
232       it_children.first.options[:transparent] = true
233     end
234   end
235 
236   children = @tree.children
237   if children.length >= 1 && children.last.type == :dl
238     children[-1].children.concat(deflist.children)
239   elsif children.length >= 2 && children[-1].type == :blank &&
240       children[-2].type == :dl
241     children.pop
242     children[-1].children.concat(deflist.children)
243   else
244     children << deflist
245   end
246 
247   children << last if last
248 
249   true
250 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:]]-?[[: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.empty?
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
53 def parse_extension_start_tag(type)
54   saved_pos = @src.save_pos
55   start_line_number = @src.current_line_number
56   @src.pos += @src.matched_size
57 
58   error_block = lambda do |msg|
59     warning(msg)
60     @src.revert_pos(saved_pos)
61     add_text(@src.getch) if type == :span
62     false
63   end
64 
65   if @src[4] || @src.matched == '{:/}'
66     name = (@src[4] ? "for '#{@src[4]}' " : '')
67     return error_block.call("Invalid extension stop tag #{name} found on line " \
68                             "#{start_line_number} - ignoring it")
69   end
70 
71   ext = @src[1]
72   opts = {}
73   body = nil
74   parse_attribute_list(@src[2] || '', opts)
75 
76   unless @src[3]
77     stop_re = (type == :block ? /#{EXT_BLOCK_STOP_STR % ext}/ : /#{EXT_STOP_STR % ext}/)
78     if (result = @src.scan_until(stop_re))
79       body = result.sub!(stop_re, '')
80       body.chomp! if type == :block
81     else
82       return error_block.call("No stop tag for extension '#{ext}' found on line " \
83                               "#{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 " \
89                      "#{start_line_number} - ignoring it")
90   else
91     true
92   end
93 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   if @footnotes[@src[1]]
27     warning("Duplicate footnote name '#{@src[1]}' on line #{start_line_number} - overwriting")
28   end
29   @tree.children << new_block_el(:eob, :footnote_def)
30   (@footnotes[@src[1]] = {})[:content] = el
31   @footnotes[@src[1]][:eob] = @tree.children.last
32   true
33 end
parse_footnote_marker() click to toggle source

Parse the footnote marker at the current location.

   # File lib/kramdown/parser/kramdown/footnote.rb
39 def parse_footnote_marker
40   start_line_number = @src.current_line_number
41   @src.pos += @src.matched_size
42   fn_def = @footnotes[@src[1]]
43   if fn_def
44     if fn_def[:eob]
45       update_attr_with_ial(fn_def[:eob].attr, fn_def[:eob].options[:ial] || {})
46       fn_def[:attr] = fn_def[:eob].attr
47       fn_def[:options] = fn_def[:eob].options
48       fn_def.delete(:eob)
49     end
50     fn_def[:marker] ||= []
51     fn_def[:marker].push(Element.new(:footnote, fn_def[:content], fn_def[:attr],
52                                      fn_def[:options].merge(name: @src[1], location: start_line_number)))
53     @tree.children << fn_def[:marker].last
54   else
55     warning("Footnote definition for '#{@src[1]}' not found on line #{start_line_number}")
56     add_text(@src.matched)
57   end
58 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     value = ::Kramdown::Utils::Entities.entity(@src[1] || (@src[2]&.to_i) || @src[3].hex)
21     @tree.children << Element.new(:entity, value,
22                                   nil, original: @src.matched, location: start_line_number)
23   rescue ::Kramdown::Error
24     @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('amp'),
25                                   nil, location: start_line_number)
26     add_text(@src.matched[1..-1])
27   end
28 end
parse_inline_math() click to toggle source

Parse the inline math at the current location.

   # File lib/kramdown/parser/kramdown/math.rb
43 def parse_inline_math
44   start_line_number = @src.current_line_number
45   @src.pos += @src.matched_size
46   @tree.children << Element.new(:math, @src[1].strip, nil, category: :span, location: start_line_number)
47 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_list() click to toggle source

Parse the ordered or unordered list at the current location.

    # File lib/kramdown/parser/kramdown/list.rb
 53 def parse_list
 54   start_line_number = @src.current_line_number
 55   type, list_start_re = (@src.check(LIST_START_UL) ? [:ul, LIST_START_UL] : [:ol, LIST_START_OL])
 56   list = new_block_el(type, nil, nil, location: start_line_number)
 57 
 58   item = nil
 59   content_re, lazy_re, indent_re = nil
 60   eob_found = false
 61   nested_list_found = false
 62   last_is_blank = false
 63   until @src.eos?
 64     start_line_number = @src.current_line_number
 65     if last_is_blank && @src.check(HR_START)
 66       break
 67     elsif @src.scan(EOB_MARKER)
 68       eob_found = true
 69       break
 70     elsif @src.scan(list_start_re)
 71       list.options[:first_list_marker] ||= @src[1].strip
 72       item = Element.new(:li, nil, nil, location: start_line_number)
 73       item.value, indentation, content_re, lazy_re, indent_re =
 74         parse_first_list_line(@src[1].length, @src[2])
 75       list.children << item
 76 
 77       item.value.sub!(self.class::LIST_ITEM_IAL) do
 78         parse_attribute_list($1, item.options[:ial] ||= {})
 79         ''
 80       end
 81 
 82       list_start_re = fetch_pattern(type, indentation)
 83       nested_list_found = (item.value =~ LIST_START)
 84       last_is_blank = false
 85       item.value = [item.value]
 86     elsif (result = @src.scan(content_re)) || (!last_is_blank && (result = @src.scan(lazy_re)))
 87       result.sub!(/^(\t+)/) { " " * 4 * $1.length }
 88       indentation_found = result.sub!(indent_re, '')
 89       if !nested_list_found && indentation_found && result =~ LIST_START
 90         item.value << +''
 91         nested_list_found = true
 92       elsif nested_list_found && !indentation_found && result =~ LIST_START
 93         result = " " * (indentation + 4) << result
 94       end
 95       item.value.last << result
 96       last_is_blank = false
 97     elsif (result = @src.scan(BLANK_LINE))
 98       nested_list_found = true
 99       last_is_blank = true
100       item.value.last << result
101     else
102       break
103     end
104   end
105 
106   @tree.children << list
107 
108   last = nil
109   list.children.each do |it|
110     temp = Element.new(:temp, nil, nil, location: it.options[:location])
111 
112     env = save_env
113     location = it.options[:location]
114     it.value.each do |val|
115       @src = ::Kramdown::Utils::StringScanner.new(val, location)
116       parse_blocks(temp)
117       location = @src.current_line_number
118     end
119     restore_env(env)
120 
121     it.children = temp.children
122     it.value = nil
123 
124     it_children = it.children
125     next if it_children.empty?
126 
127     # Handle the case where an EOB marker is inserted by a block IAL for the first paragraph
128     it_children.delete_at(1) if it_children.first.type == :p &&
129       it_children.length >= 2 && it_children[1].type == :eob && it_children.first.options[:ial]
130 
131     if it_children.first.type == :p &&
132         (it_children.length < 2 || it_children[1].type != :blank ||
133         (it == list.children.last && it_children.length == 2 && !eob_found)) &&
134         (list.children.last != it || list.children.size == 1 ||
135         list.children[0..-2].any? {|cit| !cit.children.first || cit.children.first.type != :p || cit.children.first.options[:transparent] })
136       it_children.first.children.first.value << "\n" if it_children.size > 1 && it_children[1].type != :blank
137       it_children.first.options[:transparent] = true
138     end
139 
140     last = (it_children.last.type == :blank ? it_children.pop : nil)
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   until @src.match?(paragraph_end)
35     result << @src.scan(PARAGRAPH_MATCH)
36   end
37   result.rstrip!
38   if (last_child = @tree.children.last) && last_child.type == :p
39     last_item_in_para = last_child.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, last_child)
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
19 def parse_setext_header
20   return false unless after_block_boundary?
21   text, id = parse_header_contents
22   return false if text.empty?
23   add_header(@src["level"] == '-' ? 2 : 1, text, id)
24   true
25 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
191 def parse_span_extensions
192   if @src.check(EXT_SPAN_START)
193     parse_extension_start_tag(:span)
194   elsif @src.check(IAL_SPAN_START)
195     if (last_child = @tree.children.last) && last_child.type != :text
196       @src.pos += @src.matched_size
197       attr = {}
198       parse_attribute_list(@src[1], attr)
199       update_ial_with_ial(last_child.options[:ial] ||= {}, attr)
200       update_attr_with_ial(last_child.attr, attr)
201     else
202       warning("Found span IAL after text - ignoring it")
203       add_text(@src.getch)
204     end
205   else
206     add_text(@src.getch)
207   end
208 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
101 def parse_span_html
102   line = @src.current_line_number
103   if (result = @src.scan(HTML_COMMENT_RE))
104     @tree.children << Element.new(:xml_comment, result, nil, category: :span, location: line)
105   elsif (result = @src.scan(HTML_TAG_CLOSE_RE))
106     warning("Found invalidly used HTML closing tag for '#{@src[1]}' on line #{line}")
107     add_text(result)
108   elsif (result = @src.scan(HTML_TAG_RE))
109     tag_name = @src[1]
110     tag_name.downcase! if HTML_ELEMENT[tag_name.downcase]
111     if HTML_BLOCK_ELEMENTS.include?(tag_name)
112       warning("Found block HTML tag '#{tag_name}' in span-level text on line #{line}")
113       add_text(result)
114       return
115     end
116 
117     attrs = parse_html_attributes(@src[2], line, HTML_ELEMENT[tag_name])
118     attrs.each_value {|value| value.gsub!(/\n+/, ' ') unless value.empty? }
119 
120     do_parsing = if HTML_CONTENT_MODEL[tag_name] == :raw || @tree.options[:content_model] == :raw
121                    false
122                  else
123                    @options[:parse_span_html]
124                  end
125     if (val = HTML_MARKDOWN_ATTR_MAP[attrs.delete('markdown')])
126       if val == :block
127         warning("Cannot use block-level parsing in span-level HTML tag (line #{line}) " \
128                 "- using default mode")
129       elsif val == :span
130         do_parsing = true
131       elsif val == :default
132         do_parsing = HTML_CONTENT_MODEL[tag_name] != :raw
133       elsif val == :raw
134         do_parsing = false
135       end
136     end
137 
138     el = Element.new(:html_element, tag_name, attrs, category: :span, location: line,
139                      content_model: (do_parsing ? :span : :raw), is_closed: !!@src[4])
140     @tree.children << el
141     stop_re = /<\/#{Regexp.escape(tag_name)}\s*>/
142     stop_re = Regexp.new(stop_re.source, Regexp::IGNORECASE) if HTML_ELEMENT[tag_name]
143     if !@src[4] && !HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
144       if parse_spans(el, stop_re, (do_parsing ? nil : [:span_html]))
145         @src.scan(stop_re)
146       else
147         warning("Found no end tag for '#{el.value}' (line #{line}) - auto-closing it")
148         add_text(@src.rest, el)
149         @src.terminate
150       end
151     end
152     Kramdown::Parser::Html::ElementConverter.convert(@root, el) if @options[:html_to_native]
153   else
154     add_text(@src.getch)
155   end
156 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 unless 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   until @src.eos?
 46     break unless @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) ||
 54             (left.empty? && :right) || :center
 55         end
 56       else # treat as normal separator line
 57         add_container.call(:tbody, false)
 58       end
 59     elsif @src.scan(TABLE_FSEP_LINE)
 60       add_container.call(:tbody, true) unless rows.empty?
 61       has_footer = true
 62     elsif @src.scan(TABLE_ROW_LINE)
 63       trow = Element.new(:tr)
 64 
 65       # parse possible code spans on the line and correctly split the line into cells
 66       env = save_env
 67       cells = []
 68       @src[1].split(/(<code.*?>.*?<\/code>)/).each_with_index do |str, i|
 69         if i.odd?
 70           (cells.empty? ? cells : cells.last) << str
 71         else
 72           reset_env(src: Kramdown::Utils::StringScanner.new(str, @src.current_line_number))
 73           root = Element.new(:root)
 74           parse_spans(root, nil, [:codespan])
 75 
 76           root.children.each do |c|
 77             if c.type == :raw_text
 78               f, *l = c.value.split(/(?<!\\)\|/, -1).map {|t| t.gsub(/\\\|/, '|') }
 79               (cells.empty? ? cells : cells.last) << f
 80               cells.concat(l)
 81             else
 82               delim = (c.value.scan(/`+/).max || '') + '`'
 83               tmp = +"#{delim}#{' ' if delim.size > 1}#{c.value}#{' ' if delim.size > 1}#{delim}"
 84               (cells.empty? ? cells : cells.last) << tmp
 85             end
 86           end
 87         end
 88       end
 89       restore_env(env)
 90 
 91       cells.shift if leading_pipe && cells.first.strip.empty?
 92       cells.pop if cells.last.strip.empty?
 93       cells.each do |cell_text|
 94         tcell = Element.new(:td)
 95         tcell.children << Element.new(:raw_text, cell_text.strip)
 96         trow.children << tcell
 97       end
 98       columns = [columns, cells.length].max
 99       rows << trow
100     else
101       break
102     end
103   end
104 
105   unless before_block_boundary?
106     @src.revert_pos(saved_pos)
107     return false
108   end
109 
110   # Parse all lines of the table with the code span parser
111   env = save_env
112   l_src = ::Kramdown::Utils::StringScanner.new(extract_string(orig_pos...(@src.pos - 1), @src),
113                                                @src.current_line_number)
114   reset_env(src: l_src)
115   root = Element.new(:root)
116   parse_spans(root, nil, [:codespan, :span_html])
117   restore_env(env)
118 
119   # Check if each line has at least one unescaped pipe that is not inside a code span/code
120   # HTML element
121   # Note: It doesn't matter that we parse *all* span HTML elements because the row splitting
122   # algorithm above only takes <code> elements into account!
123   pipe_on_line = false
124   while (c = root.children.shift)
125     next unless (lines = c.value)
126     lines = lines.split("\n")
127     if c.type == :codespan
128       if lines.size > 2 || (lines.size == 2 && !pipe_on_line)
129         break
130       elsif lines.size == 2 && pipe_on_line
131         pipe_on_line = false
132       end
133     else
134       break if lines.size > 1 && !pipe_on_line && lines.first !~ /^#{TABLE_PIPE_CHECK}/o
135       pipe_on_line = (lines.size > 1 ? false : pipe_on_line) || (lines.last =~ /^#{TABLE_PIPE_CHECK}/o)
136     end
137   end
138   @src.revert_pos(saved_pos) and return false unless pipe_on_line
139 
140   add_container.call(has_footer ? :tfoot : :tbody, false) unless rows.empty?
141 
142   if table.children.none? {|el| el.type == :tbody }
143     warning("Found table without body on line #{table.options[:location]} - ignoring it")
144     @src.revert_pos(saved_pos)
145     return false
146   end
147 
148   # adjust all table rows to have equal number of columns, same for alignment defs
149   table.children.each do |kind|
150     kind.children.each do |row|
151       (columns - row.children.length).times do
152         row.children << Element.new(:td)
153       end
154     end
155   end
156   if table.options[:alignment].length > columns
157     table.options[:alignment] = table.options[:alignment][0...columns]
158   else
159     table.options[:alignment] += [:default] * (columns - table.options[:alignment].length)
160   end
161 
162   @tree.children << table
163 
164   true
165 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
40 def replace_abbreviations(el, regexps = nil)
41   return if @root.options[:abbrev_defs].empty?
42   unless regexps
43     sorted_abbrevs = @root.options[:abbrev_defs].keys.sort {|a, b| b.length <=> a.length }
44     regexps = [Regexp.union(*sorted_abbrevs.map {|k| /#{Regexp.escape(k)}/ })]
45     regexps << /(?=(?:\W|^)#{regexps.first}(?!\w))/ # regexp should only match on word boundaries
46   end
47   el.children.map! do |child|
48     if child.type == :text && el.options[:content_model] != :raw
49       if child.value =~ regexps.first
50         result = []
51         strscan = Kramdown::Utils::StringScanner.new(child.value, child.options[:location])
52         text_lineno = strscan.current_line_number
53         while (temp = strscan.scan_until(regexps.last))
54           abbr_lineno = strscan.current_line_number
55           abbr = strscan.scan(regexps.first) # begin of line case of abbr with \W char as first one
56           if abbr.nil?
57             temp << strscan.scan(/\W|^/)
58             abbr = strscan.scan(regexps.first)
59           end
60           result << Element.new(:text, temp, nil, location: text_lineno)
61           result << Element.new(:abbreviation, abbr, nil, location: abbr_lineno)
62           text_lineno = strscan.current_line_number
63         end
64         result << Element.new(:text, strscan.rest, nil, location: text_lineno)
65       else
66         child
67       end
68     else
69       replace_abbreviations(child, regexps)
70       child
71     end
72   end.flatten!
73 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
40 def update_ial_with_ial(ial, opts)
41   (ial[:refs] ||= []).concat(opts[:refs]) if opts.key?(:refs)
42   opts.each do |k, v|
43     if k == IAL_CLASS_ATTR
44       ial[k] = "#{ial[k]} #{v}".lstrip
45     elsif k.kind_of?(String)
46       ial[k] = v
47     end
48   end
49 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
120 def configure_parser
121   @parsers = {}
122   (@block_parsers + @span_parsers).each do |name|
123     if self.class.has_parser?(name)
124       @parsers[name] = self.class.parser(name)
125     else
126       raise Kramdown::Error, "Unknown parser: #{name}"
127     end
128   end
129   @span_start, @span_start_re = span_parser_regexps
130 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
304 def new_block_el(*args)
305   el = Element.new(*args)
306   if @block_ial
307     el.options[:ial] = @block_ial
308     @block_ial = nil
309   end
310   el
311 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
139 def parse_blocks(el, text = nil)
140   @stack.push([@tree, @src, @block_ial])
141   @tree, @block_ial = el, nil
142   @src = (text.nil? ? @src : ::Kramdown::Utils::StringScanner.new(text, el.options[:location]))
143 
144   status = catch(:stop_block_parsing) do
145     until @src.eos?
146       @block_parsers.any? do |name|
147         if @src.check(@parsers[name].start_re)
148           send(@parsers[name].method)
149         else
150           false
151         end
152       end || begin
153         warning('Warning: this should not occur - no block parser handled the line')
154         add_text(@src.scan(/.*\n/))
155       end
156     end
157   end
158 
159   @tree, @src, @block_ial = *@stack.pop
160   status
161 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
213 def parse_spans(el, stop_re = nil, parsers = nil, text_type = @text_type)
214   @stack.push([@tree, @text_type]) unless @tree.nil?
215   @tree, @text_type = el, text_type
216 
217   span_start = @span_start
218   span_start_re = @span_start_re
219   span_start, span_start_re = span_parser_regexps(parsers) if parsers
220   parsers ||= @span_parsers
221 
222   used_re = (stop_re.nil? ? span_start_re : span_pattern_cache(stop_re, span_start))
223   stop_re_found = false
224   while !@src.eos? && !stop_re_found
225     if (result = @src.scan_until(used_re))
226       add_text(result)
227       if stop_re && @src.check(stop_re)
228         stop_re_found = (block_given? ? yield : true)
229       end
230       processed = parsers.any? do |name|
231         if @src.check(@parsers[name].start_re)
232           send(@parsers[name].method)
233           true
234         else
235           false
236         end
237       end unless stop_re_found
238       add_text(@src.getch) if !processed && !stop_re_found
239     else
240       (add_text(@src.rest); @src.terminate) unless stop_re
241       break
242     end
243   end
244 
245   @tree, @text_type = @stack.pop
246 
247   stop_re_found
248 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
252 def reset_env(opts = {})
253   opts = {text_type: :raw_text, stack: []}.merge(opts)
254   @src = opts[:src]
255   @tree = opts[:tree]
256   @block_ial = opts[:block_ial]
257   @stack = opts[:stack]
258   @text_type = opts[:text_type]
259 end
restore_env(env) click to toggle source

Restore the current parsing environment.

    # File lib/kramdown/parser/kramdown.rb
267 def restore_env(env)
268   @src, @tree, @block_ial, @stack, @text_type = *env
269 end
save_env() click to toggle source

Return the current parsing environment.

    # File lib/kramdown/parser/kramdown.rb
262 def save_env
263   [@src, @tree, @block_ial, @stack, @text_type]
264 end
span_parser_regexps(parsers = @span_parsers) click to toggle source

Create the needed span parser regexps.

    # File lib/kramdown/parser/kramdown.rb
133 def span_parser_regexps(parsers = @span_parsers)
134   span_start = /#{parsers.map {|name| @parsers[name].span_start }.join('|')}/
135   [span_start, /(?=#{span_start})/]
136 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
273 def update_attr_with_ial(attr, ial)
274   ial[:refs]&.each do |ref|
275     update_attr_with_ial(attr, ref) if (ref = @alds[ref])
276   end
277   ial.each do |k, v|
278     if k == IAL_CLASS_ATTR
279       attr[k] = "#{attr[k]} #{v}".lstrip
280     elsif k.kind_of?(String)
281       attr[k] = v
282     end
283   end
284 end
update_raw_text(item) click to toggle source

Update the raw text for automatic ID generation.

    # File lib/kramdown/parser/kramdown.rb
287 def update_raw_text(item)
288   raw_text = +''
289 
290   append_text = lambda do |child|
291     if child.type == :text
292       raw_text << child.value
293     else
294       child.children.each {|c| append_text.call(c) }
295     end
296   end
297 
298   append_text.call(item)
299   item.options[:raw_text] = raw_text
300 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
165 def update_tree(element)
166   last_blank = nil
167   element.children.map! do |child|
168     if child.type == :raw_text
169       last_blank = nil
170       reset_env(src: ::Kramdown::Utils::StringScanner.new(child.value, element.options[:location]),
171                 text_type: :text)
172       parse_spans(child)
173       child.children
174     elsif child.type == :eob
175       update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
176       []
177     elsif child.type == :blank
178       if last_blank
179         last_blank.value << child.value
180         []
181       else
182         last_blank = child
183         child
184       end
185     else
186       last_blank = nil
187       update_tree(child)
188       update_attr_with_ial(child.attr, child.options[:ial]) if child.options[:ial]
189       # DEPRECATED: option auto_id_stripping will be removed in 2.0 because then this will be
190       # the default behaviour
191       if child.type == :dt || (child.type == :header && @options[:auto_id_stripping])
192         update_raw_text(child)
193       end
194       child
195     end
196   end.flatten!
197 end

Private Instance Methods

fetch_pattern(type, indentation) click to toggle source

precomputed patterns for indentations 1..4 and fallback expression to compute pattern when indentation is outside the 1..4 range.

    # File lib/kramdown/parser/kramdown/list.rb
257 def fetch_pattern(type, indentation)
258   if type == :ul
259     case indentation
260     when 1 then %r/^( {0}[+*-])(#{PATTERN_TAIL})/o
261     when 2 then %r/^( {0,1}[+*-])(#{PATTERN_TAIL})/o
262     when 3 then %r/^( {0,2}[+*-])(#{PATTERN_TAIL})/o
263     else %r/^( {0,3}[+*-])(#{PATTERN_TAIL})/o
264     end
265   elsif type == :ol
266     case indentation
267     when 1 then %r/^( {0}\d+\.)(#{PATTERN_TAIL})/o
268     when 2 then %r/^( {0,1}\d+\.)(#{PATTERN_TAIL})/o
269     when 3 then %r/^( {0,2}\d+\.)(#{PATTERN_TAIL})/o
270     else %r/^( {0,3}\d+\.)(#{PATTERN_TAIL})/o
271     end
272   elsif type == :dl
273     case indentation
274     when 1 then %r/^( {0}:)(#{PATTERN_TAIL})/o
275     when 2 then %r/^( {0,1}:)(#{PATTERN_TAIL})/o
276     when 3 then %r/^( {0,2}:)(#{PATTERN_TAIL})/o
277     else %r/^( {0,3}:)(#{PATTERN_TAIL})/o
278     end
279   end
280 end
span_pattern_cache(stop_re, span_start) click to toggle source
    # File lib/kramdown/parser/kramdown.rb
199 def span_pattern_cache(stop_re, span_start)
200   @span_pattern_cache[stop_re][span_start] ||= /(?=#{Regexp.union(stop_re, span_start)})/
201 end