Class: Moxml::Adapter::Base

Inherits:
Object
  • Object
show all
Extended by:
XmlUtils
Defined in:
lib/moxml/adapter/base.rb

Direct Known Subclasses

Leptris, Libxml, Nokogiri, Oga, Ox, Rexml

Constant Summary

Constants included from XmlUtils

XmlUtils::VALID_ELEMENT_NAMES

Class 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

Class Method Details

.actual_native(child_native, _parent_native) ⇒ Object

Return the actual native node after an add_child operation. Override for adapters where node identity may change (e.g., LibXML doc.root=).



429
430
431
# File 'lib/moxml/adapter/base.rb', line 429

def actual_native(child_native, _parent_native)
  child_native
end

.attribute_name(attr) ⇒ Object

Local name of a native attribute node. Adapters whose natives carry qualified names override this to expose the local part; the wrapper composes the prefix.



423
424
425
# File 'lib/moxml/adapter/base.rb', line 423

def attribute_name(attr)
  attr.name
end

.bare_get_qname_safe?Boolean

Whether a BARE-name attribute READ addresses only the no-namespace attribute (qualified-name semantics) — the gate for Element#[]'s fast path (bare_attr_value). Differs per engine: rexml's bare read returns a namespaced sibling's value; oga's raw values need resolver-only marker restoration.

Returns:

  • (Boolean)


318
319
320
# File 'lib/moxml/adapter/base.rb', line 318

def bare_get_qname_safe?
  false
end

.bare_set_qname_safe?Boolean

Whether set_attribute with a BARE name behaves as a qualified-name write: replaces only the no-namespace attribute and never touches a namespaced p: sibling. Verified per engine; oga's repeated bare writes diverge, so it stays false there and assign keeps the full resolve.

Returns:

  • (Boolean)


327
328
329
# File 'lib/moxml/adapter/base.rb', line 327

def bare_set_qname_safe?
  false
end

.bulk_materialize?Boolean

Whether the engine offers a bulk materialization path for Materializer (issue #132). When true, the adapter gets #materialize_fields(native, buffers, &block) — fill the reused flat buffers and yield the eight record fields per node. Returning nil (e.g. for document shapes the bulk path cannot express) falls back to the generic wrapper walk.

Returns:

  • (Boolean)


353
354
355
# File 'lib/moxml/adapter/base.rb', line 353

def bulk_materialize?
  false
end

.children_accepts_entity_flag?Boolean

Whether this adapter's #children accepts the entity_bearing: keyword (the built-ins do; downstream overrides may keep the pre-0.5.36 one-argument signature — issue #218). Reflected once per adapter class.

Returns:

  • (Boolean)


123
124
125
126
127
128
129
130
131
# File 'lib/moxml/adapter/base.rb', line 123

def children_accepts_entity_flag?
  return @children_accepts_entity_flag unless @children_accepts_entity_flag.nil?

  kinds = %i[key keyrest keyreq]
  @children_accepts_entity_flag =
    method(:children).parameters.any? do |kind, _name|
      kinds.include?(kind)
    end
end

.create_cdata(content, owner_doc: nil) ⇒ Object



202
203
204
# File 'lib/moxml/adapter/base.rb', line 202

def create_cdata(content, owner_doc: nil)
  create_native_cdata(normalize_xml_value(content), owner_doc)
end

.create_comment(content, owner_doc: nil) ⇒ Object



206
207
208
209
# File 'lib/moxml/adapter/base.rb', line 206

def create_comment(content, owner_doc: nil)
  validate_comment_content(content)
  create_native_comment(normalize_xml_value(content), owner_doc)
end

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



221
222
223
224
225
226
227
# File 'lib/moxml/adapter/base.rb', line 221

def create_declaration(version = "1.0", encoding = "UTF-8",
                       standalone = nil)
  validate_declaration_version(version)
  validate_declaration_encoding(encoding)
  validate_declaration_standalone(standalone)
  create_native_declaration(version, encoding, standalone)
end

.create_doctype(name, external_id, system_id) ⇒ Object



211
212
213
# File 'lib/moxml/adapter/base.rb', line 211

def create_doctype(name, external_id, system_id)
  create_native_doctype(name, external_id, system_id)
end

.create_document(_native_doc = nil) ⇒ Object



111
112
113
114
115
116
117
# File 'lib/moxml/adapter/base.rb', line 111

def create_document(_native_doc = nil)
  raise Moxml::NotImplementedError.new(
    "create_document not implemented",
    feature: "create_document",
    adapter: name,
  )
end

.create_element(name, owner_doc: nil) ⇒ Object



193
194
195
196
# File 'lib/moxml/adapter/base.rb', line 193

def create_element(name, owner_doc: nil)
  validate_element_name(name)
  create_native_element(name, owner_doc)
end

.create_entity_reference(name, owner_doc = nil) ⇒ Object



247
248
249
250
# File 'lib/moxml/adapter/base.rb', line 247

def create_entity_reference(name, owner_doc = nil)
  validate_entity_reference_name(name)
  create_native_entity_reference(name, owner_doc)
end

.create_namespace(element, prefix, uri, namespace_validation_mode: :strict) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/moxml/adapter/base.rb', line 229

def create_namespace(element, prefix, uri,
namespace_validation_mode: :strict)
  if prefix && uri.to_s.empty?
    raise NamespaceError.new(
      "Prefixed namespace declaration cannot have an empty URI",
      prefix: prefix,
      uri: uri,
    )
  end
  if namespace_validation_mode == :strict
    validate_prefix(prefix) if prefix
    validate_uri(uri, mode: :strict)
  else
    validate_uri(uri, mode: :lenient)
  end
  create_native_namespace(element, prefix, uri)
end

.create_processing_instruction(target, content) ⇒ Object



215
216
217
218
219
# File 'lib/moxml/adapter/base.rb', line 215

def create_processing_instruction(target, content)
  validate_pi_target(target)
  create_native_processing_instruction(target,
                                       normalize_xml_value(content))
end

.create_text(content, owner_doc: nil) ⇒ Object



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

def create_text(content, owner_doc: nil)
  create_native_text(normalize_xml_value(content), owner_doc)
end

.decode_entities(text) ⇒ Object



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

def decode_entities(text)
  Entity.decode_entities(text)
end

.digestObject

Backends without a native subtree digest answer nil — the wrapper contract Node#digest gates on (issue #173).



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

def digest(*)
  nil
end

.duplicate_node(node) ⇒ Object



287
288
289
# File 'lib/moxml/adapter/base.rb', line 287

def duplicate_node(node)
  node.dup
end

.entity_bearing?(_native, _doc = nil) ⇒ Boolean

Whether the subtree at native can contain entity markers. Marker-tracking adapters override this so the post-serialize restore can skip its full-output scans on marker-free documents; the default stays conservative.

Returns:

  • (Boolean)


343
344
345
# File 'lib/moxml/adapter/base.rb', line 343

def entity_bearing?(_native, _doc = nil)
  true
end

.entity_reference_name(node) ⇒ Object



283
284
285
# File 'lib/moxml/adapter/base.rb', line 283

def entity_reference_name(node)
  node.name
end

.expanded_attr_reads?Boolean

Prefixed-attribute value fast path: adapters whose engine resolves expanded-name (uri, local) lookups natively answer the value here; others fall back to the resolver's attribute-list match. Capability probe, not a class identity check — Moxml::Adapter::Leptris is only defined once that adapter loads (issue #242).

Returns:

  • (Boolean)


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

def expanded_attr_reads?
  false
end

.expanded_attr_value(_element, _uri, _local) ⇒ Object



154
155
156
# File 'lib/moxml/adapter/base.rb', line 154

def expanded_attr_value(_element, _uri, _local)
  nil
end

.free_document(_native) ⇒ Object

Deterministic native-memory release for adapters backed by C trees (issue #134). GC-managed engines no-op; released documents raise the engine's use-after-free error on further access.



388
389
390
# File 'lib/moxml/adapter/base.rb', line 388

def free_document(_native)
  nil
end

.has_declaration?(_native_doc, wrapper) ⇒ Boolean

Check if the native document has an XML declaration

Parameters:

  • native_doc

    the native document object

  • wrapper (Moxml::Document)

    the wrapper with has_xml_declaration flag

Returns:

  • (Boolean)


405
406
407
# File 'lib/moxml/adapter/base.rb', line 405

def has_declaration?(_native_doc, wrapper)
  wrapper.has_xml_declaration
end

.in_scope_namespaces(element) ⇒ Object

Returns all namespaces in scope for this element, including inherited from ancestors. Adapters with native support (Nokogiri) override this. Default walks the ancestor chain.



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/moxml/adapter/base.rb', line 446

def in_scope_namespaces(element)
  namespaces = {}
  node = element

  while node
    break unless node_type(node) == :element

    namespace_definitions(node).each do |ns|
      prefix = namespace_prefix(ns)
      namespaces[prefix] = ns unless namespaces.key?(prefix)
    end
    node = parent(node)
  end

  namespaces.values
end

.iterparse(_xml, _mode = :top_level, _context = nil) ⇒ Object

Streaming incremental parse (leptris engine): yields each completed element while the parse runs, releasing prior subtrees — memory bounded by the largest subtree, not the document. Adapters without an engine iterator raise.



53
54
55
56
57
58
# File 'lib/moxml/adapter/base.rb', line 53

def iterparse(_xml, _mode = :top_level, _context = nil)
  raise Moxml::AdapterError.new(
    "Streaming iteration is not supported by the #{name.split('::').last} adapter",
    adapter: name, operation: "iterparse",
  )
end

.iterparse_file(_path, _mode = :top_level, _context = nil) ⇒ Object



60
61
62
63
64
65
# File 'lib/moxml/adapter/base.rb', line 60

def iterparse_file(_path, _mode = :top_level, _context = nil)
  raise Moxml::AdapterError.new(
    "Streaming file iteration is not supported by the #{name.split('::').last} adapter",
    adapter: name, operation: "iterparse_file",
  )
end

.line_number(_node) ⇒ Object

Source line of a native node (1-based), or nil when the underlying backend does not track source positions. Adapters that track lines (Nokogiri, LibXML) override this.



416
417
418
# File 'lib/moxml/adapter/base.rb', line 416

def line_number(_node)
  nil
end

.materialize_fields(_native, _buffers) ⇒ Object



357
358
359
# File 'lib/moxml/adapter/base.rb', line 357

def materialize_fields(_native, _buffers)
  nil
end

.native_identity_stable?Boolean

Whether add_child can keep tracking the same native — adapters that may recreate the node on attach (libxml's doc.root=) override to false so the wrapper refresh path stays armed.

Returns:

  • (Boolean)


308
309
310
# File 'lib/moxml/adapter/base.rb', line 308

def native_identity_stable?
  false
end

.native_inclusive10(_native) ⇒ Object

Engine-side inclusive C14N delegation: adapters whose engine canonicalizes byte-identically to the Ruby reference answer the String here; others (and unsupported shapes) return nil and the Ruby reference runs. Capability probe — same reasoning as expanded_attr_reads? (issue #242).



163
164
165
# File 'lib/moxml/adapter/base.rb', line 163

def native_inclusive10(_native)
  nil
end

.parse(_xml, _options = {}, _context = nil) ⇒ Object



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

def parse(_xml, _options = {}, _context = nil)
  raise Moxml::NotImplementedError.new(
    "parse not implemented",
    feature: "parse",
    adapter: name,
  )
end

.parse_errors(_native_doc) ⇒ Object

Recover-mode parse diagnostics (issue #147): the error messages the engine recorded while parsing, [] when the parse was clean. Engines with a native recover channel (Nokogiri's doc.errors) or a non-strict path that loses the raised error (leptris) override this.



397
398
399
# File 'lib/moxml/adapter/base.rb', line 397

def parse_errors(_native_doc)
  []
end

.parse_fragment(xml, _context = nil) ⇒ Object



43
44
45
46
47
# File 'lib/moxml/adapter/base.rb', line 43

def parse_fragment(xml, _context = nil)
  doc = parse("<m>#{xml}</m>")
  root = root(doc)
  root ? children(root) : []
end

.parse_html(_html, _options = {}, _context = nil) ⇒ Object

Tolerant HTML4/5 parsing into the standard DOM (engine issue leptris/leptris#659): implied end tags, void elements, case-insensitive lowercased names, the HTML named-entity table. Adapters whose engine has an HTML mode override this.



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

def parse_html(_html, _options = {}, _context = nil)
  raise Moxml::AdapterError.new(
    "HTML parsing is not supported by the #{name.split('::').last} adapter",
    adapter: name, operation: "parse_html",
  )
end

.patch_node(node, _parent = nil) ⇒ Object



291
292
293
294
# File 'lib/moxml/adapter/base.rb', line 291

def patch_node(node, _parent = nil)
  # monkey-patch the native node if necessary
  node
end

.patches_children?Boolean

Whether children() results need per-child patch_node rewriting. Adapters whose natives arrive pre-wrapped (all but ox and libxml) answer false so the wrapper layer can skip the identity map over every child list.

Returns:

  • (Boolean)


300
301
302
# File 'lib/moxml/adapter/base.rb', line 300

def patches_children?
  false
end

.plan_rows(_native) ⇒ Object

Plan row stream (Moxml::Plan) — ADAPTER CONTRACT. Yields |name, attrs_pairs (flat [k, v, ...]), first-text, depth| per element in document order (pre-order); first-text is the element's first text child or nil. Adapters implement this natively off their engine nodes (nokogiri, ox, oga, rexml, libxml, leptris do) — Moxml::Plan executes entirely on these rows with no wrapper materialization. This default returns nil, which makes Moxml::Plan fall back to the generic wrapper walk — correct but slow; last resort only.



370
371
372
# File 'lib/moxml/adapter/base.rb', line 370

def plan_rows(_native)
  nil
end

.plan_structs(_native, _spec) ⇒ Object

Struct-plan executor (Moxml::StructPlan): spec compiles the consumer's shape as => [Struct class, attrs Hash (name => slot Symbol), text slot, children slot]. Returns the array of top-level minted structs, or nil when the adapter has no C executor (the StructPlan then runs its Moxml::Plan fallback over plan_rows).



380
381
382
# File 'lib/moxml/adapter/base.rb', line 380

def plan_structs(_native, _spec)
  nil
end

.preprocess_entities(xml) ⇒ Object



11
12
13
# File 'lib/moxml/adapter/base.rb', line 11

def preprocess_entities(xml)
  Entity.preprocess_entities(xml)
end

.remove_attribute_native(attr) ⇒ Object

Remove a specific native attribute node from its owning element. Semantics (which attribute a name addresses) live in Moxml::AttributeResolver; this is the raw primitive.



279
280
281
# File 'lib/moxml/adapter/base.rb', line 279

def remove_attribute_native(attr)
  attr.remove
end

.remove_declaration(_native_doc) ⇒ Object

Clear the declaration state from the native document. Called when a Declaration node is removed from a document.



411
# File 'lib/moxml/adapter/base.rb', line 411

def remove_declaration(_native_doc); end

.restore_entities(text) ⇒ Object



19
20
21
# File 'lib/moxml/adapter/base.rb', line 19

def restore_entities(text)
  Entity.restore_entities(text)
end

.root(_document) ⇒ Object

Uniform fragment parsing: returns the fragment's top-level nodes as native objects. Engines without a fragment node type (everything but Nokogiri) get the wrapper-parse shape — the same trick Element#append_xml and #inner_xml= use — with a synthetic root whose children are the fragment's top-level nodes.



37
38
39
40
41
# File 'lib/moxml/adapter/base.rb', line 37

def root(_document)
  raise Moxml::NotImplementedError.new(
    "root not implemented", feature: "root", adapter: name
  )
end

.same_node?(one, other) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/moxml/adapter/base.rb', line 183

def same_node?(one, other)
  one == other
end

.sax_parse(_xml, _handler) ⇒ void

This method returns an undefined value.

Parse XML using SAX (event-driven) parsing

SAX parsing provides a memory-efficient way to process XML by triggering events as the document is parsed, rather than building a complete DOM tree.

Parameters:

Raises:



96
97
98
99
100
101
102
# File 'lib/moxml/adapter/base.rb', line 96

def sax_parse(_xml, _handler)
  raise Moxml::NotImplementedError.new(
    "sax_parse not implemented",
    feature: "sax_parse",
    adapter: name,
  )
end

.sax_supported?Boolean

Check if this adapter supports SAX parsing

Returns:

  • (Boolean)

    true if SAX parsing is supported



107
108
109
# File 'lib/moxml/adapter/base.rb', line 107

def sax_supported?
  method(:sax_parse).owner != Moxml::Adapter::Base.singleton_class
end

.serialize_generationObject

Generation of adapter-level state that cached serialize decisions depend on (leptris: the entity-marker document flag). Bumping invalidates wrapper-level memos; adapters whose answers are static keep the constant zero.



335
336
337
# File 'lib/moxml/adapter/base.rb', line 335

def serialize_generation
  0
end

.set_attribute_name(attribute, name) ⇒ Object

Mutation return contract: protocol methods that may change which native a wrapper tracks (set_attribute_name, set_namespace, set_attribute_value) always return the native the wrapper must keep tracking — the same object when mutated in place, a fresh object when the adapter recreates the node.



258
259
260
261
# File 'lib/moxml/adapter/base.rb', line 258

def set_attribute_name(attribute, name)
  attribute.name = name
  attribute
end

.set_attribute_value(attribute, value) ⇒ Object



271
272
273
274
# File 'lib/moxml/adapter/base.rb', line 271

def set_attribute_value(attribute, value)
  attribute.value = value
  attribute
end

.set_namespace(_node, _namespace) ⇒ Object



263
264
265
266
267
268
269
# File 'lib/moxml/adapter/base.rb', line 263

def set_namespace(_node, _namespace)
  raise Moxml::NotImplementedError.new(
    "set_namespace not implemented",
    feature: "set_namespace",
    adapter: name,
  )
end

.set_root(_doc, _element) ⇒ Object



23
24
25
26
27
28
29
# File 'lib/moxml/adapter/base.rb', line 23

def set_root(_doc, _element)
  raise Moxml::NotImplementedError.new(
    "set_root not implemented",
    feature: "set_root",
    adapter: name,
  )
end

.source_position(_native) ⇒ Object

Source position col_start, col_end for a node where the engine exposes it (leptris 1.9.181+ source_position); nil elsewhere and for nodes without a position (created nodes answer zeros upstream — pass those through as-is).



171
172
173
# File 'lib/moxml/adapter/base.rb', line 171

def source_position(_native)
  nil
end

.walk_descendants(_native, _context) ⇒ Object

Subtree walk capability: an adapter with a C-side pre-order traversal (leptris >= 1.9.174.6 visit) walks descendants in one dispatch; nil keeps the recursive children walk. Self is not yielded (each_node semantics).



179
180
181
# File 'lib/moxml/adapter/base.rb', line 179

def walk_descendants(_native, _context)
  nil
end

.wrap_native(_node, _type, _context) ⇒ Object

Protocol-level native equality; engines with more than one wrapper class over one C node override (leptris native read layer). Shared adapter examples compare through this. Extend-in-place capability (#230): an adapter whose native objects can carry the contract modules directly returns the extended native here (its @native is itself); the default mints a wrapper shell.



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

def wrap_native(_node, _type, _context)
  nil
end

.wrappers_recyclable?Boolean

Whether Node.wrap may safely memoize the wrapper for this native across calls. Adapters whose parser hands back the same Ruby object for the same logical node (nokogiri, ox, oga, rexml, leptris) opt in (default true). Adapters that mint a new Ruby object per access (libxml) opt out so the identity map does not accumulate dead entries.

Returns:

  • (Boolean)


439
440
441
# File 'lib/moxml/adapter/base.rb', line 439

def wrappers_recyclable?
  true
end