class Lutaml::XMI::Parsers::XML

Class for parsing .xmi schema files into ::Lutaml::Uml::Document

Constants

LOWER_VALUE_MAPPINGS

Attributes

main_model[R]
xmi_cache[R]

Public Class Methods

parse(io, _options = {}) click to toggle source

@param [String] io - file object with path to .xmi file

[Hash] options - options for parsing

@return [Lutaml::XMI::Model::Document]

# File lib/lutaml/xmi/parsers/xml.rb, line 21
def self.parse(io, _options = {})
  new.parse(Nokogiri::XML(io.read))
end

Public Instance Methods

parse(xmi_doc) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 25
def parse(xmi_doc)
  @xmi_cache = {}
  @main_model = xmi_doc
  ::Lutaml::Uml::Document
    .new(serialize_to_hash(xmi_doc))
end

Private Instance Methods

cardinality_max_value(node) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 300
def cardinality_max_value(node)
  upper_value_node = node.xpath(".//upperValue").first
  return unless upper_value_node

  upper_value_node.attributes["value"]&.value
end
cardinality_min_value(node) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 292
def cardinality_min_value(node)
  lower_value_node = node.xpath(".//lowerValue").first
  return unless lower_value_node

  lower_value = lower_value_node.attributes["value"]&.value
  LOWER_VALUE_MAPPINGS[lower_value]
end
class_element_metadata(klass) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 271
def class_element_metadata(klass)
  main_model.xpath(%(//element[@xmi:idref="#{klass['xmi:id']}"]))
end
connector_source_name(xmi_id) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 328
def connector_source_name(xmi_id)
  node = main_model.xpath(%(//source[@xmi:idref="#{xmi_id}"]/model)).first
  return unless node

  node.attributes["name"]&.value
end
connector_target_name(xmi_id) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 335
def connector_target_name(xmi_id)
  node = main_model.xpath(%(//target[@xmi:idref="#{xmi_id}"]/model)).first
  return unless node

  node.attributes["name"]&.value
end
doc_node_attribute_value(node, attr_name) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 307
def doc_node_attribute_value(node, attr_name)
  xmi_id = node["xmi:id"]
  doc_node = main_model.xpath(%(//element[@xmi:idref="#{xmi_id}"]/properties)).first
  return unless doc_node

  doc_node.attributes[attr_name]&.value
end
generalization_association(owner_xmi_id, link) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 251
def generalization_association(owner_xmi_id, link)
  if link.attributes["start"].value == owner_xmi_id
    xmi_id = link.attributes["end"].value
    member_end_type = "inheritance"
    member_end = lookup_entity_name(xmi_id) || connector_target_name(xmi_id)
  else
    xmi_id = link.attributes["start"].value
    member_end_type = "generalization"
    member_end = lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
  end

  member_end_node = main_model.xpath(%(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])).first
  if member_end_node
    assoc = member_end_node.parent
    member_end_cardinality = { "min" => cardinality_min_value(assoc), "max" => cardinality_max_value(assoc) }
  end

  [member_end, member_end_type, member_end_cardinality, nil]
end
lookup_attribute_definition(node) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 315
def lookup_attribute_definition(node)
  xmi_id = node["xmi:id"]
  doc_node = main_model.xpath(%(//attribute[@xmi:idref="#{xmi_id}"]/documentation)).first
  return unless doc_node

  doc_node.attributes["value"]&.value
end
lookup_entity_name(xmi_id) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 323
def lookup_entity_name(xmi_id)
  xmi_cache[xmi_id] ||= model_node_name_by_xmi_id(xmi_id)
  xmi_cache[xmi_id]
end
model_node_name_by_xmi_id(xmi_id) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 342
def model_node_name_by_xmi_id(xmi_id)
  node = main_model.xpath(%(//*[@xmi:id="#{xmi_id}"])).first
  return unless node

  node.attributes["name"]&.value
end
serialize_class_attributes(klass) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 275
def serialize_class_attributes(klass)
  klass.xpath('.//ownedAttribute[@xmi:type="uml:Property"]').map do |attribute|
    type = attribute.xpath(".//type").first || {}
    if attribute.attributes["association"].nil?
      {
        # TODO: xmi_id
        # xmi_id: klass['xmi:id'],
        name: attribute["name"],
        type: lookup_entity_name(type["xmi:idref"]) || type["xmi:idref"],
        is_derived: attribute["isDerived"],
        cardinality: { "min" => cardinality_min_value(attribute), "max" => cardinality_max_value(attribute) },
        definition: lookup_attribute_definition(attribute),
      }
    end
  end.compact
end
serialize_class_constraints(klass) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 166
def serialize_class_constraints(klass)
  class_element_metadata(klass).xpath("./constraints/constraint").map do |constraint|
    {
      xmi_id: constraint["xmi:id"],
      body: constraint["name"],
      definition: HTMLEntities.new.decode(constraint["description"])
    }
  end
end
serialize_class_operations(klass) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 152
def serialize_class_operations(klass)
  klass.xpath('.//ownedOperation').map do |attribute|
    type = attribute.xpath(".//type").first || {}
    if attribute.attributes["association"].nil?
      {
        # TODO: xmi_id
        # xmi_id: klass['xmi:id'],
        name: attribute["name"],
        definition: lookup_attribute_definition(attribute),
      }
    end
  end.compact
end
serialize_member_type(owner_xmi_id, link, link_member_name) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 210
def serialize_member_type(owner_xmi_id, link, link_member_name)
  return if link.name == 'NoteLink'
  return generalization_association(owner_xmi_id, link) if link.name == "Generalization"

  xmi_id = link.attributes[link_member_name].value
  if link.attributes["start"].value == owner_xmi_id
    xmi_id = link.attributes["end"].value
    member_end = lookup_entity_name(xmi_id) || connector_target_name(xmi_id)
  else
    xmi_id = link.attributes["start"].value
    member_end = lookup_entity_name(xmi_id) || connector_source_name(xmi_id)
  end

  if link.name == "Association"
    connector_type = link_member_name == "start" ? "source" : "target"
    assoc_connector = main_model.xpath(%(//connector[@xmi:idref="#{link['xmi:id']}"]/#{connector_type})).first
    if assoc_connector
      connector_type = assoc_connector.children.find { |node| node.name == 'type' }
      if connector_type && connector_type.attributes['multiplicity']
        cardinality = connector_type.attributes['multiplicity']&.value&.split('..')
        cardinality.unshift('1') if cardinality.length == 1
        min, max = cardinality
      end
      connector_role = assoc_connector.children.find { |node| node.name == 'role' }
      if connector_role
        member_end_attribute_name = connector_role.attributes["name"]&.value
      end
      member_end_cardinality = { "min" => LOWER_VALUE_MAPPINGS[min], "max" => max }
    end
  else
    member_end_node = main_model.xpath(%(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])).first
    if member_end_node
      assoc = member_end_node.parent
      member_end_cardinality = { "min" => cardinality_min_value(assoc), "max" => cardinality_max_value(assoc) }
      member_end_attribute_name = assoc.attributes["name"]&.value
    end
  end

  [member_end, "aggregation", member_end_cardinality, member_end_attribute_name]
end
serialize_model_associations(klass) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 128
def serialize_model_associations(klass)
  xmi_id = klass["xmi:id"]
  main_model.xpath(%(//element[@xmi:idref="#{xmi_id}"]/links/*)).map do |link|
    link_member_name = link.attributes["start"].value == xmi_id ? "end" : "start"
    linke_owner_name = link_member_name == "start" ? "end" : "start"
    member_end, member_end_type, member_end_cardinality, member_end_attribute_name = serialize_member_type(xmi_id, link, link_member_name)
    owner_end, owner_end_cardinality, owner_end_attribute_name = serialize_owned_type(xmi_id, link, linke_owner_name)
    if member_end && ((member_end_type != 'aggregation') || (member_end_type == 'aggregation' && member_end_attribute_name))
      doc_node_name = link_member_name == "start" ? "source" : "target"
      definition_node = main_model.xpath(%(//connector[@xmi:idref="#{link['xmi:id']}"]/#{doc_node_name}/documentation)).first
      definition = definition_node.attributes['value']&.value if definition_node
      {
        xmi_id: link["xmi:id"],
        member_end: member_end,
        member_end_type: member_end_type,
        member_end_cardinality: member_end_cardinality,
        member_end_attribute_name: member_end_attribute_name,
        owner_end: owner_end,
        definition: definition
      }
    end
  end.uniq
end
serialize_model_classes(model) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 58
def serialize_model_classes(model)
  model.xpath('./packagedElement[@xmi:type="uml:Class" or @xmi:type="uml:AssociationClass"]').map do |klass|
    {
      xmi_id: klass["xmi:id"],
      xmi_uuid: klass["xmi:uuid"],
      name: klass["name"],
      package: model,
      attributes: serialize_class_attributes(klass),
      associations: serialize_model_associations(klass),
      operations: serialize_class_operations(klass),
      constraints: serialize_class_constraints(klass),
      is_abstract: doc_node_attribute_value(klass, "isAbstract"),
      definition: doc_node_attribute_value(klass, "documentation"),
      stereotype: doc_node_attribute_value(klass, "stereotype")
    }
  end
end
serialize_model_data_types(model) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 99
def serialize_model_data_types(model)
  model.xpath('./packagedElement[@xmi:type="uml:DataType"]').map do |klass|
    {
      xmi_id: klass["xmi:id"],
      xmi_uuid: klass["xmi:uuid"],
      name: klass["name"],
      attributes: serialize_class_attributes(klass),
      operations: serialize_class_operations(klass),
      associations: serialize_model_associations(klass),
      constraints: serialize_class_constraints(klass),
      is_abstract: doc_node_attribute_value(klass, "isAbstract"),
      definition: doc_node_attribute_value(klass, "documentation"),
      stereotype: doc_node_attribute_value(klass, "stereotype"),
    }
  end
end
serialize_model_diagrams(node) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 116
def serialize_model_diagrams(node)
  main_model.xpath(%(//diagrams/diagram/model[@package="#{node['xmi:id']}"])).map do |diagram_model|
    diagram = diagram_model.parent
    properties = diagram.children.find {|n| n.name == 'properties' }
    {
      xmi_id: diagram["xmi:id"],
      name: properties["name"],
      definition: properties.attributes['documentation']&.value
    }
  end
end
serialize_model_enums(model) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 76
def serialize_model_enums(model)
  model.xpath('./packagedElement[@xmi:type="uml:Enumeration"]').map do |enum|
    attributes = enum
      .xpath('.//ownedLiteral[@xmi:type="uml:EnumerationLiteral"]')
      .map do |value|
        type = value.xpath(".//type").first || {}
        {
          name: value["name"],
          type: lookup_entity_name(type["xmi:idref"]) || type["xmi:idref"],
          definition: lookup_attribute_definition(value),
        }
      end
    {
      xmi_id: enum["xmi:id"],
      xmi_uuid: enum["xmi:uuid"],
      name: enum["name"],
      values: attributes,
      definition: doc_node_attribute_value(enum, "documentation"),
      stereotype: doc_node_attribute_value(enum, "stereotype"),
    }
  end
end
serialize_model_packages(model) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 42
def serialize_model_packages(model)
  model.xpath('./packagedElement[@xmi:type="uml:Package"]').map do |package|
    {
      xmi_id: package["xmi:id"],
      name: package["name"],
      classes: serialize_model_classes(package),
      enums: serialize_model_enums(package),
      data_types: serialize_model_data_types(package),
      diagrams: serialize_model_diagrams(package),
      packages: serialize_model_packages(package),
      definition: doc_node_attribute_value(package, "documentation"),
      stereotype: doc_node_attribute_value(package, "stereotype")
    }
  end
end
serialize_owned_type(owner_xmi_id, link, linke_owner_name) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 176
def serialize_owned_type(owner_xmi_id, link, linke_owner_name)
  return if link.name == 'NoteLink'
  return generalization_association(owner_xmi_id, link) if link.name == "Generalization"

  xmi_id = link.attributes[linke_owner_name].value
  owner_end = lookup_entity_name(xmi_id) || connector_source_name(xmi_id)

  if link.name == "Association"
    assoc_connector = main_model.xpath(%(//connector[@xmi:idref="#{link['xmi:id']}"]/source)).first
    if assoc_connector
      connector_type = assoc_connector.children.find { |node| node.name == 'type' }
      if connector_type && connector_type.attributes['multiplicity']
        cardinality = connector_type.attributes['multiplicity']&.value&.split('..')
        cardinality.unshift('1') if cardinality.length == 1
        min, max = cardinality
      end
      connector_role = assoc_connector.children.find { |node| node.name == 'role' }
      if connector_role
        owned_attribute_name = connector_role.attributes["name"]&.value
      end
      owned_cardinality = { "min" => LOWER_VALUE_MAPPINGS[min], "max" => max }
    end
  else
    owned_node = main_model.xpath(%(//ownedAttribute[@association]/type[@xmi:idref="#{xmi_id}"])).first
    if owned_node
      assoc = owned_node.parent
      owned_cardinality = { "min" => cardinality_min_value(assoc), "max" => cardinality_max_value(assoc) }
      owned_attribute_name = assoc.attributes["name"]&.value
    end
  end

  [owner_end, owned_cardinality, owned_attribute_name]
end
serialize_to_hash(xmi_doc) click to toggle source
# File lib/lutaml/xmi/parsers/xml.rb, line 34
def serialize_to_hash(xmi_doc)
  model = xmi_doc.xpath('//uml:Model[@xmi:type="uml:Model"]').first
  {
    name: model["name"],
    packages: serialize_model_packages(model)
  }
end