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.
-
#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) ⇒ 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) ⇒ 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 |
# 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 ) @name = name @namespace_prefix = namespace_prefix @namespace_prefix_explicit = !namespace_prefix.nil? && !namespace_prefix.empty? @attributes = attributes @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 |
#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
355 356 357 |
# File 'lib/lutaml/xml/xml_element.rb', line 355 def [](name) find_attribute_value(name) || find_children_by_name(name) end |
#add_namespace(namespace) ⇒ Object
245 246 247 248 |
# File 'lib/lutaml/xml/xml_element.rb', line 245 def add_namespace(namespace) @namespaces ||= {} @namespaces[namespace.prefix] = namespace end |
#attribute_is_namespace?(name) ⇒ Boolean
241 242 243 |
# File 'lib/lutaml/xml/xml_element.rb', line 241 def attribute_is_namespace?(name) name.to_s.start_with?(XMLNS_PREFIX) end |
#cdata ⇒ Object
310 311 312 313 314 315 |
# File 'lib/lutaml/xml/xml_element.rb', line 310 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
136 137 138 |
# File 'lib/lutaml/xml/xml_element.rb', line 136 def cdata? @node_type == :cdata end |
#cdata_children ⇒ Object
317 318 319 |
# File 'lib/lutaml/xml/xml_element.rb', line 317 def cdata_children children.select(&:cdata?) end |
#comment? ⇒ Boolean
Check if this is a comment node
141 142 143 |
# File 'lib/lutaml/xml/xml_element.rb', line 141 def comment? @node_type == :comment end |
#default_namespace ⇒ Object
250 251 252 |
# File 'lib/lutaml/xml/xml_element.rb', line 250 def default_namespace namespaces[nil] || @parent_document&.namespaces&.dig(nil) end |
#document ⇒ Object
191 192 193 |
# File 'lib/lutaml/xml/xml_element.rb', line 191 def document Document.new(self) end |
#element? ⇒ Boolean
Check if this is a regular element (not text/cdata/comment)
146 147 148 |
# File 'lib/lutaml/xml/xml_element.rb', line 146 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
327 328 329 330 331 332 333 334 |
# File 'lib/lutaml/xml/xml_element.rb', line 327 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
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/lutaml/xml/xml_element.rb', line 339 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
375 376 377 378 379 380 381 382 |
# File 'lib/lutaml/xml/xml_element.rb', line 375 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
386 387 388 389 390 391 392 393 394 395 |
# File 'lib/lutaml/xml/xml_element.rb', line 386 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
359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/lutaml/xml/xml_element.rb', line 359 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
410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/lutaml/xml/xml_element.rb', line 410 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
399 400 401 402 403 404 405 406 407 408 |
# File 'lib/lutaml/xml/xml_element.rb', line 399 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
155 156 157 158 159 |
# File 'lib/lutaml/xml/xml_element.rb', line 155 def name return @name unless namespace_prefix @prefixed_name ||= "#{namespace_prefix}:#{@name}" # rubocop:disable Naming/MemoizedInstanceVariableName end |
#namespace ⇒ Object
216 217 218 219 220 221 222 223 224 |
# File 'lib/lutaml/xml/xml_element.rb', line 216 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.
232 233 234 235 236 237 238 239 |
# File 'lib/lutaml/xml/xml_element.rb', line 232 def namespace_uri return @namespace_uri if defined?(@namespace_uri) @namespace_uri = begin ns = namespace ns&.uri end end |
#namespaced_name ⇒ Object
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/lutaml/xml/xml_element.rb', line 161 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
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/lutaml/xml/xml_element.rb', line 195 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
428 429 430 |
# File 'lib/lutaml/xml/xml_element.rb', line 428 def nil_element? find_attribute_value("xsi:nil") == "true" end |
#order ⇒ Object
254 255 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 |
# File 'lib/lutaml/xml/xml_element.rb', line 254 def order return @order_cache if @order_cache @order_cache = children.filter_map do |child| if child.text? # Skip whitespace-only text nodes (formatting between elements). # Significant text in mixed content will contain non-whitespace. next if child.text.nil? || child.text.strip.empty? # 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? # Skip comments - they're not part of schema element order nil 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
211 212 213 214 |
# File 'lib/lutaml/xml/xml_element.rb', line 211 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.
124 125 126 127 |
# File 'lib/lutaml/xml/xml_element.rb', line 124 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
151 152 153 |
# File 'lib/lutaml/xml/xml_element.rb', line 151 def processing_instruction? @node_type == :processing_instruction end |
#root ⇒ Object
294 295 296 |
# File 'lib/lutaml/xml/xml_element.rb', line 294 def root self end |
#text ⇒ Object
303 304 305 306 307 308 |
# File 'lib/lutaml/xml/xml_element.rb', line 303 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
131 132 133 |
# File 'lib/lutaml/xml/xml_element.rb', line 131 def text? @node_type == :text end |
#text_children ⇒ Object
321 322 323 |
# File 'lib/lutaml/xml/xml_element.rb', line 321 def text_children children.select { |child| child.text? && !child.cdata? } end |
#to_h ⇒ Object
424 425 426 |
# File 'lib/lutaml/xml/xml_element.rb', line 424 def to_h document.to_h end |
#unprefixed_name ⇒ Object
187 188 189 |
# File 'lib/lutaml/xml/xml_element.rb', line 187 def unprefixed_name @name end |
#xml_declaration ⇒ Object
Default: no XML declaration. Document wrappers override this.
299 300 301 |
# File 'lib/lutaml/xml/xml_element.rb', line 299 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 |