1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 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
|
# -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown which is licensed under the MIT.
#++
#
require 'kramdown/utils'
require 'kramdown/parser'
module Kramdown
module Parser
# == \Base class for parsers
#
# This class serves as base class for parsers. It provides common methods that can/should be
# used by all parsers, especially by those using StringScanner(Kramdown) for parsing.
#
# A parser object is used as a throw-away object, i.e. it is only used for storing the needed
# state information during parsing. Therefore one can't instantiate a parser object directly but
# only use the Base::parse method.
#
# == Implementing a parser
#
# Implementing a new parser is rather easy: just derive a new class from this class and put it
# in the Kramdown::Parser module -- the latter is needed so that the auto-detection of the new
# parser works correctly. Then you need to implement the +#parse+ method which has to contain
# the parsing code.
#
# Have a look at the Base::parse, Base::new and Base#parse methods for additional information!
class Base
# The hash with the parsing options.
attr_reader :options
# The array with the parser warnings.
attr_reader :warnings
# The original source string.
attr_reader :source
# The root element of element tree that is created from the source string.
attr_reader :root
# 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.
def initialize(source, options)
@source = source
@options = Kramdown::Options.merge(options)
@root = Element.new(:root, nil, nil, encoding: (source.encoding rescue nil), location: 1,
options: {}, abbrev_defs: {}, abbrev_attr: {})
@root.options[:abbrev_defs].default_proc = @root.options[:abbrev_attr].default_proc =
lambda do |h, k|
k_mod = k.gsub(/[\s\p{Z}]+/, " ")
k != k_mod ? h[k_mod] : nil
end
@warnings = []
@text_type = :text
end
private_class_method(:new, :allocate)
# Parse the +source+ string into an element tree, possibly using the parsing +options+, and
# return the root element of the element tree and an array with warning messages.
#
# Initializes a new instance of the calling class and then calls the +#parse+ method that must
# be implemented by each subclass.
def self.parse(source, options = {})
parser = new(source, options)
parser.parse
[parser.root, parser.warnings]
end
# Parse the source string into an element tree.
#
# The parsing code should parse the source provided in @source and build an element tree the
# root of which should be @root.
#
# This is the only method that has to be implemented by sub-classes!
def parse
raise NotImplementedError
end
# Add the given warning +text+ to the warning array.
def warning(text)
@warnings << text
# TODO: add position information
end
# Modify the string +source+ to be usable by the parser (unifies line ending characters to
# +\n+ and makes sure +source+ ends with a new line character).
def adapt_source(source)
unless source.valid_encoding?
raise "The source text contains invalid characters for the used encoding #{source.encoding}"
end
source = source.encode('UTF-8')
source.gsub!(/\r\n?/, "\n")
source.chomp!
source << "\n"
end
# This helper method adds the given +text+ either to the last element in the +tree+ if it is a
# +type+ element or creates a new text element with the given +type+.
def add_text(text, tree = @tree, type = @text_type)
last = tree.children.last
if last && last.type == type
last.value << text
elsif !text.empty?
location = (last && last.options[:location] || tree.options[:location])
tree.children << Element.new(type, text, nil, location: location)
end
end
# Extract the part of the StringScanner +strscan+ backed string specified by the +range+. This
# method works correctly under Ruby 1.8 and Ruby 1.9.
def extract_string(range, strscan)
result = nil
begin
enc = strscan.string.encoding
strscan.string.force_encoding('ASCII-8BIT')
result = strscan.string[range].force_encoding(enc)
ensure
strscan.string.force_encoding(enc)
end
result
end
end
end
end
|