Module: Moxml::Document

Includes:
Node
Defined in:
lib/moxml/document.rb

Constant Summary

Constants included from NodeBehavior

NodeBehavior::TYPES

Constants included from XmlUtils

XmlUtils::VALID_ELEMENT_NAMES

Instance Attribute Summary collapse

Attributes included from NodeBehavior

#context, #native, #parent_node

Instance Method Summary collapse

Methods included from Node

adapter, contract_module, contract_modules, node_type_map, wrap

Methods included from NodeBehavior

#==, #adapter, #add_next_sibling, #add_previous_sibling, #after, #ancestors, #before, #blank?, #children, #clear_native_memo!, #content, #descendants, #digest, #dup, #each, #each_node, #entity_bearing?, #first_child, #following_siblings, #has_children?, #identifier, #invalidate_namespace_cache!, #last_child, #line_number, #namespace, #namespaces, #next_sibling, #outer_xml, #parent, #path, #preceding_siblings, #previous_sibling, #prime_contract, #refresh_native!, #remove, #replace, #source_position, #text, #to_xml

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

Instance Attribute Details

#has_xml_declarationObject

Returns the value of attribute has_xml_declaration.



7
8
9
# File 'lib/moxml/document.rb', line 7

def has_xml_declaration
  @has_xml_declaration
end

Instance Method Details

#add_child(node) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/moxml/document.rb', line 106

def add_child(node)
  node = prepare_node(node)

  if node.is_a?(Declaration)
    # A proper XML document carries at most one declaration
    # (issue #23).
    if @has_xml_declaration || adapter.has_declaration?(@native, self)
      raise Moxml::ValidationError, "Document already has an XML declaration"
    end

    @has_xml_declaration = true
    adapter.add_child(@native, node.native)
  elsif root && !node.is_a?(ProcessingInstruction) && !node.is_a?(Comment) && !node.is_a?(Doctype)
    raise Error, "Document already has a root element"
  else
    adapter.add_child(@native, node.native)
    # Refresh native for adapters where identity changes (e.g., LibXML doc.root=)
    refreshed = adapter.actual_native(node.native, @native)
    node.refresh_native!(refreshed) if refreshed && refreshed != node.native
  end
  node.parent_node = self
  invalidate_children_cache!
  self
end

#add_element(name, attributes = {}, &block) ⇒ Object

Quick element creation and addition



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

def add_element(name, attributes = {}, &block)
  elem = create_element(name)
  attributes.each { |k, v| elem[k] = v }
  add_child(elem)
  block&.call(elem)
  elem
end

#at_xpath(expression, namespaces = nil) ⇒ Object



149
150
151
152
153
# File 'lib/moxml/document.rb', line 149

def at_xpath(expression, namespaces = nil)
  if (native_node = adapter.at_xpath(@native, expression, namespaces))
    Moxml::Node.wrap(native_node, context)
  end
end

#create_cdata(content) ⇒ Object



73
74
75
# File 'lib/moxml/document.rb', line 73

def create_cdata(content)
  Wrappers::Cdata.new(adapter.create_cdata(content, owner_doc: @native), context)
end

#create_comment(content) ⇒ Object



77
78
79
# File 'lib/moxml/document.rb', line 77

def create_comment(content)
  Wrappers::Comment.new(adapter.create_comment(content, owner_doc: @native), context)
end

#create_declaration(version = "1.0", encoding = "UTF-8", standalone = nil) ⇒ Object



95
96
97
98
99
# File 'lib/moxml/document.rb', line 95

def create_declaration(version = "1.0", encoding = "UTF-8",
                       standalone = nil)
  decl = adapter.create_declaration(version, encoding, standalone)
  Wrappers::Declaration.new(decl, context)
end

#create_doctype(name, external_id, system_id) ⇒ Object



81
82
83
84
85
86
# File 'lib/moxml/document.rb', line 81

def create_doctype(name, external_id, system_id)
  Wrappers::Doctype.new(
    adapter.create_doctype(name, external_id, system_id),
    context,
  )
end

#create_element(name) ⇒ Object



65
66
67
# File 'lib/moxml/document.rb', line 65

def create_element(name)
  Wrappers::Element.new(adapter.create_element(name, owner_doc: @native), context)
end

#create_entity_reference(name) ⇒ Object



101
102
103
104
# File 'lib/moxml/document.rb', line 101

def create_entity_reference(name)
  native = adapter.create_entity_reference(name, @native)
  Wrappers::EntityReference.new(native, context)
end

#create_processing_instruction(target, content) ⇒ Object



88
89
90
91
92
93
# File 'lib/moxml/document.rb', line 88

def create_processing_instruction(target, content)
  Wrappers::ProcessingInstruction.new(
    adapter.create_processing_instruction(target, content),
    context,
  )
end

#create_text(content) ⇒ Object



69
70
71
# File 'lib/moxml/document.rb', line 69

def create_text(content)
  Wrappers::Text.new(adapter.create_text(content, owner_doc: @native), context)
end

#documentObject



14
15
16
# File 'lib/moxml/document.rb', line 14

def document
  self
end

#find(xpath) ⇒ Object

Convenience find methods



165
166
167
# File 'lib/moxml/document.rb', line 165

def find(xpath)
  at_xpath(xpath)
end

#find_all(xpath) ⇒ Object



169
170
171
# File 'lib/moxml/document.rb', line 169

def find_all(xpath)
  xpath(xpath).to_a
end

#freeObject

Deterministically release the adapter's native memory for this document (issue #134) — batch workloads parsing thousands of documents otherwise hold C trees until GC finalizers run. GC-managed engines no-op. Further access raises the engine's use-after-free error; ordinary garbage-collected documents keep working via the finalizer either way.



50
51
52
53
# File 'lib/moxml/document.rb', line 50

def free
  adapter.free_document(@native)
  nil
end

#initialize(native, context, adapter = nil, node_type = nil) ⇒ Object



9
10
11
12
# File 'lib/moxml/document.rb', line 9

def initialize(native, context, adapter = nil, node_type = nil)
  super
  @has_xml_declaration = false
end

#materialize(&block) ⇒ Object

Materialize the root subtree — see Moxml::Materializer.



30
31
32
33
34
# File 'lib/moxml/document.rb', line 30

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

  root&.materialize(&block)
end

#materialize_fields(&block) ⇒ Object

Zero-allocation streaming form over the root subtree (issue #143) — see Moxml::Materializer.

Raises:

  • (ArgumentError)


38
39
40
41
42
# File 'lib/moxml/document.rb', line 38

def materialize_fields(&block)
  raise ArgumentError, "materialize_fields requires a block" unless block

  root&.materialize_fields(&block)
end

#parse_errorsObject

Parse diagnostics from the engine's recover path (issue #147): [] when the parse was clean, otherwise the recorded error messages. A non-strict leptris parse that came back empty reports the fatal error that emptied it; Nokogiri reports its recover-mode syntax errors; engines without an error channel answer [].



61
62
63
# File 'lib/moxml/document.rb', line 61

def parse_errors
  adapter.parse_errors(@native)
end

#rootObject



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

def root
  root_element = adapter.root(@native)
  root_element ? Moxml::Node.wrap(root_element, context) : nil
end

#root=(element) ⇒ Object



18
19
20
21
22
# File 'lib/moxml/document.rb', line 18

def root=(element)
  adapter.set_root(@native, element.native)
  element.parent_node = self
  invalidate_children_cache!
end

#xpath(expression, namespaces = nil) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/moxml/document.rb', line 131

def xpath(expression, namespaces = nil)
  result = adapter.xpath(@native, expression, namespaces)

  # Handle different result types:
  # - Scalar values (from functions): return directly
  # - NodeSet: already wrapped, return directly
  # - Array / LazyNodeSet: wrap in NodeSet
  case result
  when NodeSet, Float, String, TrueClass, FalseClass, NilClass
    result
  when Array, LazyNodeSet
    NodeSet.new(result, context)
  else
    # For other types, try to wrap in NodeSet
    NodeSet.new(result, context)
  end
end