Module: EquivalentXml

Defined in:
lib/equivalent-xml.rb

Defined Under Namespace

Modules: RSpecMatchers

Constant Summary

DEFAULT_OPTS =
{ :ignore_attr_values => false, :element_order => false, :normalize_whitespace => true }

Class Method Summary (collapse)

Class Method Details

+ (Object) compare_attributes(node_1, node_2, opts, &block)



70
71
72
73
74
75
76
77
78
79
# File 'lib/equivalent-xml.rb', line 70

def compare_attributes(node_1, node_2, opts, &block)

  attr_names_match = node_1.name == node_2.name

  if opts[:ignore_attr_values]
    attr_names_match
  else
    attr_names_match && (node_1.value == node_2.value)
  end
end

+ (Object) compare_cdata(node_1, node_2, opts, &block)



89
90
91
# File 'lib/equivalent-xml.rb', line 89

def compare_cdata(node_1, node_2, opts, &block)
  node_1.text == node_2.text
end

+ (Object) compare_children(node_1, node_2, opts, &block)



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/equivalent-xml.rb', line 93

def compare_children(node_1, node_2, opts, &block)
  if ignore_content?(node_1, opts)
    # Stop recursion and state a match on the children
    result = true
  else
    nodeset_1 = as_nodeset(node_1.children, opts)
    nodeset_2 = as_nodeset(node_2.children, opts)
    result = self.compare_nodesets(nodeset_1,nodeset_2,opts,&block)
  end
  
  if node_1.respond_to?(:attribute_nodes)
    attributes_1 = node_1.attribute_nodes
    attributes_2 = node_2.attribute_nodes
    result = result && self.compare_nodesets(attributes_1,attributes_2,opts,&block)
  end
  result
end

+ (Object) compare_documents(node_1, node_2, opts, &block)



62
63
64
# File 'lib/equivalent-xml.rb', line 62

def compare_documents(node_1, node_2, opts, &block)
  self.equivalent?(node_1.root,node_2.root,opts,&block)
end

+ (Object) compare_elements(node_1, node_2, opts, &block)



66
67
68
# File 'lib/equivalent-xml.rb', line 66

def compare_elements(node_1, node_2, opts, &block)
  (node_1.name == node_2.name) && self.compare_children(node_1,node_2,opts,&block)
end

+ (Object) compare_nodes(node_1, node_2, opts, &block)



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/equivalent-xml.rb', line 31

def compare_nodes(node_1, node_2, opts, &block)
  result = nil
  if [node_1, node_2].any? { |node| not node.respond_to?(:node_type) }
    result = node_1.to_s == node_2.to_s
  elsif (node_1.class != node_2.class) or self.same_namespace?(node_1,node_2) == false
    result = false
  else
    case node_1.node_type
    when Nokogiri::XML::Node::DOCUMENT_NODE
      result = self.compare_documents(node_1,node_2,opts,&block)
    when Nokogiri::XML::Node::ELEMENT_NODE
      result = self.compare_elements(node_1,node_2,opts,&block)
    when Nokogiri::XML::Node::ATTRIBUTE_NODE
      result = self.compare_attributes(node_1,node_2,opts,&block)
    when Nokogiri::XML::Node::CDATA_SECTION_NODE
      result = self.compare_cdata(node_1,node_2,opts,&block)
    when Nokogiri::XML::Node::TEXT_NODE
      result = self.compare_text(node_1,node_2,opts,&block)
    else
      result = self.compare_children(node_1,node_2,opts,&block)
    end
  end
  if block_given?
    block_result = yield(node_1, node_2, result)
    if block_result.is_a?(TrueClass) or block_result.is_a?(FalseClass)
      result = block_result
    end
  end
  return result
end

+ (Object) compare_nodesets(nodeset_1, nodeset_2, opts, &block)



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/equivalent-xml.rb', line 111

def compare_nodesets(nodeset_1, nodeset_2, opts, &block)
  local_set_1 = nodeset_1.dup
  local_set_2 = nodeset_2.dup
  
  if local_set_1.length != local_set_2.length
    return false
  end

  local_set_1.each do |search_node|
    found_node = local_set_2.find { |test_node| self.equivalent?(search_node,test_node,opts,&block) }

    if found_node.nil?
      return false
    else
      if search_node.is_a?(Nokogiri::XML::Element) and opts[:element_order]
        if search_node.parent.elements.index(search_node) != found_node.parent.elements.index(found_node)
          return false
        end
      end
      local_set_2.delete(found_node)
    end
  end
  return local_set_2.length == 0
end

+ (Object) compare_text(node_1, node_2, opts, &block)



81
82
83
84
85
86
87
# File 'lib/equivalent-xml.rb', line 81

def compare_text(node_1, node_2, opts, &block)
  if opts[:normalize_whitespace]
    node_1.text.strip.gsub(/\s+/,' ') == node_2.text.strip.gsub(/\s+/,' ')
  else
    node_1.text == node_2.text
  end
end

+ (Boolean) equivalent?(node_1, node_2, opts = {}) {|n1, n2, result| ... }

Determine if two XML documents or nodes are equivalent

Parameters:

  • node_1 (Nokogiri::XML::Node, Nokogiri::XML::NodeSet)

    The first top-level XML node to compare

  • node_2 (Nokogiri::XML::Node, Nokogiri::XML::NodeSet)

    The secton top-level XML node to compare

  • opts (Hash) (defaults to: {})

    Options that determine how certain comparisons are evaluated

Options Hash (opts):

  • :element_order (Boolean) — default: false

    Child elements must occur in the same order to be considered equivalent

  • :normalize_whitespace (Boolean) — default: true

    Collapse whitespace within Text nodes before comparing

  • :ignore_content (String, Array) — default: nil

    CSS selector(s) of nodes for which the content (text and child nodes) should be ignored when comparing for equivalence

Yields:

  • (n1, n2, result)

    The two nodes currently being evaluated, and whether they are considered equivalent. The block can return true or false to override the default evaluation

Returns:

  • (Boolean)

    true or false



19
20
21
22
23
24
25
26
27
28
29
# File 'lib/equivalent-xml.rb', line 19

def equivalent?(node_1, node_2, opts = {}, &block)
  opts = DEFAULT_OPTS.merge(opts)
  if [node_1, node_2].any? { |node| node.is_a?(Nokogiri::XML::NodeSet)}
    self.compare_nodesets(as_nodeset(node_1, opts), as_nodeset(node_2, opts), opts, &block)
  else
    # Don't let one node to coerced to a DocumentFragment if the other one is a Document
    node_2 = Nokogiri::XML(node_2) if node_1.is_a?(Nokogiri::XML::Document) and !node_2.is_a?(Nokogiri::XML::Node)
    node_1 = Nokogiri::XML(node_1) if node_2.is_a?(Nokogiri::XML::Document) and !node_1.is_a?(Nokogiri::XML::Node)
    self.compare_nodes(as_node(node_1), as_node(node_2), opts, &block)
  end
end

+ (Boolean) same_namespace?(node_1, node_2)

Determine if two nodes are in the same effective Namespace

Parameters:

  • node_1 (Nokogiri::XML::Node OR String)

    The first node to test

  • node_2 (Nokogiri::XML::Node OR String)

    The second node to test

Returns:

  • (Boolean)


140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/equivalent-xml.rb', line 140

def same_namespace?(node_1, node_2)
  args = [node_1,node_2]

  # CharacterData nodes shouldn't have namespaces. But in Nokogiri,
  # they do. And they're invisible. And they get corrupted easily.
  # So let's wilfully ignore them. And while we're at it, let's
  # ignore any class that doesn't know it has a namespace.
  if args.all? { |node| not node.respond_to?(:namespace) } or 
     args.any? { |node| node.is_a?(Nokogiri::XML::CharacterData) }
       return true
  end
  
  href1 = node_1.namespace.nil? ? '' : node_1.namespace.href
  href2 = node_2.namespace.nil? ? '' : node_2.namespace.href
  return href1 == href2
end