This is the parser for Inversion templates. It takes template source and returns a tree of Inversion::Template::Node objects (if parsing is successful).
Default values for parser configuration options.
Valid tagends by tagstart
The pattern for matching a tag opening
The pattern for matching a tag.
The parser's config options
Create a new Inversion::Parser with the specified
config options
.
# File lib/inversion/parser.rb, line 48
def initialize( template, options={} )
@template = template
@options = DEFAULT_OPTIONS.merge( options )
end
Parse the given source
into one or more
Inversion::Template::Nodes and return it as an Array.
# File lib/inversion/parser.rb, line 64
def parse( source, inherited_state=nil )
state = nil
if inherited_state
inherited_state.template = @template
state = inherited_state
else
state = Inversion::Parser::State.new( @template, self.options )
end
self.log.debug "Starting parse of template source (%0.2fK, %s)" %
[ source.bytesize/1024.0, source.encoding ]
t0 = Time.now
last_pos = last_linenum = last_colnum = 0
source.scan( TAG_PATTERN ) do |*|
match = Regexp.last_match
start_pos, end_pos = match.offset( 0 )
linenum = match.pre_match.count( "\n" ) + 1
colnum = match.pre_match.length - (match.pre_match.rindex("\n") || -1)
# Error on <?...?] and vice-versa.
unless match[:tagend] == MATCHING_BRACKETS[match[:tagstart]]
raise Inversion::ParseError,
"malformed tag %p: mismatched start and end brackets at line %d, column %d" %
[ match[0], linenum, colnum ]
end
# Check for nested tags
if match[0].index( TAG_OPEN, 2 )
raise Inversion::ParseError, "unclosed or nested tag %p at line %d, column %d" %
[ match[0], linenum, colnum ]
end
# self.log.debug " found a tag at offset: %d (%p) (line %d, col %d)" %
# [ start_pos, abbrevstring(match[0]), linenum, colnum ]
# If there were characters between the end of the last match and
# the beginning of the tag, create a text node with them
unless last_pos == start_pos
text = match.pre_match[ last_pos..-1 ]
# self.log.debug " adding literal text node: %p" % [ abbrevstring(text) ]
state << Inversion::Template::TextNode.new( text, last_linenum, last_colnum )
end
# self.log.debug " creating tag with tagname: %p, body: %p" %
# [ match[:tagname], match[:body] ]
tag = Inversion::Template::Tag.create( match[:tagname], match[:body], linenum, colnum )
if tag.nil?
unless state.options[ :ignore_unknown_tags ]
raise Inversion::ParseError, "Unknown tag %p at line %d, column %d" %
[ match[:tagname], linenum, colnum ]
end
tag = Inversion::Template::TextNode.new( match[0], linenum, colnum )
end
# self.log.debug " created tag: %p" % [ tag ]
state << tag
# Keep offsets for the next match
last_pos = end_pos
last_linenum = linenum + match[0].count( "\n" )
last_colnum = match[0].length - ( match[0].rindex("\n") || -1 )
end
# If there are any characters left over after the last tag
remainder = source[ last_pos..-1 ]
if remainder && !remainder.empty?
# self.log.debug "Remainder after last tag: %p" % [ abbrevstring(remainder) ]
# Detect unclosed tags
if remainder.index( "<?" ) || remainder.index( "[?" )
raise Inversion::ParseError,
"unclosed tag after line %d, column %d" % [ last_linenum, last_colnum ]
end
# Add any remaining text as a text node
state << Inversion::Template::TextNode.new( remainder, last_linenum, last_colnum )
end
self.log.debug " done parsing: %0.5fs" % [ Time.now - t0 ]
return state.tree
end