diff --git a/lib/nokogiri/xml/attr.rb b/lib/nokogiri/xml/attr.rb index 97e22d1803..1756259c24 100644 --- a/lib/nokogiri/xml/attr.rb +++ b/lib/nokogiri/xml/attr.rb @@ -7,6 +7,10 @@ class Attr < Node alias_method :to_s, :content alias_method :content=, :value= + def deconstruct_keys(keys) + { name: name, value: value, namespace: namespace } + end + private def inspect_attributes diff --git a/lib/nokogiri/xml/document.rb b/lib/nokogiri/xml/document.rb index 82278dfba4..dde2afd269 100644 --- a/lib/nokogiri/xml/document.rb +++ b/lib/nokogiri/xml/document.rb @@ -415,6 +415,10 @@ def xpath_doctype Nokogiri::CSS::XPathVisitor::DoctypeConfig::XML end + def deconstruct_keys(keys) + { root: root } + end + private IMPLIED_XPATH_CONTEXTS = ["//"].freeze # :nodoc: diff --git a/lib/nokogiri/xml/namespace.rb b/lib/nokogiri/xml/namespace.rb index 7b5dc020a8..88cbf1cc56 100644 --- a/lib/nokogiri/xml/namespace.rb +++ b/lib/nokogiri/xml/namespace.rb @@ -6,6 +6,10 @@ class Namespace include Nokogiri::XML::PP::Node attr_reader :document + def deconstruct_keys(keys) + { prefix: prefix, href: href } + end + private def inspect_attributes diff --git a/lib/nokogiri/xml/node.rb b/lib/nokogiri/xml/node.rb index 28da40db48..8dc0fda9a5 100644 --- a/lib/nokogiri/xml/node.rb +++ b/lib/nokogiri/xml/node.rb @@ -1310,6 +1310,14 @@ def canonicalize(mode = XML::XML_C14N_1_0, inclusive_namespaces = nil, with_comm end end + def deconstruct_keys(keys) + { name: name, attributes: attribute_nodes, children: children, namespace: namespace }.tap do |d| + if keys&.include?(:content) + d[:content] = content + end + end + end + # :section: protected diff --git a/lib/nokogiri/xml/node_set.rb b/lib/nokogiri/xml/node_set.rb index 2c902a5fd9..d3023c7a05 100644 --- a/lib/nokogiri/xml/node_set.rb +++ b/lib/nokogiri/xml/node_set.rb @@ -360,6 +360,10 @@ def inspect alias_method :+, :| + def deconstruct + to_a + end + IMPLIED_XPATH_CONTEXTS = [".//", "self::"].freeze # :nodoc: end end diff --git a/test/xml/test_node_pattern_matching.rb b/test/xml/test_node_pattern_matching.rb new file mode 100644 index 0000000000..dfae1b9b68 --- /dev/null +++ b/test/xml/test_node_pattern_matching.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require "helper" + +class TestNokogiriPatternMatching < Nokogiri::TestCase + # rubocop:disable Lint/Syntax + describe "pattern matching" do + let(:ns_default) { "http://nokogiri.org/ns/default" } + let(:ns_noko) { "http://nokogiri.org/ns/noko" } + let(:xmldoc) do + Nokogiri::XML::Document.parse(<<~XML) + + + + + hello + goodbye + + + XML + end + let(:child1) { xmldoc.at_xpath("//a:child1", {"a" => ns_default}) } + let(:child2) { xmldoc.at_xpath("//b:child2", {"b" => ns_noko}) } + let(:child3) { xmldoc.at_xpath("//a:child3", {"a" => ns_default}) } + let(:child1_attr_foo) { xmldoc.at_xpath("//a:child1/@foo", {"a" => ns_default}) } + let(:child1_attr_bar) { xmldoc.at_xpath("//a:child1/@b:bar", {"a" => ns_default, "b" => ns_noko}) } + + describe "XML::Namespace" do + it "matches :href" do + child1.namespace => { href: child1_ns_href } + assert_equal(ns_default, child1_ns_href) + + child2.namespace => { href: child2_ns_href } + assert_equal(ns_noko, child2_ns_href) + end + + it "matches :prefix" do + child1.namespace => { prefix: child1_ns_prefix } + assert_nil(child1_ns_prefix) + + child2.namespace => { prefix: child2_ns_prefix } + assert_equal("noko", child2_ns_prefix) + end + end + + describe "XML::Attr" do + it "matches :name" do + child1_attr_foo => { name: child1_foo_name } + assert_equal("foo", child1_foo_name) + + child1_attr_bar => { name: child2_bar_name } + assert_equal("bar", child2_bar_name) + end + + it "matches :value" do + child1_attr_foo => { value: child1_foo_value } + assert_equal("abc", child1_foo_value ) + + child1_attr_bar => { value: child1_bar_value } + assert_equal("def", child1_bar_value) + end + + it "matches :namespace" do + child1_attr_foo => { namespace: child1_foo_ns} + assert_nil(child1_foo_ns) + + child1_attr_bar => { namespace: child1_bar_ns} + assert_equal(ns_noko, child1_bar_ns.href) + end + end + + describe "XML::Node" do + it "matches :name" do + child1 => { name: child1_name } + assert_equal("child1", child1_name) + + child2 => { name: child2_name } + assert_equal("child2", child2_name) + end + + it "matches :attributes" do + ns = ns_noko # so we can pin it + + child1 => { attributes: [*, { name: "foo", value: child1_foo_value }, *] } + assert_equal("abc", child1_foo_value) + + child1 => { attributes: [*, { namespace: nil, name: "foo", value: child1_foo_value }, *] } + assert_equal("abc", child1_foo_value) + + child1 => { attributes: [*, { namespace: { href: ^ns }, name: "bar", value: child1_bar_value }, *] } + assert_equal("def", child1_bar_value) + + child1 => { attributes: [*, { namespace: { href: ^ns }, name: child1_bar_name }, *] } + assert_equal("bar", child1_bar_name) + end + + it "matches :namespace" do + child1 => { namespace: child1_ns } + assert_equal(ns_default, child1_ns.href) + + child2 => { namespace: child2_ns } + assert_equal(ns_noko, child2_ns.href) + end + + it "matches :children" do + child3 => { children: child3_children } + assert_equal(5, child3_children.length) # whitespace, gc1, whitespace, gc2, whitespace + + child3 => { children: [*, {name: "grandchild1", content: }, *] } + assert_equal("hello", content) + end + end + + describe "XML::Document" do + it "matches :root" do + xmldoc => { root: { name: } } + assert_equal("root", name) + end + end + end +end