class Kramdown::Parser::Kramdown
Used for parsing a document in kramdown format.
def self.parse(source, doc)
def self.parse(source, doc) new(doc).parse(source) end
def adapt_source(source)
def adapt_source(source) source.gsub(/\r\n?/, "\n").chomp + "\n" end
def add_text(text, tree = @tree)
This helper method adds the given +text+ either to the last element in the +tree+ if it is a
def add_text(text, tree = @tree) if tree.children.last && tree.children.last.type == :text tree.children.last.value << text elsif !text.empty? tree.children << Element.new(:text, text) end end
def configure_parser
def configure_parser @parsers = {} BLOCK_PARSERS.each do |name| if Registry.has_parser?(name, :block) extend(Registry.parser(name).module) @parsers[name] = Registry.parser(name) else raise Kramdown::Error, "Unknown block parser: #{name}" end end SPAN_PARSERS.each do |name| if Registry.has_parser?(name, :span) extend(Registry.parser(name).module) @parsers[name] = Registry.parser(name) else raise Kramdown::Error, "Unknown span parser: #{name}" end end @span_start = Regexp.union(*SPAN_PARSERS.map {|name| @parsers[name].start_re}) @span_start_re = /(?=#{@span_start})/ end
def initialize(doc)
def initialize(doc) @doc = doc @src = nil @tree = nil @unclosed_html_tags = [] @stack = [] @used_ids = {} @doc.parse_infos[:ald] = {} @doc.parse_infos[:link_defs] = {} @doc.parse_infos[:footnotes] = {} end
def parse(source)
def parse(source) configure_parser tree = Element.new(:root) parse_blocks(tree, adapt_source(source)) update_tree(tree) @doc.parse_infos[:footnotes].each do |name, data| update_tree(data[:content]) end tree end
def parse_blocks(el, text)
Parse all block level elements in +text+ (a string or a StringScanner object) into the
def parse_blocks(el, text) @stack.push([@tree, @src, @unclosed_html_tags]) @tree, @src, @unclosed_html_tags = el, StringScanner.new(text), [] while !@src.eos? BLOCK_PARSERS.any? do |name| if @src.check(@parsers[name].start_re) send(@parsers[name].method) else false end end || begin warning('Warning: this should not occur - no block parser handled the line') add_text(@src.scan(/.*\n/)) end end @unclosed_html_tags.reverse.each do |tag| warning("Automatically closing unclosed html tag '#{tag.value}'") end @tree, @src, @unclosed_html_tags = *@stack.pop end
def parse_spans(el, stop_re = nil)
def parse_spans(el, stop_re = nil) @stack.push(@tree) @tree = el used_re = (stop_re.nil? ? @span_start_re : /(?=#{Regexp.union(stop_re, @span_start)})/) stop_re_found = false while !@src.eos? && !stop_re_found if result = @src.scan_until(used_re) add_text(result) if stop_re && (stop_re_matched = @src.check(stop_re)) stop_re_found = (block_given? ? yield : true) end processed = SPAN_PARSERS.any? do |name| if @src.check(@parsers[name].start_re) send(@parsers[name].method) true else false end end unless stop_re_found if !processed && !stop_re_found if stop_re_matched add_text(@src.scan(/./)) else raise Kramdown::Error, 'Bug: please report!' end end else add_text(@src.scan_until(/.*/m)) unless stop_re break end end @tree = @stack.pop stop_re_found end
def update_tree(element)
Update the tree by parsing all :text elements with the span level parser (resets
def update_tree(element) element.children.map! do |child| if child.type == :text @stack, @tree = [], nil @src = StringScanner.new(child.value) parse_spans(child) child.children else update_tree(child) update_attr_with_ial(child.options[:attr] ||= {}, child.options[:ial]) if child.options[:ial] child end end.flatten! end
def warning(text)
def warning(text) @doc.warnings << text #TODO: add position information end