Class: Kramdown::Parser::Kramdown
- Includes:
- Kramdown, Html::Parser
- Defined in:
- lib/kramdown/parser/kramdown.rb,
lib/kramdown/parser/kramdown/eob.rb,
lib/kramdown/parser/kramdown/html.rb,
lib/kramdown/parser/kramdown/link.rb,
lib/kramdown/parser/kramdown/list.rb,
lib/kramdown/parser/kramdown/math.rb,
lib/kramdown/parser/kramdown/table.rb,
lib/kramdown/parser/kramdown/header.rb,
lib/kramdown/parser/kramdown/autolink.rb,
lib/kramdown/parser/kramdown/codespan.rb,
lib/kramdown/parser/kramdown/emphasis.rb,
lib/kramdown/parser/kramdown/footnote.rb,
lib/kramdown/parser/kramdown/codeblock.rb,
lib/kramdown/parser/kramdown/paragraph.rb,
lib/kramdown/parser/kramdown/blank_line.rb,
lib/kramdown/parser/kramdown/blockquote.rb,
lib/kramdown/parser/kramdown/extensions.rb,
lib/kramdown/parser/kramdown/line_break.rb,
lib/kramdown/parser/kramdown/html_entity.rb,
lib/kramdown/parser/kramdown/abbreviation.rb,
lib/kramdown/parser/kramdown/smart_quotes.rb,
lib/kramdown/parser/kramdown/escaped_chars.rb,
lib/kramdown/parser/kramdown/block_boundary.rb,
lib/kramdown/parser/kramdown/horizontal_rule.rb,
lib/kramdown/parser/kramdown/typographic_symbol.rb
Overview
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, )
super
@span_parsers.unshift(:erb_tags)
end
ERB_TAGS_START = /<%.*?%>/
def
@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
Defined Under Namespace
Classes: Data
Constant Summary collapse
- EOB_MARKER =
/^\^\s*?\n/- 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
nilis returned. {"0" => :raw, "1" => :default, "span" => :span, "block" => :block}
- TRAILING_WHITESPACE =
/[ \t]*\n/- HTML_BLOCK_START =
/^#{OPT_SPACE}<(#{REXML::Parsers::BaseParser::UNAME_STR}|\?|!--|\/)/- HTML_SPAN_START =
/<(#{REXML::Parsers::BaseParser::UNAME_STR}|\?|!--|\/)/- LINK_DEFINITION_START =
/^#{OPT_SPACE}\[([^\n\]]+)\]:[ \t]*(?:<(.*?)>|([^\n]*?\S[^\n]*?))(?:(?:[ \t]*?\n|[ \t]+?)[ \t]*?(["'])(.+?)\4)?[ \t]*?\n/- LINK_BRACKET_STOP_RE =
/(\])|!?\[/- LINK_PAREN_STOP_RE =
/(\()|(\))|\s(?=['"])/- LINK_INLINE_ID_RE =
/\s*?\[([^\]]+)?\]/- LINK_INLINE_TITLE_RE =
/\s*?(["'])(.+?)\1\s*?\)/m- LINK_START =
/!?\[(?=[^^])/- LIST_ITEM_IAL =
/^\s*(?:\{:(?!(?:#{ALD_ID_NAME})?:|\/)(#{ALD_ANY_CHARS}+)\})\s*/- LIST_ITEM_IAL_CHECK =
/^#{LIST_ITEM_IAL}?\s*\n/- PARSE_FIRST_LIST_LINE_REGEXP_CACHE =
Hash.new do |h, indentation| indent_re = /^ {#{indentation}}/ content_re = /^(?:(?:\t| {4}){#{indentation / 4}} {#{indentation % 4}}|(?:\t| {4}){#{indentation / 4 + 1}}).*\S.*\n/ lazy_re = /(?!^ {0,#{[indentation, 3].min}}(?:#{IAL_BLOCK}|#{LAZY_END_HTML_STOP}|#{LAZY_END_HTML_START})).*\S.*\n/ h[indentation] = [content_re, lazy_re, indent_re] end
- LIST_START_UL =
/^(#{OPT_SPACE}[+*-])([\t| ].*?\n)/- LIST_START_OL =
/^(#{OPT_SPACE}\d+\.)([\t| ].*?\n)/- LIST_START =
/#{LIST_START_UL}|#{LIST_START_OL}/- DEFINITION_LIST_START =
/^(#{OPT_SPACE}:)([\t| ].*?\n)/- BLOCK_MATH_START =
/^#{OPT_SPACE}(\\)?\$\$(.*?)\$\$(\s*?\n)?/m- INLINE_MATH_START =
/\$\$(.*?)\$\$/m- TABLE_SEP_LINE =
/^([+|: -]*?-[+|: -]*?)[ \t]*\n/- TABLE_HSEP_ALIGN =
/[ ]?(:?)-+(:?)[ ]?/- TABLE_FSEP_LINE =
/^[+|: =]*?=[+|: =]*?[ \t]*\n/- TABLE_ROW_LINE =
/^(.*?)[ \t]*\n/- TABLE_PIPE_CHECK =
/(?:\||.*?[^\\\n]\|)/- TABLE_LINE =
/#{TABLE_PIPE_CHECK}.*?\n/- TABLE_START =
/^#{OPT_SPACE}(?=\S)#{TABLE_LINE}/- HEADER_ID =
/(?:[ \t]+\{#([A-Za-z][\w:-]*)\})?/- SETEXT_HEADER_START =
/^(#{OPT_SPACE}[^ \t].*?)#{HEADER_ID}[ \t]*?\n(-|=)+\s*?\n/- ATX_HEADER_START =
/^\#{1,6}/- ATX_HEADER_MATCH =
/^(\#{1,6})(.+?(?:\\#)?)\s*?#*#{HEADER_ID}\s*?\n/- ACHARS =
'[[:alnum:]]'- AUTOLINK_START_STR =
"<((mailto|https?|ftps?):.+?|[-.#{ACHARS}]+@[-#{ACHARS}]+(?:\.[-#{ACHARS}]+)*\.[a-z]+)>"- AUTOLINK_START =
/#{AUTOLINK_START_STR}/u- CODESPAN_DELIMITER =
/`+/- EMPHASIS_START =
/(?:\*\*?|__?)/- FOOTNOTE_DEFINITION_START =
/^#{OPT_SPACE}\[\^(#{ALD_ID_NAME})\]:\s*?(.*?\n#{CODEBLOCK_MATCH})/- FOOTNOTE_MARKER_START =
/\[\^(#{ALD_ID_NAME})\]/- CODEBLOCK_START =
INDENT- CODEBLOCK_MATCH =
/(?:#{BLANK_LINE}?(?:#{INDENT}[ \t]*\S.*\n)+(?:(?!#{IAL_BLOCK_START}|#{EOB_MARKER}|^#{OPT_SPACE}#{LAZY_END_HTML_STOP}|^#{OPT_SPACE}#{LAZY_END_HTML_START})^[ \t]*\S.*\n)*)*/- FENCED_CODEBLOCK_START =
/^~{3,}/- FENCED_CODEBLOCK_MATCH =
/^((~){3,})\s*?(\w[\w-]*)?\s*?\n(.*?)^\1\2*\s*?\n/m- LAZY_END_HTML_SPAN_ELEMENTS =
HTML_SPAN_ELEMENTS + %w{script}
- LAZY_END_HTML_START =
/<(?>(?!(?:#{LAZY_END_HTML_SPAN_ELEMENTS.join('|')})\b)#{REXML::Parsers::BaseParser::UNAME_STR})/- LAZY_END_HTML_STOP =
/<\/(?!(?:#{LAZY_END_HTML_SPAN_ELEMENTS.join('|')})\b)#{REXML::Parsers::BaseParser::UNAME_STR}\s*>/m- LAZY_END =
/#{BLANK_LINE}|#{IAL_BLOCK_START}|#{EOB_MARKER}|^#{OPT_SPACE}#{LAZY_END_HTML_STOP}|^#{OPT_SPACE}#{LAZY_END_HTML_START}|\Z/- PARAGRAPH_START =
/^#{OPT_SPACE}[^ \t].*?\n/- PARAGRAPH_MATCH =
/^.*?\n/- PARAGRAPH_END =
/#{LAZY_END}|#{DEFINITION_LIST_START}/- BLANK_LINE =
/(?>^\s*\n)+/- BLOCKQUOTE_START =
/^#{OPT_SPACE}> ?/- IAL_CLASS_ATTR =
'class'- ALD_ID_CHARS =
/[\w-]/- ALD_ANY_CHARS =
/\\\}|[^\}]/- ALD_ID_NAME =
/\w#{ALD_ID_CHARS}*/- ALD_TYPE_KEY_VALUE_PAIR =
/(#{ALD_ID_NAME})=("|')((?:\\\}|\\\2|[^\}\2])*?)\2/- ALD_TYPE_CLASS_NAME =
/\.(-?#{ALD_ID_NAME})/- ALD_TYPE_ID_NAME =
/#([A-Za-z][\w:-]*)/- ALD_TYPE_ID_OR_CLASS =
/#{ALD_TYPE_ID_NAME}|#{ALD_TYPE_CLASS_NAME}/- ALD_TYPE_ID_OR_CLASS_MULTI =
/((?:#{ALD_TYPE_ID_NAME}|#{ALD_TYPE_CLASS_NAME})+)/- ALD_TYPE_REF =
/(#{ALD_ID_NAME})/- ALD_TYPE_ANY =
/(?:\A|\s)(?:#{ALD_TYPE_KEY_VALUE_PAIR}|#{ALD_TYPE_REF}|#{ALD_TYPE_ID_OR_CLASS_MULTI})(?=\s|\Z)/- ALD_START =
/^#{OPT_SPACE}\{:(#{ALD_ID_NAME}):(#{ALD_ANY_CHARS}+)\}\s*?\n/- EXT_STOP_STR =
"\\{:/(%s)?\\}"- EXT_START_STR =
"\\{::(\\w+)(?:\\s(#{ALD_ANY_CHARS}*?)|)(\\/)?\\}"- EXT_BLOCK_START =
/^#{OPT_SPACE}(?:#{EXT_START_STR}|#{EXT_STOP_STR % ALD_ID_NAME})\s*?\n/- EXT_BLOCK_STOP_STR =
"^#{OPT_SPACE}#{EXT_STOP_STR}\s*?\n"- IAL_BLOCK =
/\{:(?!:|\/)(#{ALD_ANY_CHARS}+)\}\s*?\n/- IAL_BLOCK_START =
/^#{OPT_SPACE}#{IAL_BLOCK}/- BLOCK_EXTENSIONS_START =
/^#{OPT_SPACE}\{:/- EXT_SPAN_START =
/#{EXT_START_STR}|#{EXT_STOP_STR % ALD_ID_NAME}/- IAL_SPAN_START =
/\{:(#{ALD_ANY_CHARS}+)\}/- SPAN_EXTENSIONS_START =
/\{:/- LINE_BREAK =
/( |\\\\)(?=\n)/- ABBREV_DEFINITION_START =
/^#{OPT_SPACE}\*\[(.+?)\]:(.*?)\n/- SQ_PUNCT =
'[!"#\$\%\'()*+,\-.\/:;<=>?\@\[\\\\\]\^_`{|}~]'- SQ_CLOSE =
%![^\ \\\\\t\r\n\\[{(-]!- SQ_RULES =
[ [/("|')(?=[_*]{1,2}\S)/, [:lquote1]], [/("|')(?=#{SQ_PUNCT}(?!\.\.)\B)/, [:rquote1]], # Special case for double sets of quotes, e.g.: # <p>He said, "'Quoted' words in a larger quote."</p> [/(\s?)"'(?=\w)/, [1, :ldquo, :lsquo]], [/(\s?)'"(?=\w)/, [1, :lsquo, :ldquo]], # Special case for decade abbreviations (the '80s): [/(\s?)'(?=\d\ds)/, [1, :rsquo]], # Get most opening single/double quotes: [/(\s)('|")(?=\w)/, [1, :lquote2]], # Single/double closing quotes: [/(#{SQ_CLOSE})('|")/, [1, :rquote2]], # Special case for e.g. "<i>Custer</i>'s Last Stand." [/("|')(\s|s\b|$)/, [:rquote1, 2]], # Any remaining single quotes should be opening ones: [/(.?)'/m, [1, :lsquo]], [/(.?)"/m, [1, :ldquo]], ]
- SQ_SUBSTS =
‘“
{ [:rquote1, '"'] => :rdquo, [:rquote1, "'"] => :rsquo, [:rquote2, '"'] => :rdquo, [:rquote2, "'"] => :rsquo, [:lquote1, '"'] => :ldquo, [:lquote1, "'"] => :lsquo, [:lquote2, '"'] => :ldquo, [:lquote2, "'"] => :lsquo, }
- SMART_QUOTES_RE =
/[^\\]?["']/- ESCAPED_CHARS =
/\\([\\.*_+`<>()\[\]{}#!:|"'\$=-])/- BLOCK_BOUNDARY =
/#{BLANK_LINE}|#{EOB_MARKER}|#{IAL_BLOCK_START}|\Z/- HR_START =
/^#{OPT_SPACE}(\*|-|_)[ \t]*\1[ \t]*\1(\1|[ \t])*\n/- TYPOGRAPHIC_SYMS =
[['---', :mdash], ['--', :ndash], ['...', :hellip], ['\\<<', '<<'], ['\\>>', '>>'], ['<< ', :laquo_space], [' >>', :raquo_space], ['<<', :laquo], ['>>', :raquo]]
- TYPOGRAPHIC_SYMS_SUBST =
- TYPOGRAPHIC_SYMS_RE =
/#{TYPOGRAPHIC_SYMS.map {|k,v| Regexp.escape(k)}.join('|')}/
Constants included from Html::Parser
Constants included from Html::Constants
Html::Constants::HTML_ATTRIBUTE_RE, Html::Constants::HTML_BLOCK_ELEMENTS, Html::Constants::HTML_COMMENT_RE, Html::Constants::HTML_CONTENT_MODEL, Html::Constants::HTML_CONTENT_MODEL_BLOCK, Html::Constants::HTML_CONTENT_MODEL_RAW, Html::Constants::HTML_CONTENT_MODEL_SPAN, Html::Constants::HTML_DOCTYPE_RE, Html::Constants::HTML_ELEMENTS_WITHOUT_BODY, Html::Constants::HTML_ENTITY_RE, Html::Constants::HTML_INSTRUCTION_RE, Html::Constants::HTML_SPAN_ELEMENTS, Html::Constants::HTML_TAG_CLOSE_RE, Html::Constants::HTML_TAG_RE
Constants included from Kramdown
Instance Attribute Summary
Attributes inherited from Base
#options, #root, #source, #warnings
Instance Method Summary collapse
-
#add_link(el, href, title, alt_text = nil, ial = nil) ⇒ Object
This helper methods adds the approriate attributes to the element
elof typeaorimgand the element itself to the @tree. -
#after_block_boundary? ⇒ Boolean
Return
trueif we are after a block boundary. -
#before_block_boundary? ⇒ Boolean
Return
trueif we are before a block boundary. -
#correct_abbreviations_attributes ⇒ Object
Correct abbreviation attributes.
- #handle_extension(name, opts, body, type, line_no = nil) ⇒ Object
- #handle_kramdown_html_tag(el, closed, handle_body) ⇒ Object
-
#initialize(source, options) ⇒ Kramdown
constructor
Create a new Kramdown parser object with the given
options. -
#normalize_link_id(id) ⇒ Object
Normalize the link identifier.
-
#parse ⇒ Object
The source string provided on initialization is parsed into the @root element.
-
#parse_abbrev_definition ⇒ Object
Parse the link definition at the current location.
-
#parse_attribute_list(str, opts) ⇒ Object
Parse the string
strand extract all attributes and add all found attributes to the hashopts. -
#parse_atx_header ⇒ Object
Parse the Atx header at the current location.
-
#parse_autolink ⇒ Object
Parse the autolink at the current location.
-
#parse_blank_line ⇒ Object
Parse the blank line at the current postition.
-
#parse_block_extensions ⇒ Object
Parse one of the block extensions (ALD, block IAL or generic extension) at the current location.
-
#parse_block_html ⇒ Object
Parse the HTML at the current position as block-level HTML.
-
#parse_block_math ⇒ Object
Parse the math block at the current location.
-
#parse_blockquote ⇒ Object
Parse the blockquote at the current location.
-
#parse_codeblock ⇒ Object
Parse the indented codeblock at the current location.
-
#parse_codeblock_fenced ⇒ Object
Parse the fenced codeblock at the current location.
-
#parse_codespan ⇒ Object
Parse the codespan at the current scanner location.
-
#parse_definition_list ⇒ Object
Parse the ordered or unordered list at the current location.
-
#parse_emphasis ⇒ Object
Parse the emphasis at the current location.
-
#parse_eob_marker ⇒ Object
Parse the EOB marker at the current location.
-
#parse_escaped_chars ⇒ Object
Parse the backslash-escaped character at the current location.
-
#parse_extension_start_tag(type) ⇒ Object
Parse the generic extension at the current point.
-
#parse_first_list_line(indentation, content) ⇒ Object
Used for parsing the first line of a list item or a definition, i.e.
-
#parse_footnote_definition ⇒ Object
Parse the foot note definition at the current location.
-
#parse_footnote_marker ⇒ Object
Parse the footnote marker at the current location.
-
#parse_horizontal_rule ⇒ Object
Parse the horizontal rule at the current location.
-
#parse_html_entity ⇒ Object
Parse the HTML entity at the current location.
-
#parse_inline_math ⇒ Object
Parse the inline math at the current location.
-
#parse_line_break ⇒ Object
Parse the line break at the current location.
-
#parse_link ⇒ Object
Parse the link at the current scanner position.
-
#parse_link_definition ⇒ Object
Parse the link definition at the current location.
-
#parse_list ⇒ Object
Parse the ordered or unordered list at the current location.
-
#parse_paragraph ⇒ Object
Parse the paragraph at the current location.
-
#parse_setext_header ⇒ Object
Parse the Setext header at the current location.
-
#parse_smart_quotes ⇒ Object
Parse the smart quotes at current location.
-
#parse_span_extensions ⇒ Object
Parse the extension span at the current location.
-
#parse_span_html ⇒ Object
Parse the HTML at the current position as span-level HTML.
-
#parse_table ⇒ Object
Parse the table at the current location.
-
#parse_typographic_syms ⇒ Object
Parse the typographic symbols at the current location.
-
#replace_abbreviations(el, regexps = nil) ⇒ Object
Replace the abbreviation text with elements.
-
#update_ial_with_ial(ial, opts) ⇒ Object
Update the
ialwith the information from the inline attribute listopts.
Methods included from Html::Parser
#handle_html_start_tag, #handle_raw_html_tag, #parse_html_attributes, #parse_raw_html
Methods included from Kramdown
Methods inherited from Base
#adapt_source, #add_text, #extract_string, parse, #warning
Constructor Details
#initialize(source, options) ⇒ Kramdown
Create a new Kramdown parser object with the given options.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/kramdown/parser/kramdown.rb', line 65 def initialize(source, ) super reset_env @alds = {} @footnotes = {} @link_defs = {} update_link_definitions(@options[:link_defs]) @block_parsers = [:blank_line, :codeblock, :codeblock_fenced, :blockquote, :atx_header, :horizontal_rule, :list, :definition_list, :block_html, :setext_header, :block_math, :table, :footnote_definition, :link_definition, :abbrev_definition, :block_extensions, :eob_marker, :paragraph] @span_parsers = [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link, :smart_quotes, :inline_math, :span_extensions, :html_entity, :typographic_syms, :line_break, :escaped_chars] end |
Instance Method Details
#add_link(el, href, title, alt_text = nil, ial = nil) ⇒ Object
This helper methods adds the approriate attributes to the element el of type a or img and the element itself to the @tree.
38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/kramdown/parser/kramdown/link.rb', line 38 def add_link(el, href, title, alt_text = nil, ial = nil) el.[:ial] = ial update_attr_with_ial(el.attr, ial) if ial if el.type == :a el.attr['href'] = href else el.attr['src'] = href el.attr['alt'] = alt_text el.children.clear end el.attr['title'] = title if title @tree.children << el end |
#after_block_boundary? ⇒ Boolean
Return true if we are after a block boundary.
21 22 23 24 |
# File 'lib/kramdown/parser/kramdown/block_boundary.rb', line 21 def after_block_boundary? !@tree.children.last || @tree.children.last.type == :blank || (@tree.children.last.type == :eob && @tree.children.last.value.nil?) || @block_ial end |
#before_block_boundary? ⇒ Boolean
Return true if we are before a block boundary.
27 28 29 |
# File 'lib/kramdown/parser/kramdown/block_boundary.rb', line 27 def before_block_boundary? @src.check(self.class::BLOCK_BOUNDARY) end |
#correct_abbreviations_attributes ⇒ Object
Correct abbreviation attributes.
31 32 33 34 35 |
# File 'lib/kramdown/parser/kramdown/abbreviation.rb', line 31 def correct_abbreviations_attributes @root.[:abbrev_attr].keys.each do |k| @root.[:abbrev_attr][k] = @root.[:abbrev_attr][k].attr end end |
#handle_extension(name, opts, body, type, line_no = nil) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/kramdown/parser/kramdown/extensions.rb', line 95 def handle_extension(name, opts, body, type, line_no = nil) case name when 'comment' @tree.children << Element.new(:comment, body, nil, :category => type, :location => line_no) if body.kind_of?(String) true when 'nomarkdown' @tree.children << Element.new(:raw, body, nil, :category => type, :location => line_no, :type => opts['type'].to_s.split(/\s+/)) if body.kind_of?(String) true when 'options' opts.select do |k,v| k = k.to_sym if Kramdown::Options.defined?(k) begin val = Kramdown::Options.parse(k, v) @options[k] = val (@root.[:options] ||= {})[k] = val rescue end false else true end end.each do |k,v| warning("Unknown kramdown option '#{k}'") end @tree.children << new_block_el(:eob, :extension) if type == :block true else false end end |
#handle_kramdown_html_tag(el, closed, handle_body) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/kramdown/parser/kramdown/html.rb', line 25 def handle_kramdown_html_tag(el, closed, handle_body) if @block_ial el.[:ial] = @block_ial @block_ial = nil end content_model = if @tree.type != :html_element || @tree.[:content_model] != :raw (@options[:parse_block_html] ? HTML_CONTENT_MODEL[el.value] : :raw) else :raw end if val = HTML_MARKDOWN_ATTR_MAP[el.attr.delete('markdown')] content_model = (val == :default ? HTML_CONTENT_MODEL[el.value] : val) end @src.scan(TRAILING_WHITESPACE) if content_model == :block el.[:content_model] = content_model el.[:is_closed] = closed if !closed && handle_body if content_model == :block if !parse_blocks(el) warning("Found no end tag for '#{el.value}' (line #{el.[:location]}) - auto-closing it") end elsif content_model == :span curpos = @src.pos if @src.scan_until(/(?=<\/#{el.value}\s*>)/mi) add_text(extract_string(curpos...@src.pos, @src), el) @src.scan(HTML_TAG_CLOSE_RE) else add_text(@src.rest, el) @src.terminate warning("Found no end tag for '#{el.value}' (line #{el.[:location]}) - auto-closing it") end else parse_raw_html(el, &method(:handle_kramdown_html_tag)) end @src.scan(TRAILING_WHITESPACE) unless (@tree.type == :html_element && @tree.[:content_model] == :raw) end end |
#normalize_link_id(id) ⇒ Object
Normalize the link identifier.
17 18 19 |
# File 'lib/kramdown/parser/kramdown/link.rb', line 17 def normalize_link_id(id) id.gsub(/[\s]+/, ' ').downcase end |
#parse ⇒ Object
The source string provided on initialization is parsed into the @root element.
87 88 89 90 91 92 93 94 |
# File 'lib/kramdown/parser/kramdown.rb', line 87 def parse configure_parser parse_blocks(@root, adapt_source(source)) update_tree(@root) correct_abbreviations_attributes replace_abbreviations(@root) @footnotes.each {|name,data| update_tree(data[:content])} end |
#parse_abbrev_definition ⇒ Object
Parse the link definition at the current location.
17 18 19 20 21 22 23 24 25 26 27 |
# File 'lib/kramdown/parser/kramdown/abbreviation.rb', line 17 def parse_abbrev_definition start_line_number = @src.current_line_number @src.pos += @src.matched_size abbrev_id, abbrev_text = @src[1], @src[2] abbrev_text.strip! warning("Duplicate abbreviation ID '#{abbrev_id}' on line #{start_line_number} - overwriting") if @root.[:abbrev_defs][abbrev_id] @tree.children << new_block_el(:eob, :abbrev_def) @root.[:abbrev_defs][abbrev_id] = abbrev_text @root.[:abbrev_attr][abbrev_id] = @tree.children.last true end |
#parse_attribute_list(str, opts) ⇒ Object
Parse the string str and extract all attributes and add all found attributes to the hash opts.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/kramdown/parser/kramdown/extensions.rb', line 18 def parse_attribute_list(str, opts) return if str.strip.empty? || str.strip == ':' attrs = str.scan(ALD_TYPE_ANY) attrs.each do |key, sep, val, ref, id_and_or_class, _, _| if ref (opts[:refs] ||= []) << ref elsif id_and_or_class id_and_or_class.scan(ALD_TYPE_ID_OR_CLASS).each do |id_attr, class_attr| if class_attr opts[IAL_CLASS_ATTR] = (opts[IAL_CLASS_ATTR] || '') << " #{class_attr}" opts[IAL_CLASS_ATTR].lstrip! else opts['id'] = id_attr end end else val.gsub!(/\\(\}|#{sep})/, "\\1") opts[key] = val end end warning("No or invalid attributes found in IAL/ALD content: #{str}") if attrs.length == 0 end |
#parse_atx_header ⇒ Object
Parse the Atx header at the current location.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/kramdown/parser/kramdown/header.rb', line 40 def parse_atx_header return false if !after_block_boundary? start_line_number = @src.current_line_number @src.check(ATX_HEADER_MATCH) level, text, id = @src[1], @src[2].to_s.strip, @src[3] return false if text.empty? @src.pos += @src.matched_size el = new_block_el(:header, nil, nil, :level => level.length, :raw_text => text, :location => start_line_number) add_text(text, el) el.attr['id'] = id if id @tree.children << el true end |
#parse_autolink ⇒ Object
Parse the autolink at the current location.
25 26 27 28 29 30 31 32 |
# File 'lib/kramdown/parser/kramdown/autolink.rb', line 25 def parse_autolink start_line_number = @src.current_line_number @src.pos += @src.matched_size href = (@src[2].nil? ? "mailto:#{@src[1]}" : @src[1]) el = Element.new(:a, nil, {'href' => href}, :location => start_line_number) add_text(@src[1].sub(/^mailto:/, ''), el) @tree.children << el end |
#parse_blank_line ⇒ Object
Parse the blank line at the current postition.
17 18 19 20 21 22 23 24 25 |
# File 'lib/kramdown/parser/kramdown/blank_line.rb', line 17 def parse_blank_line @src.pos += @src.matched_size if @tree.children.last && @tree.children.last.type == :blank @tree.children.last.value << @src.matched else @tree.children << new_block_el(:blank, @src.matched) end true end |
#parse_block_extensions ⇒ Object
Parse one of the block extensions (ALD, block IAL or generic extension) at the current location.
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/kramdown/parser/kramdown/extensions.rb', line 152 def parse_block_extensions if @src.scan(ALD_START) parse_attribute_list(@src[2], @alds[@src[1]] ||= Utils::OrderedHash.new) @tree.children << new_block_el(:eob, :ald) true elsif @src.check(EXT_BLOCK_START) parse_extension_start_tag(:block) elsif @src.scan(IAL_BLOCK_START) if @tree.children.last && @tree.children.last.type != :blank && (@tree.children.last.type != :eob || [:link_def, :abbrev_def, :footnote_def].include?(@tree.children.last.value)) parse_attribute_list(@src[1], @tree.children.last.[:ial] ||= Utils::OrderedHash.new) @tree.children << new_block_el(:eob, :ial) unless @src.check(IAL_BLOCK_START) else parse_attribute_list(@src[1], @block_ial ||= Utils::OrderedHash.new) end true else false end end |
#parse_block_html ⇒ Object
Parse the HTML at the current position as block-level HTML.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/kramdown/parser/kramdown/html.rb', line 70 def parse_block_html line = @src.current_line_number if result = @src.scan(HTML_COMMENT_RE) @tree.children << Element.new(:xml_comment, result, nil, :category => :block, :location => line) @src.scan(TRAILING_WHITESPACE) true elsif result = @src.scan(HTML_INSTRUCTION_RE) @tree.children << Element.new(:xml_pi, result, nil, :category => :block, :location => line) @src.scan(TRAILING_WHITESPACE) true else if result = @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase) @src.pos += @src.matched_size handle_html_start_tag(line, &method(:handle_kramdown_html_tag)) Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native] true elsif result = @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase) name = @src[1].downcase if @tree.type == :html_element && @tree.value == name @src.pos += @src.matched_size throw :stop_block_parsing, :found else false end else false end end end |
#parse_block_math ⇒ Object
Parse the math block at the current location.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/kramdown/parser/kramdown/math.rb', line 19 def parse_block_math start_line_number = @src.current_line_number if !after_block_boundary? return false elsif @src[1] @src.scan(/^#{OPT_SPACE}\\/) if @src[3] return false end saved_pos = @src.save_pos @src.pos += @src.matched_size data = @src[2].strip if before_block_boundary? @tree.children << new_block_el(:math, data, nil, :category => :block, :location => start_line_number) true else @src.revert_pos(saved_pos) false end end |
#parse_blockquote ⇒ Object
Parse the blockquote at the current location.
21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/kramdown/parser/kramdown/blockquote.rb', line 21 def parse_blockquote start_line_number = @src.current_line_number result = @src.scan(PARAGRAPH_MATCH) while !@src.match?(self.class::LAZY_END) result << @src.scan(PARAGRAPH_MATCH) end result.gsub!(BLOCKQUOTE_START, '') el = new_block_el(:blockquote, nil, nil, :location => start_line_number) @tree.children << el parse_blocks(el, result) true end |
#parse_codeblock ⇒ Object
Parse the indented codeblock at the current location.
23 24 25 26 27 28 29 30 |
# File 'lib/kramdown/parser/kramdown/codeblock.rb', line 23 def parse_codeblock start_line_number = @src.current_line_number data = @src.scan(self.class::CODEBLOCK_MATCH) data.gsub!(/\n( {0,3}\S)/, ' \\1') data.gsub!(INDENT, '') @tree.children << new_block_el(:codeblock, data, nil, :location => start_line_number) true end |
#parse_codeblock_fenced ⇒ Object
Parse the fenced codeblock at the current location.
38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/kramdown/parser/kramdown/codeblock.rb', line 38 def parse_codeblock_fenced if @src.check(self.class::FENCED_CODEBLOCK_MATCH) start_line_number = @src.current_line_number @src.pos += @src.matched_size el = new_block_el(:codeblock, @src[4], nil, :location => start_line_number) lang = @src[3].to_s.strip el.attr['class'] = "language-#{lang}" unless lang.empty? @tree.children << el true else false end end |
#parse_codespan ⇒ Object
Parse the codespan at the current scanner location.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/kramdown/parser/kramdown/codespan.rb', line 17 def parse_codespan start_line_number = @src.current_line_number result = @src.scan(CODESPAN_DELIMITER) simple = (result.length == 1) saved_pos = @src.save_pos if simple && @src.pre_match =~ /\s\Z/ && @src.match?(/\s/) add_text(result) return end if text = @src.scan_until(/#{result}/) text.sub!(/#{result}\Z/, '') if !simple text = text[1..-1] if text[0..0] == ' ' text = text[0..-2] if text[-1..-1] == ' ' end @tree.children << Element.new(:codespan, text, nil, :location => start_line_number) else @src.revert_pos(saved_pos) add_text(result) end end |
#parse_definition_list ⇒ Object
Parse the ordered or unordered list at the current location.
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/kramdown/parser/kramdown/list.rb', line 152 def parse_definition_list children = @tree.children if !children.last || (children.length == 1 && children.last.type != :p ) || (children.length >= 2 && children[-1].type != :p && (children[-1].type != :blank || children[-1].value != "\n" || children[-2].type != :p)) return false end first_as_para = false deflist = new_block_el(:dl) para = @tree.children.pop if para.type == :blank para = @tree.children.pop first_as_para = true end deflist.[:location] = para.[:location] # take location from preceding para which is the first definition term para.children.first.value.split(/\n/).each do |term| el = Element.new(:dt, nil, nil, :location => @src.current_line_number) el.children << Element.new(:raw_text, term) deflist.children << el end deflist.[:ial] = para.[:ial] item = nil content_re, lazy_re, indent_re = nil def_start_re = DEFINITION_LIST_START last_is_blank = false while !@src.eos? start_line_number = @src.current_line_number if @src.scan(def_start_re) item = Element.new(:dd, nil, nil, :location => start_line_number) item.[:first_as_para] = first_as_para item.value, indentation, content_re, lazy_re, indent_re = parse_first_list_line(@src[1].length, @src[2]) deflist.children << item item.value.sub!(self.class::LIST_ITEM_IAL) do |match| parse_attribute_list($1, item.[:ial] ||= {}) '' end def_start_re = /^( {0,#{[3, indentation - 1].min}}:)([\t| ].*?\n)/ first_as_para = false last_is_blank = false elsif @src.check(EOB_MARKER) break elsif (result = @src.scan(content_re)) || (!last_is_blank && (result = @src.scan(lazy_re))) result.sub!(/^(\t+)/) { " "*($1 ? 4*$1.length : 0) } result.sub!(indent_re, '') item.value << result first_as_para = false last_is_blank = false elsif result = @src.scan(BLANK_LINE) first_as_para = true item.value << result last_is_blank = true else break end end last = nil deflist.children.each do |it| next if it.type == :dt parse_blocks(it, it.value) it.value = nil next if it.children.size == 0 if it.children.last.type == :blank last = it.children.pop else last = nil end if it.children.first && it.children.first.type == :p && !it..delete(:first_as_para) it.children.first.children.first.value << "\n" if it.children.size > 1 it.children.first.[:transparent] = true end end if @tree.children.length >= 1 && @tree.children.last.type == :dl @tree.children[-1].children.concat(deflist.children) elsif @tree.children.length >= 2 && @tree.children[-1].type == :blank && @tree.children[-2].type == :dl @tree.children.pop @tree.children[-1].children.concat(deflist.children) else @tree.children << deflist end @tree.children << last if !last.nil? true end |
#parse_emphasis ⇒ Object
Parse the emphasis at the current location.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/kramdown/parser/kramdown/emphasis.rb', line 17 def parse_emphasis start_line_number = @src.current_line_number saved_pos = @src.save_pos result = @src.scan(EMPHASIS_START) element = (result.length == 2 ? :strong : :em) type = result[0..0] if (type == '_' && @src.pre_match =~ /[[:alnum:]]\z/) || @src.check(/\s/) || @tree.type == element || @stack.any? {|el, _| el.type == element} add_text(result) return end sub_parse = lambda do |delim, elem| el = Element.new(elem, nil, nil, :location => start_line_number) stop_re = /#{Regexp.escape(delim)}/ found = parse_spans(el, stop_re) do (@src.pre_match[-1, 1] !~ /\s/) && (elem != :em || !@src.match?(/#{Regexp.escape(delim*2)}(?!#{Regexp.escape(delim)})/)) && (type != '_' || !@src.match?(/#{Regexp.escape(delim)}[[:alnum:]]/)) && el.children.size > 0 end [found, el, stop_re] end found, el, stop_re = sub_parse.call(result, element) if !found && element == :strong && @tree.type != :em @src.revert_pos(saved_pos) @src.pos += 1 found, el, stop_re = sub_parse.call(type, :em) end if found @src.scan(stop_re) @tree.children << el else @src.revert_pos(saved_pos) @src.pos += result.length add_text(result) end end |
#parse_eob_marker ⇒ Object
Parse the EOB marker at the current location.
17 18 19 20 21 |
# File 'lib/kramdown/parser/kramdown/eob.rb', line 17 def parse_eob_marker @src.pos += @src.matched_size @tree.children << new_block_el(:eob) true end |
#parse_escaped_chars ⇒ Object
Parse the backslash-escaped character at the current location.
17 18 19 20 |
# File 'lib/kramdown/parser/kramdown/escaped_chars.rb', line 17 def parse_escaped_chars @src.pos += @src.matched_size add_text(@src[1]) end |
#parse_extension_start_tag(type) ⇒ Object
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.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/kramdown/parser/kramdown/extensions.rb', line 56 def parse_extension_start_tag(type) saved_pos = @src.save_pos start_line_number = @src.current_line_number @src.pos += @src.matched_size error_block = lambda do |msg| warning(msg) @src.revert_pos(saved_pos) add_text(@src.getch) if type == :span false end if @src[4] || @src.matched == '{:/}' name = (@src[4] ? "for '#{@src[4]}' " : '') return error_block.call("Invalid extension stop tag #{name} found on line #{start_line_number} - ignoring it") end ext = @src[1] opts = {} body = nil parse_attribute_list(@src[2] || '', opts) if !@src[3] stop_re = (type == :block ? /#{EXT_BLOCK_STOP_STR % ext}/ : /#{EXT_STOP_STR % ext}/) if result = @src.scan_until(stop_re) body = result.sub!(stop_re, '') body.chomp! if type == :block else return error_block.call("No stop tag for extension '#{ext}' found on line #{start_line_number} - ignoring it") end end if !handle_extension(ext, opts, body, type, start_line_number) error_block.call("Invalid extension with name '#{ext}' specified on line #{start_line_number} - ignoring it") else true end end |
#parse_first_list_line(indentation, content) ⇒ Object
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.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/kramdown/parser/kramdown/list.rb', line 32 def parse_first_list_line(indentation, content) if content =~ self.class::LIST_ITEM_IAL_CHECK indentation = 4 else while content =~ /^ *\t/ temp = content.scan(/^ */).first.length + indentation content.sub!(/^( *)(\t+)/) {$1 << " "*(4 - (temp % 4) + ($2.length - 1)*4)} end indentation += content[/^ */].length end content.sub!(/^\s*/, '') [content, indentation, *PARSE_FIRST_LIST_LINE_REGEXP_CACHE[indentation]] end |
#parse_footnote_definition ⇒ Object
Parse the foot note definition at the current location.
21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/kramdown/parser/kramdown/footnote.rb', line 21 def parse_footnote_definition start_line_number = @src.current_line_number @src.pos += @src.matched_size el = Element.new(:footnote_def, nil, nil, :location => start_line_number) parse_blocks(el, @src[2].gsub(INDENT, '')) warning("Duplicate footnote name '#{@src[1]}' on line #{start_line_number} - overwriting") if @footnotes[@src[1]] @tree.children << new_block_el(:eob, :footnote_def) (@footnotes[@src[1]] = {})[:content] = el @footnotes[@src[1]][:eob] = @tree.children.last true end |
#parse_footnote_marker ⇒ Object
Parse the footnote marker at the current location.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/kramdown/parser/kramdown/footnote.rb', line 39 def parse_footnote_marker start_line_number = @src.current_line_number @src.pos += @src.matched_size fn_def = @footnotes[@src[1]] if fn_def if fn_def[:eob] update_attr_with_ial(fn_def[:eob].attr, fn_def[:eob].[:ial] || {}) fn_def[:attr] = fn_def[:eob].attr fn_def[:options] = fn_def[:eob]. fn_def.delete(:eob) end fn_def[:marker] ||= [] fn_def[:marker].push(Element.new(:footnote, fn_def[:content], fn_def[:attr], fn_def[:options].merge(:name => @src[1], :location => start_line_number))) @tree.children << fn_def[:marker].last else warning("Footnote definition for '#{@src[1]}' not found on line #{start_line_number}") add_text(@src.matched) end end |
#parse_horizontal_rule ⇒ Object
Parse the horizontal rule at the current location.
17 18 19 20 21 22 |
# File 'lib/kramdown/parser/kramdown/horizontal_rule.rb', line 17 def parse_horizontal_rule start_line_number = @src.current_line_number @src.pos += @src.matched_size @tree.children << new_block_el(:hr, nil, nil, :location => start_line_number) true end |
#parse_html_entity ⇒ Object
Parse the HTML entity at the current location.
17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/kramdown/parser/kramdown/html_entity.rb', line 17 def parse_html_entity start_line_number = @src.current_line_number @src.pos += @src.matched_size begin @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity(@src[1] || (@src[2] && @src[2].to_i) || @src[3].hex), nil, :original => @src.matched, :location => start_line_number) rescue ::Kramdown::Error @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('amp'), nil, :location => start_line_number) add_text(@src.matched[1..-1]) end end |
#parse_inline_math ⇒ Object
Parse the inline math at the current location.
45 46 47 48 49 |
# File 'lib/kramdown/parser/kramdown/math.rb', line 45 def parse_inline_math start_line_number = @src.current_line_number @src.pos += @src.matched_size @tree.children << Element.new(:math, @src[1].strip, nil, :category => :span, :location => start_line_number) end |
#parse_line_break ⇒ Object
Parse the line break at the current location.
17 18 19 20 |
# File 'lib/kramdown/parser/kramdown/line_break.rb', line 17 def parse_line_break @tree.children << Element.new(:br, nil, nil, :location => @src.current_line_number) @src.pos += @src.matched_size end |
#parse_link ⇒ Object
Parse the link at the current scanner position. This method is used to parse normal links as well as image links.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/kramdown/parser/kramdown/link.rb', line 60 def parse_link start_line_number = @src.current_line_number result = @src.scan(LINK_START) cur_pos = @src.pos saved_pos = @src.save_pos link_type = (result =~ /^!/ ? :img : :a) # no nested links allowed if link_type == :a && (@tree.type == :img || @tree.type == :a || @stack.any? {|t,s| t && (t.type == :img || t.type == :a)}) add_text(result) return end el = Element.new(link_type, nil, nil, :location => start_line_number) count = 1 found = parse_spans(el, LINK_BRACKET_STOP_RE) do count = count + (@src[1] ? -1 : 1) count - el.children.select {|c| c.type == :img}.size == 0 end if !found || (link_type == :a && el.children.empty?) @src.revert_pos(saved_pos) add_text(result) return end alt_text = extract_string(cur_pos...@src.pos, @src).gsub(ESCAPED_CHARS, '\1') @src.scan(LINK_BRACKET_STOP_RE) # reference style link or no link url if @src.scan(LINK_INLINE_ID_RE) || !@src.check(/\(/) link_id = normalize_link_id(@src[1] || alt_text) if @link_defs.has_key?(link_id) add_link(el, @link_defs[link_id][0], @link_defs[link_id][1], alt_text, @link_defs[link_id][2] && @link_defs[link_id][2].[:ial]) else warning("No link definition for link ID '#{link_id}' found on line #{start_line_number}") @src.revert_pos(saved_pos) add_text(result) end return end # link url in parentheses if @src.scan(/\(<(.*?)>/) link_url = @src[1] if @src.scan(/\)/) add_link(el, link_url, nil, alt_text) return end else link_url = '' nr_of_brackets = 0 while temp = @src.scan_until(LINK_PAREN_STOP_RE) link_url << temp if @src[2] nr_of_brackets -= 1 break if nr_of_brackets == 0 elsif @src[1] nr_of_brackets += 1 else break end end link_url = link_url[1..-2] link_url.strip! if nr_of_brackets == 0 add_link(el, link_url, nil, alt_text) return end end if @src.scan(LINK_INLINE_TITLE_RE) add_link(el, link_url, @src[2], alt_text) else @src.revert_pos(saved_pos) add_text(result) end end |
#parse_link_definition ⇒ Object
Parse the link definition at the current location.
24 25 26 27 28 29 30 31 32 |
# File 'lib/kramdown/parser/kramdown/link.rb', line 24 def parse_link_definition return false if @src[3].to_s =~ /[ \t]+["']/ @src.pos += @src.matched_size link_id, link_url, link_title = normalize_link_id(@src[1]), @src[2] || @src[3], @src[5] warning("Duplicate link ID '#{link_id}' on line #{@src.current_line_number} - overwriting") if @link_defs[link_id] @tree.children << new_block_el(:eob, :link_def) @link_defs[link_id] = [link_url, link_title, @tree.children.last] true end |
#parse_list ⇒ Object
Parse the ordered or unordered list at the current location.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/kramdown/parser/kramdown/list.rb', line 53 def parse_list start_line_number = @src.current_line_number type, list_start_re = (@src.check(LIST_START_UL) ? [:ul, LIST_START_UL] : [:ol, LIST_START_OL]) list = new_block_el(type, nil, nil, :location => start_line_number) item = nil content_re, lazy_re, indent_re = nil eob_found = false nested_list_found = false last_is_blank = false while !@src.eos? start_line_number = @src.current_line_number if last_is_blank && @src.check(HR_START) break elsif @src.scan(EOB_MARKER) eob_found = true break elsif @src.scan(list_start_re) item = Element.new(:li, nil, nil, :location => start_line_number) item.value, indentation, content_re, lazy_re, indent_re = parse_first_list_line(@src[1].length, @src[2]) list.children << item item.value.sub!(self.class::LIST_ITEM_IAL) do |match| parse_attribute_list($1, item.[:ial] ||= {}) '' end list_start_re = (type == :ul ? /^( {0,#{[3, indentation - 1].min}}[+*-])([\t| ].*?\n)/ : /^( {0,#{[3, indentation - 1].min}}\d+\.)([\t| ].*?\n)/) nested_list_found = (item.value =~ LIST_START) last_is_blank = false item.value = [item.value] elsif (result = @src.scan(content_re)) || (!last_is_blank && (result = @src.scan(lazy_re))) result.sub!(/^(\t+)/) { " " * 4 * $1.length } result.sub!(indent_re, '') if !nested_list_found && result =~ LIST_START item.value << '' nested_list_found = true end item.value.last << result last_is_blank = false elsif result = @src.scan(BLANK_LINE) nested_list_found = true last_is_blank = true item.value.last << result else break end end @tree.children << list last = nil list.children.each do |it| temp = Element.new(:temp, nil, nil, :location => it.[:location]) env = save_env location = it.[:location] it.value.each do |val| @src = ::Kramdown::Utils::StringScanner.new(val, location) parse_blocks(temp) location = @src.current_line_number end restore_env(env) it.children = temp.children it.value = nil next if it.children.size == 0 # Handle the case where an EOB marker is inserted by a block IAL for the first paragraph it.children.delete_at(1) if it.children.first.type == :p && it.children.length >= 2 && it.children[1].type == :eob && it.children.first.[:ial] if it.children.first.type == :p && (it.children.length < 2 || it.children[1].type != :blank || (it == list.children.last && it.children.length == 2 && !eob_found)) && (list.children.last != it || list.children.size == 1 || list.children[0..-2].any? {|cit| !cit.children.first || cit.children.first.type != :p || cit.children.first.[:transparent]}) it.children.first.children.first.value << "\n" if it.children.size > 1 && it.children[1].type != :blank it.children.first.[:transparent] = true end if it.children.last.type == :blank last = it.children.pop else last = nil end end @tree.children << last if !last.nil? && !eob_found true end |
#parse_paragraph ⇒ Object
Parse the paragraph at the current location.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/kramdown/parser/kramdown/paragraph.rb', line 31 def parse_paragraph start_line_number = @src.current_line_number result = @src.scan(PARAGRAPH_MATCH) while !@src.match?(self.class::PARAGRAPH_END) result << @src.scan(PARAGRAPH_MATCH) end result.rstrip! if @tree.children.last && @tree.children.last.type == :p @tree.children.last.children.first.value << "\n" << result else @tree.children << new_block_el(:p, nil, nil, :location => start_line_number) result.lstrip! add_text(result, @tree.children.last) end true end |
#parse_setext_header ⇒ Object
Parse the Setext header at the current location.
20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/kramdown/parser/kramdown/header.rb', line 20 def parse_setext_header return false if !after_block_boundary? start_line_number = @src.current_line_number @src.pos += @src.matched_size text, id, level = @src[1], @src[2], @src[3] text.strip! el = new_block_el(:header, nil, nil, :level => (level == '-' ? 2 : 1), :raw_text => text, :location => start_line_number) add_text(text, el) el.attr['id'] = id if id @tree.children << el true end |
#parse_smart_quotes ⇒ Object
Parse the smart quotes at current location.
158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/kramdown/parser/kramdown/smart_quotes.rb', line 158 def parse_smart_quotes start_line_number = @src.current_line_number substs = SQ_RULES.find {|reg, subst| @src.scan(reg)}[1] substs.each do |subst| if subst.kind_of?(Integer) add_text(@src[subst]) else val = SQ_SUBSTS[[subst, @src[subst.to_s[-1,1].to_i]]] || subst @tree.children << Element.new(:smart_quote, val, nil, :location => start_line_number) end end end |
#parse_span_extensions ⇒ Object
Parse the extension span at the current location.
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/kramdown/parser/kramdown/extensions.rb', line 180 def parse_span_extensions if @src.check(EXT_SPAN_START) parse_extension_start_tag(:span) elsif @src.check(IAL_SPAN_START) if @tree.children.last && @tree.children.last.type != :text @src.pos += @src.matched_size attr = Utils::OrderedHash.new parse_attribute_list(@src[1], attr) update_ial_with_ial(@tree.children.last.[:ial] ||= Utils::OrderedHash.new, attr) update_attr_with_ial(@tree.children.last.attr, attr) else warning("Found span IAL after text - ignoring it") add_text(@src.getch) end else add_text(@src.getch) end end |
#parse_span_html ⇒ Object
Parse the HTML at the current position as span-level HTML.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/kramdown/parser/kramdown/html.rb', line 106 def parse_span_html line = @src.current_line_number if result = @src.scan(HTML_COMMENT_RE) @tree.children << Element.new(:xml_comment, result, nil, :category => :span, :location => line) elsif result = @src.scan(HTML_INSTRUCTION_RE) @tree.children << Element.new(:xml_pi, result, nil, :category => :span, :location => line) elsif result = @src.scan(HTML_TAG_CLOSE_RE) warning("Found invalidly used HTML closing tag for '#{@src[1]}' on line #{line}") add_text(result) elsif result = @src.scan(HTML_TAG_RE) tag_name = @src[1].downcase if HTML_BLOCK_ELEMENTS.include?(tag_name) warning("Found block HTML tag '#{tag_name}' in span-level text on line #{line}") add_text(result) return end attrs = parse_html_attributes(@src[2], line) attrs.each {|name, value| value.gsub!(/\n+/, ' ')} do_parsing = (HTML_CONTENT_MODEL[tag_name] == :raw || @tree.[:content_model] == :raw ? false : @options[:parse_span_html]) if val = HTML_MARKDOWN_ATTR_MAP[attrs.delete('markdown')] if val == :block warning("Cannot use block-level parsing in span-level HTML tag (line #{line}) - using default mode") elsif val == :span do_parsing = true elsif val == :default do_parsing = HTML_CONTENT_MODEL[tag_name] != :raw elsif val == :raw do_parsing = false end end el = Element.new(:html_element, tag_name, attrs, :category => :span, :location => line, :content_model => (do_parsing ? :span : :raw), :is_closed => !!@src[4]) @tree.children << el stop_re = /<\/#{Regexp.escape(tag_name)}\s*>/i if !@src[4] && !HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) if parse_spans(el, stop_re, (do_parsing ? nil : [:span_html])) @src.scan(stop_re) else warning("Found no end tag for '#{el.value}' (line #{line}) - auto-closing it") add_text(@src.rest, el) @src.terminate end end Kramdown::Parser::Html::ElementConverter.convert(@root, el) if @options[:html_to_native] else add_text(@src.getch) end end |
#parse_table ⇒ Object
Parse the table at the current location.
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/kramdown/parser/kramdown/table.rb', line 25 def parse_table return false if !after_block_boundary? saved_pos = @src.save_pos orig_pos = @src.pos table = new_block_el(:table, nil, nil, :alignment => [], :location => @src.current_line_number) leading_pipe = (@src.check(TABLE_LINE) =~ /^\s*\|/) @src.scan(TABLE_SEP_LINE) rows = [] = false columns = 0 add_container = lambda do |type, force| if ! || type != :tbody || force cont = Element.new(type) cont.children, rows = rows, [] table.children << cont end end while !@src.eos? break if !@src.check(TABLE_LINE) if @src.scan(TABLE_SEP_LINE) if rows.empty? # nothing to do, ignoring multiple consecutive separator lines elsif table.[:alignment].empty? && ! add_container.call(:thead, false) table.[:alignment] = @src[1].scan(TABLE_HSEP_ALIGN).map do |left, right| (left.empty? && right.empty? && :default) || (right.empty? && :left) || (left.empty? && :right) || :center end else # treat as normal separator line add_container.call(:tbody, false) end elsif @src.scan(TABLE_FSEP_LINE) add_container.call(:tbody, true) if !rows.empty? = true elsif @src.scan(TABLE_ROW_LINE) trow = Element.new(:tr) # parse possible code spans on the line and correctly split the line into cells env = save_env cells = [] @src[1].split(/(<code.*?>.*?<\/code>)/).each_with_index do |str, i| if i % 2 == 1 (cells.empty? ? cells : cells.last) << str else reset_env(:src => Kramdown::Utils::StringScanner.new(str, @src.current_line_number)) root = Element.new(:root) parse_spans(root, nil, [:codespan]) root.children.each do |c| if c.type == :raw_text # Only on Ruby 1.9: f, *l = c.value.split(/(?<!\\)\|/).map {|t| t.gsub(/\\\|/, '|')} f, *l = c.value.split(/\\\|/, -1).map {|t| t.split(/\|/, -1)}.inject([]) do |memo, t| memo.last << "|#{t.shift}" if memo.size > 0 memo.concat(t) end (cells.empty? ? cells : cells.last) << f cells.concat(l) else delim = (c.value.scan(/`+/).max || '') + '`' tmp = "#{delim}#{' ' if delim.size > 1}#{c.value}#{' ' if delim.size > 1}#{delim}" (cells.empty? ? cells : cells.last) << tmp end end end end restore_env(env) cells.shift if leading_pipe && cells.first.strip.empty? cells.pop if cells.last.strip.empty? cells.each do |cell_text| tcell = Element.new(:td) tcell.children << Element.new(:raw_text, cell_text.strip) trow.children << tcell end columns = [columns, cells.length].max rows << trow else break end end if !before_block_boundary? @src.revert_pos(saved_pos) return false end # Parse all lines of the table with the code span parser env = save_env l_src = ::Kramdown::Utils::StringScanner.new(extract_string(orig_pos...(@src.pos-1), @src), @src.current_line_number) reset_env(:src => l_src) root = Element.new(:root) parse_spans(root, nil, [:codespan, :span_html]) restore_env(env) # Check if each line has at least one unescaped pipe that is not inside a code span/code # HTML element # Note: It doesn't matter that we parse *all* span HTML elements because the row splitting # algorithm above only takes <code> elements into account! pipe_on_line = false while (c = root.children.shift) lines = c.value.split(/\n/) if c.type == :codespan if lines.size > 2 || (lines.size == 2 && !pipe_on_line) break elsif lines.size == 2 && pipe_on_line pipe_on_line = false end else break if lines.size > 1 && !pipe_on_line && lines.first !~ /^#{TABLE_PIPE_CHECK}/ pipe_on_line = (lines.size > 1 ? false : pipe_on_line) || (lines.last =~ /^#{TABLE_PIPE_CHECK}/) end end @src.revert_pos(saved_pos) and return false if !pipe_on_line add_container.call( ? :tfoot : :tbody, false) if !rows.empty? if !table.children.any? {|el| el.type == :tbody} warning("Found table without body on line #{table.[:location]} - ignoring it") @src.revert_pos(saved_pos) return false end # adjust all table rows to have equal number of columns, same for alignment defs table.children.each do |kind| kind.children.each do |row| (columns - row.children.length).times do row.children << Element.new(:td) end end end if table.[:alignment].length > columns table.[:alignment] = table.[:alignment][0...columns] else table.[:alignment] += [:default] * (columns - table.[:alignment].length) end @tree.children << table true end |
#parse_typographic_syms ⇒ Object
Parse the typographic symbols at the current location.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/kramdown/parser/kramdown/typographic_symbol.rb', line 22 def parse_typographic_syms start_line_number = @src.current_line_number @src.pos += @src.matched_size val = TYPOGRAPHIC_SYMS_SUBST[@src.matched] if val.kind_of?(Symbol) @tree.children << Element.new(:typographic_sym, val, nil, :location => start_line_number) elsif @src.matched == '\\<<' @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('lt'), nil, :location => start_line_number) @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('lt'), nil, :location => start_line_number) else @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('gt'), nil, :location => start_line_number) @tree.children << Element.new(:entity, ::Kramdown::Utils::Entities.entity('gt'), nil, :location => start_line_number) end end |
#replace_abbreviations(el, regexps = nil) ⇒ Object
Replace the abbreviation text with elements.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/kramdown/parser/kramdown/abbreviation.rb', line 38 def replace_abbreviations(el, regexps = nil) return if @root.[:abbrev_defs].empty? if !regexps sorted_abbrevs = @root.[:abbrev_defs].keys.sort {|a,b| b.length <=> a.length} regexps = [Regexp.union(*sorted_abbrevs.map {|k| /#{Regexp.escape(k)}/})] regexps << /(?=(?:\W|^)#{regexps.first}(?!\w))/ # regexp should only match on word boundaries end el.children.map! do |child| if child.type == :text if child.value =~ regexps.first result = [] strscan = Kramdown::Utils::StringScanner.new(child.value, child.[:location]) text_lineno = strscan.current_line_number while temp = strscan.scan_until(regexps.last) abbr_lineno = strscan.current_line_number abbr = strscan.scan(regexps.first) # begin of line case of abbr with \W char as first one if abbr.nil? temp << strscan.scan(/\W|^/) abbr = strscan.scan(regexps.first) end result << Element.new(:text, temp, nil, :location => text_lineno) result << Element.new(:abbreviation, abbr, nil, :location => abbr_lineno) text_lineno = strscan.current_line_number end result << Element.new(:text, strscan.rest, nil, :location => text_lineno) else child end else replace_abbreviations(child, regexps) child end end.flatten! end |
#update_ial_with_ial(ial, opts) ⇒ Object
Update the ial with the information from the inline attribute list opts.
42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/kramdown/parser/kramdown/extensions.rb', line 42 def update_ial_with_ial(ial, opts) (ial[:refs] ||= []) << opts[:refs] opts.each do |k,v| if k == IAL_CLASS_ATTR ial[k] = (ial[k] || '') << " #{v}" ial[k].lstrip! elsif k.kind_of?(String) ial[k] = v end end end |