module Liquid class BlockBody FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om 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 unless token.empty? case when token.start_with?(TAGSTART) whitespace_handler(token, parse_context) if token =~ FullToken tag_name = $1 markup = $2 # fetch the tag from registered blocks if tag = registered_tags[tag_name] new_tag = tag.parse(tag_name, markup, tokenizer, parse_context) @blank &&= new_tag.blank? @nodelist << new_tag else # end parsing if we reach an unknown tag and let the caller decide # determine how to proceed return yield tag_name, markup end else raise_missing_tag_terminator(token, parse_context) end 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 =~ /\A\s*\z/) end 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 @nodelist.each do |token| # Break out if we have any unhanded interrupts. break if context.interrupt? begin # 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 %} if token.is_a?(Continue) || token.is_a?(Break) context.push_interrupt(token.interrupt) break end node_output = render_node(token, context) unless token.is_a?(Block) && token.blank? output << node_output end rescue MemoryError => e raise e rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e context.handle_error(e, token.line_number) output << nil rescue ::StandardError => e output << context.handle_error(e, token.line_number) end end output.join end private def render_node(node, context) node_output = (node.respond_to?(:render) ? node.render(context) : node) node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s context.resource_limits.render_length += node_output.length if context.resource_limits.reached? raise MemoryError.new("Memory limits exceeded".freeze) end node_output 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