Class: Canon::Diff::NodeSerializer

Inherits:
Object
  • Object
show all
Defined in:
lib/canon/diff/node_serializer.rb

Overview

Serializes nodes from different parsing libraries into canonical strings This abstraction allows Canon to work with any parsing library (Nokogiri, Moxml, etc.) without being tied to a specific implementation.

This is library-agnostic because it detects node type and uses the appropriate serialization method.

Class Method Summary collapse

Class Method Details

.element_name(node) ⇒ String

Get element name from a node Handles both Nokogiri and Canon nodes

Parameters:

  • node (Object)

    Node to get name from

Returns:

  • (String)

    Element name



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/canon/diff/node_serializer.rb', line 138

def self.element_name(node)
  return "" if node.nil?

  # Handle Canon::Xml::Nodes::ElementNode
  if node.is_a?(Canon::Xml::Nodes::ElementNode)
    return node.name
  end

  # Handle Nokogiri elements
  if node.respond_to?(:name)
    return node.name.to_s
  end

  ""
end

.extract_attributes(node) ⇒ Hash

Extract attributes from a node as a normalized hash Handles both Nokogiri and Canon nodes

Parameters:

  • node (Object)

    Node to extract attributes from

Returns:

  • (Hash)

    Normalized attributes hash



96
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
123
124
125
126
127
128
129
130
131
# File 'lib/canon/diff/node_serializer.rb', line 96

def self.extract_attributes(node)
  return {} if node.nil?

  # Handle Canon::Xml::Nodes::ElementNode
  if node.is_a?(Canon::Xml::Nodes::ElementNode)
    attrs = {}
    node.attribute_nodes.each do |attr|
      attrs[attr.name] = attr.value
    end
    return attrs
  end

  # Handle Nokogiri elements
  if node.respond_to?(:attributes) && node.attributes.is_a?(Hash)
    attrs = {}
    node.attributes.each do |name, attr|
      # Nokogiri attributes have different structure
      value = if attr.respond_to?(:value)
                attr.value
              elsif attr.is_a?(String)
                attr
              else
                attr.to_s
              end
      attrs[name] = value
    end
    return attrs
  end

  # Handle TreeNode attributes (already a hash)
  if node.is_a?(Hash)
    return node
  end

  {}
end

.serialize(node) ⇒ String

Serialize a node to a string for display Handles both Nokogiri and Canon nodes

Parameters:

  • node (Object)

    Node to serialize (Nokogiri, Canon, or nil)

Returns:

  • (String)

    Serialized string representation



24
25
26
27
28
29
30
31
32
33
34
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/diff/node_serializer.rb', line 24

def self.serialize(node)
  return "" if node.nil?

  # Handle Canon::Xml::Nodes::TextNode
  if node.is_a?(Canon::Xml::Nodes::TextNode)
    # Use original text (with entity references) if available,
    # otherwise fall back to value (decoded text)
    return node.original || node.value
  end

  # Handle Canon::Xml::Nodes::CommentNode
  if node.is_a?(Canon::Xml::Nodes::CommentNode)
    return "<!--#{node.value}-->"
  end

  # Handle Canon::Xml::Nodes::ElementNode
  if node.is_a?(Canon::Xml::Nodes::ElementNode)
    return serialize_element_node(node)
  end

  # Handle Canon::Xml::Nodes::ProcessingInstructionNode
  if node.is_a?(Canon::Xml::Nodes::ProcessingInstructionNode)
    return "<?#{node.target} #{node.data}?>"
  end

  # Handle Canon::Xml::Nodes::RootNode - serialize children
  if node.is_a?(Canon::Xml::Nodes::RootNode)
    return node.children.map { |child| serialize(child) }.join
  end

  # Handle Nokogiri nodes
  if node.respond_to?(:to_html)
    return node.to_html
  end

  if node.respond_to?(:to_xml)
    return node.to_xml
  end

  # Fallback to string
  node.to_s
end

.serialize_attributes(attributes) ⇒ String

Serialize attributes to string format Returns attributes in “ name="value"” format

Parameters:

  • attributes (Hash)

    Attributes hash

Returns:

  • (String)

    Serialized attributes



184
185
186
187
188
189
190
# File 'lib/canon/diff/node_serializer.rb', line 184

def self.serialize_attributes(attributes)
  return "" if attributes.nil? || attributes.empty?

  attributes.sort.map do |name, value|
    " #{name}=\"#{value}\""
  end.join
end

.serialize_element_node(element) ⇒ String

Serialize an ElementNode to HTML/XML string

Parameters:

Returns:

  • (String)

    Serialized element



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/canon/diff/node_serializer.rb', line 71

def self.serialize_element_node(element)
  # Build opening tag with attributes
  tag = "<#{element.name}"

  # Add attributes
  element.sorted_attribute_nodes.each do |attr|
    tag += " #{attr.name}=\"#{attr.value}\""
  end

  # Check if element has children
  if element.children.empty?
    # Self-closing tag for empty elements
    "#{tag}/>"
  else
    # Full element with children
    content = element.children.map { |child| serialize(child) }.join
    "#{tag}>#{content}</#{element.name}>"
  end
end

.text_content(node) ⇒ String

Get text content from a node Handles both Nokogiri and Canon nodes

Parameters:

  • node (Object)

    Node to get text from

Returns:

  • (String)

    Text content



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/canon/diff/node_serializer.rb', line 159

def self.text_content(node)
  return "" if node.nil?

  # Handle Canon::Xml::Nodes::TextNode
  if node.is_a?(Canon::Xml::Nodes::TextNode)
    return node.value.to_s
  end

  # Handle Nokogiri text nodes
  if node.respond_to?(:text)
    return node.text.to_s
  end

  if node.respond_to?(:content)
    return node.content.to_s
  end

  ""
end