Module: Moxml::ElementBehavior
- Included in:
- Element
- Defined in:
- lib/moxml/element.rb
Overview
Instance behavior for Moxml::Element, extracted so the leptris adapter can extend natives with it in place (issue #230); the class remains the consumer-facing contract.
Instance Method Summary collapse
-
#[](name) ⇒ Object
Unified XML-correct name resolution (see AttributeResolver): bare names match only no-namespace attributes; prefixed names resolve through in-scope declarations to expanded names.
-
#[]=(name, value) ⇒ Object
Write path shares the resolver's expanded-name semantics (see AttributeResolver.assign): bare names target no-namespace attributes only; prefixed names replace by namespace URI and require a declared prefix.
- #add_namespace(prefix, uri) ⇒ Object (also: #add_namespace_definition)
-
#append_xml(fragment) ⇒ Moxml::Element
Append a raw XML fragment's top-level nodes as children.
- #attribute(name) ⇒ Object
-
#attribute_pairs ⇒ Object
Read-only [name, value] pairs in document order, duplicates included — no Attribute node wrappers.
-
#attribute_read_cache ⇒ Object
Resolution reads the element.s in-scope namespaces, so the cache rides the context.s namespace-scope generation clock — every attribute or namespace mutation anywhere bumps it.
- #attributes ⇒ Object
-
#declared_namespaces ⇒ Object
The element's OWN namespace declarations as [prefix, uri] pairs (nil prefix = default namespace) — not the inherited scope.
-
#expanded_name ⇒ Object
Returns the expanded name including namespace prefix.
- #find_all(xpath) ⇒ Object
-
#find_element(xpath) ⇒ Object
Convenience find methods.
-
#get(attr_name) ⇒ Object
Returns attribute value by name (used by XPath engine).
-
#identifier ⇒ String
Returns the primary identifier for this element (its tag name).
-
#in_scope_namespaces ⇒ Object
Returns all namespaces in scope for this element, including those inherited from ancestor elements.
- #inner_text ⇒ Object
- #inner_xml ⇒ Object
- #inner_xml=(xml) ⇒ Object
-
#invalidate_attribute_cache! ⇒ Object
Called by the namespace-scoped attribute paths (xmlns writes and removals) and Attribute#name=.
-
#invalidate_attribute_value_cache! ⇒ Object
Attribute mutations that cannot change namespace scope (bare and non-xmlns prefixed names) invalidate locally: the resolved-read cache alone for value writes, the wrapper list too when the attribute set changes.
- #invalidate_local_attribute_cache! ⇒ Object
-
#invalidate_namespace_cache! ⇒ Object
Clear the namespace caches and bump the context's scope generation so every wrapper — including ones not reachable from any children cache — recomputes on next read.
- #name ⇒ Object
- #name=(value) ⇒ Object
-
#namespace ⇒ Object
it's NOT the same as namespaces.first.
-
#namespace=(ns_or_hash) ⇒ Object
add the prefix to the element name and add the namespace to the list of namespace definitions.
-
#namespace_definitions ⇒ Object
The element's OWN namespace declarations only (not inherited).
-
#namespace_name ⇒ Object
Returns the namespace URI of this element (alias for namespace_uri).
-
#namespace_prefix ⇒ Object
Returns the namespace prefix of this element.
-
#namespace_uri ⇒ Object
Returns the namespace URI of this element.
-
#namespaces ⇒ Object
All namespaces IN SCOPE for this element — its own declarations plus everything inherited from ancestors — matching the Nokogiri #namespaces contract consumers port against (issue #198: this returned only own declarations, losing ancestor scope under every backend).
-
#nodes ⇒ Object
Alias for children (used by XPath engine).
-
#raw_inner_text ⇒ Object
Returns inner text without entity marker restoration.
- #remove_attribute(name) ⇒ Object
-
#set_attributes(attributes_hash) ⇒ Object
Bulk attribute setting.
- #text ⇒ Object (also: #content)
- #text=(content) ⇒ Object
-
#with_attribute(name, value) ⇒ Object
Fluent interface methods.
-
#with_child(child) ⇒ Object
Chainable child addition.
- #with_namespace(prefix, uri) ⇒ Object
- #with_text(content) ⇒ Object
Instance Method Details
#[](name) ⇒ Object
Unified XML-correct name resolution (see AttributeResolver): bare names match only no-namespace attributes; prefixed names resolve through in-scope declarations to expanded names.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/moxml/element.rb', line 61 def [](name) key = name.to_s # Bare names on qualified-name engines: one adapter call with # the adapter's own marker restoration (bare_attr_value). The # resolver's expanded-name semantics only pay for prefixed # names, where resolution is real work. adapter = self.adapter # Capability memo: the adapter predicate is a per-read # dispatch for a lifetime constant. if !key.include?(":") && ((@bare_qname_safe ||= adapter.) == true) # The entity-restore decision rides the wrapper's generation # memo (as Element#text) — the adapter-level probe walks to # the document and the attachment store on every read, which # dominated the fast path. value = adapter.(@native, key) return adapter.restore_entities(value) if value.is_a?(String) && entity_bearing? return value end cache = attribute_read_cache unless cache.key?(key) cache[key] = if key.include?(":") Moxml::AttributeResolver.resolve_value(self, key) else Moxml::AttributeResolver.resolve(self, key)&.value end end cache[key] end |
#[]=(name, value) ⇒ Object
Write path shares the resolver's expanded-name semantics (see AttributeResolver.assign): bare names target no-namespace attributes only; prefixed names replace by namespace URI and require a declared prefix. Cache coherence is the resolver's.
51 52 53 54 55 56 |
# File 'lib/moxml/element.rb', line 51 def []=(name, value) # Every assign path (native set or value=) ends in # invalidate_attribute_cache!, which bumps the generation — # a leading bump here was one per write. Moxml::AttributeResolver.assign(self, name, normalize_xml_value(value)) end |
#add_namespace(prefix, uri) ⇒ Object Also known as: add_namespace_definition
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/moxml/element.rb', line 176 def add_namespace(prefix, uri) adapter.create_namespace(@native, prefix, uri, namespace_validation_mode: context.config.namespace_validation_mode) invalidate_namespace_cache! self rescue ValidationError => e # Re-raise as NamespaceError, provide attributes for error context # but the to_s will only add details if provided raise Moxml::NamespaceError.new( e., prefix: prefix, uri: uri, element: self, ) end |
#append_xml(fragment) ⇒ Moxml::Element
Append a raw XML fragment's top-level nodes as children.
Bulk construction rides the engine's parser instead of per-node create/attach calls — measured 2.0x faster than per-node building through the same adapter, and faster than per-node building on Nokogiri (the parse is C; the per-node path pays an FFI crossing per node).
The fragment must be namespace-self-contained (declarations inside it ride along with the moved subtrees; prefixes relying on THIS element's scope must be spelled out in the fragment) and well-formed as the content of a single wrapper element — declarations and doctypes raise ParseError.
162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/moxml/element.rb', line 162 def append_xml(fragment) if context.config.adapter_name == :ox raise Moxml::AdapterError.new( "append_xml is not supported by the Ox adapter (its customized node wrappers do not survive cross-document attachment)", adapter: "Ox", operation: "append_xml", ) end wrapper = context.parse("<m>#{fragment}</m>") children = wrapper.root.children.to_a children.each { |child| add_child(child) } self end |
#attribute(name) ⇒ Object
93 94 95 |
# File 'lib/moxml/element.rb', line 93 def attribute(name) Moxml::AttributeResolver.resolve(self, name) end |
#attribute_pairs ⇒ Object
Read-only [name, value] pairs in document order, duplicates included — no Attribute node wrappers. The walk-hot shape for consumers that only read name/value (leptris-ruby#278): on leptris >= 1.9.208.1 this answers in one C crossing with zero per-attribute objects. Names may be shared frozen strings — dup before mutating. Mutation and per-attribute namespace resolution stay on #attributes.
125 126 127 |
# File 'lib/moxml/element.rb', line 125 def attribute_pairs adapter.attribute_pairs(@native) end |
#attribute_read_cache ⇒ Object
Resolution reads the element.s in-scope namespaces, so the cache rides the context.s namespace-scope generation clock — every attribute or namespace mutation anywhere bumps it.
100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/moxml/element.rb', line 100 def attribute_read_cache generation = context.namespace_scope_generation # @attribute_cache.nil? covers the local invalidation clears — # a value or list change without a scope bump (see # invalidate_local_attribute_cache!) — so writes never allocate # a replacement hash; the next read materializes one lazily. if @attribute_cache.nil? || @attribute_cache_generation != generation @attribute_cache = {} @attribute_cache_generation = generation end @attribute_cache end |
#attributes ⇒ Object
129 130 131 132 133 134 135 136 137 138 |
# File 'lib/moxml/element.rb', line 129 def attributes # Primed like Node.wrap: the adapter and type are known at # mint, so the first name/value/attribute? access skips the # context hop and the adapter's type probe. @attributes ||= adapter.attributes(@native).map do |attr| a = Wrappers::Attribute.new(attr, context, adapter, :attribute) a.parent_node = self a end end |
#declared_namespaces ⇒ Object
The element's OWN namespace declarations as [prefix, uri] pairs (nil prefix = default namespace) — not the inherited scope. The shape materialize records carry (issue #138).
237 238 239 240 241 242 |
# File 'lib/moxml/element.rb', line 237 def declared_namespaces adapter.namespace_definitions(@native).map do |ns| wrapper = Wrappers::Namespace.new(ns, context) [wrapper.prefix, wrapper.uri] end end |
#expanded_name ⇒ Object
Returns the expanded name including namespace prefix
27 28 29 30 31 32 33 |
# File 'lib/moxml/element.rb', line 27 def if namespace_prefix && !namespace_prefix.empty? "#{namespace_prefix}:#{name}" else name end end |
#find_all(xpath) ⇒ Object
332 333 334 |
# File 'lib/moxml/element.rb', line 332 def find_all(xpath) xpath(xpath).to_a end |
#find_element(xpath) ⇒ Object
Convenience find methods
328 329 330 |
# File 'lib/moxml/element.rb', line 328 def find_element(xpath) at_xpath(xpath) end |
#get(attr_name) ⇒ Object
Returns attribute value by name (used by XPath engine)
114 115 116 |
# File 'lib/moxml/element.rb', line 114 def get(attr_name) self[attr_name] end |
#identifier ⇒ String
Returns the primary identifier for this element (its tag name)
22 23 24 |
# File 'lib/moxml/element.rb', line 22 def identifier name end |
#in_scope_namespaces ⇒ Object
Returns all namespaces in scope for this element, including those inherited from ancestor elements.
246 247 248 249 250 251 252 253 254 255 |
# File 'lib/moxml/element.rb', line 246 def in_scope_namespaces generation = context.namespace_scope_generation if @in_scope_namespaces.nil? || @in_scope_generation != generation @in_scope_namespaces = adapter.in_scope_namespaces(@native).map do |ns| Wrappers::Namespace.new(ns, context) end @in_scope_generation = generation end @in_scope_namespaces end |
#inner_text ⇒ Object
277 278 279 280 |
# File 'lib/moxml/element.rb', line 277 def inner_text text = raw_inner_text entity_bearing? ? adapter.restore_entities(text) : text end |
#inner_xml ⇒ Object
288 289 290 |
# File 'lib/moxml/element.rb', line 288 def inner_xml adapter.inner_xml(@native) end |
#inner_xml=(xml) ⇒ Object
292 293 294 295 296 297 |
# File 'lib/moxml/element.rb', line 292 def inner_xml=(xml) wrapper = "_moxml_inner_#{Process.pid}_#{object_id}" doc = context.parse("<#{wrapper}>#{xml}</#{wrapper}>") adapter.replace_children(@native, doc.root.children.map(&:native)) invalidate_children_cache! end |
#invalidate_attribute_cache! ⇒ Object
Called by the namespace-scoped attribute paths (xmlns writes and removals) and Attribute#name=. Clears the attribute list and the resolved-read cache, and bumps the context generation so cross-wrapper reads recompute.
360 361 362 363 364 |
# File 'lib/moxml/element.rb', line 360 def invalidate_attribute_cache! @attributes = nil @attribute_cache = nil context.bump_namespace_scope_generation end |
#invalidate_attribute_value_cache! ⇒ Object
Attribute mutations that cannot change namespace scope (bare and non-xmlns prefixed names) invalidate locally: the resolved-read cache alone for value writes, the wrapper list too when the attribute set changes. No context generation bump — that would evict every wrapper's caches document-wide on each write of a bulk build.
347 348 349 |
# File 'lib/moxml/element.rb', line 347 def invalidate_attribute_value_cache! @attribute_cache = nil end |
#invalidate_local_attribute_cache! ⇒ Object
351 352 353 354 |
# File 'lib/moxml/element.rb', line 351 def invalidate_local_attribute_cache! @attributes = nil @attribute_cache = nil end |
#invalidate_namespace_cache! ⇒ Object
Clear the namespace caches and bump the context's scope generation so every wrapper — including ones not reachable from any children cache — recomputes on next read.
369 370 371 372 373 374 |
# File 'lib/moxml/element.rb', line 369 def invalidate_namespace_cache! @namespaces = nil @namespace_definitions = nil @in_scope_namespaces = nil context.bump_namespace_scope_generation end |
#name ⇒ Object
8 9 10 11 12 13 |
# File 'lib/moxml/element.rb', line 8 def name # The adapter read is an FFI call plus (on several adapters) a # defensive string copy; names only change through name= and # native adoption, both of which clear this. @name ||= adapter.node_name(@native) end |
#name=(value) ⇒ Object
15 16 17 18 |
# File 'lib/moxml/element.rb', line 15 def name=(value) @name = nil adapter.set_node_name(@native, value) end |
#namespace ⇒ Object
it's NOT the same as namespaces.first
194 195 196 197 |
# File 'lib/moxml/element.rb', line 194 def namespace ns = adapter.namespace(@native) ns && Wrappers::Namespace.new(ns, context) end |
#namespace=(ns_or_hash) ⇒ Object
add the prefix to the element name and add the namespace to the list of namespace definitions
201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/moxml/element.rb', line 201 def namespace=(ns_or_hash) if ns_or_hash.is_a?(Hash) adapter.set_namespace( @native, adapter.create_namespace(@native, *ns_or_hash.to_a.first, namespace_validation_mode: context.config.namespace_validation_mode), ) else adapter.set_namespace(@native, ns_or_hash&.native) end invalidate_namespace_cache! end |
#namespace_definitions ⇒ Object
The element's OWN namespace declarations only (not inherited). C14n's visibly-utilized calculation and the materializer's declaration pairs want exactly this shape.
226 227 228 229 230 231 232 |
# File 'lib/moxml/element.rb', line 226 def namespace_definitions return @namespace_definitions unless @namespace_definitions.nil? @namespace_definitions = adapter.namespace_definitions(@native).map do |ns| Wrappers::Namespace.new(ns, context) end end |
#namespace_name ⇒ Object
Returns the namespace URI of this element (alias for namespace_uri)
258 259 260 |
# File 'lib/moxml/element.rb', line 258 def namespace_name namespace_uri end |
#namespace_prefix ⇒ Object
Returns the namespace prefix of this element
36 37 38 39 |
# File 'lib/moxml/element.rb', line 36 def namespace_prefix ns = namespace ns&.prefix end |
#namespace_uri ⇒ Object
Returns the namespace URI of this element
42 43 44 45 |
# File 'lib/moxml/element.rb', line 42 def namespace_uri ns = namespace ns&.uri end |
#namespaces ⇒ Object
All namespaces IN SCOPE for this element — its own declarations plus everything inherited from ancestors — matching the Nokogiri #namespaces contract consumers port against (issue #198: this returned only own declarations, losing ancestor scope under every backend).
219 220 221 |
# File 'lib/moxml/element.rb', line 219 def namespaces in_scope_namespaces end |
#nodes ⇒ Object
Alias for children (used by XPath engine)
337 338 339 |
# File 'lib/moxml/element.rb', line 337 def nodes children end |
#raw_inner_text ⇒ Object
Returns inner text without entity marker restoration. Used internally when raw content with markers is needed (e.g., for DOM construction).
284 285 286 |
# File 'lib/moxml/element.rb', line 284 def raw_inner_text adapter.inner_text(@native) end |
#remove_attribute(name) ⇒ Object
140 141 142 143 144 145 |
# File 'lib/moxml/element.rb', line 140 def remove_attribute(name) # Both remove paths end in invalidate_attribute_cache!, which # bumps the generation. Moxml::AttributeResolver.remove(self, name) self end |
#set_attributes(attributes_hash) ⇒ Object
Bulk attribute setting
316 317 318 319 |
# File 'lib/moxml/element.rb', line 316 def set_attributes(attributes_hash) attributes_hash.each { |name, value| self[name] = value } self end |
#text ⇒ Object Also known as: content
262 263 264 265 266 267 268 |
# File 'lib/moxml/element.rb', line 262 def text val = adapter.text_content(@native) # Entity-free documents (the common case) skip the marker # restore scans entirely — the per-wrapper entity_bearing? # memo rides the adapter's serialize generation. entity_bearing? ? adapter.restore_entities(val) : val end |
#text=(content) ⇒ Object
272 273 274 275 |
# File 'lib/moxml/element.rb', line 272 def text=(content) adapter.set_text_content(@native, normalize_xml_value(content)) invalidate_children_cache! end |
#with_attribute(name, value) ⇒ Object
Fluent interface methods
300 301 302 303 |
# File 'lib/moxml/element.rb', line 300 def with_attribute(name, value) self[name] = value self end |
#with_child(child) ⇒ Object
Chainable child addition
322 323 324 325 |
# File 'lib/moxml/element.rb', line 322 def with_child(child) add_child(child) self end |
#with_namespace(prefix, uri) ⇒ Object
305 306 307 308 |
# File 'lib/moxml/element.rb', line 305 def with_namespace(prefix, uri) add_namespace(prefix, uri) self end |
#with_text(content) ⇒ Object
310 311 312 313 |
# File 'lib/moxml/element.rb', line 310 def with_text(content) self.text = content self end |