class Webgen::Utils::TagParser

This class is used to parse a string for webgen tags and replace them with dynamically generated content. See replace_tags for more information.

Public Class Methods

new(prefix = nil) click to toggle source

Create a new TagParser object, optionally providing a tag prefix.

# File lib/webgen/utils/tag_parser.rb, line 23
def initialize(prefix = nil)
  @start_re = /(\\*)\{#{prefix}(\w+)(::?)/
  @end_re = /(\\*)\{#{prefix}(\w+)\}/
end

Public Instance Methods

parse_params(param_string, tag) click to toggle source

Parse the parameter string and return the result.

# File lib/webgen/utils/tag_parser.rb, line 114
def parse_params(param_string, tag)
  YAML::load("--- #{param_string}")
rescue ArgumentError, SyntaxError, YAML::SyntaxError => e
  raise Error.new("Could not parse parameter string '#{param_string}' for tag '#{tag}': #{e.message}")
end
replace_tags(content) { |tag_name, params, body| ... } click to toggle source

Return the content with all webgen tags replaced.

When a webgen tag is encountered by the parser, the method yields all found information and substitutes the returned string for the tag.

# File lib/webgen/utils/tag_parser.rb, line 36
def replace_tags(content) #:yields: tag_name, params, body
  scanner = StringScanner.new(content)
  data = ProcessingStruct.new(:before_tag)
  while true
    case data.state
    when :before_tag
      if scanner.skip_until(@start_re)
        data.state = :in_start_tag
        data.backslashes = scanner[1].length
        data.brackets = 1
        data.tag = scanner[2]
        data.simple_tag = (scanner[3] == ':')
        data.params_start_pos = scanner.pos
        data.start_pos = scanner.pos - scanner.matched.length
      else
        data.state = :done
      end

    when :in_start_tag
      data.brackets += (scanner[1] == '{' ? 1 : -1) while data.brackets != 0 && scanner.skip_until(BRACKETS_RE)
      if data.brackets != 0
        raise Error.new("Unbalanced curly brackets found for tag '#{data.tag}'")
      else
        data.params_end_pos = data.body_end_pos = data.end_pos = scanner.pos - 1
        data.state = (data.simple_tag ? :process : :in_body)
      end

    when :process
      begin
        enc = scanner.string.encoding
        scanner.string.force_encoding('ASCII-8BIT')
        if data.backslashes % 2 == 0
          params = parse_params(scanner.string[data.params_start_pos...data.params_end_pos].force_encoding(enc),
                                data.tag)
          result = yield(data.tag, params,
                         scanner.string[(data.params_end_pos+1)...data.body_end_pos].to_s.force_encoding(enc))
          result = result.to_s.force_encoding('ASCII-8BIT')
          scanner.string[data.start_pos..data.end_pos] = "\\" * (data.backslashes / 2) + result
          scanner.pos = data.start_pos + result.length
        else
          scanner.string[data.start_pos, 1 + data.backslashes / 2] = ''
          scanner.pos -= 1 + data.backslashes / 2
        end
      ensure
        scanner.string.force_encoding(enc)
      end
      data.state = :before_tag

    when :in_body
      while (result = scanner.skip_until(@end_re))
        next unless scanner[2] == data.tag
        if scanner[1].length % 2 == 1
          scanner.string[(scanner.pos - scanner.matched.length), 1 + scanner[1].length / 2] = ''
          scanner.pos -= 1 + scanner[1].length / 2
        else
          break
        end
      end
      if result
        data.state = :process
        data.end_pos = scanner.pos - 1
        data.body_end_pos = scanner.pos - scanner.matched.length + scanner[1].length / 2
      else
        raise Error.new("Invalid body part - no end tag found for '#{data.tag}'")
      end

    when :done
      break
    end
  end
  scanner.string
rescue Error => e
  e.line = scanner.string[0...data.start_pos].scan("\n").size + 1
  e.column = data.start_pos - (scanner.string.rindex("\n", data.start_pos) || -1)
  raise
end