Class: Lutaml::Xml::Adapter::BaseAdapter Abstract

Inherits:
Document
  • Object
show all
Extended by:
AdapterHelpers, XmlParser, DocTypeExtractor
Includes:
NamespaceUriCollector, PlanBasedBuilder, XmlSerializer, DeclarationHandler, PolymorphicValueHandler
Defined in:
lib/lutaml/xml/adapter/base_adapter.rb

Overview

This class is abstract.

Subclass and implement required methods

Base class for XML adapters providing shared functionality.

This class extracts common code from NokogiriAdapter, OxAdapter, OgaAdapter, and RexmlAdapter to reduce duplication and ensure consistent behavior across adapters.

Subclasses must implement:

  • MOXML_ADAPTER - Moxml adapter implementation for parsing

  • BUILDER_CLASS - Builder implementation for serialization

  • PARSED_ELEMENT_CLASS - Adapter element class returned by parsing

Direct Known Subclasses

NokogiriAdapter, OgaAdapter, OxAdapter, RexmlAdapter

Constant Summary collapse

EMPTY_DOCUMENT_ERROR_MESSAGE =
"Document has no root element. " \
"The XML may be empty, contain only whitespace, " \
"or consist only of an XML declaration."
EMPTY_DOCUMENT_ERROR_TYPE =
:invalid_format
PARSE_ERROR_CLASS =
nil

Constants included from AdapterHelpers

AdapterHelpers::TEXT_CLASSES

Instance Attribute Summary

Attributes inherited from Document

#doctype, #encoding, #parsed_doc, #register, #root, #xml_declaration

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DocTypeExtractor

extract_doctype_from_xml

Methods included from AdapterHelpers

name_of, namespaced_attr_name, namespaced_name_of, node_type_of, prefixed_name_of, text_node?

Methods included from XmlParser

parse

Methods included from PlanBasedBuilder

#build_element_with_plan, #build_ordered_element_with_plan, #build_unordered_children_with_plan, #build_xml_element

Methods included from XmlSerializer

#add_value, #build_serializable_xml, #build_xml_element_with_plan, #to_xml

Methods included from NamespaceUriCollector

#collect_original_namespace_uris

Methods included from PolymorphicValueHandler

#polymorphic_value?

Methods included from DeclarationHandler

extract_attribute, extract_xml_declaration, #should_include_declaration?

Methods inherited from Document

#add_value, #attributes, #cdata, #children, #declaration, #doctype_declaration, #element_children, #element_children_index, encoding, #initialize, name_of, namespaced_name_of, order_of, parse, #parse_element, #text, text_of, #to_h, type

Constructor Details

This class inherits a constructor from Lutaml::Xml::Document

Class Method Details

.extract_document_processing_instructions(moxml_doc) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 55

def self.extract_document_processing_instructions(moxml_doc)
  pis = []
  root = moxml_doc.root
  moxml_doc.children.each do |child|
    break if child == root
    next unless child.is_a?(Moxml::ProcessingInstruction)

    pis << Lutaml::Xml::DataModel::XmlProcessingInstruction.new(
      child.target, child.content.to_s.strip
    )
  end
  pis
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).

Returns:

  • (Boolean)


51
52
53
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 51

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:”



40
41
42
43
44
45
46
47
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 40

def self.fpi_to_urn(fpi)
  return nil unless fpi.is_a?(String) && fpi.start_with?("-//", "+//")

  # Replace spaces with + per RFC 3151
  normalized = fpi.gsub(" ", "+")

  "urn:publicid:#{normalized}"
end

.namespaced_name(namespace_uri, prefix, name) ⇒ String

Build a namespaced element name

Parameters:

  • namespace_uri (String, nil)

    the namespace URI

  • prefix (String, nil)

    the namespace prefix

  • name (String)

    the element name

Returns:

  • (String)

    the qualified element name



75
76
77
78
79
80
81
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 75

def self.namespaced_name(namespace_uri, prefix, name)
  if namespace_uri
    prefix ? "#{prefix}:#{name}" : name
  else
    name
  end
end

Instance Method Details

#attribute_definition_for(element, rule, mapper_class: nil) ⇒ Attribute?

Get attribute definition for an element and rule

Parameters:

  • element (Object)

    the model instance

  • rule (MappingRule)

    the mapping rule

  • mapper_class (Class, nil) (defaults to: nil)

    optional mapper class

Returns:

  • (Attribute, nil)

    the attribute definition



146
147
148
149
150
151
152
153
154
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 146

def attribute_definition_for(element, rule, mapper_class: nil)
  klass = mapper_class || element.class
  return klass.attributes[rule.to] unless rule.delegate

  delegated_obj = element.public_send(rule.delegate)
  return nil if delegated_obj.nil?

  delegated_obj.class.attributes[rule.to]
end

#attribute_value_for(element, rule) ⇒ Object?

Get attribute value for an element and rule

Parameters:

  • element (Object)

    the model instance

  • rule (MappingRule)

    the mapping rule

Returns:

  • (Object, nil)

    the attribute value or nil if delegate is nil



161
162
163
164
165
166
167
168
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 161

def attribute_value_for(element, rule)
  return element.public_send(rule.to) unless rule.delegate

  delegate_obj = element.public_send(rule.delegate)
  return nil if delegate_obj.nil?

  delegate_obj.public_send(rule.to)
end

#attributes_hash(element) ⇒ Hash

Build attributes hash from element attributes

Parameters:

  • element (Object)

    the element with attributes

Returns:

  • (Hash)

    hash of attribute names to values



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 198

def attributes_hash(element)
  result = Lutaml::Model::MappingHash.new

  attribute_values(element) do |attr|
    if schema_location_attribute?(attr)
      result["__schema_location"] = {
        namespace: attr.namespace,
        prefix: attribute_namespace_prefix(attr),
        schema_location: attr.value,
      }
    else
      result[attribute_hash_name(attr)] = attr.value
    end
  end

  result
end

#determine_encoding(options) ⇒ String?

Determine encoding for XML output Returns nil when encoding is explicitly set to nil (to not set encoding at all)

Parameters:

  • options (Hash)

    serialization options

Returns:

  • (String, nil)

    the encoding to use, or nil to skip setting encoding



90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 90

def determine_encoding(options)
  if options.key?(:encoding)
    # Return nil if encoding is explicitly nil (don't set encoding)
    # Return the value otherwise
    options[:encoding]
  elsif options.key?(:parse_encoding)
    options[:parse_encoding]
  elsif @encoding && @encoding.to_s.upcase != "ASCII-8BIT"
    @encoding
  else
    "UTF-8"
  end
end

#orderObject



136
137
138
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 136

def order
  root.order
end

#ordered?(element, options = {}) ⇒ Boolean

Check if element has ordered content

Parameters:

  • element (Object)

    the model instance

  • options (Hash) (defaults to: {})

    serialization options

Returns:

  • (Boolean)

    true if element has ordered content



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 119

def ordered?(element, options = {})
  return false unless element.is_a?(Lutaml::Model::Serialize)

  mapper_class = options[:mapper_class]
  xml_mapping = mapper_class&.mappings_for(:xml)

  # Class mapping is the authoritative source for ordered/mixed.
  # Instance @ordered/@mixed are stale after class definition changes.
  if xml_mapping&.mixed_content? || xml_mapping&.ordered?
    return !element.element_order.nil? && !element.element_order.empty?
  end

  return options[:mixed_content] if options.key?(:mixed_content)

  false
end

#process_content_mapping(element, content_rule, xml, mapper_class) ⇒ Object

Process content mapping for an element

Parameters:

  • element (Object)

    the model instance

  • content_rule (MappingRule)

    the content mapping rule

  • xml (Builder)

    the XML builder

  • mapper_class (Class)

    the mapper class



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 176

def process_content_mapping(element, content_rule, xml, mapper_class)
  return unless content_rule

  if content_rule.custom_methods[:to]
    mapper_class.new.public_send(
      content_rule.custom_methods[:to],
      element,
      xml.parent,
      xml,
    )
  else
    text = content_rule.serialize(element)
    text = text.join if text.is_a?(Array)

    xml.add_text(xml, text, cdata: content_rule.cdata)
  end
end

#render_element?(rule, element, value) ⇒ Boolean

Check if an element should be rendered

Parameters:

  • rule (MappingRule)

    the mapping rule

  • element (Object)

    the model instance

  • value (Object)

    the value to check

Returns:

  • (Boolean)

    true if the element should be rendered



110
111
112
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 110

def render_element?(rule, element, value)
  rule.render?(value, element)
end