nokogiri-diff-0.3.0/0000755000004100000410000000000014574707366014314 5ustar www-datawww-datanokogiri-diff-0.3.0/.gitignore0000644000004100000410000000004214574707366016300 0ustar www-datawww-data/Gemfile.lock /coverage /doc /pkg nokogiri-diff-0.3.0/.document0000644000004100000410000000003414574707366016130 0ustar www-datawww-data- ChangeLog.md LICENSE.txt nokogiri-diff-0.3.0/.github/0000755000004100000410000000000014574707366015654 5ustar www-datawww-datanokogiri-diff-0.3.0/.github/workflows/0000755000004100000410000000000014574707366017711 5ustar www-datawww-datanokogiri-diff-0.3.0/.github/workflows/ruby.yml0000644000004100000410000000112314574707366021412 0ustar www-datawww-dataname: CI on: [ push, pull_request ] jobs: tests: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - '3.0' - '3.1' - '3.2' - '3.3' - jruby - truffleruby name: Ruby ${{ matrix.ruby }} steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - name: Install dependencies run: bundle install --jobs 4 --retry 3 - name: Run tests run: bundle exec rake test nokogiri-diff-0.3.0/lib/0000755000004100000410000000000014574707366015062 5ustar www-datawww-datanokogiri-diff-0.3.0/lib/nokogiri/0000755000004100000410000000000014574707366016703 5ustar www-datawww-datanokogiri-diff-0.3.0/lib/nokogiri/diff/0000755000004100000410000000000014574707366017613 5ustar www-datawww-datanokogiri-diff-0.3.0/lib/nokogiri/diff/xml.rb0000644000004100000410000000013314574707366020735 0ustar www-datawww-data# frozen_string_literal: true require_relative 'xml/node' require_relative 'xml/document' nokogiri-diff-0.3.0/lib/nokogiri/diff/xml/0000755000004100000410000000000014574707366020413 5ustar www-datawww-datanokogiri-diff-0.3.0/lib/nokogiri/diff/xml/node.rb0000644000004100000410000000471314574707366021672 0ustar www-datawww-data# frozen_string_literal: true require 'nokogiri' require 'tdiff' class Nokogiri::XML::Node include TDiff include TDiff::Unordered # # Compares the XML/HTML node with another. # # @param [Nokogiri::XML::Node] node # The other XMl/HTML node. # # @return [Boolean] # Specifies whether the two nodes are equal. # def tdiff_equal(node) if (self.class == node.class) case node when Nokogiri::XML::Attr (self.name == node.name && self.value == node.value) when Nokogiri::XML::Element, Nokogiri::XML::DTD self.name == node.name when Nokogiri::XML::Text, Nokogiri::XML::Comment self.text == node.text when Nokogiri::XML::ProcessingInstruction (self.name == node.name && self.content == node.content) else false end else false end end # # Enumerates over the children of another XML/HTML node. # # @param [Nokogiri::XML::Node] node # The other XMl/HTML node. # # @yield [child] # The given block will be passed every child of the node. # # @yieldparam [Nokogiri::XML::Node] node # A child node. # def tdiff_each_child(node,&block) if node.kind_of?(Nokogiri::XML::Element) node.attribute_nodes.sort_by(&:name).each(&block) end node.children.each(&block) end # # Finds the differences between the node and another node. # # @param [Nokogiri::XML::Node] other # The other node to compare against. # # @param [Hash] options # Additional options for filtering changes. # # @option options [Boolean] :added # Yield nodes that were added. # # @option options [Boolean] :removed # Yield nodes that were removed. # # @yield [change, node] # The given block will be passed each changed node. # # @yieldparam [' ', '-', '+'] change # Indicates whether the node stayed the same, was removed or added. # # @yieldparam [Nokogiri::XML::Node] node # The changed node. # # @return [Enumerator] # If no block was given, an Enumerator object will be returned. # def diff(other,options={},&block) return enum_for(__method__,other,options) unless block if (options[:added] || options[:removed]) tdiff_unordered(other) do |change,node| if (change == '+' && options[:added]) then yield change, node elsif (change == '-' && options[:removed]) then yield change, node end end else tdiff(other,&block) end end end nokogiri-diff-0.3.0/lib/nokogiri/diff/xml/document.rb0000644000004100000410000000106514574707366022560 0ustar www-datawww-data# frozen_string_literal: true require_relative 'node' class Nokogiri::XML::Document < Nokogiri::XML::Node # # Overrides `tdiff` to only compare the child nodes of the document. # def tdiff(tree,&block) return enum_for(__method__,tree) unless block tdiff_recursive(tree,&block) return self end # # Overrides `tdiff_unordered` to only compare the child nodes of the document. # def tdiff_unordered(tree,&block) return enum_for(__method__,tree) unless block tdiff_recursive_unordered(tree,&block) return self end end nokogiri-diff-0.3.0/lib/nokogiri/diff/version.rb0000644000004100000410000000017114574707366021624 0ustar www-datawww-data# frozen_string_literal: true module Nokogiri module Diff # nokogiri-diff version VERSION = '0.3.0' end end nokogiri-diff-0.3.0/lib/nokogiri/diff.rb0000644000004100000410000000013314574707366020135 0ustar www-datawww-data# frozen_string_literal: true require_relative 'diff/xml' require_relative 'diff/version' nokogiri-diff-0.3.0/LICENSE.txt0000644000004100000410000000204514574707366016140 0ustar www-datawww-dataCopyright (c) 2010-2024 Hal Brodigan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nokogiri-diff-0.3.0/spec/0000755000004100000410000000000014574707366015246 5ustar www-datawww-datanokogiri-diff-0.3.0/spec/spec_helper.rb0000644000004100000410000000006414574707366020064 0ustar www-datawww-datarequire 'rspec' require 'simplecov' SimpleCov.start nokogiri-diff-0.3.0/spec/diff_spec.rb0000644000004100000410000002171214574707366017520 0ustar www-datawww-datarequire 'spec_helper' require 'nokogiri/diff' describe "nokogiri/diff" do let(:contents) { '

one

' } let(:doc) { Nokogiri::XML(contents) } let(:added_text) { Nokogiri::XML('

one

two
') } let(:added_element) { Nokogiri::XML('

one

two

') } let(:added_attr) { Nokogiri::XML('

one

') } let(:added_attrs) { Nokogiri::XML('

one

') } let(:changed_text) { Nokogiri::XML('

two

') } let(:changed_element) { Nokogiri::XML('
one
') } let(:changed_attr_name) { Nokogiri::XML('

one

') } let(:changed_attr_value) { Nokogiri::XML('

one

') } let(:changed_attr_order) { Nokogiri::XML('

one

') } let(:removed_text) { Nokogiri::XML('

two
') } let(:removed_element) { Nokogiri::XML('
') } let(:removed_attr) { Nokogiri::XML('

one

') } it "should add #diff to Nokogiri::XML::Docuemnt" do expect(doc).to respond_to(:diff) end it "should add #diff to Nokogiri::XML::Element" do expect(added_element.at('div')).to respond_to(:diff) end it "should add #diff to Nokogiri::XML::Text" do expect(added_text.at('p/text()')).to respond_to(:diff) end it "should add #diff to Nokogiri::XML::Attr" do expect(added_attr.at('p/@id')).to respond_to(:diff) end it "should not compare the Document objects" do change = doc.diff(doc).first expect(change[0]).to eq(' ') expect(change[1]).to eq(doc.root) end it "should determine when two different documents are identical" do expect(doc.diff(Nokogiri::XML(contents)).all? { |change,node| change == ' ' }).to eq(true) end it "should search down within Nokogiri::XML::Document objects" do expect(doc.diff(changed_text).any? { |change,node| change != ' ' }).to eq(true) end it "should determine when text nodes are added" do changes = doc.at('div').diff(added_text.at('div')).to_a expect(changes.length).to eq(4) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(doc.at('div')) expect(changes[1][0]).to eq(' ') expect(changes[1][1]).to eq(doc.at('//p')) expect(changes[2][0]).to eq('+') expect(changes[2][1]).to eq(added_text.at('//div/text()')) expect(changes[3][0]).to eq(' ') expect(changes[3][1]).to eq(doc.at('//p/text()')) end it "should determine when elements are added" do changes = doc.at('div').diff(added_element.at('div')).to_a expect(changes.length).to eq(5) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(doc.at('div')) expect(changes[1][0]).to eq('+') expect(changes[1][1]).to eq(added_element.at('//p[1]')) expect(changes[2][0]).to eq(' ') expect(changes[2][1]).to eq(doc.at('//p')) expect(changes[3][0]).to eq('-') expect(changes[3][1]).to eq(doc.at('//p/text()')) expect(changes[4][0]).to eq('+') expect(changes[4][1]).to eq(added_element.at('//p[2]/text()')) end it "should ignore when attribute order changes" do changes = added_attrs.at('p').diff(changed_attr_order.at('p')).to_a expect(changes.all? { |change| change[0] == ' ' }).to be_truthy end it "should determine when attributes are added" do changes = doc.at('p').diff(added_attr.at('p')).to_a expect(changes.length).to eq(3) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(doc.at('p')) expect(changes[1][0]).to eq('+') expect(changes[1][1]).to eq(added_attr.at('//p/@id')) expect(changes[2][0]).to eq(' ') expect(changes[2][1]).to eq(doc.at('//p/text()')) end it "should determine when text nodes differ" do changes = doc.at('p').diff(changed_text.at('p')).to_a expect(changes.length).to eq(3) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(doc.at('p')) expect(changes[1][0]).to eq('-') expect(changes[1][1]).to eq(doc.at('//p/text()')) expect(changes[2][0]).to eq('+') expect(changes[2][1]).to eq(changed_text.at('//p/text()')) end it "should determine when element names differ" do changes = doc.at('div').diff(changed_element.at('div')).to_a expect(changes.length).to eq(3) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(doc.at('div')) expect(changes[1][0]).to eq('-') expect(changes[1][1]).to eq(doc.at('p')) expect(changes[2][0]).to eq('+') expect(changes[2][1]).to eq(changed_element.at('span')) end it "should determine when attribute names differ" do changes = added_attr.at('p').diff(changed_attr_name.at('p')).to_a expect(changes.length).to eq(4) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(added_attr.at('p')) expect(changes[1][0]).to eq('-') expect(changes[1][1]).to eq(added_attr.at('//p/@id')) expect(changes[2][0]).to eq('+') expect(changes[2][1]).to eq(changed_attr_name.at('//p/@i')) expect(changes[3][0]).to eq(' ') expect(changes[3][1]).to eq(added_attr.at('//p/text()')) end it "should determine when attribute values differ" do changes = added_attr.at('p').diff(changed_attr_value.at('p')).to_a expect(changes.length).to eq(4) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(added_attr.at('p')) expect(changes[1][0]).to eq('-') expect(changes[1][1]).to eq(added_attr.at('//p/@id')) expect(changes[2][0]).to eq('+') expect(changes[2][1]).to eq(changed_attr_value.at('//p/@id')) expect(changes[3][0]).to eq(' ') expect(changes[3][1]).to eq(added_attr.at('//p/text()')) end it "should determine when text nodes are removed" do changes = added_text.at('div').diff(removed_text.at('div')).to_a expect(changes.length).to eq(4) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(added_text.at('div')) expect(changes[1][0]).to eq(' ') expect(changes[1][1]).to eq(added_text.at('p')) expect(changes[2][0]).to eq(' ') expect(changes[2][1]).to eq(added_text.at('//div/text()')) expect(changes[3][0]).to eq('-') expect(changes[3][1]).to eq(added_text.at('//p/text()')) end it "should determine when elements are removed" do changes = added_element.at('div').diff(removed_element.at('div')).to_a expect(changes.length).to eq(3) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(added_element.at('div')) expect(changes[1][0]).to eq('-') expect(changes[1][1]).to eq(added_element.at('//p[1]')) expect(changes[2][0]).to eq('-') expect(changes[2][1]).to eq(added_element.at('//p[2]')) end it "should ignore when attributes change order" do end it "should determine when attributes are removed" do changes = added_attr.at('div').diff(removed_attr.at('div')).to_a expect(changes.length).to eq(4) expect(changes[0][0]).to eq(' ') expect(changes[0][1]).to eq(added_attr.at('div')) expect(changes[1][0]).to eq(' ') expect(changes[1][1]).to eq(added_attr.at('p')) expect(changes[2][0]).to eq('-') expect(changes[2][1]).to eq(added_attr.at('//p/@id')) expect(changes[3][0]).to eq(' ') expect(changes[3][1]).to eq(added_attr.at('//p/text()')) end context ":added" do it "should determine only when text nodes are added" do changes = doc.at('div').diff(added_text.at('div'), :added => true).to_a expect(changes.length).to eq(1) expect(changes[0][0]).to eq('+') expect(changes[0][1]).to eq(added_text.at('//div/text()')) end it "should determine only when elements are added" do changes = doc.at('div').diff(added_element.at('div'), :added => true).to_a expect(changes.length).to eq(1) expect(changes[0][0]).to eq('+') expect(changes[0][1]).to eq(added_element.at('//div/p[2]')) end it "should determine only when attributes are added" do changes = doc.at('div').diff(added_attr.at('div'), :added => true).to_a expect(changes.length).to eq(1) expect(changes[0][0]).to eq('+') expect(changes[0][1]).to eq(added_attr.at('//p/@id')) end end context ":removed" do it "should determine only when text nodes are removed" do changes = doc.at('div').diff(removed_text.at('div'), :removed => true).to_a expect(changes.length).to eq(1) expect(changes[0][0]).to eq('-') expect(changes[0][1]).to eq(doc.at('//p/text()')) end it "should determine only when elements are removed" do changes = doc.at('div').diff(removed_element.at('div'), :removed => true).to_a expect(changes.length).to eq(1) expect(changes[0][0]).to eq('-') expect(changes[0][1]).to eq(doc.at('//div/p')) end it "should determine only when attributes are removed" do changes = added_attr.at('div').diff(removed_attr.at('div'), :removed => true).to_a expect(changes.length).to eq(1) expect(changes[0][0]).to eq('-') expect(changes[0][1]).to eq(added_attr.at('//p/@id')) end end end nokogiri-diff-0.3.0/.yardopts0000644000004100000410000000010414574707366016155 0ustar www-datawww-data--markup markdown --title "nokogiri-diff Documentation" --protected nokogiri-diff-0.3.0/gemspec.yml0000644000004100000410000000147014574707366016464 0ustar www-datawww-dataname: nokogiri-diff summary: Calculate the differences between two XML/HTML documents. description: Nokogiri::Diff adds the ability to calculate the differences (added or removed nodes) between two XML/HTML documents. license: MIT authors: Postmodern email: postmodern.mod3@gmail.com homepage: https://github.com/postmodern/nokogiri-diff#readme has_yard: true metadata: documentation_uri: https://rubydoc.info/gems/nokogiri-diff source_code_uri: https://github.com/postmodern/nokogiri-diff bug_tracker_uri: https://github.com/postmodern/nokogiri-diff/issues changelog_uri: https://github.com/postmodern/nokogiri-diff/blob/main/ChangeLog.md rubygems_mfa_required: 'true' required_ruby_version: ">= 2.0.0" dependencies: tdiff: ~> 0.4 nokogiri: ~> 1.5 development_dependencies: bundler: ~> 2.0 nokogiri-diff-0.3.0/.rspec0000644000004100000410000000004014574707366015423 0ustar www-datawww-data--colour --format documentation nokogiri-diff-0.3.0/Rakefile0000644000004100000410000000027214574707366015762 0ustar www-datawww-datarequire 'rubygems/tasks' Gem::Tasks.new require 'rspec/core/rake_task' RSpec::Core::RakeTask.new task :test => :spec task :default => :spec require 'yard' YARD::Rake::YardocTask.new nokogiri-diff-0.3.0/nokogiri-diff.gemspec0000644000004100000410000000355714574707366020422 0ustar www-datawww-data# encoding: utf-8 require 'yaml' Gem::Specification.new do |gem| gemspec = YAML.load_file('gemspec.yml') gem.name = gemspec.fetch('name') gem.version = gemspec.fetch('version') do require_relative 'lib/nokogiri/diff/version' Nokogiri::Diff::VERSION end gem.summary = gemspec['summary'] gem.description = gemspec['description'] gem.licenses = Array(gemspec['license']) gem.authors = Array(gemspec['authors']) gem.email = gemspec['email'] gem.homepage = gemspec['homepage'] glob = lambda { |patterns| gem.files & Dir[*patterns] } gem.files = `git ls-files`.split($/) gem.files = glob[gemspec['files']] if gemspec['files'] gem.executables = gemspec.fetch('executables') do glob['bin/*'].map { |path| File.basename(path) } end gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.' gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb'] gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb'] gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}'] gem.require_paths = Array(gemspec.fetch('require_paths') { %w[ext lib].select { |dir| File.directory?(dir) } }) gem.requirements = gemspec['requirements'] gem.required_ruby_version = gemspec['required_ruby_version'] gem.required_rubygems_version = gemspec['required_rubygems_version'] gem.post_install_message = gemspec['post_install_message'] split = lambda { |string| string.split(/,\s*/) } if gemspec['dependencies'] gemspec['dependencies'].each do |name,versions| gem.add_dependency(name,split[versions]) end end if gemspec['development_dependencies'] gemspec['development_dependencies'].each do |name,versions| gem.add_development_dependency(name,split[versions]) end end end nokogiri-diff-0.3.0/ChangeLog.md0000644000004100000410000000210714574707366016465 0ustar www-datawww-data### 0.3.0 / 2024-01-24 * Require [ruby](http://www.ruby-lang.org/) >= 2.0.0. * Require [tdiff](http://github.com/postmodern/tdiff) ~> 0.4. * Switched to using `require_relative` to improve load-times. * Added `# frozen_string_literal: true` to all files. ### 0.2.0 / 2013-04-22 * {Nokogiri::XML::Node#tdiff_each_child} now sorts attributes by name, so that changes in attribute order is ignored. (thanks @bhollis) * {Nokogiri::XML::Node#tdiff_equal} now supports `Nokogiri::XML::Comment` and `Nokogiri::XML::ProcessingInstruction` objects. (thanks @bhollis) ### 0.1.2 / 2012-05-28 * Require tdiff ~> 0.3, >= 0.3.2. * Added {Nokogiri::Diff::VERSION}. * Replaced ore-tasks with [rubygems-tasks](https://github.com/postmodern/rubygems-tasks#readme). ### 0.1.1 / 2012-05-09 * Require nokogiri ~> 1.5. ### 0.1.0 / 2010-11-29 * Initial release: * Performs a breadth-first comparison between children nodes. * Compares XML/HTML Elements, Attributes, Text nodes and DTD nodes. * Allows calculating differences between documents, or just enumerating the added or removed nodes. nokogiri-diff-0.3.0/Gemfile0000644000004100000410000000041614574707366015610 0ustar www-datawww-datasource 'https://rubygems.org' gemspec group :development do gem 'rake' gem 'rubygems-tasks', '~> 0.2' gem 'rspec', '~> 3.0' gem 'simplecov', '~> 0.20', require: false gem 'kramdown' gem 'redcarpet', platform: :mri gem 'yard', '~> 0.9' end nokogiri-diff-0.3.0/README.md0000644000004100000410000000401214574707366015570 0ustar www-datawww-data# nokogiri-diff [![CI](https://github.com/postmodern/nokogiri-diff/actions/workflows/ruby.yml/badge.svg)](https://github.com/postmodern/nokogiri-diff/actions/workflows/ruby.yml) * [Source](https://github.com/postmodern/nokogiri-diff) * [Issues](https://github.com/postmodern/nokogiri-diff/issues) * [Documentation](http://rubydoc.info/gems/nokogiri-diff/frames) ## Description nokogiri-diff adds the ability to calculate the differences (added or removed nodes) between two XML/HTML documents. ## Features * Performs a breadth-first comparison between children nodes. * Compares XML/HTML Elements, Attributes, Text nodes and DTD nodes. * Allows calculating differences between documents, or just enumerating the added or removed nodes. ## Examples Enumerate over the differences between two HTML documents: ```ruby require 'nokogiri/diff' doc1 = Nokogiri::HTML('

one

two
') doc2 = Nokogiri::HTML('

one

three

') doc1.diff(doc2) do |change,node| puts "#{change} #{node.to_html}".ljust(30) + node.parent.path end #
#

one

two
/ #

one

/div # - two /div # + /div # +

three

/div # + id="1" /div/p[1] # one /div/p ``` Only find the added nodes: ```ruby doc1.diff(doc2, :added => true) do |change,node| puts node.to_html.ljust(30) + node.parent.path end # /div #

three

/div # id="1" /div/p[1] ``` Only find the removed nodes: ```ruby doc1.diff(doc2, :removed => true) do |change,node| puts node.to_html.ljust(30) + node.parent.path end # two /div ``` ## Requirements * [ruby](http://www.ruby-lang.org/) >= 2.0.0 * [tdiff](http://github.com/postmodern/tdiff) ~> 0.4 * [nokogiri](http://nokogiri.rubyforge.org/) ~> 1.5 ## Install ```shell $ gem install nokogiri-diff ``` ## License See {file:LICENSE.txt} for details.