Module: Moxml::AttributeResolver
- Defined in:
- lib/moxml/attribute_resolver.rb
Overview
Canonical, adapter-independent attribute-name resolution.
XML Namespaces 1.0 semantics, implemented once on the Moxml object model (the unified interface); adapters provide raw access only:
- An attribute's expanded name is (namespace URI or none) + local name. Unprefixed attributes are never in a namespace — the default namespace declaration does not apply to attributes (Namespaces 1.0 §5.2).
- "prefix:local" resolves the prefix against the element's in-scope declarations and matches by expanded name: an attribute written as q:type matches a lookup as p:type whenever both prefixes are bound to the same URI.
- "xml" is prebound to the XML namespace URI; it never needs a declaration (Namespaces 1.0 §4).
- "xmlns"/"xmlns:*" are declarations, not attributes: never match.
- An undeclared prefix names nothing: nil. No silent prefix-string or bare-name fallback.
When several attributes match (only possible in namespace-ill-formed documents), the first in document order wins, matching every adapter's attributes() ordering contract.
Constant Summary collapse
- XML_NAMESPACE_URI =
"http://www.w3.org/XML/1998/namespace"
Class Method Summary collapse
-
.assign(element, name, value) ⇒ String
Assign a value to the attribute named by XML-correct resolution, mirroring #resolve:.
-
.attribute_test?(element, attr, prefix, local) ⇒ Boolean
XPath 1.0 attribute node test, sharing the resolver's expanded-name semantics:.
-
.attribute_test_uri?(element, attr, uri, local) ⇒ Boolean
Attribute node test with a URI resolved at XPath compile time (the static context's prefix mapping wins over the document's own declarations).
-
.attribute_uri(element, attr, attr_name = nil) ⇒ Object
URI of an attribute's namespace, nil for the no-namespace case.
-
.local_name(name) ⇒ Object
Local part of a wrapper attribute name.
- .prefix_part(name) ⇒ Object
-
.prefix_uri(element, prefix) ⇒ Object
URI a prefix is bound to for lookups on this element, or nil when the prefix is undeclared.
-
.remove(element, name) ⇒ Moxml::Attribute?
Remove the attribute named by XML-correct resolution (see #assign for the name semantics).
-
.resolve(element, name) ⇒ Moxml::Attribute?
The matching attribute wrapper.
Class Method Details
.assign(element, name, value) ⇒ String
Assign a value to the attribute named by XML-correct resolution, mirroring #resolve:
- "xmlns"/"xmlns:*" are namespace declarations, not attributes: delegated verbatim to the adapter (declaration creation is adapter territory).
- A bare name replaces the no-namespace attribute only — a namespaced attribute sharing the local name is left alone.
- A prefixed name replaces by expanded name: assigning p:x overwrites an attribute spelled q:x when both prefixes are bound to the same URI.
When nothing matches, the attribute is created with the given spelling. A prefix not yet in scope is stored raw — builders legitimately write detached children before attaching them under the declaring ancestor, and reads resolve by expanded name once the prefix is in scope (matching nokogiri/libxml creation behavior; namespace validity is a property of the serialized document).
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/moxml/attribute_resolver.rb', line 85 def assign(element, name, value) name = name.to_s adapter = element.context.config.adapter if name == "xmlns" || name.start_with?("xmlns:") adapter.set_attribute(element.native, name, value) element.invalidate_attribute_cache! return value end existing = resolve(element, name) if existing existing.value = value return value end adapter.set_attribute(element.native, name, value) element.invalidate_attribute_cache! value end |
.attribute_test?(element, attr, prefix, local) ⇒ Boolean
XPath 1.0 attribute node test, sharing the resolver's expanded-name semantics:
138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/moxml/attribute_resolver.rb', line 138 def attribute_test?(element, attr, prefix, local) return false if local && local_name(attr.name) != local case prefix when :any then true when nil then attribute_uri(element, attr).nil? else uri = prefix_uri(element, prefix) !uri.nil? && uri == attribute_uri(element, attr) end end |
.attribute_test_uri?(element, attr, uri, local) ⇒ Boolean
Attribute node test with a URI resolved at XPath compile time (the static context's prefix mapping wins over the document's own declarations). See #attribute_test? for local semantics.
155 156 157 158 159 |
# File 'lib/moxml/attribute_resolver.rb', line 155 def attribute_test_uri?(element, attr, uri, local) return false if local && local_name(attr.name) != local attribute_uri(element, attr) == uri end |
.attribute_uri(element, attr, attr_name = nil) ⇒ Object
URI of an attribute's namespace, nil for the no-namespace case.
Uses the attribute's own namespace when the native binding exposes one with a URI (nokogiri/oga/libxml, and rexml/ox for declared prefixes). Otherwise reconstructs it from the attribute prefix via the element's in-scope declarations — covering adapters that report a prefix without resolving it (rexml/ox for the prebound xml prefix) and adapters that expose neither namespace nor separate prefix (leptris qualified names).
177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/moxml/attribute_resolver.rb', line 177 def attribute_uri(element, attr, attr_name = nil) ns = attr.namespace return ns.uri unless ns.nil? || ns.uri.to_s.empty? prefix = ns&.prefix || prefix_part(attr_name || attr.name) return nil if prefix.nil? || prefix.empty? return XML_NAMESPACE_URI if prefix == "xml" element.in_scope_namespaces .find { |candidate| candidate.prefix == prefix }&.uri end |
.local_name(name) ⇒ Object
Local part of a wrapper attribute name. Local names cannot contain ':' in namespace-well-formed documents; splitting is for adapters whose natives carry the qualified name (leptris).
164 165 166 |
# File 'lib/moxml/attribute_resolver.rb', line 164 def local_name(name) name.include?(":") ? name.split(":", 2)[1] : name end |
.prefix_part(name) ⇒ Object
189 190 191 |
# File 'lib/moxml/attribute_resolver.rb', line 189 def prefix_part(name) name.include?(":") ? name.split(":", 2)[0] : nil end |
.prefix_uri(element, prefix) ⇒ Object
URI a prefix is bound to for lookups on this element, or nil when the prefix is undeclared.
57 58 59 60 61 62 |
# File 'lib/moxml/attribute_resolver.rb', line 57 def prefix_uri(element, prefix) return XML_NAMESPACE_URI if prefix == "xml" element.in_scope_namespaces .find { |candidate| candidate.prefix == prefix }&.uri end |
.remove(element, name) ⇒ Moxml::Attribute?
Remove the attribute named by XML-correct resolution (see #assign for the name semantics).
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/moxml/attribute_resolver.rb', line 109 def remove(element, name) name = name.to_s adapter = element.context.config.adapter if name == "xmlns" || name.start_with?("xmlns:") adapter.remove_attribute(element.native, name) element.invalidate_attribute_cache! return nil end attr = resolve(element, name) return nil unless attr adapter.remove_attribute_native(attr.native) element.invalidate_attribute_cache! attr end |
.resolve(element, name) ⇒ Moxml::Attribute?
Returns the matching attribute wrapper.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/moxml/attribute_resolver.rb', line 32 def resolve(element, name) name = name.to_s if name.include?(":") prefix, local = name.split(":", 2) return nil if prefix == "xmlns" uri = prefix_uri(element, prefix) return nil if uri.nil? element.attributes.find do |attr| # attr.name allocates on several adapters (native string, # plus leptris' force_encoding dup) — read it once per probe attr_name = attr.name local_name(attr_name) == local && attribute_uri(element, attr, attr_name) == uri end else element.attributes.find do |attr| attr_name = attr.name local_name(attr_name) == name && attribute_uri(element, attr, attr_name).nil? end end end |