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
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
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
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
Create a new Kramdown
parser object with the given options
.
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
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
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
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
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 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
# 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 begin 107 val = Kramdown::Options.parse(k, v) 108 @options[k] = val 109 (@root.options[:options] ||= {})[k] = val 110 rescue 111 end 112 false 113 else 114 true 115 end 116 end.each do |k,v| 117 warning("Unknown kramdown option '#{k}'") 118 end 119 @tree.children << new_block_el(:eob, :extension) if type == :block 120 true 121 else 122 false 123 end 124 end
# 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 the link identifier.
# File lib/kramdown/parser/kramdown/link.rb 16 def normalize_link_id(id) 17 id.gsub(/[\s]+/, ' ').downcase 18 end
# File lib/kramdown/parser/kramdown/paragraph.rb 55 def paragraph_end 56 self.class::PARAGRAPH_END 57 end
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 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 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 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 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 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 one of the block extensions (ALD, block IAL or generic extension) at the current location.
# File lib/kramdown/parser/kramdown/extensions.rb 152 def parse_block_extensions 153 if @src.scan(ALD_START) 154 parse_attribute_list(@src[2], @alds[@src[1]] ||= Utils::OrderedHash.new) 155 @tree.children << new_block_el(:eob, :ald) 156 true 157 elsif @src.check(EXT_BLOCK_START) 158 parse_extension_start_tag(:block) 159 elsif @src.scan(IAL_BLOCK_START) 160 if @tree.children.last && @tree.children.last.type != :blank && 161 (@tree.children.last.type != :eob || [:link_def, :abbrev_def, :footnote_def].include?(@tree.children.last.value)) 162 parse_attribute_list(@src[1], @tree.children.last.options[:ial] ||= Utils::OrderedHash.new) 163 @tree.children << new_block_el(:eob, :ial) unless @src.check(IAL_BLOCK_START) 164 else 165 parse_attribute_list(@src[1], @block_ial ||= Utils::OrderedHash.new) 166 end 167 true 168 else 169 false 170 end 171 end
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 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 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 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 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 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 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 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 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 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 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
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 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 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 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 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 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 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 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 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 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 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 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 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 the extension span at the current location.
# File lib/kramdown/parser/kramdown/extensions.rb 180 def parse_span_extensions 181 if @src.check(EXT_SPAN_START) 182 parse_extension_start_tag(:span) 183 elsif @src.check(IAL_SPAN_START) 184 if @tree.children.last && @tree.children.last.type != :text 185 @src.pos += @src.matched_size 186 attr = Utils::OrderedHash.new 187 parse_attribute_list(@src[1], attr) 188 update_ial_with_ial(@tree.children.last.options[:ial] ||= Utils::OrderedHash.new, attr) 189 update_attr_with_ial(@tree.children.last.attr, attr) 190 else 191 warning("Found span IAL after text - ignoring it") 192 add_text(@src.getch) 193 end 194 else 195 add_text(@src.getch) 196 end 197 end
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 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 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 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 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
# 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
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
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 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
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 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 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 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
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
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 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 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 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 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