class Rtml::Rules::DomRuleset def initialize @tags = HashWithIndifferentAccess.new @root = nil @order = HashWithIndifferentAccess.new end def freeze super @tags.freeze @root.freeze @order.freeze self end # Loads the ruleset from a file. def parse(file) instance_eval File.read(file), file, 1 self end # Loads the ruleset from a file. def self.parse(file) self.new.parse(file) end # Returns the tag with the specified name, or nil if it can't be found def tag_rules(name) return nil unless tags.include?(name) tag(name) end # If a parent and children are supplied, the parent element may contain the specified children. This does not # enforce order. # If a block is supplied, notation can be simplified. # # If a parent is supplied, the valid children for that parent are returned. # If no parent is supplied, nil is returned. # # A few options are also allowed: # :minimum => the minimum number of children, 0 for no minimum # :maximum => the maximum number of children, 0 for no maximum # # Examples: # ruleset.children :head, :base, :link, :defaults # [:base, :link, :defaults] are valid children of :head # # # Does the same thing: # ruleset.children do # head :base, :link, :defaults # end # # # Retrieve the array of valid children for :head # ruleset.children(:head) # def children(parent = nil, *children, &block) r = nil if parent # make sure all tags are registered options = children.extract_options! tags = tag(parent, *children) parent_tag = tags.shift parent_tag.child_tags << (children.flatten.collect { |i| i.to_s } + [options]) unless tags.empty? r = parent_tag.children end if block_given? proxy_into(:children).new(self).instance_eval &block end r end # Creates a proxy class for the specified method. def proxy_into(method_name) @proxy_classes ||= HashWithIndifferentAccess.new return @proxy_classes[method_name] if @proxy_classes[method_name] klass = Class.new line = __LINE__ + 2 code = <<-end_code def initialize(parent) @parent = parent # Proxy known tags in case they clash with Ruby methods @parent.tags.each do |tag| line = __LINE__ + 2 kode = <<-end_kode def \#{tag}(*args, &block) @parent.#{method_name} :\#{tag}, *args, &block end end_kode instance_eval kode, __FILE__, line end end def method_missing(name, *args, &block) @parent.#{method_name} name, *args, █ end end_code klass.class_eval code, __FILE__, line @proxy_classes[method_name] = klass end # If parent and children are supplied, the order of the children within the parent element will be enforced. # If a block is supplied, notation can be simplified. # # If a parent is supplied, then the order of its children is returned. # If no parent is supplied, then nil is returned. # # Examples: # ruleset.order :head, :base, :link, :defaults # order [:base, :link, :defaults] beneath :head # # # Does the same thing: # ruleset.order do # head :base, :link, :defaults # end # # # Retrieve the order of :head # ruleset.order(:head) # def order(parent = nil, *children, &block) if parent # make sure all tags are registered parent_tag = tag(parent, *children).first parent_tag.order.concat children.flatten.collect { |i| i.to_s } end if block_given? proxy_into(:order).new(self).instance_eval &block end return tag(parent).order if parent return nil end # Declares one or more tags. If a tag is used that has not been previously declared, it will be # added automatically. Returns an array of all added tags, or the tag itself if there was only one. # # If no arguments are specified, nothing happens and an array of all registered tags is returned. def tag(*value) return @tags if value.empty? ts = [] value.flatten.each do |t| if @tags.key?(t) || !frozen? ts << (@tags[t] ||= Rtml::Rules::DomTag.new(t.to_s)) end end ts.length == 1 ? ts.first : ts end def all_tag_names tags.collect { |a| a[0] } end alias tags tag # Returns true if the specified String or Symbol represents a root tag. def root?(name) name.to_s == @root end # Returns true if the specified String or Symbol represents a tag. def tag?(name) tags.collect { |t| t.name }.include?(name.to_s) end # Specifies a root tag. If a root tag has already been specified, an error will be raised. # If no root tag is specified, the current root tag is returned instead, or nil if there is none. def root(*value) unless value.empty? raise Rtml::Errors::MultipleRootsError, "Can only have one root element" if @root @root = value.shift.to_s tag @root end @root end end