class EJX::Template

Attributes

source[RW]

Public Class Methods

new(source, options={}) click to toggle source
# File lib/ejx/template.rb, line 15
def initialize(source, options={})
  @source = source.strip

  @js_start_tags = [options[:open_tag] || EJX.settings[:open_tag]]
  @html_start_tags = ['<']
  @start_tags = @js_start_tags + @html_start_tags
  
  @js_close_tags = [options[:close_tag] || EJX.settings[:close_tag]]
  @html_close_tags = ['/>', '>']
  @close_tags = @js_close_tags + @html_close_tags
  
  @open_tag_modifiers = EJX.settings[:open_tag_modifiers].merge(options[:open_tag_modifiers] || {})
  @close_tag_modifiers = EJX.settings[:close_tag_modifiers].merge(options[:close_tag_modifiers] || {})
  
  @escape = options[:escape]
  process
end

Public Instance Methods

process() click to toggle source
# File lib/ejx/template.rb, line 33
def process
  seek(0)
  @tree =   [EJX::Template::Base.new(escape: @escape)]
  @stack =  [:str]
  
  while !eos?
    case @stack.last
    when :str
      scan_until(Regexp.new("(#{@start_tags.map{|s| Regexp.escape(s) }.join('|')}|\\z)"))
      if !pre_match.strip.empty?
        @tree.last.children << EJX::Template::String.new(pre_match)
      end
      
      if !matched.nil?
        if @js_start_tags.include?(matched)
          @stack << :js
        elsif @html_start_tags.include?(matched)
          @stack << :html_tag
        end
      end
    when :js
      pre_js = pre_match
      scan_until(Regexp.new("(#{@js_close_tags.map{|s| Regexp.escape(s) }.join('|')})"))
      pm = pre_match
      open_modifier = @open_tag_modifiers.find { |k,v| pm.start_with?(v) }&.first
      close_modifier = @close_tag_modifiers.find { |k,v| matched.end_with?(v) }&.first
      pm.slice!(0, open_modifier[1].size) if open_modifier
      pm.slice!(pm.size - close_modifier[1].size, close_modifier[1].size) if close_modifier
      
      if pm =~ /\A\s*import/
        import = pm.strip
        import += ';' if !import.end_with?(';')
        @tree.first.imports << import
        @stack.pop
      elsif @tree.last.is_a?(EJX::Template::Subtemplate) && pm.match(/\A\s*\}.*\)/m) && !pm.match(/\A\s*\}.*\{\s*\Z/m)
        subtemplate = @tree.pop
        subtemplate.children << pm.strip
        @tree.last.children << subtemplate
        @stack.pop
      elsif pm.match(/function\s*\([^\)]*\)\s*\{\s*\Z/m) || pm.match(/=>\s*\{\s*\Z/m)
        @tree << EJX::Template::Subtemplate.new(pm.strip, [open_modifier, close_modifier].compact, append: !pm.match?(/\A\s*(var|const|let)?\s*[^(]+\s*=/))
        @stack.pop
      else
        if open_modifier != :comment && !pre_js.empty? && @tree.last.children.last.is_a?(EJX::Template::JS)
          @tree.last.children << EJX::Template::String.new(' ')
        end
        value = EJX::Template::JS.new(pm.strip, [open_modifier, close_modifier].compact)

        @stack.pop
        case @stack.last
        when :html_tag
          @tree.last.tag_name = value
          push(:html_tag_attr_key)
        when :html_tag_attr_key
          @tree.last.attrs << value
        when :html_tag_attr_value
          @tree.last.attrs << {@stack_info.last => value}
          @stack.pop
        else
          @tree.last.children << value
        end
      end
    when :html_tag
      if @tree.last.children.last.is_a?(EJX::Template::JS)
        @tree.last.children << EJX::Template::String.new(' ')
      end

      scan_until(Regexp.new("(#{@js_start_tags.map{|s| Regexp.escape(s) }.join('|')}|\\/|[^\\s>]+)"))
      if @js_start_tags.include?(matched)
        @tree << EJX::Template::HTMLTag.new
        @stack << :js
      elsif matched == '/'
        @stack.pop
        @stack << :html_close_tag
      else
        @tree << EJX::Template::HTMLTag.new
        @tree.last.tag_name = matched
        @stack << :html_tag_attr_key
      end
    when :html_close_tag
      scan_until(Regexp.new("(#{@js_start_tags.map{|s| Regexp.escape(s) }.join('|')}|[^\\s>]+)"))

      if @js_start_tags.include?(matched)
        @stack << :js
      else
        el = @tree.pop
        if el.tag_name != matched
          raise EJX::TemplateError.new("Expected to close #{el.tag_name} tag, instead closed #{matched}\n#{cursor}")
        end
        @tree.last.children << el
        scan_until(Regexp.new("(#{@html_close_tags.map{|s| Regexp.escape(s) }.join('|')})"))
        @stack.pop
      end
    when :html_tag_attr_key
      scan_until(Regexp.new("(#{(@js_start_tags+@html_close_tags).map{|s| Regexp.escape(s) }.join('|')}|[^\\s=>]+)"))
      if @js_start_tags.include?(matched)
        @stack << :js
      elsif @html_close_tags.include?(matched)
        if matched == '/>' || EJX::VOID_ELEMENTS.include?(@tree.last.tag_name)
          el = @tree.pop
          @tree.last.children << el
          @stack.pop
          @stack.pop
        else
          @stack << :str
        end
      else
        key = if matched.start_with?('"') && matched.end_with?('"')
          matched[1..-2]
        elsif matched.start_with?('"') && matched.end_with?('"')
          matched[1..-2]
        else
          matched
        end
        @tree.last.attrs << key
        @stack << :html_tag_attr_value_tx
      end
    when :html_tag_attr_value_tx
      scan_until(Regexp.new("(#{(@js_start_tags+@html_close_tags).map{|s| Regexp.escape(s) }.join('|')}|=|\\S)"))
      tag_key = @tree.last.attrs.pop
      if @js_start_tags.include?(matched)
        @stack << :js
      elsif @html_close_tags.include?(matched)
        el = @tree.last
        el.attrs << tag_key
        if EJX::VOID_ELEMENTS.include?(el.tag_name)
          @tree.pop
          @tree.last.children << el
        end
        @stack.pop
        @stack.pop
        @stack.pop
      elsif matched == '='
        @stack.pop
        @tree.last.attrs << tag_key
        @stack << :html_tag_attr_value
      else
        @stack.pop
        @tree.last.attrs << tag_key
        rewind(1)
      end

    when :html_tag_attr_value
      scan_until(Regexp.new("(#{(@js_start_tags+@html_close_tags).map{|s| Regexp.escape(s) }.join('|')}|'|\"|\\S+)"))

      if @js_start_tags.include?(matched)
        push(:js)
      elsif matched == '"'
        @stack.pop
        @stack << :html_tag_attr_value_double_quoted
      elsif matched == "'"
        @stack.pop
        @stack << :html_tag_attr_value_single_quoted
      else
        @stack.pop
        key = @tree.last.attrs.pop
        @tree.last.namespace = matched if key == 'xmlns'
        @tree.last.attrs << { key => matched }
      end
    when :html_tag_attr_value_double_quoted
      quoted_value = []
      scan_until(/("|\[\[=)/)
      while matched == '[[='
        quoted_value << pre_match if !pre_match.strip.empty?
        scan_until(/\]\]/)
        quoted_value << EJX::Template::JS.new(pre_match.strip)
        scan_until(/("|\[\[=)/)
      end
      quoted_value << pre_match if !pre_match.strip.empty?
      rewind(1)

      quoted_value = EJX::Template::HTMLTag::AttributeValue.new(quoted_value)

      key = @tree.last.attrs.pop
      @tree.last.namespace = quoted_value if key == 'xmlns'
      @tree.last.attrs << { key => quoted_value }
      scan_until(/\"/)
      @stack.pop
    when :html_tag_attr_value_single_quoted
      quoted_value = []
      scan_until(/('|\[\[=)/)
      while matched == '[[='
        quoted_value << pre_match if !pre_match.strip.empty?
        scan_until(/\]\]/)
        quoted_value << EJX::Template::JS.new(pre_match.strip)
        scan_until(/('|\[\[=)/)
      end
      quoted_value << pre_match if !pre_match.strip.empty?
      rewind(1)

      quoted_value = EJX::Template::HTMLTag::AttributeValue.new(quoted_value)

      key = @tree.last.attrs.pop
      @tree.last.namespace = quoted_value if key == 'xmlns'
      @tree.last.attrs << { key => quoted_value }
      scan_until(/\'/)
      @stack.pop
    end
  end
end
to_module() click to toggle source
# File lib/ejx/template.rb, line 234
def to_module
  @tree.first.to_module
end