Class: Lutaml::Xml::XmlElement
- Inherits:
-
Object
- Object
- Lutaml::Xml::XmlElement
- Defined in:
- lib/lutaml/xml/xml_element.rb
Direct Known Subclasses
Constant Summary collapse
- XML_NAMESPACE_URI =
"http://www.w3.org/XML/1998/namespace"- TEXT_NODE_NAME =
Performance: Frozen string constants for frequently used values
"text"- CDATA_NODE_NAME =
"#cdata-section"- XMLNS_PREFIX =
"xmlns"- EMPTY_NAMESPACES =
Performance: Frozen empty hash to reduce allocations
{}.freeze
- EMPTY_CHILDREN_ARRAY =
Performance: Frozen empty array for child lookups
[].freeze
- NODE_TYPES =
Node types for XML elements
-
:element - regular XML element
-
:text - text content node
-
:cdata - CDATA section
-
:comment - XML comment
-
:processing_instruction - processing instruction
-
%i[element text cdata comment processing_instruction].freeze
Instance Attribute Summary collapse
-
#adapter_node ⇒ Object
Returns the value of attribute adapter_node.
-
#attribute_order ⇒ Object
readonly
Returns the value of attribute attribute_order.
-
#attributes ⇒ Object
readonly
Returns the value of attribute attributes.
-
#children ⇒ Object
Returns the value of attribute children.
-
#namespace_prefix ⇒ Object
readonly
Returns the value of attribute namespace_prefix.
-
#namespace_prefix_explicit ⇒ Object
readonly
Returns the value of attribute namespace_prefix_explicit.
-
#node_type ⇒ Object
readonly
Returns the value of attribute node_type.
-
#order_cache ⇒ Object
writeonly
Cache for order method - invalidated when children change.
-
#parent_document ⇒ Object
readonly
Returns the value of attribute parent_document.
-
#processing_instructions ⇒ Object
Returns the value of attribute processing_instructions.
Class Method Summary collapse
-
.detect_explicit_no_namespace(has_empty_xmlns:, node_namespace_nil:) ⇒ Boolean
Detect if xmlns=“” is explicitly set (W3C explicit no namespace) This is a helper method for adapters to use during element initialization.
-
.fpi?(uri) ⇒ Boolean
Detect if a string is an FPI (Formal Public Identifier), not a valid namespace URI.
-
.fpi_to_urn(fpi) ⇒ Object
Convert a Formal Public Identifier (FPI) to a URN per RFC 3151.
Instance Method Summary collapse
- #[](name) ⇒ Object
- #add_namespace(namespace) ⇒ Object
- #attribute_is_namespace?(name) ⇒ Boolean
- #cdata ⇒ Object
-
#cdata? ⇒ Boolean
Check if this is a CDATA section.
- #cdata_children ⇒ Object
-
#comment? ⇒ Boolean
Check if this is a comment node.
- #default_namespace ⇒ Object
- #document ⇒ Object
-
#element? ⇒ Boolean
Check if this is a regular element (not text/cdata/comment).
-
#element_children ⇒ Object
Get child elements (excluding text, strings, and symbols) Performance: Cached to avoid repeated filtering per rule.
-
#element_children_index ⇒ Object
Index of element children by namespaced_name for O(1) lookup Returns nil for single-child elements (not worth indexing) Performance: Cached to avoid repeated index building.
-
#ensure_attribute_index ⇒ Object
Performance: Build index for O(1) attribute lookups by namespaced_name.
-
#ensure_children_index ⇒ Object
Performance: Build index for O(1) child lookups by name Called once per element, then reused for all lookups.
- #find_attribute_value(attribute_name) ⇒ Object
- #find_child_by_name(name) ⇒ Object
-
#find_children_by_name(name) ⇒ Object
Find children by namespaced name using indexed lookup Performance: O(1) for single name, O(k) for k names.
-
#initialize(node, attributes = {}, children = [], text = nil, name: nil, parent_document: nil, namespace_prefix: nil, default_namespace: nil, explicit_no_namespace: false, node_type: nil, attribute_order: nil) ⇒ XmlElement
constructor
A new instance of XmlElement.
- #name ⇒ Object
- #namespace ⇒ Object
- #namespace_scope_config ⇒ Object
-
#namespace_uri ⇒ String?
Get the namespace URI of this element.
- #namespaced_name ⇒ Object
- #namespaces ⇒ Object
- #nil_element? ⇒ Boolean
- #order ⇒ Object
- #original_namespace_uri ⇒ Object
- #original_xml_element ⇒ Object
- #own_namespaces ⇒ Object
-
#pretty_print_instance_variables ⇒ Object
This tells which attributes to pretty print, So we remove the so much repeatative output.
-
#processing_instruction? ⇒ Boolean
Check if this is a processing instruction.
- #root ⇒ Object
- #text ⇒ Object
-
#text? ⇒ Boolean
Check if this is a text content node Uses explicit node_type instead of name-based detection.
- #text_children ⇒ Object
- #to_h ⇒ Object
- #unprefixed_name ⇒ Object
-
#xml_declaration ⇒ Object
Default: no XML declaration.
-
#xml_namespace_prefix ⇒ Object
Stubs for DataModel::XmlElement compatibility.
Constructor Details
#initialize(node, attributes = {}, children = [], text = nil, name: nil, parent_document: nil, namespace_prefix: nil, default_namespace: nil, explicit_no_namespace: false, node_type: nil, attribute_order: nil) ⇒ XmlElement
Returns a new instance of XmlElement.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/lutaml/xml/xml_element.rb', line 84 def initialize( node, attributes = {}, children = [], text = nil, name: nil, parent_document: nil, namespace_prefix: nil, default_namespace: nil, explicit_no_namespace: false, node_type: nil, attribute_order: nil ) @name = name @namespace_prefix = namespace_prefix @namespace_prefix_explicit = !namespace_prefix.nil? && !namespace_prefix.empty? @attributes = attributes @attribute_order = attribute_order @children = children @text = text @parent_document = parent_document @default_namespace = default_namespace @explicit_no_namespace = explicit_no_namespace # Set node_type, defaulting to :element # Backward compatibility: infer from name if node_type not provided @node_type = node_type || infer_node_type_from_name(name) self.adapter_node = node end |
Instance Attribute Details
#adapter_node ⇒ Object
Returns the value of attribute adapter_node.
29 30 31 |
# File 'lib/lutaml/xml/xml_element.rb', line 29 def adapter_node @adapter_node end |
#attribute_order ⇒ Object (readonly)
Returns the value of attribute attribute_order.
27 28 29 |
# File 'lib/lutaml/xml/xml_element.rb', line 27 def attribute_order @attribute_order end |
#attributes ⇒ Object (readonly)
Returns the value of attribute attributes.
27 28 29 |
# File 'lib/lutaml/xml/xml_element.rb', line 27 def attributes @attributes end |
#children ⇒ Object
Returns the value of attribute children.
27 28 29 |
# File 'lib/lutaml/xml/xml_element.rb', line 27 def children @children end |
#namespace_prefix ⇒ Object (readonly)
Returns the value of attribute namespace_prefix.
27 28 29 |
# File 'lib/lutaml/xml/xml_element.rb', line 27 def namespace_prefix @namespace_prefix end |
#namespace_prefix_explicit ⇒ Object (readonly)
Returns the value of attribute namespace_prefix_explicit.
27 28 29 |
# File 'lib/lutaml/xml/xml_element.rb', line 27 def namespace_prefix_explicit @namespace_prefix_explicit end |
#node_type ⇒ Object (readonly)
Returns the value of attribute node_type.
27 28 29 |
# File 'lib/lutaml/xml/xml_element.rb', line 27 def node_type @node_type end |
#order_cache=(value) ⇒ Object (writeonly)
Cache for order method - invalidated when children change
32 33 34 |
# File 'lib/lutaml/xml/xml_element.rb', line 32 def order_cache=(value) @order_cache = value end |
#parent_document ⇒ Object (readonly)
Returns the value of attribute parent_document.
27 28 29 |
# File 'lib/lutaml/xml/xml_element.rb', line 27 def parent_document @parent_document end |
#processing_instructions ⇒ Object
Returns the value of attribute processing_instructions.
29 30 31 |
# File 'lib/lutaml/xml/xml_element.rb', line 29 def processing_instructions @processing_instructions end |
Class Method Details
.detect_explicit_no_namespace(has_empty_xmlns:, node_namespace_nil:) ⇒ Boolean
Detect if xmlns=“” is explicitly set (W3C explicit no namespace) This is a helper method for adapters to use during element initialization
60 61 62 63 |
# File 'lib/lutaml/xml/xml_element.rb', line 60 def self.detect_explicit_no_namespace(has_empty_xmlns:, node_namespace_nil:) has_empty_xmlns && node_namespace_nil end |
.fpi?(uri) ⇒ Boolean
Detect if a string is an FPI (Formal Public Identifier), not a valid namespace URI. FPIs start with -// or +// (SGML-style, not a URI scheme).
80 81 82 |
# File 'lib/lutaml/xml/xml_element.rb', line 80 def self.fpi?(uri) uri.is_a?(String) && uri.start_with?("-//", "+//") end |
.fpi_to_urn(fpi) ⇒ Object
Convert a Formal Public Identifier (FPI) to a URN per RFC 3151. FPI examples: “-//OASIS//DTD XML Exchange Table Model 19990315//EN” Returns nil if the string is not an FPI.
RFC 3151 format: urn:publicid:prefix:+/-//registrant//description//language// Conversion: replace spaces with +, prepend “urn:publicid:”
71 72 73 74 75 76 |
# File 'lib/lutaml/xml/xml_element.rb', line 71 def self.fpi_to_urn(fpi) return nil unless fpi.is_a?(String) && fpi.start_with?("-//", "+//") # Replace spaces with + per RFC 3151 "urn:publicid:#{fpi.gsub(' ', '+')}" end |
Instance Method Details
#[](name) ⇒ Object
356 357 358 |
# File 'lib/lutaml/xml/xml_element.rb', line 356 def [](name) find_attribute_value(name) || find_children_by_name(name) end |
#add_namespace(namespace) ⇒ Object
247 248 249 250 |
# File 'lib/lutaml/xml/xml_element.rb', line 247 def add_namespace(namespace) @namespaces ||= {} @namespaces[namespace.prefix] = namespace end |
#attribute_is_namespace?(name) ⇒ Boolean
243 244 245 |
# File 'lib/lutaml/xml/xml_element.rb', line 243 def attribute_is_namespace?(name) name.to_s.start_with?(XMLNS_PREFIX) end |
#cdata ⇒ Object
311 312 313 314 315 316 |
# File 'lib/lutaml/xml/xml_element.rb', line 311 def cdata return @text if children.empty? return cdata_children.map(&:text) if children.count > 1 cdata_children.map(&:text).join end |
#cdata? ⇒ Boolean
Check if this is a CDATA section
138 139 140 |
# File 'lib/lutaml/xml/xml_element.rb', line 138 def cdata? @node_type == :cdata end |
#cdata_children ⇒ Object
318 319 320 |
# File 'lib/lutaml/xml/xml_element.rb', line 318 def cdata_children children.select(&:cdata?) end |
#comment? ⇒ Boolean
Check if this is a comment node
143 144 145 |
# File 'lib/lutaml/xml/xml_element.rb', line 143 def comment? @node_type == :comment end |
#default_namespace ⇒ Object
252 253 254 |
# File 'lib/lutaml/xml/xml_element.rb', line 252 def default_namespace namespaces[nil] || @parent_document&.namespaces&.dig(nil) end |
#document ⇒ Object
193 194 195 |
# File 'lib/lutaml/xml/xml_element.rb', line 193 def document Document.new(self) end |
#element? ⇒ Boolean
Check if this is a regular element (not text/cdata/comment)
148 149 150 |
# File 'lib/lutaml/xml/xml_element.rb', line 148 def element? @node_type == :element end |
#element_children ⇒ Object
Get child elements (excluding text, strings, and symbols) Performance: Cached to avoid repeated filtering per rule
328 329 330 331 332 333 334 335 |
# File 'lib/lutaml/xml/xml_element.rb', line 328 def element_children return @element_children if defined?(@element_children) @element_children = children.reject do |child| child.is_a?(String) || child.is_a?(Symbol) || (child.is_a?(XmlElement) && child.text?) end end |
#element_children_index ⇒ Object
Index of element children by namespaced_name for O(1) lookup Returns nil for single-child elements (not worth indexing) Performance: Cached to avoid repeated index building
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/lutaml/xml/xml_element.rb', line 340 def element_children_index return @element_children_index if defined?( @element_children_index ) ec = element_children @element_children_index = if ec.size > 1 index = {} ec.each do |child| key = child.namespaced_name (index[key] ||= []) << child end index end end |
#ensure_attribute_index ⇒ Object
Performance: Build index for O(1) attribute lookups by namespaced_name
376 377 378 379 380 381 382 383 |
# File 'lib/lutaml/xml/xml_element.rb', line 376 def ensure_attribute_index return if @attribute_index @attribute_index = {} attributes.each_value do |attr| @attribute_index[attr.namespaced_name] = attr.value end end |
#ensure_children_index ⇒ Object
Performance: Build index for O(1) child lookups by name Called once per element, then reused for all lookups
387 388 389 390 391 392 393 394 395 396 |
# File 'lib/lutaml/xml/xml_element.rb', line 387 def ensure_children_index return if @children_index @children_index = {} @children.each do |child| key = child.namespaced_name @children_index[key] ||= [] @children_index[key] << child end end |
#find_attribute_value(attribute_name) ⇒ Object
360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/lutaml/xml/xml_element.rb', line 360 def find_attribute_value(attribute_name) # Performance: Use hash index for O(1) lookup instead of linear scan ensure_attribute_index if attribute_name.is_a?(Array) attribute_name.each do |name| val = @attribute_index[name] return val unless val.nil? end nil else @attribute_index[attribute_name] end end |
#find_child_by_name(name) ⇒ Object
411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/lutaml/xml/xml_element.rb', line 411 def find_child_by_name(name) ensure_children_index if name.is_a?(Array) name.each do |n| found = @children_index[n]&.first return found if found end nil else @children_index[name]&.first end end |
#find_children_by_name(name) ⇒ Object
Find children by namespaced name using indexed lookup Performance: O(1) for single name, O(k) for k names
400 401 402 403 404 405 406 407 408 409 |
# File 'lib/lutaml/xml/xml_element.rb', line 400 def find_children_by_name(name) ensure_children_index if name.is_a?(Array) # Multiple names: collect from index name.flat_map { |n| @children_index[n] || EMPTY_CHILDREN_ARRAY } else @children_index[name] || EMPTY_CHILDREN_ARRAY end end |
#name ⇒ Object
157 158 159 160 161 |
# File 'lib/lutaml/xml/xml_element.rb', line 157 def name return @name unless namespace_prefix @prefixed_name ||= "#{namespace_prefix}:#{@name}" # rubocop:disable Naming/MemoizedInstanceVariableName end |
#namespace ⇒ Object
218 219 220 221 222 223 224 225 226 |
# File 'lib/lutaml/xml/xml_element.rb', line 218 def namespace return @namespace if defined?(@namespace) @namespace = if namespace_prefix namespaces[namespace_prefix] else default_namespace end end |
#namespace_scope_config ⇒ Object
48 49 50 |
# File 'lib/lutaml/xml/xml_element.rb', line 48 def namespace_scope_config nil end |
#namespace_uri ⇒ String?
Get the namespace URI of this element.
Returns the URI string for namespace-aware type resolution. Returns nil if the element has no namespace.
234 235 236 237 238 239 240 241 |
# File 'lib/lutaml/xml/xml_element.rb', line 234 def namespace_uri return @namespace_uri if defined?(@namespace_uri) @namespace_uri = begin ns = namespace ns&.uri end end |
#namespaced_name ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/lutaml/xml/xml_element.rb', line 163 def namespaced_name return @namespaced_name if defined?(@namespaced_name) @namespaced_name = begin return @name if text? # If xmlns="" was explicitly set, element has NO namespace return @name if @explicit_no_namespace # Priority order for namespace resolution: # 1. If has explicit prefix, use namespaces[prefix] # 2. If has @default_namespace, use it (preferred for default ns) # 3. Fall back to namespaces[nil] if exists # 4. Return unprefixed name ns = namespaces if namespace_prefix && ns[namespace_prefix] "#{ns[namespace_prefix].uri}:#{@name}" elsif @default_namespace "#{@default_namespace}:#{@name}" elsif ns[nil] "#{ns[nil].uri}:#{@name}" else @name end end end |
#namespaces ⇒ Object
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/lutaml/xml/xml_element.rb', line 197 def namespaces # When @namespaces is non-empty, return it directly (element has own declarations) # When @namespaces is nil or empty, fall back to parent's in-scope namespaces # This supports the new namespace_definitions approach where each element only # stores its own declarations, and child elements inherit from parent # # NOTE: Not cached here because Oga::Element#initialize calls this before # super() sets @parent_document, which would cache an incorrect empty result. # The hot path (namespaced_name) is cached separately. if @namespaces&.any? @namespaces else @parent_document&.namespaces || EMPTY_NAMESPACES end end |
#nil_element? ⇒ Boolean
429 430 431 |
# File 'lib/lutaml/xml/xml_element.rb', line 429 def nil_element? find_attribute_value("xsi:nil") == "true" end |
#order ⇒ Object
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/lutaml/xml/xml_element.rb', line 256 def order return @order_cache if @order_cache @order_cache = children.filter_map do |child| if child.text? next if child.text.nil? # For text nodes: # - name is "text" for backward compatibility with tests # - text_content contains the actual text for round-trip serialization # - node_type explicitly marks this as a text node Lutaml::Xml::Element.new("Text", "text", text_content: child.text, node_type: :text) elsif child.cdata? # For CDATA sections: # - name is "#cdata-section" for backward compatibility # - text_content contains the actual CDATA content # - node_type explicitly marks this as CDATA Lutaml::Xml::Element.new("Text", "#cdata-section", text_content: child.text, node_type: :cdata) elsif child.comment? Lutaml::Xml::Element.new("Comment", "comment", text_content: child.text, node_type: :comment) else # For regular elements: # - name is the actual element name # - node_type explicitly marks this as an element # - namespace_uri and namespace_prefix preserve namespace info for rule matching Lutaml::Xml::Element.new("Element", child.unprefixed_name, node_type: :element, namespace_uri: child.namespace_uri, namespace_prefix: child.namespace_prefix) end end end |
#original_namespace_uri ⇒ Object
44 45 46 |
# File 'lib/lutaml/xml/xml_element.rb', line 44 def original_namespace_uri nil end |
#original_xml_element ⇒ Object
40 41 42 |
# File 'lib/lutaml/xml/xml_element.rb', line 40 def original_xml_element nil end |
#own_namespaces ⇒ Object
213 214 215 216 |
# File 'lib/lutaml/xml/xml_element.rb', line 213 def own_namespaces # Return only this element's own namespace declarations (not inherited) @namespaces || EMPTY_NAMESPACES end |
#pretty_print_instance_variables ⇒ Object
This tells which attributes to pretty print, So we remove the so much repeatative output.
126 127 128 129 |
# File 'lib/lutaml/xml/xml_element.rb', line 126 def pretty_print_instance_variables (instance_variables - %i[@adapter_node @parent_document @children_index]).sort end |
#processing_instruction? ⇒ Boolean
Check if this is a processing instruction
153 154 155 |
# File 'lib/lutaml/xml/xml_element.rb', line 153 def processing_instruction? @node_type == :processing_instruction end |
#root ⇒ Object
295 296 297 |
# File 'lib/lutaml/xml/xml_element.rb', line 295 def root self end |
#text ⇒ Object
304 305 306 307 308 309 |
# File 'lib/lutaml/xml/xml_element.rb', line 304 def text return @text if children.empty? return text_children.map(&:text) if children.count > 1 text_children.map(&:text).join end |
#text? ⇒ Boolean
Check if this is a text content node Uses explicit node_type instead of name-based detection
133 134 135 |
# File 'lib/lutaml/xml/xml_element.rb', line 133 def text? @node_type == :text end |
#text_children ⇒ Object
322 323 324 |
# File 'lib/lutaml/xml/xml_element.rb', line 322 def text_children children.select { |child| child.text? && !child.cdata? } end |
#to_h ⇒ Object
425 426 427 |
# File 'lib/lutaml/xml/xml_element.rb', line 425 def to_h document.to_h end |
#unprefixed_name ⇒ Object
189 190 191 |
# File 'lib/lutaml/xml/xml_element.rb', line 189 def unprefixed_name @name end |
#xml_declaration ⇒ Object
Default: no XML declaration. Document wrappers override this.
300 301 302 |
# File 'lib/lutaml/xml/xml_element.rb', line 300 def xml_declaration nil end |
#xml_namespace_prefix ⇒ Object
Stubs for DataModel::XmlElement compatibility. These return nil on adapter XmlElements (the data lives on DataModel::XmlElement).
36 37 38 |
# File 'lib/lutaml/xml/xml_element.rb', line 36 def xml_namespace_prefix nil end |