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, #generate_declaration, #generate_doctype_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



64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 64

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)


60
61
62
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 60

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



49
50
51
52
53
54
55
56
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 49

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



84
85
86
87
88
89
90
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 84

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



155
156
157
158
159
160
161
162
163
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 155

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.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



170
171
172
173
174
175
176
177
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 170

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

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

  delegate_obj.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



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 207

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



99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 99

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



145
146
147
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 145

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



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 128

def ordered?(element, options = {})
  return false unless element.respond_to?(:element_order)

  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



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 185

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

  if content_rule.custom_methods[:to]
    mapper_class.new.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



119
120
121
# File 'lib/lutaml/xml/adapter/base_adapter.rb', line 119

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