144 lines
4.6 KiB
Ruby
144 lines
4.6 KiB
Ruby
module Liquid
|
|
class BlockBody
|
|
FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
|
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
|
WhitespaceOrNothing = /\A\s*\z/
|
|
TAGSTART = "{%".freeze
|
|
VARSTART = "{{".freeze
|
|
|
|
attr_reader :nodelist
|
|
|
|
def initialize
|
|
@nodelist = []
|
|
@blank = true
|
|
end
|
|
|
|
def parse(tokenizer, parse_context)
|
|
parse_context.line_number = tokenizer.line_number
|
|
while token = tokenizer.shift
|
|
next if token.empty?
|
|
case
|
|
when token.start_with?(TAGSTART)
|
|
whitespace_handler(token, parse_context)
|
|
unless token =~ FullToken
|
|
raise_missing_tag_terminator(token, parse_context)
|
|
end
|
|
tag_name = $1
|
|
markup = $2
|
|
# fetch the tag from registered blocks
|
|
unless tag = registered_tags[tag_name]
|
|
# end parsing if we reach an unknown tag and let the caller decide
|
|
# determine how to proceed
|
|
return yield tag_name, markup
|
|
end
|
|
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
|
|
@blank &&= new_tag.blank?
|
|
@nodelist << new_tag
|
|
when token.start_with?(VARSTART)
|
|
whitespace_handler(token, parse_context)
|
|
@nodelist << create_variable(token, parse_context)
|
|
@blank = false
|
|
else
|
|
if parse_context.trim_whitespace
|
|
token.lstrip!
|
|
end
|
|
parse_context.trim_whitespace = false
|
|
@nodelist << token
|
|
@blank &&= !!(token =~ WhitespaceOrNothing)
|
|
end
|
|
parse_context.line_number = tokenizer.line_number
|
|
end
|
|
|
|
yield nil, nil
|
|
end
|
|
|
|
def whitespace_handler(token, parse_context)
|
|
if token[2] == WhitespaceControl
|
|
previous_token = @nodelist.last
|
|
if previous_token.is_a? String
|
|
previous_token.rstrip!
|
|
end
|
|
end
|
|
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
|
end
|
|
|
|
def blank?
|
|
@blank
|
|
end
|
|
|
|
def render(context)
|
|
output = []
|
|
context.resource_limits.render_score += @nodelist.length
|
|
|
|
idx = 0
|
|
while node = @nodelist[idx]
|
|
case node
|
|
when String
|
|
check_resources(context, node)
|
|
output << node
|
|
when Variable
|
|
render_node_to_output(node, output, context)
|
|
when Block
|
|
render_node_to_output(node, output, context, node.blank?)
|
|
break if context.interrupt? # might have happened in a for-block
|
|
when Continue, Break
|
|
# If we get an Interrupt that means the block must stop processing. An
|
|
# Interrupt is any command that stops block execution such as {% break %}
|
|
# or {% continue %}
|
|
context.push_interrupt(node.interrupt)
|
|
break
|
|
else # Other non-Block tags
|
|
render_node_to_output(node, output, context)
|
|
break if context.interrupt? # might have happened through an include
|
|
end
|
|
idx += 1
|
|
end
|
|
|
|
output.join
|
|
end
|
|
|
|
private
|
|
|
|
def render_node_to_output(node, output, context, skip_output = false)
|
|
node_output = node.render(context)
|
|
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
|
check_resources(context, node_output)
|
|
output << node_output unless skip_output
|
|
rescue MemoryError => e
|
|
raise e
|
|
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
|
context.handle_error(e, node.line_number)
|
|
output << nil
|
|
rescue ::StandardError => e
|
|
line_number = node.is_a?(String) ? nil : node.line_number
|
|
output << context.handle_error(e, line_number)
|
|
end
|
|
|
|
def check_resources(context, node_output)
|
|
context.resource_limits.render_length += node_output.length
|
|
return unless context.resource_limits.reached?
|
|
raise MemoryError.new("Memory limits exceeded".freeze)
|
|
end
|
|
|
|
def create_variable(token, parse_context)
|
|
token.scan(ContentOfVariable) do |content|
|
|
markup = content.first
|
|
return Variable.new(markup, parse_context)
|
|
end
|
|
raise_missing_variable_terminator(token, parse_context)
|
|
end
|
|
|
|
def raise_missing_tag_terminator(token, parse_context)
|
|
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_termination".freeze, token: token, tag_end: TagEnd.inspect))
|
|
end
|
|
|
|
def raise_missing_variable_terminator(token, parse_context)
|
|
raise SyntaxError.new(parse_context.locale.t("errors.syntax.variable_termination".freeze, token: token, tag_end: VariableEnd.inspect))
|
|
end
|
|
|
|
def registered_tags
|
|
Template.tags
|
|
end
|
|
end
|
|
end
|