最近帮别人写了一个XML的比较的小程序,分享一下
module XMLTool
class Node
attr_reader :nodes, :name, :text, :attributes
def initialize name, text, attributes = {}, ancestor_nodes = []
@name = name
@attributes = attributes
@text = text
@ancestor_nodes = ancestor_nodes.clone
@nodes ||= {}
end
def ancestor_nodes
@ancestor_nodes.clone
end
def add_node node
@nodes[node.name] ||= []
@nodes[node.name] << node
end
# blk params: node name, node array
def each &blk
@nodes.each &blk
end
def has_children?
[email protected]?
end
def compare_with a_node, failed_nodes = [], passed_nodes = []#, *ignore_nodes
#puts "Current Node: #{self.name}, Compared Node: #{a_node.name}"
#failed_nodes = failed_nodes.clone
pass_fg = true
if has_children?
if a_node.has_children?
each do |name, children_nodes|
if a_node.nodes[name].nil?
#puts "Not Found. Node: #{name}, #{attributes}, #{text}"
pass_fg = false
break
else
children_nodes.each do |child_node|
ps_fg = false
a_node.nodes[name].each do |a_child|
if child_node.attributes == a_child.attributes
if child_node.compare_with(a_child, failed_nodes, passed_nodes)[0]
ps_fg = true
break
end
end
end
if ps_fg
next
else
pass_fg = false
break
end
end
end
end
#puts "Not Found. Node: #{name}, #{attributes}, #{text}" if !pass_fg
else
# "Structure Error."
pass_fg = false
end
else
if a_node.has_children?
# "Structure Error: Node :#{name}, #{a_node.name}"
pass_fg = false
else
if a_node.name != name || a_node.attributes != attributes || a_node.text != text
#puts %Q{Node Error:
#Node Name:#{name}, #{a_node.name}
#Attributes: #{attributes}, #{a_node.attributes}
#Value: #{text}, #{a_node.text}
#}
pass_fg = false
end
end
end
#puts error_msg.join("\n") if !error_msg.empty?
if pass_fg
#puts self.name
passed_nodes << self
failed_nodes.delete(self) if failed_nodes.include?(self)
else
failed_nodes << self if !passed_nodes.include? (self)
end
return([pass_fg, failed_nodes])
end
end
end
module XMLTool
class Tree
attr_reader :root
def initialize xml_file
require 'rexml/document'
File.open(xml_file) do |file|
doc = REXML::Document.new(file)
@root = parse_xml(doc.root, Node.new(doc.root.name, doc.root.text, doc.root.attributes))
end
end
def compare_with another_tree, *ignore_nodes
end
def self.filter_failed_nodes failed_nodes
rets = []
failed_nodes.each do |node|
ancestor_chain = node.ancestor_nodes << node
rets << ancestor_chain if rets.empty?
rets.each do |ret|
if (ret & ancestor_chain) == ret
rets.delete ret
rets << ancestor_chain
else
if (ret & ancestor_chain) != ancestor_chain
rets << ancestor_chain
end
end
end
end
return rets
end
private
# Fill Node according element
def parse_xml element, node
#puts element.name
#puts node.ancestor_nodes
if element.has_elements?
element.elements.each do |sub_ele|
ancestor_nodes = node.ancestor_nodes << node
if sub_ele.has_elements?
child_node = Node.new(sub_ele.name, sub_ele.text, sub_ele.attributes, ancestor_nodes)
node.add_node(parse_xml(sub_ele, child_node))
else
node.add_node(Node.new(sub_ele.name, sub_ele.text, sub_ele.attributes, ancestor_nodes))
end
end
end
node
end
end
end
测试代码
$LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include? File.dirname(__FILE__)
require 'xmltool'
include XMLTool
root1 = Tree.new('Completion090_MsgValue_XML.xml').root
root2 = Tree.new('Completion090_MsgValue_XML5.xml').root
#tree.nodes[0].nodes[0].nodes[0].nodes.each do |e|
# puts e.tag_name
# puts e.text
#end
ret = root1.compare_with(root2)
#ret = root2.compare_with(root1)
puts ret[0]
Tree.filter_failed_nodes(ret[1]).each do |ancestor_chain|
ancestor_chain.each_with_index do |node, i|
puts "#{"\s" * 2 * i}#{node.name} #{node.attributes} ->"
end
puts ancestor_chain.last.text
puts "------------------"
end
#puts ret[1][0].name
#puts ret[1][0].attributes
#puts ret[1][1].name
#puts ret[1][1].attributes
之前都没做过XML的东西,nokogiri看了一下不会用,就用rexml解析xml,目前可能还有点bug,不过能凑合用,由于时间跟观众太少的关系,就不解释了。