diff --git a/lib/uniword.rb b/lib/uniword.rb index 16bd59d4..cd8f6f4f 100644 --- a/lib/uniword.rb +++ b/lib/uniword.rb @@ -46,10 +46,16 @@ module Uniword # directly, falling back to :default for Uniword's own types. Setting # Config.default_register means from_xml/to_xml calls without an explicit # register: argument use :uniword automatically. + # + # OmmlIntegration (autoloaded below) then substitutes omml's minimal + # WordprocessingML stubs with Uniword's richer classes via the Register's + # type substitution mechanism — single source of truth for WordprocessingML. require "omml" Omml::Configuration.register_in(:uniword) Lutaml::Model::Config.default_register = :uniword + autoload :OmmlIntegration, "uniword/omml_integration" + # Version constant autoload :VERSION, "uniword/version" autoload :ModelAttributeAccess, "uniword/model_attribute_access" @@ -344,4 +350,8 @@ def html_to_docx(html, path) from_html(html).to_file(path) end end + + # Register WordprocessingML substitutions after autoloads are declared + # so OmmlIntegration can resolve Uniword class constants via const_get. + OmmlIntegration.register end diff --git a/lib/uniword/omml_integration.rb b/lib/uniword/omml_integration.rb new file mode 100644 index 00000000..f1e6dcbb --- /dev/null +++ b/lib/uniword/omml_integration.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "omml" + +module Uniword + # Registers Uniword's WordprocessingML classes as substitutions for + # Omml::Models' minimal WordprocessingML stubs. When an Omml model + # (e.g. CTOMath) resolves a WordprocessingML type symbol (e.g. + # +:ct_br+), the Register's substitution table redirects resolution + # to the corresponding Uniword class. + # + # This keeps a single source of truth for WordprocessingML types: + # Uniword owns the rich, builder-friendly classes; omml owns the math + # schema. The two meet through register substitution, not through + # duplicate model definitions. + module OmmlIntegration + SUBSTITUTIONS = { + "Omml::Models::CTBr" => "Uniword::Wordprocessingml::Break", + }.freeze + + class << self + def register + ensure_omml_context + register_substitutions + end + + private + + def ensure_omml_context + return if Lutaml::Model::GlobalContext.context(:uniword) + + Omml::Configuration.register_in(:uniword) + Lutaml::Model::Config.default_register = :uniword + end + + def register_substitutions + register_handle = Lutaml::Model::GlobalRegister.lookup(:uniword) + return unless register_handle + + SUBSTITUTIONS.each do |omml_class_name, uniword_class_name| + omml_class = constantize(omml_class_name) + uniword_class = constantize(uniword_class_name) + next unless omml_class && uniword_class + + register_handle.register_global_type_substitution( + from_type: omml_class, + to_type: uniword_class, + ) + end + end + + def constantize(class_name) + parts = class_name.split("::") + parts.reduce(Object) do |scope, part| + scope.const_get(part) + end + rescue NameError + nil + end + end + end +end