#encoding: utf-8 module Xampl class RemoveWhitespaceContentVisitor < Visitor # # This can be useful for cleaning up DSLs embedded in XML/xampl # def initialize super end def after_visit(xampl) xampl.after_visit_by_element_kind(self) if xampl.respond_to? "after_visit_by_element_kind" end def after_visit_simple_content(xampl) text = xampl.content xampl.content = nil if text && text.match(/\S/).nil? end def after_visit_data_content(xampl) text = xampl.content xampl.content = nil if text && text.match(/\S/).nil? end end class CountingVisitor < Visitor attr_accessor :count def initialize super @count = 0 end def before_visit(xampl) @count += 1 end end class ResetIsChanged < Visitor def initialize super end def start(xampl, verbose=false) @verbose = verbose if verbose puts "RESET IS CHANGED.... #{xampl}" puts "SKIPPING!!!" unless xampl.persist_required and xampl.load_needed end return if xampl.persist_required and xampl.load_needed super(xampl) end def before_visit(xampl) if xampl.is_changed then puts "RESET CHANGED: #{xampl} and continue" if verbose xampl.is_changed = false; else puts "RESET CHANGED: #{xampl} block" if verbose @no_children = true end end end class MarkChangedDeep < Visitor def initialize super end def before_visit(xampl) xampl.changed if xampl.persist_required end end class PrettyXML < Visitor attr_accessor :ns_to_prefix, :start_body, :body, :out attr_accessor :indent, :indent_step @@compact = true def PrettyXML.compact @@compact end def PrettyXML.compact=(v) @@compact = v end def initialize(out="", skip=[]) super() @out = out @indent = "" @indent_step = " " @start_attr_indent = "" @was_attr = false @depth = 0 @skip = {} skip.each { |ns| @skip[ns] = ns } @ns_to_prefix = {} @start_body = nil @body = "" @attr_list = nil @insert_comment = nil end def short_circuit body << @insert_comment if @insert_comment @insert_comment = nil end def cycle(xampl) @short_circuit = true @insert_comment = "" return true end def revisit(xampl) @insert_comment = "" #body << "" return true end def register_ns(ns) if (0 == ns.length) then return "" end prefix = ns_to_prefix[ns] if (nil == prefix) then preferred = XamplObject.lookup_preferred_ns_prefix(ns) prefix = "" << preferred << ":" if preferred prefix = "ns" << ns_to_prefix.size.to_s << ":" unless prefix ns_to_prefix[ns] = prefix end return prefix end def attr_esc(s) if (s.kind_of? XamplObject) return attr_esc(s.to_xml) end result = s.to_s.dup result.gsub!("&", "&") result.gsub!("<", "<") result.gsub!(">", ">") result.gsub!("'", "'") result.gsub!("\"", """) return result end def content_esc(s) result = s.to_s.dup return result if (s.kind_of? XamplObject) result.gsub!("&", "&") result.gsub!("<", "<") return result end def attribute(xampl) @attr_list = [] pid = nil if (nil != xampl.attributes) then xampl.attributes.each do |attr_spec| unless @skip[attr_spec[2]] then value = xampl.instance_variable_get(attr_spec[0]) if value then prefix = (2 < attr_spec.length) ? register_ns(attr_spec[2]) : "" @attr_list << (" " << prefix << attr_spec[1] << "='" << attr_esc(value) << "'") end end end @attr_list.sort! end #@attr_list << " xampl:marker='OKAY: #{ xampl }'" end def persist_attribute(xampl) @attr_list = [] if xampl.persist_required then index = xampl.indexed_by.to_s if index then value = xampl.get_the_index @attr_list << (" " << index << "='" << attr_esc(value) << "'") if value end else attribute(xampl) #@attr_list << " xampl:wtf='WTF??'" end end def show_attributes(attr_indent) if (nil == @attr_list) then return "" else result = @attr_list.join(attr_indent) if (0 == result.length) then return "" else return result end end end def do_indent return "\n" << @indent << (@indent_step * @depth) end def start_element(xampl) xampl.accessed if @revisiting or @cycling then @short_circuit = true persist_attribute(xampl) else attribute(xampl) end tag = xampl.tag ns = xampl.ns indent = do_indent tag_info = "" << "<" << register_ns(ns) << tag attr_indent = "" << indent << (" " * tag_info.size) unless @start_body then @start_attr_indent = attr_indent attr_defn = show_attributes(attr_indent) @start_body = "" << indent << tag_info << attr_defn @was_attr = true if 0 < attr_defn.size else @body << indent << tag_info << show_attributes(attr_indent) end end def end_element(xampl) tag = xampl.tag ns = xampl.ns if @@compact then @body << "" else @body << do_indent << "" end end def define_ns result = "" indent = @was_attr ns_to_prefix.each do |ns, prefix| result = sprintf("%s%s xmlns:%s='%s'", result, indent ? @start_attr_indent : "", prefix[0..-2], ns) indent = true end return result end def done out << @start_body << define_ns << @body end def before_visit_without_content(xampl) start_element(xampl) @body << "/>" end def before_visit_simple_content(xampl) start_element(xampl) @body << ">" @body << content_esc(xampl._content) if xampl._content end_element(xampl) end def before_visit_data_content(xampl) start_element(xampl) @body << ">" @body << content_esc(xampl._content) if xampl._content @depth += 1 end def after_visit_data_content(xampl) @depth += -1 end_element(xampl) end def before_visit_mixed_content(xampl) start_element(xampl) @body << ">" @depth += 1 end def after_visit_mixed_content(xampl) @depth -= 1 end_element(xampl) end def before_visit(xampl) unless xampl.kind_of?(XamplObject) and @skip[xampl.ns] then if xampl.respond_to? "before_visit_by_element_kind" then xampl.before_visit_by_element_kind(self) else @body << xampl.to_s end else @no_children = true end end def after_visit(xampl) xampl.after_visit_by_element_kind(self) if xampl.respond_to? "after_visit_by_element_kind" end def visit_string(string) @body << string end end class CopyXML < Visitor attr_accessor :ns_to_prefix, :start_body, :body def CopyXML.copy(root, translate_pids={}) CopyXML.new.make_copy(root, translate_pids) end def make_copy(root, translate_pids) @was_attr = false @ns_to_prefix = {} @start_body = nil @body = "" @attr_list = nil @pid_translations_old_to_new = translate_pids @pid_translations_new_to_old = translate_pids.invert # @persisted_xampl_found = { @current_root.get_the_index => root } @persisted_xampl_found = {(@current_root || root).get_the_index => root} #TODO -- is root ever okay here? @copies_by_old_pid = {} while true do copy_these = [] @persisted_xampl_found.each do |pid, xampl| copy_these << xampl unless @copies_by_old_pid[pid] end break if 0 == copy_these.length @persisted_xampl_found = {} copy_these.each do |xampl| @current_root = xampl @out = "" @copies_by_old_pid[@current_root.get_the_index] = @out copy_xampl(@current_root) end end return @copies_by_old_pid end def copy_xampl(root) start(root).done end @@base_pid = Time.now.to_i.to_s + "_" @@gen_pid = 0 def get_the_new_pid(xampl) current_pid = xampl.get_the_index @persisted_xampl_found[current_pid] = xampl new_pid = @pid_translations_old_to_new[current_pid] unless new_pid then @@gen_pid += 1 new_pid = @@base_pid + @@gen_pid.to_s @pid_translations_old_to_new[current_pid] = new_pid @pid_translations_new_to_old[new_pid] = current_pid end return new_pid end def cycle(xampl) raise XamplException.new(:cycle_detected_in_xampl_cluster) unless xampl.kind_of?(XamplPersistedObject) return true end def revisit(xampl) return true end def register_ns(ns) if (0 == ns.length) then return "" end prefix = ns_to_prefix[ns] if (nil == prefix) then preferred = XamplObject.lookup_preferred_ns_prefix(ns) prefix = "" << preferred << ":" if preferred prefix = "ns" << ns_to_prefix.size.to_s << ":" unless prefix ns_to_prefix[ns] = prefix end return prefix end def attr_esc(s) if (s.kind_of? XamplObject) return attr_esc(s.to_xml) end result = s.to_s.dup result.gsub!("&", "&") result.gsub!("<", "<") result.gsub!(">", ">") result.gsub!("'", "'") result.gsub!("\"", """) return result end def content_esc(s) result = s.to_s.dup return result if (s.kind_of? XamplObject) result.gsub!("&", "&") result.gsub!("<", "<") return result end def attribute(xampl) @attr_list = [] if (nil != xampl.attributes) then xampl.attributes.each { |attr_spec| prefix = (2 < attr_spec.length) ? register_ns(attr_spec[2]) : "" value = get_the_new_pid(xampl.instance_variable_get(attr_spec[0])) @attr_list << (" " << prefix << attr_spec[1] << "='" << attr_esc(value) << "'") \ unless nil == value } end end def persist_attribute(xampl) @attr_list = [] pattr = xampl.indexed_by.to_s if (nil != xampl.attributes) then xampl.attributes.each { |attr_spec| if pattr == attr_spec[1] then prefix = (2 < attr_spec.length) ? register_ns(attr_spec[2]) : "" value = xampl.instance_variable_get(attr_spec[0]) @attr_list << (" " << prefix << attr_spec[1] << "='" << attr_esc(value) << "'") \ unless nil == value break end } end end def show_attributes result = @attr_list.join(" ") if (0 == result.length) then return "" else return result end end def start_element(xampl) tag = xampl.tag ns = xampl.ns tag_info = "" << "<" << register_ns(ns) << tag unless @start_body then attribute(xampl) attr_defn = show_attributes @start_body = "" << tag_info << attr_defn @was_attr = true if 0 < attr_defn.size else if xampl.persist_required then @no_children = true persist_attribute(xampl) else attribute(xampl) end @body << tag_info << show_attributes end end def end_element(xampl) tag = xampl.tag ns = xampl.ns @body << "" end def define_ns result = "" ns_to_prefix.each { |ns, prefix| result = sprintf("%s xmlns:%s='%s'", result, prefix[0..-2], ns) } return result end def done out << @start_body << define_ns << @body end def before_visit_without_content(xampl) start_element(xampl) @body << "/>" end def before_visit_simple_content(xampl) start_element(xampl) if @no_children then @body << "/>" else @body << ">" @body << content_esc(xampl._content) if xampl._content end_element(xampl) end end def before_visit_data_content(xampl) start_element(xampl) if @no_children then @body << "/>" else @body << ">" @body << content_esc(xampl._content) if xampl._content end end def after_visit_data_content(xampl) end_element(xampl) unless @no_children end def before_visit_mixed_content(xampl) if @no_children then @body << "/>" else start_element(xampl) @body << ">" end end def after_visit_mixed_content(xampl) end_element(xampl) unless @no_children end def before_visit(xampl) if xampl.respond_to? "before_visit_by_element_kind" then xampl.before_visit_by_element_kind(self) else @body << xampl.to_s end end def after_visit(xampl) xampl.after_visit_by_element_kind(self) if xampl.respond_to? "after_visit_by_element_kind" end def visit_string(string) @body << string end end module XamplObject def remove_ws_content() RemoveWhitespaceContentVisitor.new.start(self) end def pp_xml(out="", skip=[]) PrettyXML.new(out, skip).start(self).done end def copy_xampl(root, translate_pids={}) CopyXML.copy(root, translate_pids) end def mark_changed_deep MarkChangedDeep.new.start(self) end end end