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
358 359 360 |
# File 'lib/lutaml/xml/xml_element.rb', line 358 def [](name) find_attribute_value(name) || find_children_by_name(name) end |
#add_namespace(namespace) ⇒ Object
251 252 253 254 |
# File 'lib/lutaml/xml/xml_element.rb', line 251 def add_namespace(namespace) @namespaces ||= {} @namespaces[namespace.prefix] = namespace end |
#attribute_is_namespace?(name) ⇒ Boolean
247 248 249 |
# File 'lib/lutaml/xml/xml_element.rb', line 247 def attribute_is_namespace?(name) name.to_s.start_with?(XMLNS_PREFIX) end |
#cdata ⇒ Object
312 313 314 315 316 317 |
# File 'lib/lutaml/xml/xml_element.rb', line 312 def cdata return @text if children.empty? return cdata_children.map(&:text) if content_bearing_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
319 320 321 |
# File 'lib/lutaml/xml/xml_element.rb', line 319 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
256 257 258 |
# File 'lib/lutaml/xml/xml_element.rb', line 256 def default_namespace namespaces[nil] || @parent_document&.namespaces&.dig(nil) end |
#document ⇒ Object
196 197 198 |
# File 'lib/lutaml/xml/xml_element.rb', line 196 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
329 330 331 332 333 334 335 336 337 |
# File 'lib/lutaml/xml/xml_element.rb', line 329 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? || child.processing_instruction?)) 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
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/lutaml/xml/xml_element.rb', line 342 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
378 379 380 381 382 383 384 385 |
# File 'lib/lutaml/xml/xml_element.rb', line 378 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
389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/lutaml/xml/xml_element.rb', line 389 def ensure_children_index return if @children_index @children_index = {} @children.each do |child| next if child.is_a?(XmlElement) && child.processing_instruction? key = child.namespaced_name @children_index[key] ||= [] @children_index[key] << child end end |
#find_attribute_value(attribute_name) ⇒ Object
362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/lutaml/xml/xml_element.rb', line 362 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
415 416 417 418 419 420 421 422 423 424 425 426 427 |
# File 'lib/lutaml/xml/xml_element.rb', line 415 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
404 405 406 407 408 409 410 411 412 413 |
# File 'lib/lutaml/xml/xml_element.rb', line 404 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
221 222 223 224 225 226 227 228 229 |
# File 'lib/lutaml/xml/xml_element.rb', line 221 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.
237 238 239 240 241 242 243 244 245 |
# File 'lib/lutaml/xml/xml_element.rb', line 237 def namespace_uri return @namespace_uri if defined?(@namespace_uri) @namespace_uri = begin ns = namespace uri = ns&.uri uri ? -uri : nil 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 188 189 190 |
# 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 raw = 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 # Deduplicate the string to reduce GC pressure from repeated # namespace URIs across thousands of elements. -raw end end |
#namespaces ⇒ Object
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/lutaml/xml/xml_element.rb', line 200 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
433 434 435 |
# File 'lib/lutaml/xml/xml_element.rb', line 433 def nil_element? find_attribute_value("xsi:nil") == "true" end |
#order ⇒ Object
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 |
# File 'lib/lutaml/xml/xml_element.rb', line 260 def order return @order_cache if @order_cache @order_cache = children.filter_map do |child| if child.cdata? Lutaml::Xml::Element.new("Text", "#cdata-section", text_content: child.text, node_type: :cdata) elsif child.text? next if child.text.nil? Lutaml::Xml::Element.new("Text", "text", text_content: child.text, node_type: :text) elsif child.comment? Lutaml::Xml::Element.new("Comment", "comment", text_content: child.text, node_type: :comment) elsif child.processing_instruction? Lutaml::Xml::Element.new("ProcessingInstruction", child.unprefixed_name, text_content: child.text, node_type: :processing_instruction) else Lutaml::Xml::Element.new("Element", child.unprefixed_name, node_type: :element, namespace_uri: child.namespace_uri, namespace_prefix: child.namespace_prefix) end end.each(&:freeze).freeze 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
216 217 218 219 |
# File 'lib/lutaml/xml/xml_element.rb', line 216 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
292 293 294 |
# File 'lib/lutaml/xml/xml_element.rb', line 292 def root self end |
#text ⇒ Object
301 302 303 304 305 306 307 308 309 310 |
# File 'lib/lutaml/xml/xml_element.rb', line 301 def text return @text if children.empty? return @computed_text if defined?(@computed_text) @computed_text = if content_bearing_children_count > 1 text_children.map(&:text) else text_children.map(&:text).join end 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
323 324 325 |
# File 'lib/lutaml/xml/xml_element.rb', line 323 def text_children children.select { |child| child.text? && !child.cdata? } end |
#to_h ⇒ Object
429 430 431 |
# File 'lib/lutaml/xml/xml_element.rb', line 429 def to_h document.to_h end |
#unprefixed_name ⇒ Object
192 193 194 |
# File 'lib/lutaml/xml/xml_element.rb', line 192 def unprefixed_name @name end |
#xml_declaration ⇒ Object
Default: no XML declaration. Document wrappers override this.
297 298 299 |
# File 'lib/lutaml/xml/xml_element.rb', line 297 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 |