Module: Canon::DiffFormatter::DiffDetailFormatterHelpers::LocationExtractor

Defined in:
lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb

Overview

Location extraction from diffs

Extracts and formats location information (XPath, file position).

Class Method Summary collapse

Class Method Details

.calculate_sibling_index(node, name) ⇒ Integer

Calculate sibling index for XPath

Parameters:

  • node (Object)

    Node to calculate index for

  • name (String)

    Node name

Returns:

  • (Integer)

    1-based index



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb', line 97

def self.calculate_sibling_index(node, name)
  return 1 unless node.respond_to?(:parent) || node.respond_to?(:parent_node)

  parent = if node.respond_to?(:parent)
             node.parent
           elsif node.respond_to?(:parent_node)
             node.parent_node
           end

  return 1 unless parent

  # Get siblings with same name
  siblings = if parent.respond_to?(:children)
               parent.children.select do |n|
                 n.respond_to?(:name) && n.name == name
               end
             elsif parent.respond_to?(:child_nodes)
               parent.child_nodes.select do |n|
                 n.respond_to?(:name) && n.name == name
               end
             else
               [node]
             end

  siblings.index(node) + 1
end

.extract_location(diff) ⇒ String

Extract location information from a diff

Parameters:

  • diff (DiffNode, Hash)

    Difference node

Returns:

  • (String)

    Location string



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb', line 16

def self.extract_location(diff)
  return "" unless diff

  # Prefer pre-computed path if available (populated by MetadataEnricher)
  if diff.respond_to?(:path) && !diff.path.nil? && !diff.path.empty?
    return "Location: #{diff.path}"
  end

  # Fall back to extracting from nodes
  node = if diff.respond_to?(:node1)
           diff.node1 || diff.node2
         elsif diff.is_a?(Hash)
           diff[:node1] || diff[:node2]
         end

  return "" unless node

  xpath = extract_xpath(node)
  xpath.empty? ? "" : "Location: #{xpath}"
end

.extract_xpath(node) ⇒ String

Extract XPath from a node

Parameters:

  • node (Object)

    Node to extract XPath from

Returns:

  • (String)

    XPath string



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb', line 41

def self.extract_xpath(node)
  return "" unless node

  # Use PathBuilder if available
  if defined?(Canon::Diff::PathBuilder)
    begin
      path = Canon::Diff::PathBuilder.build_path(node)
      return path unless path.nil? || path.empty?
    rescue StandardError
      # Fall through to manual extraction
    end
  end

  # Manual XPath extraction
  manual_xpath(node)
end

.manual_xpath(node) ⇒ String

Manual XPath extraction fallback

Parameters:

  • node (Object)

    Node to extract XPath from

Returns:

  • (String)

    XPath string



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/canon/diff_formatter/diff_detail_formatter/location_extractor.rb', line 62

def self.manual_xpath(node)
  return "" unless node

  parts = []
  current = node

  while current
    break unless current.respond_to?(:name)

    name = current.name
    break if name.nil? || name.empty?

    # Calculate position among siblings
    index = calculate_sibling_index(current, name)
    parts.unshift("#{name}[#{index}]")

    # Move to parent
    current = if current.respond_to?(:parent)
                current.parent
              elsif current.respond_to?(:parent_node)
                current.parent_node
              end

    # Stop at document root
    break if current.respond_to?(:document) && current == current.document
  end

  parts.empty? ? "" : "/#{parts.join('/')}"
end