module MasterView module Analyzer ExpandAlwaysElements = %w{ script textarea } module Common def calc_hash(data) #MasterView::Log.warn { 'hash= '+data.hash.to_s+' data='+data } data.gsub( /\s+/, ' ' ).hash #collapse whitespace end def src_hash(data_orig) data = data_orig.gsub MasterView::NamespacePrefix+'import_render', MasterView::NamespacePrefix+'gen_partial' data = data.gsub MasterView::NamespacePrefix+'import', MasterView::NamespacePrefix+'generate' calc_hash(data) end end class ContentEntry include Common attr_accessor :hash, :data def initialize(data) @data = data @hash = src_hash(data) end end class StackEntry attr_accessor :name, :buffer, :depth, :import, :parts def initialize(name, depth, import) @name = name @depth = depth @import = import @buffer = [] @parts = 0 end def inc_parts @parts += 1 end end class ListEntry attr_accessor :name, :index, :import attr_writer :hash_invalid def initialize(name, index, import) @name = name @index = index @import = import end def hash_invalid? @hash_invalid end end # builder class facilitates the hash_check and building process by monitoring if all parts of a # file have been requested, if not when a higher index part is requested all the other previous # parts will be sent along as well, this is necessary for cases where a file doesn't have as many # internal parts or none at all. This way the hash and all the parts of the file will still be there # if layout has two parts and only last part -1 is requested, this builder will give back part 0 and part 1 # if layout would have had three parts then it would have given back all three. # if a part does not exist then it will come back as empty string, same for an index out of bounds class Builder include Common def initialize(content_hash) @content_hash = content_hash @builder_hash = {} #will contain next index to use end def hash(name, index) src_hash(data(name, index)) end def data(name, index) content = '' content_parts = @content_hash[name] content_parts_length = (content_parts) ? content_parts.length : 0 next_index_to_use = (nind = @builder_hash[name]) ? nind : 0 next_index_to_use = 0 if (-1 == index) && (next_index_to_use+1 > content_parts_length) # allow -1 to rerequest part as needed (singlefile) return '' if content_parts.nil? || (next_index_to_use+1 > content_parts_length) highest_index = content_parts.length - 1 requested_index = (index == -1) ? highest_index : index content = content_parts[next_index_to_use..requested_index].collect do |ce| (ce.data.gsub( /\s/, '' ).empty?) ? '' : ce.data # if only white space then get rid of it end.join @builder_hash[name] = requested_index + 1 # store next index to use content end end class Listener include REXML::SAX2Listener include DirectiveHelpers include Common attr_accessor :list, :content def initialize( options = {} ) @depth = 0 @stack = [] @list = [] @content = options[:content_hash] || {} @options = options @only_check_hash = options[:only_check_hash] || false @builder = Builder.new(@content) @default_extname = options[:default_extname] || ::MasterView::IOMgr.erb.default_extension @keyword_expander = KeywordExpander.new @keyword_expander.set_template_pathname( options[:template_pathname], @default_extname ) if options[:template_pathname] Log.debug { "only_check_hash => true" } if @only_check_hash end def only_check_hash? @only_check_hash end def xmldecl(version, encoding, standalone) #todo end def start_document #todo end def doctype(name, pub, sys, long_name, uri) #todo end def start_element(uri, localname, qname, attributes) unescape_attributes!(attributes) @depth += 1 import = false if attributes[::MasterView::NamespacePrefix+'generate'] path = @keyword_expander.expand_keywords(attributes[::MasterView::NamespacePrefix+'generate']) attributes[::MasterView::NamespacePrefix+'generate'] = path # put back expanded version in case of rebuilding elsif attributes[::MasterView::NamespacePrefix+'gen_partial'] expanded_partial_attr = @keyword_expander.expand_keywords(attributes[::MasterView::NamespacePrefix+'gen_partial']) attributes[::MasterView::NamespacePrefix+'gen_partial'] = expanded_partial_attr # put back expanded version in case of rebuilding partial = find_string_val_in_string_hash( expanded_partial_attr, :partial) path = render_partial_name_to_file_name(partial, @default_extname) elsif attributes[::MasterView::NamespacePrefix+'import'] path = @keyword_expander.expand_keywords(attributes[::MasterView::NamespacePrefix+'import']) attributes[::MasterView::NamespacePrefix+'import'] = path # put back expanded version in case of rebuilding import = true elsif attributes[::MasterView::NamespacePrefix+'import_render'] expanded_partial_attr = @keyword_expander.expand_keywords(attributes[::MasterView::NamespacePrefix+'import_render']) attributes[::MasterView::NamespacePrefix+'import_render'] = expanded_partial_attr # put back expanded version in case of rebuilding partial = find_string_val_in_string_hash( expanded_partial_attr, :partial) path = render_partial_name_to_file_name(partial, @default_extname) import = true end if path store_last_buffer false @stack << StackEntry.new(path, @depth, import) end unless @stack.empty? @stack.last.buffer << "<#{qname}" sorted_attributes = attributes.sort do |a,b| # sort import and import_render like generate and gen_partial so hashs work a_working = a[0] b_working = b[0] working_cmp = [a_working, b_working].collect do |working| if working == ::MasterView::NamespacePrefix+'import_render' working = ::MasterView::NamespacePrefix+'gen_partial' elsif working == ::MasterView::NamespacePrefix+'import' working = ::MasterView::NamespacePrefix+'generate' end working end working_cmp[0] <=> working_cmp[1] end sorted_attributes.each do |name, value| @stack.last.buffer << " #{name}=\"#{value}\"" end @stack.last.buffer << '>' #must output as separate string so simplify_empty_elements can find it end end def characters(text) @stack.last.buffer << text unless @stack.empty? end def comment(comment) @stack.last.buffer << '' unless @stack.empty? end def cdata(content) (@stack.last.buffer << '') unless @stack.empty? end def end_element(uri, localname, qname) unless @stack.empty? if @stack.last.buffer.last == '>' && !ExpandAlwaysElements.include?(qname) #simplify empty elements @stack.last.buffer.pop @stack.last.buffer << '/>' else @stack.last.buffer << '" #must output