Class: Lutaml::Xml::Adapter::RexmlAdapter
- Inherits:
-
BaseAdapter
- Object
- Document
- BaseAdapter
- Lutaml::Xml::Adapter::RexmlAdapter
- Extended by:
- AdapterHelpers
- Defined in:
- lib/lutaml/xml/adapter/rexml_adapter.rb
Constant Summary collapse
- TEXT_CLASSES =
[Moxml::Text, Moxml::Cdata].freeze
Instance Attribute Summary
Attributes inherited from Document
#doctype, #encoding, #parsed_doc, #register, #root, #xml_declaration
Class Method Summary collapse
- .normalize_xml_for_rexml(xml) ⇒ Object
- .order_of(element) ⇒ Object
- .parse(xml, options = {}) ⇒ Object
-
.text_of(element) ⇒ Object
NOTE: name_of, prefixed_name_of, namespaced_attr_name, namespaced_name_of are provided by AdapterHelpers module via extend.
Instance Method Summary collapse
- #add_simple_value(xml, rule, value, attribute, plan: nil, mapping: nil) ⇒ Object
- #attributes_hash(element) ⇒ Object
- #build_element_with_plan(xml, element, plan, options = {}) ⇒ Object
-
#build_xml_element_with_plan(xml, xml_element, plan, _options = {}) ⇒ Object
Build XML from XmlDataModel::XmlElement using DeclarationPlan tree (PARALLEL TRAVERSAL).
-
#handle_nested_elements_with_plan(xml, value, rule, attribute, plan, options) ⇒ Object
NOTE: build_unordered_children_with_plan and build_ordered_element_with_plan are inherited from BaseAdapter and use child_plan_for for unified plan access.
- #order ⇒ Object
- #to_xml(options = {}) ⇒ Object
Methods included from AdapterHelpers
build_element_from_child, name_of, namespace_uri_hoisted?, namespaced_attr_name, namespaced_name_of, node_type_of, prefix_for_namespace_uri, prefixed_name_of, text_node?
Methods inherited from BaseAdapter
#add_value, #attribute_definition_for, #attribute_value_for, #build_ordered_element_with_plan, #build_unordered_children_with_plan, #child_plan_for, extract_document_processing_instructions, fpi?, fpi_to_urn, name_of, namespaced_attr_name, namespaced_name, namespaced_name_of, #ordered?, prefixed_name_of, #process_content_mapping, #render_element?
Methods included from PolymorphicValueHandler
Methods included from DeclarationHandler
extract_attribute, extract_xml_declaration, #generate_declaration, #generate_doctype_declaration, #should_include_declaration?
Methods inherited from Document
#add_value, #attribute_definition_for, #attribute_value_for, #attributes, #cdata, #children, #declaration, #doctype_declaration, #element_children, #element_children_index, encoding, #initialize, name_of, namespaced_name_of, #ordered?, #parse_element, #process_content_mapping, #render_element?, #text, #to_h, type
Constructor Details
This class inherits a constructor from Lutaml::Xml::Document
Class Method Details
.normalize_xml_for_rexml(xml) ⇒ Object
269 270 271 272 273 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 269 def self.normalize_xml_for_rexml(xml) return xml unless xml.is_a?(String) && xml.encoding.to_s != "UTF-8" xml.encode("UTF-8") end |
.order_of(element) ⇒ Object
258 259 260 261 262 263 264 265 266 267 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 258 def self.order_of(element) element.children.map do |child| instance_args = if TEXT_CLASSES.include?(child.class) ["Text", "text"] else ["Element", name_of(child)] end Element.new(*instance_args) end end |
.parse(xml, options = {}) ⇒ Object
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 13 def self.parse(xml, = {}) parse_encoding = encoding(xml, ) xml = normalize_xml_for_rexml(xml) parsed = Moxml::Adapter::Rexml.parse(xml, encoding: parse_encoding) root_element = parsed.root if root_element.nil? raise REXML::ParseException.new( "Malformed XML: Unable to parse the provided XML document. " \ "The document structure is invalid or incomplete.", ) end @root = Rexml::Element.new(root_element) @root.processing_instructions = extract_document_processing_instructions(parsed) new(@root, parse_encoding) end |
.text_of(element) ⇒ Object
NOTE: name_of, prefixed_name_of, namespaced_attr_name, namespaced_name_of are provided by AdapterHelpers module via extend
242 243 244 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 242 def self.text_of(element) element.content end |
Instance Method Details
#add_simple_value(xml, rule, value, attribute, plan: nil, mapping: nil) ⇒ Object
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 504 def add_simple_value(xml, rule, value, attribute, plan: nil, mapping: nil) # Apply value_map transformation BEFORE checking if should render value = rule.render_value_for(value) if rule # Handle array values by creating multiple elements if value.is_a?(Array) # For empty arrays, check if we should render based on render_empty option if value.empty? # Only create element if render_empty is set to render (not :omit) if rule.render_empty? # Create single empty element for the collection # Determine how to render based on render_empty option if rule.render_empty_as_nil? # render_empty: :as_nil xml.create_and_add_element(rule.name, attributes: { "xsi:nil" => true }, prefix: nil) else # render_empty: :as_blank or :as_empty xml.create_and_add_element(rule.name, attributes: nil, prefix: nil) end end # Don't iterate over empty array return end # Non-empty array: create element for each value value.each do |val| add_simple_value(xml, rule, val, attribute, plan: plan, mapping: mapping) end return end # Get form_default from parent's schema (namespace class) form_default = mapping&.namespace_class&.element_form_default || :qualified # Resolve element's namespace first to know which namespace we're dealing with temp_ns_info = rule.resolve_namespace( attr: attribute, register: register, parent_ns_uri: mapping&.namespace_uri, parent_ns_class: mapping&.namespace_class, form_default: form_default, use_prefix: false, # Temporary, just to get namespace parent_prefix: nil, ) element_ns_uri = temp_ns_info[:uri] # NAMESPACE RESOLUTION: Determine if element should use prefix # Cases: # 1. namespace: :inherit → always use parent prefix # 2. Type namespace → use Type's namespace from plan # 3. Parent uses prefix format AND element has no explicit/type namespace → inherit parent # 4. Element has namespace matching parent → check plan[:namespaces][ns_class] # 5. Element has explicit namespace: nil → NO prefix ever use_prefix = false parent_prefix = nil # PRIORITY: Check explicit form and prefix options FIRST # These override all other considerations if rule.qualified? # Explicit form: :qualified - element MUST use prefix use_prefix = true # Find appropriate prefix for the element's namespace if element_ns_uri && plan && plan[:namespaces] ns_entry = plan[:namespaces].find do |_key, ns_config| ns_config[:ns_object].uri == element_ns_uri end if ns_entry _key, ns_config = ns_entry parent_prefix = ns_config[:ns_object].prefix_default end end elsif rule.unqualified? # Explicit form: :unqualified - element MUST NOT use prefix use_prefix = false parent_prefix = nil elsif rule.prefix_set? # Explicit prefix option - element should use specified prefix use_prefix = true # If prefix is a string, use it; if true, use namespace's default prefix if rule.prefix.is_a?(String) parent_prefix = rule.prefix elsif element_ns_uri && plan && plan[:namespaces] ns_entry = plan[:namespaces].find do |_key, ns_config| ns_config[:ns_object].uri == element_ns_uri end if ns_entry _key, ns_config = ns_entry parent_prefix = ns_config[:ns_object].prefix_default end end elsif rule.namespace_param == :inherit # Case 1: Explicit :inherit - always use parent format use_prefix = true if plan && mapping&.namespace_class key = mapping.namespace_class.to_key ns_config = plan[:namespaces][key] if ns_config && ns_config[:format] == :prefix # CRITICAL: Use the ns_object from plan (may be override with custom prefix) parent_prefix = ns_config[:ns_object].prefix_default end end elsif plan && plan[:type_namespaces] && plan[:type_namespaces][rule.to] # Case 2: Type namespace - this attribute's type defines its own namespace # Priority: Type namespace takes precedence over parent inheritance type_ns_class = plan[:type_namespaces][rule.to] key = type_ns_class.to_key ns_config = plan[:namespaces][key] if ns_config && ns_config[:format] == :prefix use_prefix = true # CRITICAL: Use ns_object from plan (may be override with custom prefix) parent_prefix = ns_config[:ns_object].prefix_default end elsif !rule.namespace_set? && !element_ns_uri && mapping&.namespace_class && plan # Case 3: NEW - Format Matching Rule # When parent uses prefix format AND element has no explicit namespace AND no type namespace, # element inherits parent's namespace and prefix for consistent formatting. # This handles the test case where children should match parent's serialization format. # IMPORTANT: Only applies when element_form_default is :qualified key = mapping.namespace_class.to_key ns_config = plan[:namespaces][key] if ns_config && ns_config[:format] == :prefix && form_default == :qualified # Parent is using prefix format AND schema requires qualified elements use_prefix = true parent_prefix = ns_config[:ns_object].prefix_default # Override element_ns_uri to parent's URI for proper resolution element_ns_uri = mapping.namespace_uri end elsif element_ns_uri # Case 4: Element has explicit namespace - check if it's in prefix mode # Need to find the namespace class by URI to look up config if plan && plan[:namespaces] # Find namespace entry that matches this URI ns_entry = plan[:namespaces].find do |_key, ns_config| ns_config[:ns_object].uri == element_ns_uri end if ns_entry _key, ns_config = ns_entry use_prefix = ns_config[:format] == :prefix parent_prefix = ns_config[:ns_object].prefix_default if use_prefix end end elsif !rule.namespace_set? && element_ns_uri && element_ns_uri == mapping&.namespace_uri # Case 5: Element has SAME namespace as parent (not nil, not unqualified) # Element has a resolved namespace that matches parent -> inherit parent format # Truly unqualified elements (element_ns_uri.nil?) do NOT inherit if plan && mapping&.namespace_class key = mapping.namespace_class.to_key ns_config = plan[:namespaces][key] if ns_config && ns_config[:format] == :prefix use_prefix = true # CRITICAL: Use the ns_object from plan (may be override with custom prefix) parent_prefix = ns_config[:ns_object].prefix_default end end end # Case 6: explicit namespace: nil is handled by namespace_set? && namespace_param == nil # Case 7: truly unqualified (element_ns_uri.nil?) falls through with use_prefix = false # Now resolve with correct use_prefix ns_info = rule.resolve_namespace( attr: attribute, register: register, parent_ns_uri: mapping&.namespace_uri, parent_ns_class: mapping&.namespace_class, form_default: form_default, use_prefix: use_prefix, parent_prefix: parent_prefix, ) # Use resolved namespace directly, BUT handle special cases: # 1. namespace: :inherit → ALWAYS use parent prefix (resolved has parent URI) # 2. Truly unqualified elements (element_ns_uri==nil) → NO prefix unless :inherit resolved_prefix = if rule.namespace_param == :inherit || (use_prefix && parent_prefix) parent_prefix else ns_info[:prefix] end # Prepare attributes (no xmlns declaration - handled by DeclarationPlanner) attributes = {} # Check if this namespace needs local declaration (out of scope) if resolved_prefix && plan && plan[:namespaces] # Find the namespace config for this prefix/URI ns_entry = plan[:namespaces].find do |_key, ns_config| ns_config[:ns_object].prefix_default == resolved_prefix || (ns_info[:uri] && ns_config[:ns_object].uri == ns_info[:uri]) end if ns_entry _key, ns_config = ns_entry # If namespace is marked for local declaration, add xmlns attribute if ns_config[:declared_at] == :local_on_use xmlns_attr = "xmlns:#{resolved_prefix}" attributes[xmlns_attr] = ns_config[:ns_object].uri end end end if value.nil? # Check render_nil option to determine how to render nil value if rule.render_nil_as_blank? || rule.render_nil_as_empty? # render_nil: :as_blank or :as_empty - create blank element without xsi:nil xml.create_and_add_element(rule.name, attributes: attributes, prefix: resolved_prefix) else # render_nil: :as_nil or default - create element with xsi:nil="true" xml.create_and_add_element(rule.name, attributes: attributes.merge({ "xsi:nil" => true }), prefix: resolved_prefix) end elsif ::Lutaml::Model::Utils.uninitialized?(value) # Handle uninitialized values - don't try to serialize them as text # This should not normally happen as render? should filter these out # But if render_omitted is set, we might reach here nil elsif ::Lutaml::Model::Utils.empty?(value) xml.create_and_add_element(rule.name, attributes: attributes, prefix: resolved_prefix) elsif rule.raw_mapping? xml.add_xml_fragment(xml, value) elsif value.is_a?(::Hash) && attribute&.type(register) == Lutaml::Model::Type::Hash # Check if value is Hash type that needs wrapper - do this BEFORE any wrapping/serialization # Value is already transformed by ExportTransformer before reaching here xml.create_and_add_element(rule.name, attributes: attributes, prefix: resolved_prefix) do value.each do |key, val| xml.create_and_add_element(key.to_s) do xml.add_text(xml, val.to_s) end end end else xml.create_and_add_element(rule.name, attributes: attributes, prefix: resolved_prefix) do add_value(xml, value, attribute, cdata: rule.cdata) end end end |
#attributes_hash(element) ⇒ Object
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 221 def attributes_hash(element) result = Lutaml::Model::MappingHash.new element.attributes.each_value do |attr| if attr.name == "schemaLocation" result["__schema_location"] = { namespace: attr.namespace, prefix: attr.namespace.prefix, schema_location: attr.value, } else result[self.class.namespaced_attr_name(attr)] = attr.value end end result end |
#build_element_with_plan(xml, element, plan, options = {}) ⇒ Object
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 275 def build_element_with_plan(xml, element, plan, = {}) mapper_class = [:mapper_class] || element.class xml_mapping = mapper_class.mappings_for(:xml) return xml unless xml_mapping plan ||= { namespaces: {}, children_plans: {}, type_namespaces: {}, } # TYPE-ONLY MODELS: No element wrapper, serialize children directly # BUT if we have a tag_name in options, that means parent wants a wrapper if xml_mapping.no_element? # If parent provided a tag_name, create that wrapper first if [:tag_name] xml.create_and_add_element([:tag_name]) do |inner_xml| # Serialize type-only model's children inside parent's wrapper xml_mapping.elements.each do |element_rule| next if [:except]&.include?(element_rule.to) attribute_def = mapper_class.attributes[element_rule.to] next unless attribute_def value = element.send(element_rule.to) next unless element_rule.render?(value, element) # For type-only models, children plans may not be available # Serialize children directly if value && attribute_def.type(register)&.<=(Lutaml::Model::Serialize) # Nested model - recursively build it child_plan = child_plan_for(plan, element_rule.to) || { namespaces: {}, children_plans: {}, type_namespaces: {}, } build_element_with_plan( inner_xml, value, child_plan, { mapper_class: attribute_def.type(register), tag_name: element_rule.name }, ) else # Simple value - create element directly inner_xml.create_and_add_element(element_rule.name) do add_value(inner_xml, value, attribute_def, cdata: element_rule.cdata) end end end end else # No wrapper at all - serialize children directly (for root-level type-only) xml_mapping.elements.each do |element_rule| next if [:except]&.include?(element_rule.to) attribute_def = mapper_class.attributes[element_rule.to] next unless attribute_def value = element.send(element_rule.to) next unless element_rule.render?(value, element) child_plan = child_plan_for(plan, element_rule.to) if value && attribute_def.type(register)&.<=(Lutaml::Model::Serialize) handle_nested_elements_with_plan( xml, value, element_rule, attribute_def, child_plan, , ) else add_simple_value(xml, element_rule, value, attribute_def, plan: plan, mapping: xml_mapping) end end end return xml end # Use xmlns declarations from plan attributes = {} # Apply namespace declarations from plan plan[:namespaces]&.each_value do |ns_config| next unless ns_config[:declared_at] == :here ns_class = ns_config[:ns_object] # Parse the ready-to-use declaration string decl = ns_config[:xmlns_declaration] if decl.start_with?("xmlns:") # Prefixed namespace: "xmlns:prefix=\"uri\"" prefix = decl[/xmlns:(\w+)=/, 1] attributes["xmlns:#{prefix}"] = ns_class.uri else # Default namespace: "xmlns=\"uri\"" attributes["xmlns"] = ns_class.uri end end # Collect attribute custom methods to call after element creation attribute_custom_methods = [] # Add regular attributes (non-xmlns) xml_mapping.attributes.each do |attribute_rule| next if [:except]&.include?(attribute_rule.to) # Collect custom methods for later execution (after element is created) if attribute_rule.custom_methods[:to] attribute_custom_methods << attribute_rule next end mapping_rule_name = if attribute_rule.multiple_mappings? attribute_rule.name.first else attribute_rule.name end attr = attribute_definition_for(element, attribute_rule, mapper_class: mapper_class) value = attribute_rule.to_value_for(element) value = attr.serialize(value, :xml, register) if attr value = ExportTransformer.call(value, attribute_rule, attr, format: :xml) value = value&.join(attribute_rule.delimiter) if attribute_rule.delimiter if attribute_rule.as_list && attribute_rule.as_list[:export] value = attribute_rule.as_list[:export].call(value) end if render_element?(attribute_rule, element, value) # Resolve attribute namespace from plan ns_info = resolve_attribute_namespace(attribute_rule, attr, .merge(mapper_class: mapper_class)) attr_name = if ns_info[:prefix] "#{ns_info[:prefix]}:#{mapping_rule_name}" else attribute_rule.prefixed_name end attributes[attr_name] = value ? value.to_s : value end end # Add schema_location attribute from ElementNode if present # This is for the plan-based path where schema_location_attr is computed during planning attributes.merge!(plan.root_node.schema_location_attr) if plan.respond_to?(:root_node) && plan.root_node&.schema_location_attr # Determine prefix from plan prefix = nil option_rule = [:rule] namespace_class = if option_rule&.prefix_set? || option_rule&.namespace_set? option_rule.namespace_class else xml_mapping.namespace_class end if namespace_class key = namespace_class.to_key ns_config = plan[:namespaces][key] if ns_config && ns_config[:format] == :prefix # Use prefix from the plan's namespace object (may be custom override) prefix = ns_config[:ns_object].prefix_default end end tag_name = [:tag_name] || xml_mapping.root_element return if [:except]&.include?(tag_name) xml.create_and_add_element(tag_name, prefix: prefix, attributes: attributes.compact) do # Call attribute custom methods now that element is created attribute_custom_methods.each do |attribute_rule| mapper_class.new.send(attribute_rule.custom_methods[:to], element, xml.parent, xml) end if ordered?(element, .merge(mapper_class: mapper_class)) build_ordered_element_with_plan(xml, element, plan, .merge(mapper_class: mapper_class, parent_prefix: prefix)) else build_unordered_children_with_plan(xml, element, plan, .merge(mapper_class: mapper_class, parent_prefix: prefix)) end end end |
#build_xml_element_with_plan(xml, xml_element, plan, _options = {}) ⇒ Object
Build XML from XmlDataModel::XmlElement using DeclarationPlan tree (PARALLEL TRAVERSAL)
122 123 124 125 126 127 128 129 130 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 122 def build_xml_element_with_plan(xml, xml_element, plan, = {}) # Add processing instructions before root element xml_element.processing_instructions.each do |pi| xml.add_processing_instruction(pi.target, pi.content) end build_rexml_element(xml, xml_element, plan.root_node, plan.global_prefix_registry, plan) end |
#handle_nested_elements_with_plan(xml, value, rule, attribute, plan, options) ⇒ Object
NOTE: build_unordered_children_with_plan and build_ordered_element_with_plan are inherited from BaseAdapter and use child_plan_for for unified plan access
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 468 def handle_nested_elements_with_plan(xml, value, rule, attribute, plan, ) = .merge( rule: rule, attribute: attribute, tag_name: rule.name, mapper_class: attribute.type(register), # Override with child's type ) if value.is_a?(Lutaml::Model::Collection) value.collection.each do |val| build_element_with_plan(xml, val, plan, ) end return end case value when Array value.each do |val| if plan build_element_with_plan(xml, val, plan, ) else # Fallback for cases without plan build_element(xml, val, ) end end else if plan build_element_with_plan(xml, value, plan, ) else # Fallback for cases without plan build_element(xml, value, ) end end end |
#order ⇒ Object
246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 246 def order children.filter_map do |child| if child.text? next if child.text.nil? || child.text.strip.empty? Element.new("Text", child.unprefixed_name) else Element.new("Element", child.unprefixed_name) end end end |
#to_xml(options = {}) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/lutaml/xml/adapter/rexml_adapter.rb', line 32 def to_xml( = {}) encoding = determine_encoding() = encoding ? { encoding: encoding } : {} builder = Builder::Rexml.build() do |xml| if @root.is_a?(Rexml::Element) # Case A: Old parsed XML (from Rexml::Element) - use build_xml @root.build_xml(xml) else # Cases B & C: XmlElement or Model instance # ARCHITECTURE: Normalize to XmlElement, then use single rendering path # Determine the source (XmlElement or model instance) original_model = nil xml_element = if @root.is_a?(Lutaml::Xml::DataModel::XmlElement) # Case B: Already an XmlElement @root else # Case C: Model instance - check for custom methods first mapper_class = [:mapper_class] || @root.class xml_mapping = mapper_class.mappings_for(:xml) # Check if model has map_all with custom methods # Custom methods work with model instances, not XmlElement trees has_custom_map_all = xml_mapping.raw_mapping&.custom_methods && xml_mapping.raw_mapping.custom_methods[:to] if has_custom_map_all # Use legacy path for custom methods - don't transform nil else # Transform model to XmlElement tree original_model = @root transformation = mapper_class.transformation_for( :xml, register ) transformation.transform(@root, ) end end if xml_element # Modern path: Use XmlElement + DeclarationPlan tree mapper_class = [:mapper_class] || xml_element.class mapping = mapper_class.mappings_for(:xml) # Phase 1: Collect namespace needs from XmlElement tree collector = NamespaceCollector.new(register) needs = collector.collect(xml_element, mapping, mapper_class: mapper_class) # Phase 2: Plan namespace declarations (builds ElementNode tree) planner = DeclarationPlanner.new(register) plan = planner.plan(xml_element, mapping, needs, options: ) # Phase 3: Render using XmlElement + DeclarationPlan = .merge(is_root_element: true) if original_model [:original_model] = original_model end build_xml_element_with_plan(xml, xml_element, plan, ) else # Legacy path: Model instance with custom methods mapper_class = [:mapper_class] || @root.class xml_mapping = mapper_class.mappings_for(:xml) collector = NamespaceCollector.new(register) needs = collector.collect(@root, xml_mapping) planner = DeclarationPlanner.new(register) plan = planner.plan(@root, xml_mapping, needs, options: ) build_element_with_plan(xml, @root, plan, ) end end end xml_data = builder.to_xml [:declaration] ? declaration() + xml_data : xml_data end |