Class: Moxml::Node

Inherits:
Object
  • Object
show all
Includes:
Enumerable, 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.



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

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

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



13
14
15
# File 'lib/moxml/node.rb', line 13

def context
  @context
end

#nativeObject (readonly)

Returns the value of attribute native.



13
14
15
# File 'lib/moxml/node.rb', line 13

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.



263
264
265
# File 'lib/moxml/node.rb', line 263

def parent_node=(value)
  @parent_node = value
end

Class Method Details

.adapter(context) ⇒ Object



271
272
273
# File 'lib/moxml/node.rb', line 271

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

.node_type_mapObject

Registry mapping node type symbols to wrapper classes. Built lazily to avoid load-order issues with subclasses.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/moxml/node.rb', line 236

def self.node_type_map
  @node_type_map ||= {
    element: Element,
    text: Text,
    cdata: Cdata,
    comment: Comment,
    processing_instruction: ProcessingInstruction,
    document: Document,
    declaration: Declaration,
    doctype: Doctype,
    attribute: Attribute,
    entity_reference: EntityReference,
  }.freeze
end

.wrap(node, context) ⇒ Object



251
252
253
254
255
256
257
258
# File 'lib/moxml/node.rb', line 251

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

  type = adapter(context).node_type(node)
  klass = node_type_map[type] || self

  klass.new(node, context)
end

Instance Method Details

#==(other) ⇒ Object



212
213
214
# File 'lib/moxml/node.rb', line 212

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

#add_child(node) ⇒ Object



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

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



69
70
71
72
73
74
# File 'lib/moxml/node.rb', line 69

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



62
63
64
65
66
67
# File 'lib/moxml/node.rb', line 62

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

#after(node) ⇒ Object



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

def after(node)
  add_next_sibling(node)
end

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



109
110
111
112
# File 'lib/moxml/node.rb', line 109

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

#before(node) ⇒ Object



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

def before(node)
  add_previous_sibling(node)
end

#blank?Boolean

Returns:

  • (Boolean)


201
202
203
# File 'lib/moxml/node.rb', line 201

def blank?
  text.strip.empty?
end

#childrenObject



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

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

#contentObject

Returns the content/value of this node as a string. Each subclass overrides this with type-specific semantics:

  • Text, Comment, Cdata: raw text content

  • ProcessingInstruction: instruction content

  • Attribute: attribute value

  • Element: delegates to text (descendant text concatenation)



150
151
152
# File 'lib/moxml/node.rb', line 150

def content
  ""
end

#documentObject



27
28
29
# File 'lib/moxml/node.rb', line 27

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

#dupObject Also known as: clone

Deep copy of the node (both dup and clone create deep copies for XML nodes)



206
207
208
# File 'lib/moxml/node.rb', line 206

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

#each(&block) ⇒ Object

Yield direct children, enabling Enumerable on the node.



183
184
185
186
187
# File 'lib/moxml/node.rb', line 183

def each(&block)
  return to_enum(:each) unless block

  children.each(&block)
end

#each_node(&block) ⇒ Object

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



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

def each_node(&block)
  children.each do |child|
    yield child
    child.each_node(&block)
  end
end

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

Convenience find methods (aliases for xpath methods)



115
116
117
# File 'lib/moxml/node.rb', line 115

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

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



119
120
121
# File 'lib/moxml/node.rb', line 119

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

#first_childObject

Get first/last child



129
130
131
# File 'lib/moxml/node.rb', line 129

def first_child
  children.first
end

#has_children?Boolean

Check if node has any children

Returns:

  • (Boolean)


124
125
126
# File 'lib/moxml/node.rb', line 124

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



230
231
232
# File 'lib/moxml/node.rb', line 230

def identifier
  nil
end

#last_childObject



133
134
135
# File 'lib/moxml/node.rb', line 133

def last_child
  children.last
end

#namespaceObject

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



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

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



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

def namespaces
  return [] unless element?

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

#next_siblingObject



43
44
45
# File 'lib/moxml/node.rb', line 43

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

#outer_xmlObject



189
190
191
# File 'lib/moxml/node.rb', line 189

def outer_xml
  to_xml
end

#parentObject



31
32
33
# File 'lib/moxml/node.rb', line 31

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

#previous_siblingObject



47
48
49
# File 'lib/moxml/node.rb', line 47

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)



23
24
25
# File 'lib/moxml/node.rb', line 23

def refresh_native!(new_native)
  @native = new_native
end

#removeObject



76
77
78
79
80
81
# File 'lib/moxml/node.rb', line 76

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

#replace(node) ⇒ Object



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

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



140
141
142
# File 'lib/moxml/node.rb', line 140

def text
  ""
end

#to_xml(options = {}) ⇒ Object



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

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)

  result = adapter.serialize(@native, serialize_options)
  result = apply_line_ending(result, serialize_options[:line_ending])

  # Restore entity markers to named entity references
  adapter.restore_entities(result)
end

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



105
106
107
# File 'lib/moxml/node.rb', line 105

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