Git Product home page Git Product logo

equivalent-xml's Introduction

equivalent-xml

Description

Problem

Testing XML output is difficult:

  • Comparing text output is brittle due to the vagaries of serialization.
  • Attribute order doesn't matter.
  • Element order matters sometimes, but not always.
  • Text sometimes needs to be normalized, but CDATA doesn't.
  • Nodes in the same namespace don't always use the same prefix
  • Etc.

Solution

EquivalentXml - Now for Nokogiri and Oga!

Build Status Dependency Status

Use

EquivalentXml.equivalent?(node_1, node_2, opts = { :element_order => false, :normalize_whitespace => true }) { |n1, n2, result| ... }

node_1 and node_2 can be any Node descendants (or any string containing an XML document or document fragment). The most common use case is to compare two Document instances.

node_1 is equivalent to node_2 if and only if:

  • node_1 and node_2 are of the same class
  • node_1 and node_2 are in the same namespace
  • node_1 and node_2 have the same number of child nodes (excluding ProcessingInstructions, Comments and empty Text nodes)
  • For each child of node_1, there is exactly one equal child of node_2
  • If called with :element_order => true, equivalent child elements must be in the same relative position in order to be considered equal

If a block is given, the block will be called every time two nodes are compared. The parameters will be the two nodes being compared as well as the result of the comparison. If the block explicitly returns true or false (a real TrueClass or FalseClass, not just an expression that can be coerced to true or false), the return value will override the result of the comparison.

Element nodes are equivalent if they have the same name, and their child nodesets are equal (as defined above)

Attribute nodes are equivalent if their names and values match exactly

CDATA nodes are equivalent if their text strings match exactly, including leading, trailing, and internal whitespace

Non-CDATA CharacterData nodes are equivalent if their text strings match after stripping leading and trailing whitespace and collapsing internal whitespace to a single space

Document nodes are equivalent if their root nodes are equal

ProcessingInstruction and Comment nodes are ignored

Options

:element_order => true

Require elements to be in the same relative position in order to be considered equivalent.

:normalize_whitespace => false

Don't normalize whitespace within text nodes; require text nodes to match exactly.

:ignore_content => ["Device > SerialNumber", "Device > ICCID"]

A single CSS selector, or an array of CSS selectors, of nodes for which the content (text and child nodes) should be ignored when comparing for equivalence. Defaults to nil. (Uses Nokogiri's Node#css(*rules) to conduct the search.)

Using with RSpec

EquivalentXml includes a custom matcher for RSpec (version >=1.2.4) that makes including XML equivalencies in your spec tests a cinch!

Add below two line to spec_helper.rb:

require 'rspec/matchers' # req by equivalent-xml custom matcher `be_equivalent_to`
require 'equivalent-xml'

Equivalency:

expect(node_1).to be_equivalent_to(node_2)
expect(node_1).not_to be_equivalent_to(node_2)

Chained modifiers:

expect(node_1).to be_equivalent_to(node_2).respecting_element_order
expect(node_1).to be_equivalent_to(node_2).with_whitespace_intact
expect(node_1).to be_equivalent_to(node_2).respecting_element_order.with_whitespace_intact
expect(node_1).to be_equivalent_to(node_2).ignoring_content_of("SerialNumber")

Contributing to equivalent-xml

  • Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
  • Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
  • Fork the project
  • Start a feature/bugfix branch
  • Commit and push until you are happy with your contribution
  • Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

History

  • 1.0.0 - Added Oga support! Without breaking Nokogiri support!
  • 0.6.0 - Add ability to ignore specific attributes (from paclough); remove circular dependencies (nbibler); Simplify compatibility workaround for message methods (jirutka)
  • 0.5.1 - Fix false negative when comparing a Nokogiri::XML::Node to a string (introduced in 0.5.0)
  • 0.5.0 - Allow to compare XML-Fragments in Strings (contrib. by webmasters)
  • 0.4.4 - Fix rspec 3 deprecation warnings while maintaining compatibility with rspec 1 & 2 (w/assist from barelyknown & DanielHeath)
  • 0.4.3 - Updates for rspec 3
  • 0.4.2 - Move version back into gemspec for now
  • 0.4.1 - Improved RSpec version checking (contrib. by elandesign)
  • 0.4.0 - Added :ignore_attr_values option (contrib. by ivannovosad)
  • 0.3.0 - Added :ignore_content option (contrib. by moklett)
  • 0.2.9 - Fix for rspec-rails >= 2.7 (contrib. by jcoyne)
  • 0.2.8 - Allow comparison against nodesets (contrib. by gkellogg)
  • 0.2.7 - Auto-require RSpec matchers if RSpec is loaded
  • 0.2.6 - Added documentation for RSpec matchers
  • 0.2.5 - Added YARD documentation
  • 0.2.4 - Fixed comparison of non-XML input
  • 0.2.3 - Improved handling of non-XML input
  • 0.2.2 - Dependency update
  • 0.2.1 - Hotfix: Missing files in gemspec
  • 0.2.0 - Custom rspec matchers
  • 0.1.6 - Allow caller to override comparison result in block
  • 0.1.5 - Allow XML documents to be passed as strings, re-instituting dependency on nokogiri
  • 0.1.4 - Hotfix: Missing block parameter on compare_nodesets()
  • 0.1.3 - Hotfix: Missing block parameter on compare_nodesets()
  • 0.1.2 - Yield evaluated nodes and provisional result if block given
  • 0.1.1 - Removed explicit runtime dependency on nokogiri
  • 0.1.0 - Initial release

Copyright

Copyright (c) 2011-16 Michael B. Klein. See LICENSE.txt for further details.

equivalent-xml's People

Contributors

coffeejunk avatar doolin avatar elandesign avatar eric-guo avatar gkellogg avatar ivannovosad avatar jcoyne avatar jeremyf avatar jirutka avatar marcoemrich avatar mbklein avatar moklett avatar nbibler avatar paclough avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

equivalent-xml's Issues

can't require 'version'

/home/travis/.rvm/gems/ruby-2.0.0-p353/gems/activesupport-4.0.4/lib/active_support/dependencies.rb:229:in require': cannot load such file -- equivalent-xml/version (LoadError) from /home/travis/.rvm/gems/ruby-2.0.0-p353/gems/activesupport-4.0.4/lib/active_support/dependencies.rb:229:inblock in require'
from /home/travis/.rvm/gems/ruby-2.0.0-p353/gems/activesupport-4.0.4/lib/active_support/dependencies.rb:214:in load_dependency' from /home/travis/.rvm/gems/ruby-2.0.0-p353/gems/activesupport-4.0.4/lib/active_support/dependencies.rb:229:inrequire'
from /home/travis/.rvm/gems/ruby-2.0.0-p353/gems/equivalent-xml-0.4.1/lib/equivalent-xml.rb:1:in `<top (required)>'

Ability to set global default options

First up, thanks for this gem! It is exactly what I need for testing some XML output and is vastly simplifying a lot of code I wrote. Thank you!

One thing I would like to change is the ability to set the default options. In my tests I always want to respect element order, yet I have to call (using the rspec matchers) .respecting_element_order every time. I'd prefer to be able to set that once for my test suite and not have to repeat myself.

I'd love to help add this, if you're interested. Let me know.

Can we have a new version on rubygems.org? Please??

equivalent-xml is awesome, except the latest version on rubygems.org does not appear to contain this commit:

a843004

It would be great to get a new version up on rubygems.org so we could all use this feature without pulling straight from the GitHub repository.

Thanks!
tommy

Detecting unescaped xml entities

equivalent-xml ignores unescaped XML entities. Try with this spec:

  it "should detect xml entities" do
    doc1 = "<doc xmlns='foo:bar'><first order='1'>Sam & Max</first><second>things</second></doc>"
    doc2 = "<doc xmlns='foo:bar'><first order='1'>Sam Max</first><second>things</second></doc>"
    expect(doc1).not_to be_equivalent_to(doc2)
  end

That fails because Nokogiri is not configured with STRICT or NOENT options (see here)

I'm not sure what would be the most appropriate behaviour. Using STRICT would break other specs, like the one where you're comparing HTML:

  context "(on fragments consisting of multiple nodes)" do
    it "should compare all nodes" do
      doc1 = "<h1>Headline</h1><h1>Headline</h1>"
      doc2 = "<h1>Headline</h1><h2>Headline2</h2>"
      expect(doc1).not_to be_equivalent_to(doc2)
    end
  end

So if you need equivalient-xml to handle invalid XML strings, you're left with NOENT. Unfortunately, I don't see any way to use parsing options with Nokogiri::XML#fragment (here), that's why I haven't opened a PR, sorry.

option to log out where the difference is

Hi,

I'm new user of this gem, and also new to GitHup. This is not an issue, it's actually a request.
I just recently use this gem for my work project, it's working great, I love it.
It will be really helpful if the gem can show/print where the difference is?
Thanks for your time reading this.

Regards,
Phuong

Compare over more node with the same name:

the following nodes, will be wrong compare:

text_1 = '<?xml version="1.0"?>
<info>
<entry
   kind="dir"
   path="."
   revision="29">
<url>file:///home/user/svnrep/svnserver</url>
<repository>
<root>file:///home/user/svnrep/svnserver</root>
<uuid>23296ed4-733e-4fb8-a59a-8fb1a468b90c</uuid>
</repository>
<wc-info>
<schedule>normal</schedule>
<depth>infinity</depth>
</wc-info>
<commit
   revision="29">
<author>user</author>
<date>2013-03-15T12:20:26.098031Z</date>
</commit>
</entry>


<entry
   kind="dir"
   path="Projekt"
   revision="28">
<url>file:///home/user/svnrep/svnserver/Projekt</url>
<repository>
<root>file:///home/user/svnrep/svnserver</root>
<uuid>423296ed4-733e-4fb8-a59a-8fb1a468b90c</uuid>
</repository>
<wc-info>
<schedule>normal</schedule>
<depth>infinity</depth>
</wc-info>
<commit
   revision="28">
<author>user</author>
<gooo a="4" b="2">
<date>2013-03-08T15:19:56.178552Z</date>
</commit>


</entry>
<entry
   kind="dir"
   path="./"
   revision="29">
<url>file:///home/user/svnrep/svnserver</url>
<repository>
<root>file:///home/user/svnrep/svnserver</root>
<uuid>23296ed4-733e-4fb8-a59a-8fb1a468b90c</uuid>
</repository>
<wc-info>
<schedule>normal</schedule>
<depth>infinity</depth>
</wc-info>
<commit
   revision="29">
<author>user</author>
<date>2013-03-15T12:20:26.098031Z</date>
</commit>
</entry> 


<entry
   kind="dir"
   path="Projekt"
   revision="1">
<url>file:///home/user/svnrep/svnserver/Projekt</url>
<repository>
<root>file:///home/user/svnrep/svnserver</root>
<uuid>423296ed4-733e-4fb8-a59a-8fb1a468b90c</uuid>
</repository>
<wc-info>
<schedule>normal</schedule>
<depth>infinity</depth>
</wc-info>
<commit
   revision="28">
<author>user</author>
<gooo a="4" b="2">
<date>2013-03-08T15:19:56.178552Z</date>
</commit>
</entry>
</info>
'
node_1 = Nokogiri::XML(text_1)
text_2 = '<?xml version="1.0"?>
<info>
<entry
   kind="dir"
   path="./"
   revision="29">
<url>file:///home/user/svnrep/svnserver</url>
<repository>
<root>file:///home/user/svnrep/svnserver</root>
<uuid>23296ed4-733e-4fb8-a59a-8fb1a468b90c</uuid>
</repository>
<wc-info>
<schedule>normal</schedule>
<depth>infinity</depth>
</wc-info>
<commit
   revision="29">
<author>user</author>
<date>2013-03-15T12:20:26.098031Z</date>
</commit>
</entry>

<entry
   kind="dir"
   path="Projekt"
   revision="28">
<url>file:///home/user/svnrep/svnserver/Projekt</url>
<repository>
<root>file:///home/user/svnrep/svnserver</root>
<uuid>423296ed4-733e-4fb8-a59a-8fb1a468b90c</uuid>
</repository>
<wc-info>
<schedule>normal</schedule>
<depth>infinity</depth>
</wc-info>
<commit
   revision="28">
<author>user</author>
<gooo a="4" b="2">
<date>2013-03-08T15:19:56.178552Z</date>
</commit>
</entry>

<entry
   kind="dir"
   path="."
   revision="29">
<url>file:///home/user/svnrep/svnserver</url>
<repository>
<root>file:///home/user/svnrep/svnserver</root>
<uuid>23296ed4-733e-4fb8-a59a-8fb1a468b90c</uuid>
</repository>
<wc-info>
<schedule>normal</schedule>
<depth>infinity</depth>
</wc-info>
<commit
   revision="29">
<author>user</author>
<date>2013-03-15T12:20:26.098031Z</date>
</commit>
</entry> 

<entry
   kind="dir"
   path="Projekt"
   revision="1">
<url>file:///home/user/svnrep/svnserver/Projekt</url>
<repository>
<root>file:///home/user/svnrep/svnserver</root>
<uuid>423296ed4-733e-4fb8-a59a-8fb1a468b90c</uuid>
</repository>
<wc-info>
<schedule>normal</schedule>
<depth>infinity</depth>
</wc-info>
<commit
   revision="28">
<author>user</author>
<gooo a="4" b="2">
<date>2013-03-08T15:19:56.178552Z</date>
</commit>
</entry>
</info>
'

with the options: :element_order => false, :normalize_whitespace => true

but with a diff these are the same:

@@ -3 +3 @@
-<entry kind="dir" path="." revision="29">
+<entry kind="dir" path="./" revision="29">
@@ -19 +18,0 @@
-
@@ -35,2 +33,0 @@
-
-
@@ -38 +35,2 @@
-<entry kind="dir" path="./" revision="29">
+
+<entry kind="dir" path="." revision="29">
@@ -54 +51,0 @@
-

After my search: The algorithm get in the second compare (compare_nodesets) a wrong input:

NODESETS (Infomation shorted for visibility)

 <url>file:///home/user/svnrep/svnserver</url><repository><root>file:///home/user/svnrep/svnserver</root><uuid>23296ed4-733e-4fb8-a59a-8fb1a468b90c</uuid></repository><wc-info><schedule>normal</schedule><
depth>infinity</depth></wc-info><commit revision="29"><author>user</author><date>2013-03-15T12:20:26.098031Z</date></commit>

in nodeset_1 and

 <url>file:///home/user/svnrep/svnserver/Projekt</url><repository><root>file:///home/user/svnrep/svnserver
        <entry kind="dir" path="." revision="29"><url>file:///home/user/svnrep/svnserver</url><repository><root>file:///home/user/svnrep/svnserver
        <entry kind="dir" path="Projekt" revision="1"><url>file:///home/user/svnrep/svnserver/Projekt</url><repository><root>file:///home/user/svnrep/svnserver

in nodeset_2 and so the algorithm return false, because the wrong length

Use: ruby 1.8.7
OS Linux
Version: official ruby gem version and current (date:25.03.13)

contain matcher

I think it would be useful, to have something like expect(xml).to contain_xml("<foo>bar</foo>"). With this, you would be able to match a partial xml to a parent document.

NoMethodError: undefined method `equivalent_to?'

Ran into:

NoMethodError: undefined method `equivalent_to?'

When using with rspec. I was able to fix this by adding require 'equivalent-xml/rspec_matchers' to my spec_helper.rb file. I didn't have require either matchers or equivalent-xml as per the README.

This was using rspec 2.99 and rails 4.1.5.

Encountering error for Rspec 3

When running specs that make use of Equivalent-XML, I receive the following deprecation warning:

`require 'rspec-expectations'` is deprecated. Use `require 'rspec/expectations'` instead.

(Pull request is on its way. Verifying behavior against rspec 2 and rspec 3.)

documents with space prior to xml preamble fail to compare on jruby

I found this relatively obscure bug when using equivalent-xml with jruby, which I think is a bug in nokogiri:

when comparing to xml documents where one of them starts with a string before the xml preamble, nokogiri ignores the following elements in the document.

jruby-1.7.0 :001 > require 'nokogiri'
 => true 
jruby-1.7.0 :002 > doc1 = Nokogiri::XML(" <?xml version='1.0' encoding='utf-8' ?><first \>")
 => #<Nokogiri::XML::Document:0x7fa name="document"> 
jruby-1.7.0 :003 > doc2 = Nokogiri::XML("<?xml version='1.0' encoding='utf-8' ?><first \>")
 => #<Nokogiri::XML::Document:0x7fe name="document" children=[#<Nokogiri::XML::Element:0x7fc name="first">]>

this works perfectly fine in ruby 1.8, 1.9, rbx1.8 and rbx1.9:

1.9.3-p286 :001 > require 'nokogiri'
 => true 
1.9.3-p286 :002 > doc1 = Nokogiri::XML(" <?xml version='1.0' encoding='utf-8' ?><first \>")
 => #<Nokogiri::XML::Document:0x3fd36c8e30bc name="document" children=[#<Nokogiri::XML::ProcessingInstruction:0x3fd36c8e2b80 name="xml">, #<Nokogiri::XML::Element:0x3fd36c8e2964 name="first">]> 
1.9.3-p286 :003 > doc2 = Nokogiri::XML("<?xml version='1.0' encoding='utf-8' ?><first \>")
 => #<Nokogiri::XML::Document:0x3fd36c8d7f28 name="document" children=[#<Nokogiri::XML::Element:0x3fd36c8d776c name="first">]>

weird however is, that this works as originally expected with all ruby implementations when the first element is not the xml preamble:

jruby-1.7.0 :004 > doc1 = Nokogiri::XML("<bar><foo /></bar>")
 => #<Nokogiri::XML::Document:0x804 name="document" children=[#<Nokogiri::XML::Element:0x802 name="bar" children=[#<Nokogiri::XML::Element:0x800 name="foo">]>]> 
jruby-1.7.0 :005 > doc2 = Nokogiri::XML(" <bar><foo /></bar>")
 => #<Nokogiri::XML::Document:0x80a name="document" children=[#<Nokogiri::XML::Element:0x808 name="bar" children=[#<Nokogiri::XML::Element:0x806 name="foo">]>]> 
1.9.3-p286 :004 > doc1 = Nokogiri::XML("<bar><foo /></bar>")
 => #<Nokogiri::XML::Document:0x3fd36c8cef90 name="document" children=[#<Nokogiri::XML::Element:0x3fd36c8ceb30 name="bar" children=[#<Nokogiri::XML::Element:0x3fd36c8ce8d8 name="foo">]>]> 
1.9.3-p286 :005 > doc2 = Nokogiri::XML(" <bar><foo /></bar>")
 => #<Nokogiri::XML::Document:0x3fd36c8c8848 name="document" children=[#<Nokogiri::XML::Element:0x3fd36c8c8280 name="bar" children=[#<Nokogiri::XML::Element:0x3fd36c8c7b50 name="foo">]>]>

Although I assume that this is a bug in nokogiri, I published two test cases, to my fork of the repository, showing off the odd behaviour because I think this should be a know issue.

  # this passes on jruby
  it "should ignore leading whitespace #1" do
    doc1 = Nokogiri::XML("<bar><foo /></bar>")
    doc2 = Nokogiri::XML(" <bar><foo /></bar>")
    doc1.should be_equivalent_to(doc2)
  end

  # this fails on jruby
  it "should ignore leading whitespace #2" do
    doc1 = Nokogiri::XML("<?xml version='1.0' encoding='utf-8' ?><foo />")
    doc2 = Nokogiri::XML(" <?xml version='1.0' encoding='utf-8' ?><foo />")
    doc1.should be_equivalent_to(doc2)
  end
Failures:

  1) EquivalentXml should ignore leading whitespace #2
     Failure/Error: doc1.should be_equivalent_to(doc2)
       expected:
       <?xml version="1.0"?>

       got:
       <?xml version="1.0"?>
       <foo/>
     # ./spec/equivalent-xml_spec.rb:123:in `(root)'

the complete output is visible on travis ci

0.5.0 stopped ignoring xml declaration

     Failure/Error: doc.should be_equivalent_to expected_result
       expected:
       <test_document><foo/><test_xml/></test_document>
       got:
       <?xml version="1.0"?>
       <test_document>
         <foo/>
         <test_xml/>
       </test_document>

Fix Travis Build

Dependencies aren't compatible anymore on half of the build environments, e.g.:

NoMethodError: undefined method `last_comment' for #<Rake::Application:0x00000001216348>
/home/travis/.rvm/gems/ruby-2.3.0/gems/rspec-core-2.99.2/lib/rspec/core/rake_task.rb:143:in `initialize'
/home/travis/build/mbklein/equivalent-xml/Rakefile:17:in `new'
/home/travis/build/mbklein/equivalent-xml/Rakefile:17:in `<top (required)>'
/home/travis/.rvm/gems/ruby-2.3.0/gems/rake-12.3.0/exe/rake:27:in `<top (required)>'
/home/travis/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `eval'
/home/travis/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `<main>'
(See full trace by running task with --trace)

https://travis-ci.org/mbklein/equivalent-xml/jobs/350014687#L1128

We need to specify a new enough version of rspec (safest option), or cap at a sufficiently old version of rake. StackOverflow explains, as usual.

Recommend updating rspec.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.