|
require "pry" |
|
require "liquid" |
|
require "net/http" |
|
require "rexml/document" |
|
require "uri" |
|
|
|
@metadata_filename = "odata-metadata.xml" |
|
@markdown_template_content = File.read("metadata.template.md") |
|
@markdown_output_filename = "odata-metadata.md" |
|
@classdiagram_template_content = File.read("metadata.template.mmd") |
|
@classdiagram_mermaid_temp_output_filename = "odata-classes.mmd" |
|
@classdiagram_svg_output_filename = "odata-classes.svg" |
|
|
|
def main |
|
root = parse_xml_document(@metadata_filename) |
|
xml_metadata_schema = get_xml_metadata_schema(root) |
|
|
|
template_args = build_template_args(xml_metadata_schema) |
|
render_classdiagram_mermaid_template(template_args) |
|
render_markdown_template(template_args.merge("classdiagram_svg_filename" => @classdiagram_svg_output_filename)) |
|
end |
|
|
|
def parse_xml_document(xml_filename) |
|
xmldoc = REXML::Document.new(File.new(xml_filename)) |
|
xmldoc.root |
|
end |
|
|
|
def get_xml_metadata_schema(root) |
|
xml_metadata_schemas = root.get_elements("//Schema") |
|
raise "We expect exactly one 'Schema': #{xml_metadata_schemas.inspect}" unless xml_metadata_schemas.size == 1 |
|
|
|
xml_metadata_schemas.first |
|
end |
|
|
|
def build_template_args(xml_metadata_schema) |
|
odata_namespace_name = attribute_value(xml_metadata_schema, "Namespace") |
|
xml_entities = xml_metadata_schema.get_elements("EntityType") |
|
xml_associations = xml_metadata_schema.get_elements("Association") |
|
|
|
associations = xml_associations.map { |xa| parse_association(xa, odata_namespace_name) } |
|
entities = xml_entities.map { |xe| parse_entity(xe, associations) } |
|
|
|
{ |
|
"namespace" => odata_namespace_name, |
|
"entities" => entities, |
|
"associations" => associations |
|
} |
|
end |
|
|
|
def parse_association(xml_association, odata_namespace_name) |
|
xml_association_ends = xml_association.get_elements("End") |
|
raise "We expect exactly two association 'End' elements: #{xml_association.inspect}" unless xml_association_ends.size == 2 |
|
|
|
from = attribute_value(xml_association_ends[0], "Type").delete_prefix("#{odata_namespace_name}.") |
|
from_multiplicity = attribute_value(xml_association_ends[0], "Multiplicity") |
|
to = attribute_value(xml_association_ends[1], "Type").delete_prefix("#{odata_namespace_name}.") |
|
to_multiplicity = attribute_value(xml_association_ends[1], "Multiplicity") |
|
|
|
{from:, from_multiplicity:, to:, to_multiplicity:}.transform_keys(&:to_s) |
|
end |
|
|
|
def parse_entity(xml_entity, associations) |
|
xml_properties = xml_entity.get_elements("Property") |
|
|
|
name = attribute_value(xml_entity, "Name") |
|
key_properties = xml_entity.get_elements("Key/PropertyRef").map { |pr| attribute_value(pr, "Name") } |
|
properties = xml_properties.map { |xp| parse_property(xp, key_properties) } |
|
associations_with = associations |
|
.map { |association| association.values_at("from", "to") } |
|
.filter { |association_entities| association_entities.include?(name) } |
|
.flat_map { |association_entities| association_entities - [name] } |
|
|
|
{name:, properties:, associations_with:}.transform_keys(&:to_s) |
|
end |
|
|
|
def parse_property(xml_property, key_properties) |
|
prop_names = %w[Name Type Nullable label MaxLength Precision Scale filterable] |
|
property = prop_names.to_h { |prop| [prop, attribute_value(xml_property, prop)] } |
|
property["Type"] = property["Type"]&.delete_prefix("Edm.") |
|
property["IsKey"] = key_properties.include?(property["Name"]) |
|
property["Nullable"] = property["Nullable"] != "false" |
|
property["filterable"] = property["filterable"] != "false" |
|
property |
|
end |
|
|
|
def attribute_value(xml_element, attribute_name) |
|
xml_element.attribute(attribute_name)&.value |
|
end |
|
|
|
def render_markdown_template(template_args) |
|
template = Liquid::Template.parse(@markdown_template_content, error_mode: :strict) |
|
rendered_output = template.render(template_args) |
|
File.write(@markdown_output_filename, rendered_output) |
|
end |
|
|
|
def render_classdiagram_mermaid_template(template_args) |
|
template = Liquid::Template.parse(@classdiagram_template_content, error_mode: :strict) |
|
rendered_output = template.render(template_args) |
|
File.write(@classdiagram_mermaid_temp_output_filename, rendered_output) |
|
File.write(@classdiagram_svg_output_filename, post_kroki_mermaid_svg(rendered_output)) |
|
end |
|
|
|
def post_kroki_mermaid_svg(data) |
|
res = ::Net::HTTP.post( |
|
URI("https://kroki.io/mermaid/svg"), |
|
data, |
|
"Content-Type" => "text/plain" |
|
) |
|
res.body |
|
end |
|
|
|
main |