module Haml::Precompiler

def self.build_attributes(is_html, attr_wrapper, attributes = {})

This is a class method so it can be accessed from Buffer.
def self.build_attributes(is_html, attr_wrapper, attributes = {})
  quote_escape = attr_wrapper == '"' ? """ : "'"
  other_quote_char = attr_wrapper == '"' ? "'" : '"'
  result = attributes.collect do |attr, value|
    next if value.nil?
    if value == true
      next " #{attr}" if is_html
      next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
    elsif value == false
      next
    end
    value = Haml::Helpers.preserve(Haml::Helpers.escape_once(value.to_s))
    # We want to decide whether or not to escape quotes
    value.gsub!('"', '"')
    this_attr_wrapper = attr_wrapper
    if value.include? attr_wrapper
      if value.include? other_quote_char
        value = value.gsub(attr_wrapper, quote_escape)
      else
        this_attr_wrapper = other_quote_char
      end
    end
    " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
  end
  result.compact.sort.join
end

def balance(scanner, start, finish, count = 0)

def balance(scanner, start, finish, count = 0)
  str = ''
  scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
  regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]")
  while scanner.scan(regexp)
    str << scanner.matched
    count += 1 if scanner.matched[-1] == start
    count -= 1 if scanner.matched[-1] == finish
    return [str.strip, scanner.rest] if count == 0
  end
  raise SyntaxError.new("Unbalanced brackets.")
end

def close

Closes the most recent item in @to_close_stack.
def close
  tag, value = @to_close_stack.pop
  case tag
  when :script; close_block
  when :comment; close_comment value
  when :element; close_tag value
  when :loud; close_loud value
  when :filtered; close_filtered value
  when :haml_comment; close_haml_comment
  end
end

def close_block

Closes a Ruby block.
def close_block
  push_silent "end", true
  @template_tabs -= 1
end

def close_comment(has_conditional)

Closes a comment.
def close_comment(has_conditional)
  @output_tabs -= 1
  @template_tabs -= 1
  close_tag = has_conditional ? "<![endif]-->" : "-->"
  push_text(close_tag, -1)
end

def close_filtered(filter)

Closes a filtered block.
def close_filtered(filter)
  @flat_spaces = -1
  filter.internal_compile(self, @filter_buffer)
  @filter_buffer = nil
  @template_tabs -= 1
end

def close_haml_comment

def close_haml_comment
  @haml_comment = false
  @template_tabs -= 1
end

def close_loud(command)

Closes a loud Ruby block.
def close_loud(command)
  push_silent 'end', true
  @precompiled << command
  @template_tabs -= 1
end

def close_tag(value)

the most recently opened tag.
Puts a line in @precompiled that will add the closing tag of
def close_tag(value)
  tag, nuke_outer_whitespace, nuke_inner_whitespace = value
  @output_tabs -= 1 unless nuke_inner_whitespace
  @template_tabs -= 1
  rstrip_buffer! if nuke_inner_whitespace
  push_merged_text("</#{tag}>" + (nuke_outer_whitespace ? "" : "\n"),
                   nuke_inner_whitespace ? 0 : -1, !nuke_inner_whitespace)
  @dont_indent_next_line = nuke_outer_whitespace
end

def concat_merged_text(text)

Concatenate text to @buffer without tabulation.
def concat_merged_text(text)
  @merged_text  << text
end

def contains_interpolation?(str)

def contains_interpolation?(str)
  str.include?('#{')
end

def count_soft_tabs(line)

Counts the tabulation of a line.
def count_soft_tabs(line)
  spaces = line.index(/([^ ]|$)/)
  if line[spaces] == ?\t
    return 0, 0 if line.strip.empty?
    raise SyntaxError.new(<<END.strip, @next_line.index)
b character was used for indentation. Haml must be indented using two spaces.
you sure you have soft tabs enabled in your editor?
  end
  [spaces, spaces/2]
end

def flat?

def flat?
  @flat_spaces != -1
end

def flush_merged_text

def flush_merged_text
  return if @merged_text.empty?
  @precompiled  << "_hamlout.push_text(#{@merged_text.dump}"
  @precompiled  << ", #{@dont_tab_up_next_text.inspect}" if @dont_tab_up_next_text || @tab_change != 0
  @precompiled  << ", #{@tab_change}" if @tab_change != 0
  @precompiled  << ");"
  @merged_text   = ''
  @dont_tab_up_next_text = false
  @tab_change    = 0
end

def handle_multiline(line)

rendered normally.
This returns whether or not the line should be

the beginning, continuation, or end of a multiline sequence.
Deals with all the logic of figuring out whether a given line is
def handle_multiline(line)
  text = line.text
  # A multiline string is active, and is being continued
  if is_multiline?(text) && @multiline
    @multiline.text << text[0...-1]
    return true
  end
  # A multiline string has just been activated, start adding the lines
  if is_multiline?(text) && (MULTILINE_STARTERS.include? text[0])
    @multiline = Line.new text[0...-1], nil, line.index, nil, line.tabs
    process_indent(line)
    return true
  end
  # A multiline string has just ended, make line into the result
  if @multiline && !line.text.empty?
    process_line(@multiline.text, @multiline.index, line.tabs > @multiline.tabs)
    @multiline = nil
  end
  return false
end

def is_multiline?(text)

Checks whether or not +line+ is in a multiline sequence.
def is_multiline?(text)
  text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s
end

def locals_code(names)

def locals_code(names)
  names = names.keys if Hash == names
  names.map do |name|
    "#{name} = _haml_locals[#{name.to_sym.inspect}] || _haml_locals[#{name.to_s.inspect}]"
  end.join(';') + ';'
end

def mid_block_keyword?(text)

of Ruby's mid-block keywords.
Returns whether or not the text is a silent script text with one
def mid_block_keyword?(text)
  text.length > 2 && text[0] == SILENT_SCRIPT && MID_BLOCK_KEYWORDS.include?(text[1..-1].split[0])
end

def newline

def newline
  @newlines += 1
end

def newline_now

def newline_now
  @precompiled << "\n"
  @newlines -= 1
end

def parse_attributes(line)

def parse_attributes(line)
  scanner = StringScanner.new(line)
  attributes_hash, rest = balance(scanner, ?{, ?})
  attributes_hash = attributes_hash[1...-1] if attributes_hash
  return attributes_hash, rest
end

def parse_class_and_id(list)

that can then be merged with another attributes hash.
and # syntax, and returns a hash with them as attributes,
Iterates through the classes and ids supplied through .
def parse_class_and_id(list)
  attributes = {}
  list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property|
    case type
    when '.'
      if attributes['class']
        attributes['class'] += " "
      else
        attributes['class'] = ""
      end
      attributes['class'] += property
    when '#'; attributes['id'] = property
    end
  end
  attributes
end

def parse_literal_value(text)

def parse_literal_value(text)
  return nil unless text
  text.match(LITERAL_VALUE_REGEX)
  # $2 holds the value matched by a symbol, but is nil for a string match
  # $5 holds the value matched by a string
  $2 || $5
end

def parse_static_hash(text)

def parse_static_hash(text)
  return {} unless text
  attributes = {}
  text.split(',').each do |attrib|
    key, value, more = attrib.split('=>')
    # Make sure the key and value and only the key and value exist
    # Otherwise, it's too complicated or dynamic and we'll defer it to the actual Ruby parser
    key = parse_literal_value key
    value = parse_literal_value value
    return nil if more || key.nil? || value.nil?
    attributes[key] = value
  end
  attributes
end

def parse_tag(line)

Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
def parse_tag(line)
  raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-\w\.\#]*)(.*)/)[0]
  tag_name, attributes, rest = match
  attributes_hash, rest = parse_attributes(rest) if rest[0] == ?{
  if rest
    object_ref, rest = balance(rest, ?[, ?]) if rest[0] == ?[
    attributes_hash, rest = parse_attributes(rest) if rest[0] == ?{ && attributes_hash.nil?
    nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
    nuke_whitespace ||= ''
    nuke_outer_whitespace = nuke_whitespace.include? '>'
    nuke_inner_whitespace = nuke_whitespace.include? '<'
  end
  value = value.to_s.strip
  [tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace,
   nuke_inner_whitespace, action, value]
end

def precompile

def precompile
  old_line = Line.new
  @template.split(/\r\n|\r|\n/).each_with_index do |text, index|
    @next_line = line = Line.new(text.strip, text.lstrip.chomp, index)
    line.spaces, line.tabs = count_soft_tabs(text)
    if line.text.empty? && !flat?
      newline
      next
    end
    suppress_render = handle_multiline(old_line) unless flat?
    if old_line.text.nil? || suppress_render
      old_line = line
      resolve_newlines
      newline
      next
    end
    process_indent(old_line) unless old_line.text.empty?
    if flat?
      push_flat(old_line)
      old_line = line
      newline
      next
    end
    if old_line.spaces != old_line.tabs * 2
      raise SyntaxError.new(<<END.strip, old_line.index)
d_line.spaces} space#{old_line.spaces == 1 ? ' was' : 's were'} used for indentation. Haml must be indented using two spaces.
    end
    unless old_line.text.empty? || @haml_comment
      process_line(old_line.text, old_line.index, line.tabs > old_line.tabs && !line.text.empty?)
    end
    resolve_newlines
    if !flat? && line.tabs - old_line.tabs > 1
      raise SyntaxError.new(<<END.strip, line.index)
ne.spaces} spaces were used for indentation. Haml must be indented using two spaces.
    end
    old_line = line
    newline
  end
  # Close all the open tags
  close until @to_close_stack.empty?
  flush_merged_text
end

def precompiled_with_ambles(local_names)

Returns the precompiled string with the preamble and postamble
def precompiled_with_ambles(local_names)
  preamble = <<END.gsub("\n", ";")
nd Haml::Helpers
lout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
out = _hamlout.buffer
  postamble = <<END.gsub("\n", ";")
l_buffer = @haml_buffer.upper
out
  preamble + locals_code(local_names) + @precompiled + postamble
end

def prerender_tag(name, self_close, attributes)

def prerender_tag(name, self_close, attributes)
  attributes_string = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
  "<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
end

def process_indent(line)

Processes and deals with lowering indentation.
def process_indent(line)
  return unless line.tabs <= @template_tabs && @template_tabs > 0
  to_close = @template_tabs - line.tabs
  to_close.times { |i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text) }
end

def process_line(text, index, block_opened)

adds the appropriate code to @precompiled.
This method doesn't return anything; it simply processes the line and

Processes a single line of Haml.
def process_line(text, index, block_opened)
  @block_opened = block_opened
  @index = index + 1
  case text[0]
  when DIV_CLASS, DIV_ID; render_div(text)
  when ELEMENT; render_tag(text)
  when COMMENT; render_comment(text[1..-1].strip)
  when SANITIZE
    return push_script(unescape_interpolation(text[3..-1].strip), false, false, false, true) if text[1..2] == "=="
    return push_script(text[2..-1].strip, false, false, false, true) if text[1] == SCRIPT
    push_plain text
  when SCRIPT
    return push_script(unescape_interpolation(text[2..-1].strip), false) if text[1] == SCRIPT
    return push_script(text[1..-1], false, false, false, true) if options[:escape_html]
    push_script(text[1..-1], false)
  when FLAT_SCRIPT; push_flat_script(text[1..-1])
  when SILENT_SCRIPT
    return start_haml_comment if text[1] == SILENT_COMMENT
    raise SyntaxError.new(<<END.rstrip, index) if text[1..-1].strip == "end"
don't need to use "- end" in Haml. Use indentation instead:
 foo?
trong Foo!
se
t foo.
    push_silent(text[1..-1], true)
    newline_now
    if (@block_opened && !mid_block_keyword?(text)) || text[1..-1].split(' ', 2)[0] == "case"
      push_and_tabulate([:script])
    end
  when FILTER; start_filtered(text[1..-1].downcase)
  when DOCTYPE
    return render_doctype(text) if text[0...3] == '!!!'
    return push_script(unescape_interpolation(text[3..-1].strip), false) if text[1..2] == "=="
    return push_script(text[2..-1].strip, false) if text[1] == SCRIPT
    push_plain text
  when ESCAPE; push_plain text[1..-1]
  else push_plain text
  end
end

def push_and_tabulate(value)

@template_tabs.
Pushes value onto @to_close_stack and increases
def push_and_tabulate(value)
  @to_close_stack.push(value)
  @template_tabs += 1
end

def push_flat(line)

Adds +text+ to @buffer while flattening text.
def push_flat(line)
  tabulation = line.spaces - @flat_spaces
  tabulation = tabulation > -1 ? tabulation : 0
  @filter_buffer << "#{' ' * tabulation}#{line.unstripped}\n"
end

def push_flat_script(text)

to be run on it afterwards.
Causes text to be evaluated, and Haml::Helpers#find_and_flatten
def push_flat_script(text)
  flush_merged_text
  raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
  push_script(text, true)
end

def push_merged_text(text, tab_change = 0, indent = true)

without parsing it.
Adds text to @buffer with appropriate tabulation
def push_merged_text(text, tab_change = 0, indent = true)
  @merged_text  << (!indent || @dont_indent_next_line || @options[:ugly] ? text : "#{'  ' * @output_tabs}#{text}")
  @dont_indent_next_line = false
  @tab_change   += tab_change
end

def push_plain(text)

Also checks for an illegally opened block.
Renders a block of text as plain text.
def push_plain(text)
  if @block_opened
    raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", @next_line.index)
  end
  push_text text
end

def push_script(text, preserve_script, in_tag = false, preserve_tag = false,

the result before it is added to @buffer
If preserve_script is true, Haml::Helpers#find_and_flatten is run on

the scope object and the result to be added to @buffer.
Causes text to be evaluated in the context of
def push_script(text, preserve_script, in_tag = false, preserve_tag = false,
                escape_html = false, nuke_inner_whitespace = false)
  # Prerender tabulation unless we're in a tag
  push_merged_text '' unless in_tag
  flush_merged_text
  return if options[:suppress_eval]
  raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
  push_silent "haml_temp = #{text}"
  newline_now
  args = [preserve_script, in_tag, preserve_tag,
          escape_html, nuke_inner_whitespace].map { |a| a.inspect }.join(', ')
  out = "haml_temp = _hamlout.push_script(haml_temp, #{args});"
  if @block_opened
    push_and_tabulate([:loud, out])
  else
    @precompiled << out
  end
end

def push_silent(text, can_suppress = false)

does not output the result.
Evaluates text in the context of the scope object, but
def push_silent(text, can_suppress = false)
  flush_merged_text
  return if can_suppress && options[:suppress_eval]
  @precompiled << "#{text};"
end

def push_text(text, tab_change = 0)

def push_text(text, tab_change = 0)
  push_merged_text("#{text}\n", tab_change)
end

def render_comment(line)

Renders an XHTML comment.
def render_comment(line)
  conditional, line = balance(line, ?[, ?]) if line[0] == ?[
  line.strip!
  conditional << ">" if conditional
  if @block_opened && !line.empty?
    raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', @next_line.index)
  end
  open = "<!--#{conditional} "
  # Render it statically if possible
  unless line.empty?
    return push_text("#{open}#{line} #{conditional ? "<![endif]-->" : "-->"}")
  end
  push_text(open, 1)
  @output_tabs += 1
  push_and_tabulate([:comment, !conditional.nil?])
  unless line.empty?
    push_text(line)
    close
  end
end

def render_div(line)

. or #.
Renders a line that creates an XHTML tag and has an implicit div because of
def render_div(line)
  render_tag('%div' + line)
end

def render_doctype(line)

Renders an XHTML doctype or XML shebang.
def render_doctype(line)
  raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", @next_line.index) if @block_opened
  doctype = text_for_doctype(line)
  push_text doctype if doctype
end

def render_tag(line)

render that tag to @precompiled.
Parses a line that will render as an XHTML tag, and adds the code that will
def render_tag(line)
  tag_name, attributes, attributes_hash, object_ref, nuke_outer_whitespace,
    nuke_inner_whitespace, action, value = parse_tag(line)
  raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
  # Get rid of whitespace outside of the tag if we need to
  rstrip_buffer! if nuke_outer_whitespace
  preserve_tag = options[:preserve].include?(tag_name)
  nuke_inner_whitespace ||= preserve_tag
  preserve_tag &&= !options[:ugly]
  case action
  when '/'; self_closing = xhtml?
  when '~'; parse = preserve_script = true
  when '='
    parse = true
    value = unescape_interpolation(value[1..-1].strip) if value[0] == ?=
  when '&', '!'
    if value[0] == ?=
      parse = true
      value = (value[1] == ?= ? unescape_interpolation(value[2..-1].strip) : value[1..-1].strip)
    end
  end
  if parse && @options[:suppress_eval]
    parse = false
    value = ''
  end
  escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
  object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
  static_attributes = parse_static_hash(attributes_hash) # Try pre-compiling a static attributes hash
  attributes_hash = nil if static_attributes || @options[:suppress_eval]
  attributes = parse_class_and_id(attributes)
  Buffer.merge_attrs(attributes, static_attributes) if static_attributes
  raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", @next_line.index) if @block_opened && self_closing
  raise SyntaxError.new("Illegal nesting: content can't be both given on the same line as %#{tag_name} and nested within it.", @next_line.index) if @block_opened && !value.empty?
  raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.") if parse && value.empty?
  raise SyntaxError.new("Self-closing tags can't have content.") if self_closing && !value.empty?
  self_closing ||= !!( !@block_opened && value.empty? && @options[:autoclose].include?(tag_name) )
  dont_indent_next_line =
    (nuke_outer_whitespace && !@block_opened) ||
    (nuke_inner_whitespace && @block_opened)
  # Check if we can render the tag directly to text and not process it in the buffer
  if object_ref == "nil" && attributes_hash.nil? && !preserve_script
    tag_closed = !@block_opened && !self_closing && !parse
    open_tag  = prerender_tag(tag_name, self_closing, attributes)
    if tag_closed
      open_tag << "#{value}</#{tag_name}>"
      open_tag << "\n" unless nuke_outer_whitespace
    else
      open_tag << "\n" unless parse || nuke_inner_whitespace || (self_closing && nuke_outer_whitespace)
    end
    
    push_merged_text(open_tag, tag_closed || self_closing || nuke_inner_whitespace ? 0 : 1,
                     !nuke_outer_whitespace)
    @dont_indent_next_line = dont_indent_next_line
    return if tag_closed
  else
    flush_merged_text
    content = value.empty? || parse ? 'nil' : value.dump
    attributes_hash = ', ' + attributes_hash if attributes_hash
    args = [tag_name, self_closing, !@block_opened, preserve_tag, escape_html,
            attributes, nuke_outer_whitespace, nuke_inner_whitespace
           ].map { |v| v.inspect }.join(', ')
    push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_hash})"
    @dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
  end
  return if self_closing
  if value.empty?
    push_and_tabulate([:element, [tag_name, nuke_outer_whitespace, nuke_inner_whitespace]])
    @output_tabs += 1 unless nuke_inner_whitespace
    return
  end
  if parse
    flush_merged_text
    push_script(value, preserve_script, true, preserve_tag, escape_html, nuke_inner_whitespace)
    @dont_tab_up_next_text = true
    concat_merged_text("</#{tag_name}>" + (nuke_outer_whitespace ? "" : "\n"))
  end
end

def resolve_newlines

def resolve_newlines
  return unless @newlines > 0
  @precompiled << "\n" * @newlines
  @newlines = 0
end

def rstrip_buffer!

or the merged text
Get rid of and whitespace at the end of the buffer
def rstrip_buffer!
  unless @merged_text.empty?
    @merged_text.rstrip!
  else
    push_silent("_erbout.rstrip!", false)
    @dont_tab_up_next_text = true
  end
end

def start_filtered(name)

Starts a filtered block.
def start_filtered(name)
  raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
  unless filter = Filters.defined[name]
    if filter == 'redcloth' || filter == 'markdown' || filter == 'textile'
      raise Error.new("You must have the RedCloth gem installed to use \"#{name}\" filter")
    end
    raise Error.new("Filter \"#{name}\" is not defined.")
  end
  push_and_tabulate([:filtered, filter])
  @flat_spaces = @template_tabs * 2
  @filter_buffer = String.new
  @block_opened = false
end

def start_haml_comment

def start_haml_comment
  return unless @block_opened
  @haml_comment = true
  push_and_tabulate([:haml_comment])
end

def text_for_doctype(text)

def text_for_doctype(text)
  text = text[3..-1].lstrip.downcase
  if text.index("xml") == 0
    return nil if html?
    wrapper = @options[:attr_wrapper]
    return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
  end
  if html5?
    '<!DOCTYPE html>'
  else
    version, type = text.scan(DOCTYPE_REGEX)[0]
    if xhtml?
      if version == "1.1"
        '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
      else
        case type
        when "strict";   '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
        when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
        else             '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
        end
      end
    elsif html4?
      case type
      when "strict";   '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
      when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
      else             '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
      end
    end
  end
end

def unescape_interpolation(str)

def unescape_interpolation(str)
  scan = StringScanner.new(str.dump)
  str = ''
  while scan.scan(/(.*?)(\\+)\#\{/)
    escapes = (scan[2].size - 1) / 2
    str << scan.matched[0...-3 - escapes]
    if escapes % 2 == 1
      str << '#{'
    else
      # Use eval to get rid of string escapes
      str << '#{' + eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"') + "}"
    end
  end
  str + scan.rest
end