Class: Moxml::Node

Inherits:
Object
  • Object
show all
Includes:
XmlUtils
Defined in:
lib/moxml/node.rb

Constant Summary collapse

TYPES =
%i[
  element text cdata comment processing_instruction document
  declaration doctype namespace attribute unknown entity_reference
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from XmlUtils

#encode_entities, #normalize_xml_value, #validate_comment_content, #validate_declaration_encoding, #validate_declaration_standalone, #validate_declaration_version, #validate_element_name, #validate_entity_reference_name, #validate_pi_target, #validate_prefix, #validate_uri

Constructor Details

#initialize(native, context) ⇒ Node

Returns a new instance of Node.



17
18
19
20
21
# File 'lib/moxml/node.rb', line 17

def initialize(native, context)
  @context = context
  @native = native
  @parent_node = nil
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



15
16
17
# File 'lib/moxml/node.rb', line 15

def context
  @context
end

#nativeObject (readonly)

Returns the value of attribute native.



15
16
17
# File 'lib/moxml/node.rb', line 15

def native
  @native
end

#parent_node=(value) ⇒ Object (writeonly)

Internal: Set the parent node for cache invalidation tracking. Called by NodeSet, Document, Element when establishing parent-child relationships. Public to allow cross-class usage within Moxml internals.



243
244
245
# File 'lib/moxml/node.rb', line 243

def parent_node=(value)
  @parent_node = value
end

Class Method Details

.adapter(context) ⇒ Object



251
252
253
# File 'lib/moxml/node.rb', line 251

def self.adapter(context)
  context.config.adapter
end

.wrap(node, context) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/moxml/node.rb', line 220

def self.wrap(node, context)
  return nil if node.nil?

  klass = case adapter(context).node_type(node)
          when :element then Element
          when :text then Text
          when :cdata then Cdata
          when :comment then Comment
          when :processing_instruction then ProcessingInstruction
          when :document then Document
          when :declaration then Declaration
          when :doctype then Doctype
          when :attribute then Attribute
          when :entity_reference then EntityReference
          else self
          end

  klass.new(node, context)
end

Instance Method Details

#==(other) ⇒ Object



198
199
200
# File 'lib/moxml/node.rb', line 198

def ==(other)
  self.class == other.class && @native == other.native
end

#[](name) ⇒ Object

Attribute accessor - only works on Element nodes Returns nil for non-element nodes



157
158
159
160
161
162
# File 'lib/moxml/node.rb', line 157

def [](name)
  return nil unless respond_to?(:attribute)

  attr = attribute(name)
  attr&.value if attr.respond_to?(:value)
end

#add_child(node) ⇒ Object



53
54
55
56
57
58
59
60
61
62
# File 'lib/moxml/node.rb', line 53

def add_child(node)
  node = prepare_node(node)
  adapter.add_child(@native, node.native)
  # Refresh native in case adapter changed identity (e.g., LibXML doc.root=)
  refreshed = adapter.actual_native(node.native, @native)
  node.refresh_native!(refreshed) if refreshed && refreshed != node.native
  node.parent_node = self
  invalidate_children_cache!
  self
end

#add_next_sibling(node) ⇒ Object



71
72
73
74
75
76
# File 'lib/moxml/node.rb', line 71

def add_next_sibling(node)
  node = prepare_node(node)
  adapter.add_next_sibling(@native, node.native)
  invalidate_parent_children_cache!
  self
end

#add_previous_sibling(node) ⇒ Object



64
65
66
67
68
69
# File 'lib/moxml/node.rb', line 64

def add_previous_sibling(node)
  node = prepare_node(node)
  adapter.add_previous_sibling(@native, node.native)
  invalidate_parent_children_cache!
  self
end

#at_xpath(expression, namespaces = {}) ⇒ Object



107
108
109
110
# File 'lib/moxml/node.rb', line 107

def at_xpath(expression, namespaces = {})
  Moxml::Node.wrap(adapter.at_xpath(@native, expression, namespaces),
                   context)
end

#childrenObject



37
38
39
40
41
42
43
# File 'lib/moxml/node.rb', line 37

def children
  @children ||= NodeSet.new(
    adapter.children(@native).map { adapter.patch_node(_1, @native) },
    context,
    self,
  )
end

#cloneObject Also known as: dup

Clone node (deep copy)



193
194
195
# File 'lib/moxml/node.rb', line 193

def clone
  Moxml::Node.wrap(adapter.dup(@native), context)
end

#documentObject



29
30
31
# File 'lib/moxml/node.rb', line 29

def document
  Document.wrap(adapter.document(@native), context)
end

#each_node(&block) ⇒ Object

Recursively yield all descendant nodes Used by XPath descendant-or-self and descendant axes



185
186
187
188
189
190
# File 'lib/moxml/node.rb', line 185

def each_node(&block)
  children.each do |child|
    yield child
    child.each_node(&block) if child.respond_to?(:each_node)
  end
end

#find(xpath_expression, namespaces = {}) ⇒ Object

Convenience find methods (aliases for xpath methods)



113
114
115
# File 'lib/moxml/node.rb', line 113

def find(xpath_expression, namespaces = {})
  at_xpath(xpath_expression, namespaces)
end

#find_all(xpath_expression, namespaces = {}) ⇒ Object



117
118
119
# File 'lib/moxml/node.rb', line 117

def find_all(xpath_expression, namespaces = {})
  xpath(xpath_expression, namespaces).to_a
end

#first_childObject

Get first/last child



127
128
129
# File 'lib/moxml/node.rb', line 127

def first_child
  children.first
end

#has_children?Boolean

Check if node has any children

Returns:

  • (Boolean)


122
123
124
# File 'lib/moxml/node.rb', line 122

def has_children?
  !children.empty?
end

#identifierString?

Returns the primary identifier for this node type For Element: the tag name For Attribute: the attribute name For ProcessingInstruction: the target For content nodes (Text, Comment, Cdata, Declaration): nil (no identifier) For Doctype: nil (not fully implemented across adapters)

Returns:

  • (String, nil)

    the node’s primary identifier or nil



216
217
218
# File 'lib/moxml/node.rb', line 216

def identifier
  nil
end

#last_childObject



131
132
133
# File 'lib/moxml/node.rb', line 131

def last_child
  children.last
end

#namespaceObject

Returns the namespace of this node Only applicable to Element nodes, returns nil for others



166
167
168
169
170
171
# File 'lib/moxml/node.rb', line 166

def namespace
  return nil unless element?

  ns = adapter.namespace(@native)
  ns && Namespace.new(ns, context)
end

#namespacesObject

Returns all namespace definitions on this node Only applicable to Element nodes, returns empty array for others



175
176
177
178
179
180
181
# File 'lib/moxml/node.rb', line 175

def namespaces
  return [] unless element?

  adapter.namespace_definitions(@native).map do |ns|
    Namespace.new(ns, context)
  end
end

#next_siblingObject



45
46
47
# File 'lib/moxml/node.rb', line 45

def next_sibling
  Moxml::Node.wrap(adapter.next_sibling(@native), context)
end

#parentObject



33
34
35
# File 'lib/moxml/node.rb', line 33

def parent
  Moxml::Node.wrap(adapter.parent(@native), context)
end

#previous_siblingObject



49
50
51
# File 'lib/moxml/node.rb', line 49

def previous_sibling
  Moxml::Node.wrap(adapter.previous_sibling(@native), context)
end

#refresh_native!(new_native) ⇒ Object

Update native reference after identity-changing operations (e.g., LibXML doc.root= creates a new Ruby wrapper)



25
26
27
# File 'lib/moxml/node.rb', line 25

def refresh_native!(new_native)
  @native = new_native
end

#removeObject



78
79
80
81
82
83
# File 'lib/moxml/node.rb', line 78

def remove
  invalidate_parent_children_cache!
  adapter.remove(@native)
  invalidate_children_cache!
  self
end

#replace(node) ⇒ Object



85
86
87
88
89
90
91
# File 'lib/moxml/node.rb', line 85

def replace(node)
  node = prepare_node(node)
  invalidate_parent_children_cache!
  adapter.replace(@native, node.native)
  invalidate_children_cache!
  self
end

#textObject

Returns the text content of this node Subclasses should override this method Element and Text have their own implementations



138
139
140
141
142
143
144
145
146
# File 'lib/moxml/node.rb', line 138

def text
  if respond_to?(:content)
    content
  elsif respond_to?(:children)
    children.grep(Text).map(&:content).join
  else
    ""
  end
end

#to_xml(options = {}) ⇒ Object



93
94
95
96
97
98
99
100
101
# File 'lib/moxml/node.rb', line 93

def to_xml(options = {})
  # Determine if we should include XML declaration
  # For Document nodes: check native then wrapper, unless explicitly overridden
  # For other nodes: default to no declaration unless explicitly set
  serialize_options = default_options.merge(options)
  serialize_options[:no_declaration] = !should_include_declaration?(options)

  adapter.serialize(@native, serialize_options)
end

#xpath(expression, namespaces = {}) ⇒ Object



103
104
105
# File 'lib/moxml/node.rb', line 103

def xpath(expression, namespaces = {})
  NodeSet.new(adapter.xpath(@native, expression, namespaces), context)
end