require 'htree/modules' require 'htree/tag' require 'htree/context' require 'htree/container' module HTree class Elem # :stopdoc: class << self alias new! new end # :startdoc: # The first argument _name_ should be an instance of String or HTree::Name. # # The rest of arguments should be a sequence of follows. # [Hash object] used as attributes. # [String object] specified string is converted to HTree::Text. # [HTree::Node object] used as a child. # [HTree::Doc object] # used as children. # It is expanded except HTree::XMLDecl and HTree::DocType objects. # [Array of String, HTree::Node, HTree::Doc] used as children. # [HTree::Context object] # used as as context which represents XML namespaces. # This should apper once at most. # # HTree::Location object is accepted just as HTree::Node. # # If the rest arguments consists only # Hash and HTree::Context, empty element is created. # # p HTree::Elem.new("e").empty_element? # => true # p HTree::Elem.new("e", []).empty_element? # => false def Elem.new(name, *args) attrs = [] children = [] context = nil args.each {|arg| arg = arg.to_node if HTree::Location === arg case arg when Context raise ArgumentError, "multiple context" if context context = arg when Hash arg.each {|k, v| attrs << [k, v] } when Array arg.each {|a| a = a.to_node if HTree::Location === a case a when HTree::Doc children.concat(a.children.reject {|c| HTree::XMLDecl === c || HTree::DocType === c }) when HTree::Node children << a when String children << Text.new(a) else raise TypeError, "unexpected argument: #{arg.inspect}" end } when HTree::Doc children.concat(arg.children.reject {|c| HTree::XMLDecl === c || HTree::DocType === c }) when HTree::Node children << arg when String children << Text.new(arg) else raise TypeError, "unexpected argument: #{arg.inspect}" end } context ||= DefaultContext if children.empty? && args.all? {|arg| Hash === arg || Context === arg } children = nil end new!(STag.new(name, attrs, context), children) end def initialize(stag, children=nil, etag=nil) # :notnew: unless stag.class == STag raise TypeError, "HTree::STag expected: #{stag.inspect}" end unless !children || children.all? {|c| c.kind_of?(HTree::Node) and !c.kind_of?(HTree::Doc) } unacceptable = children.reject {|c| c.kind_of?(HTree::Node) and !c.kind_of?(HTree::Doc) } unacceptable = unacceptable.map {|uc| uc.inspect }.join(', ') raise TypeError, "Unacceptable element child: #{unacceptable}" end unless !etag || etag.class == ETag raise TypeError, "HTree::ETag expected: #{etag.inspect}" end @stag = stag @children = (children ? children.dup : []).freeze @empty = children == nil && etag == nil @etag = etag end def context; @stag.context end # +element_name+ returns the name of the element name as a Name object. def element_name() @stag.element_name end def empty_element? @empty end def each_attribute(&block) # :yields: attr_name, attr_text @stag.each_attribute(&block) end def get_subnode_internal(index) # :nodoc: case index when String name = Name.parse_attribute_name(index, DefaultContext) update_attribute_hash[name.universal_name] when Name update_attribute_hash[index.universal_name] when Integer if index < 0 || @children.length <= index nil else @children[index] end else raise TypeError, "invalid index: #{index.inspect}" end end # call-seq: # elem.subst_subnode(pairs) -> elem # # The argument _pairs_ should be a hash or an assocs. # # The key of pairs should be one of following. # [HTree::Name or String object] attribute name. # [Integer object] child index. # # The value of pairs should be one of follows. # [HTree::Node object] specified object is used as is. # [String object] specified string is converted to HTree::Text # [Array of above] specified HTree::Node and String is used in that order. # [nil] delete corresponding node. # # e = HTree('').root # p e.subst_subnode({0=>HTree(''), 2=>HTree('')}) # p e.subst_subnode([[0, HTree('')], [2,HTree('')]]) # # => # {elem {emptyelem } {emptyelem } {emptyelem }} # {elem {emptyelem } {emptyelem } {emptyelem }} # def subst_subnode(pairs) hash = {} pairs.each {|index, value| case index when Name, Integer when String index = Name.parse_attribute_name(index, DefaultContext) else raise TypeError, "invalid index: #{index.inspect}" end value = value.to_node if HTree::Location === value case value when Node value = [value] when String value = [value] when Array value = value.dup when nil value = [] else raise TypeError, "invalid value: #{value.inspect}" end value.map! {|v| v = v.to_node if HTree::Location === v case v when Node v when String Text.new(v) else raise TypeError, "invalid value: #{v.inspect}" end } if !hash.include?(index) hash[index] = [] end hash[index].concat value } attrs = [] @stag.attributes.each {|k, v| if hash.include? k v = hash[k] if !v.empty? attrs << {k=>Text.concat(*v)} end hash.delete k else attrs << {k=>v} end } hash.keys.each {|k| if Name === k v = hash[k] if !v.empty? attrs << {k=>Text.concat(*v)} end hash.delete k end } children_left = [] children = @children.dup children_right = [] hash.keys.sort.each {|index| value = hash[index] if index < 0 children_left << value elsif children.length <= index children_right << value else children[index] = value end } children = [children_left, children, children_right].flatten if children.empty? && @empty Elem.new( @stag.element_name, @stag.context, *attrs) else Elem.new( @stag.element_name, @stag.context, children, *attrs) end end end module Elem::Trav private def update_attribute_hash if defined?(@attribute_hash) @attribute_hash else h = {} each_attribute {|name, text| h[name.universal_name] = text } @attribute_hash = h end end end end