Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lutaml-Model integration for YAML and XML processing #27

Merged
merged 1 commit into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/dependent-repos.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"repo": [
"plurimath/plurimath"
]
}
16 changes: 16 additions & 0 deletions .github/workflows/depenedent-gems.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: dependent-gems-test

on:
push:
branches: [ main ]
tags: [ v* ]
pull_request:
workflow_dispatch:
repository_dispatch:
types: [ release-passed ]

jobs:
rake:
uses: metanorma/ci/.github/workflows/dependent-rake.yml@main
with:
command: bundle exec rspec
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ gemspec
gem "byebug"
gem "equivalent-xml"
gem "oga"
gem "ox"
gem "plurimath"
gem "pry"
gem "rake"
Expand Down
32 changes: 26 additions & 6 deletions lib/unitsml.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# frozen_string_literal: true

module Unitsml
UNITSML_NS = "https://schema.unitsml.org/unitsml/1.0".freeze

def self.parse(string)
Unitsml::Parser.new(string).parse
end
end

require "unitsml/error"
require "unitsml/sqrt"
require "unitsml/unit"
Expand All @@ -10,9 +20,19 @@
require "unitsml/extender"
require "unitsml/dimension"
require "unitsml/transform"

module Unitsml
def self.parse(string)
Unitsml::Parser.new(string).parse
end
end
require "unitsml/unitsdb/units"
require "unitsml/unitsdb/prefixes"
require "unitsml/unitsdb/dimension"
require "unitsml/unitsdb/dimensions"
require "unitsml/unitsdb/quantities"
require "unitsml/unitsdb/dimension_quantity"
require "unitsdb/config"
Unitsdb::Config.models = {
units: Unitsml::Unitsdb::Units,
prefixes: Unitsml::Unitsdb::Prefixes,
dimension: Unitsml::Unitsdb::Dimension,
dimensions: Unitsml::Unitsdb::Dimensions,
quantities: Unitsml::Unitsdb::Quantities,
dimension_quantity: Unitsml::Unitsdb::DimensionQuantity,
}
require "unitsdb"
77 changes: 43 additions & 34 deletions lib/unitsml/dimension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,53 +15,46 @@ def ==(object)
power_numerator == object&.power_numerator
end

def dim_hash
Unitsdb.parsable_dimensions[dimension_name]
def dim_instance
@dim ||= Unitsdb.dimensions.find_parsables_by_id(dimension_name)
end

def dim_symbols
dim_hash&.values&.last&.values&.first["dim_symbols"].first
dim_instance.send(@dim.processed_keys.last).dim_symbols.first
end

def to_mathml
value = dim_symbols["mathml"]
value = Ox.parse(value)
# MathML key's value in unitsdb/dimensions.yaml file includes mi tags only.
value = ::Mml::Mi.from_xml(dim_symbols.mathml)
method_name = power_numerator ? :msup : :mi
if power_numerator
msup = Utility.ox_element("msup")
msup << (Utility.ox_element("mrow") << value)
msup << (
Utility.ox_element("mrow") << (
Utility.ox_element("mn") << power_numerator
)
value = ::Mml::Msup.new(
mrow_value: [
::Mml::Mrow.new(mi_value: [value]),
::Mml::Mrow.new(
mn_value: [::Mml::Mn.new(value: power_numerator)],
),
]
)
value = msup
end
value
{ method_name: method_name, value: value }
end

def to_latex
value = dim_symbols["latex"]
if power_numerator
value = "#{value}^#{power_numerator}"
end
value
power_numerator_generic_code(:latex)
end

def to_asciimath
value = dim_symbols["ascii"]
value = "#{value}^#{power_numerator}" if power_numerator
value
power_numerator_generic_code(:ascii)
end

def to_html
value = dim_symbols["html"]
value = "#{value}<sup>#{power_numerator}</sup>" if power_numerator
value
def to_unicode
power_numerator_generic_code(:unicode)
end

def to_unicode
value = dim_symbols["unicode"]
value = "#{value}^#{power_numerator}" if power_numerator
def to_html
value = dim_symbols.html
value = "#{value}<sup>#{power_numerator}</sup>" if power_numerator
value
end

Expand All @@ -70,16 +63,32 @@ def generate_id
end

def to_xml
fields = dim_hash[:fields]
symbol = fields.values.first["symbol"]
power_numerator_value = power_numerator || 1
attributes = { symbol: symbol, powerNumerator: power_numerator_value }
element_name = modelize(fields.keys.first.capitalize)
Utility.ox_element(element_name, attributes: attributes)
attributes = {
symbol: dim_instance.processed_symbol,
power_numerator: power_numerator || 1,
}
Model::DimensionQuantities.const_get(modelize(element_name)).new(attributes)
end

def xml_instances_hash
{ element_name => to_xml }
end

def modelize(value)
value&.split("_")&.map(&:capitalize)&.join
end

private

def power_numerator_generic_code(method_name)
value = dim_symbols.public_send(method_name)
return value unless power_numerator

"#{value}^#{power_numerator}"
end

def element_name
dim_instance.processed_keys.first
end
end
end
5 changes: 4 additions & 1 deletion lib/unitsml/extender.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ def ==(object)
end

def to_mathml
Utility.ox_element("mo") << "&#x22c5;"
{
method_name: :mo,
value: ::Mml::Mo.new(value: "&#x22c5;"),
}
end

def to_latex
Expand Down
74 changes: 45 additions & 29 deletions lib/unitsml/formula.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

require_relative "utility"
require "mml"
require "htmlentities"
require "unitsml/utility"
module Unitsml
class Formula
attr_accessor :value, :explicit_value, :root
Expand All @@ -26,14 +28,21 @@ def ==(object)

def to_mathml
if root
attributes = { xmlns: "http://www.w3.org/1998/Math/MathML", display: "block" }
math = Utility.ox_element("math", attributes: attributes)
Ox.dump(
Utility.update_nodes(
math,
value.map(&:to_mathml).flatten,
),
).gsub(/&amp;(.*?)(?=<\/)/, '&\1')
nullify_mml_models if plurimath_available?
math = ::Mml::MathWithNamespace.new(display: "block")
math.ordered = true
math.element_order ||= []
value.each do |instance|
processed_instance = instance.to_mathml
case processed_instance
when Array
processed_instance.each { |hash| add_math_element(math, hash) }
when Hash
add_math_element(math, processed_instance)
end
end
reset_mml_models if plurimath_available?
math.to_xml.gsub(/&amp;(.*?)(?=<\/)/, '&\1')
else
value.map(&:to_mathml)
end
Expand All @@ -56,8 +65,7 @@ def to_unicode
end

def to_xml
dimensions_array = extract_dimensions(value)
if (dimensions_array).any?
if (dimensions_array = extract_dimensions(value)).any?
dimensions(sort_dims(dimensions_array))
elsif @orig_text.match(/-$/)
prefixes
Expand All @@ -76,9 +84,7 @@ def to_plurimath
private

def extract_dimensions(formula)
dimensions = []

formula.each do |term|
formula.each_with_object([]) do |term, dimensions|
if term.is_a?(Dimension)
dimensions << term
elsif term.is_a?(Sqrt) && term.value.is_a?(Dimension)
Expand All @@ -89,14 +95,10 @@ def extract_dimensions(formula)
dimensions.concat(extract_dimensions(term.value))
end
end

dimensions
end

def extract_units(formula)
units_arr = []

formula.each do |term|
formula.each_with_object([]) do |term, units_arr|
case term
when Unit
units_arr << term.dup
Expand All @@ -108,8 +110,6 @@ def extract_units(formula)
units_arr << term.value
end
end

units_arr
end

def units
Expand All @@ -135,13 +135,9 @@ def unique_dimensions(dims, norm_text)

def dimensions(dims)
dim_id = dims.map(&:generate_id).join
attributes = { xmlns: Utility::UNITSML_NS, "xml:id": "D_#{dim_id}" }
Ox.dump(
Utility.update_nodes(
Utility.ox_element("Dimension", attributes: attributes),
dims.map(&:to_xml),
),
)
attributes = { id: "D_#{dim_id}" }
dims.each { |dim| attributes.merge!(dim.xml_instances_hash) }
Model::Dimension.new(attributes).to_xml
end

def sort_dims(values)
Expand All @@ -162,11 +158,31 @@ def prefixes
end

def ensure_plurimath_defined!
return if Object.const_defined?(:Plurimath)
return if plurimath_available?

require "plurimath"
rescue LoadError => e
raise Errors::PlurimathLoadError
end

def add_math_element(math_instance, child_hash)
method_name = child_hash[:method_name]
method_value = math_instance.public_send(:"#{method_name}_value")
method_value += Array(child_hash[:value])
math_instance.public_send(:"#{method_name}_value=", method_value)
math_instance.element_order << Lutaml::Model::XmlAdapter::Element.new("Element", method_name.to_s)
end

def plurimath_available?
Object.const_defined?(:Plurimath)
end

def nullify_mml_models
Plurimath::Mathml::Parser::CONFIGURATION.each_key { |klass| klass.model(klass) }
end

def reset_mml_models
::Mml::Configuration.custom_models = Plurimath::Mathml::Parser::CONFIGURATION
end
end
end
42 changes: 42 additions & 0 deletions lib/unitsml/model/dimension.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require "unitsml/model/dimension_quantities/quantity"
require "unitsml/model/dimension_quantities/length"
require "unitsml/model/dimension_quantities/mass"
require "unitsml/model/dimension_quantities/time"
require "unitsml/model/dimension_quantities/electric_current"
require "unitsml/model/dimension_quantities/thermodynamic_temperature"
require "unitsml/model/dimension_quantities/amount_of_substance"
require "unitsml/model/dimension_quantities/luminous_intensity"
require "unitsml/model/dimension_quantities/plane_angle"

module Unitsml
module Model
class Dimension < Lutaml::Model::Serializable
attribute :id, :string
attribute :length, DimensionQuantities::Length
attribute :mass, DimensionQuantities::Mass
attribute :time, DimensionQuantities::Time
attribute :electric_current, DimensionQuantities::ElectricCurrent
attribute :thermodynamic_temperature, DimensionQuantities::ThermodynamicTemperature
attribute :amount_of_substance, DimensionQuantities::AmountOfSubstance
attribute :luminous_intensity, DimensionQuantities::LuminousIntensity
attribute :plane_angle, DimensionQuantities::PlaneAngle

xml do
root "Dimension"
namespace Unitsml::UNITSML_NS

map_attribute :id, to: :id, namespace: nil, prefix: "xml"
map_element :Length, to: :length
map_element :Mass, to: :mass
map_element :Time, to: :time
map_element :ElectricCurrent, to: :electric_current
map_element :ThermodynamicTemperature, to: :thermodynamic_temperature
map_element :AmountOfSubstance, to: :amount_of_substance
map_element :LuminousIntensity, to: :luminous_intensity
map_element :PlaneAngle, to: :plane_angle
end
end
end
end
10 changes: 10 additions & 0 deletions lib/unitsml/model/dimension_quantities/amount_of_substance.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

module Unitsml
module Model
module DimensionQuantities
class AmountOfSubstance < Quantity
end
end
end
end
10 changes: 10 additions & 0 deletions lib/unitsml/model/dimension_quantities/electric_current.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

module Unitsml
module Model
module DimensionQuantities
class ElectricCurrent < Quantity
end
end
end
end
Loading
Loading