Class: Canon::Comparison::XmlComparatorHelpers::AttributeFilter

Inherits:
Object
  • Object
show all
Defined in:
lib/canon/comparison/xml_comparator/attribute_filter.rb

Overview

Attribute filtering logic Handles filtering of attributes based on options and match settings

Class Method Summary collapse

Class Method Details

.expanded_attribute_name(attr) ⇒ Object

Expanded attribute key: "namespace-urilocal-name", or the bare local name when the attribute is in no namespace. Prefixed-but-unresolvable attributes (namespace-invalid documents — an undeclared prefix has no expanded name) fall back to the local name: recovery comparison cannot do better.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/canon/comparison/xml_comparator/attribute_filter.rb', line 72

def self.expanded_attribute_name(attr)
  case attr
  when Canon::Xml::Nodes::AttributeNode
    uri = attr.namespace_uri
    attr.prefix
  when defined?(Nokogiri) && Nokogiri::XML::Attr
    uri = attr.namespace&.href
    attr.namespace&.prefix
  else
    uri = attr.namespace_uri
    nil
  end
  uri && !uri.empty? ? "{#{uri}}#{attr.name}" : attr.name
end

.filter(attributes, opts) ⇒ Hash

Filter attributes based on options

Parameters:

  • Raw attributes

  • Comparison options

Returns:

  • Filtered attributes



14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/canon/comparison/xml_comparator/attribute_filter.rb', line 14

def self.filter(attributes, opts)
  filtered = {}
  match_opts = opts[:match_opts]

  # Handle Canon::Xml::Node attribute format (array of AttributeNode)
  if attributes.is_a?(Array)
    filter_array_attributes(attributes, opts, match_opts, filtered)
  else
    # Handle Nokogiri and Moxml attribute formats (Hash-like)
    filter_hash_attributes(attributes, opts, match_opts, filtered)
  end

  filtered
end

.filter_array_attributes(attributes, opts, match_opts, filtered) ⇒ Object

Filter array-format attributes (Canon::Xml::Node)

Parameters:

  • Array of AttributeNode objects

  • Comparison options

  • Resolved match options

  • Output hash to populate



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
61
62
63
64
65
# File 'lib/canon/comparison/xml_comparator/attribute_filter.rb', line 35

def self.filter_array_attributes(attributes, opts, match_opts, filtered)
  attributes.each do |attr|
    # Expanded name (XML Namespaces 1.0 §5.2/§5.3): an
    # unprefixed attribute is in NO namespace — {no-ns}srsName
    # and {uri}srsName are different attributes, and two
    # prefixes bound to the same URI are the same attribute.
    # Local-name keys conflated qualified with unqualified
    # attributes whenever the prefix matched the element's
    # (issue #155).
    name = expanded_attribute_name(attr)
    value = attr.value

    # Skip namespace declarations - they're handled separately
    next if namespace_declaration?(attr.name)

    # Skip if attribute name should be ignored — by local name
    # (user-facing list) or expanded key, so ignore lists keep
    # working against both forms.
    next if ignore_by_name?(attr.name, opts) ||
      ignore_by_name?(name, opts)

    # Skip if attribute content should be ignored
    next if ignore_by_content?(value, opts)

    # Apply match options for attribute values
    behavior = match_opts[:attribute_values] || :strict
    value = MatchOptions.process_attribute_value(value, behavior)

    filtered[name] = value
  end
end

.filter_hash_attributes(attributes, opts, match_opts, filtered) ⇒ Object

Filter hash-format attributes (Nokogiri/Moxml)

Parameters:

  • Hash-like attributes

  • Comparison options

  • Resolved match options

  • Output hash to populate



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/canon/comparison/xml_comparator/attribute_filter.rb', line 93

def self.filter_hash_attributes(attributes, opts, match_opts, filtered)
  attributes.each do |key, val|
    # Normalize key and value
    name, value = normalize_attribute_pair(key, val)

    # Skip namespace declarations - they're handled separately
    next if namespace_declaration?(name)

    # Skip if attribute name should be ignored
    next if ignore_by_name?(name, opts)

    # Skip if attribute content should be ignored
    next if ignore_by_content?(value, opts)

    # Apply match options for attribute values
    behavior = match_opts[:attribute_values] || :strict
    value = MatchOptions.process_attribute_value(value, behavior)

    filtered[name] = value
  end
end

.ignore_by_content?(value, opts) ⇒ Boolean

Check if attribute should be ignored by content

Parameters:

  • Attribute value

  • Comparison options

Returns:

  • true if should ignore



146
147
148
149
150
# File 'lib/canon/comparison/xml_comparator/attribute_filter.rb', line 146

def self.ignore_by_content?(value, opts)
  opts[:ignore_attr_content].any? do |pattern|
    value.to_s.include?(pattern)
  end
end

.ignore_by_name?(name, opts) ⇒ Boolean

Check if attribute should be ignored by name

Parameters:

  • Attribute name

  • Comparison options

Returns:

  • true if should ignore



137
138
139
# File 'lib/canon/comparison/xml_comparator/attribute_filter.rb', line 137

def self.ignore_by_name?(name, opts)
  opts[:ignore_attrs_by_name].any? { |pattern| name.include?(pattern) }
end

.namespace_declaration?(attr_name) ⇒ Boolean

Returns:



152
153
154
# File 'lib/canon/comparison/xml_comparator/attribute_filter.rb', line 152

def self.namespace_declaration?(attr_name)
  Canon::Xml::NamespaceHelper.namespace_declaration?(attr_name)
end

.normalize_attribute_pair(key, val) ⇒ Array<String, String>

Normalize attribute key-value pair from different formats

Parameters:

  • Attribute key (String or Attribute object)

  • Attribute value

Returns:

  • Normalized [name, value] pair



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/canon/comparison/xml_comparator/attribute_filter.rb', line 120

def self.normalize_attribute_pair(key, val)
  if key.is_a?(String)
    name = key
    value = val.is_a?(String) ? val : val.value
  else
    name = key.name
    value = key.value
  end

  [name, value]
end