metanorma-mko 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "unitsml/model"
4
+
5
+ module Metanorma
6
+ module Mko
7
+ # Units register (#55 GAP-1): every unit the document uses, carried
8
+ # by the model's typed UnitsML classes (or parsed from raw container
9
+ # XML). Emitted as unitsml.jsonl — one entry per line — and
10
+ # referenced by id from formula payloads and table columns.
11
+ # Consumers compare on quantity kinds, never unit strings.
12
+ module Units
13
+ Entry = Struct.new(:id, :symbol, :name, :quantity_kind, :dimension,
14
+ :dimension_url, :si_to, :si_expression,
15
+ keyword_init: true)
16
+
17
+ # SI base-quantity symbols in canonical vector order: L M T I Θ N J
18
+ # plus plane angle. Shared by the XML parser and the model
19
+ # projection so both compose identical vectors.
20
+ BASE_QUANTITY_SYMBOLS = {
21
+ "Length" => "L", "Mass" => "M", "Time" => "T",
22
+ "ElectricCurrent" => "I", "ThermodynamicTemperature" => "Θ",
23
+ "AmountOfSubstance" => "N", "LuminousIntensity" => "J",
24
+ "PlaneAngle" => "φ"
25
+ }.freeze
26
+
27
+ class << self
28
+ # unitsml_xml: the <UnitsML> container XML (or the inner
29
+ # <UnitSet>). Returns [Entry].
30
+ def parse(unitsml_xml)
31
+ require "nokogiri"
32
+ doc = Nokogiri::XML(unitsml_xml)
33
+ units = doc.remove_namespaces!
34
+ .xpath("//UnitSet/Unit").map do |node|
35
+ wrap_unit(node)
36
+ end
37
+ dimensions = parse_dimensions(doc)
38
+ units.each do |u|
39
+ dim = dimensions[u.dimension_url]
40
+ u.dimension = dim if dim
41
+ end
42
+ units
43
+ end
44
+
45
+ # Write unitsml.jsonl into a bundle; returns the entry list.
46
+ def write(entries, dir)
47
+ path = File.join(dir, "unitsml.jsonl")
48
+ File.write(path,
49
+ entries.map { |e| JSON.generate(entry_hash(e)) }
50
+ .join("\n") + "\n")
51
+ entries
52
+ end
53
+
54
+ def entry_hash(entry)
55
+ {
56
+ "id" => entry.id,
57
+ "symbol" => entry.symbol,
58
+ "name" => entry.name,
59
+ "quantity_kind" => entry.quantity_kind,
60
+ "dimension" => entry.dimension,
61
+ "si_conversion" => entry.si_to || entry.si_expression ? {
62
+ "to" => entry.si_to,
63
+ "expression" => entry.si_expression,
64
+ } : nil,
65
+ }.compact
66
+ end
67
+
68
+ # SI base-quantity vector from (symbol, powerNumerator,
69
+ # powerDenominator) triples: "M·L^2·T^-2", "Θ", ...
70
+ def vectorize(parts)
71
+ parts.filter_map do |sym, n, d|
72
+ next sym if n.nil? || (n == 1 && (d.nil? || d == 1))
73
+
74
+ exp = d && d != 1 ? "#{n || 1}/#{d}" : n.to_s
75
+ "#{sym}^#{exp}"
76
+ end.join("·")
77
+ end
78
+
79
+ private
80
+
81
+ def wrap_unit(node)
82
+ Entry.new(
83
+ id: node["id"],
84
+ symbol: node.xpath(".//UnitSymbol/text()").map(&:text).join,
85
+ name: node.xpath(".//UnitName/text()").map(&:text).join,
86
+ quantity_kind: nil,
87
+ dimension_url: node["dimensionURL"]&.sub("#", ""),
88
+ )
89
+ end
90
+
91
+ def parse_dimensions(doc)
92
+ doc.xpath("//Dimension").each_with_object({}) do |d, h|
93
+ h[d["id"]] = dimension_vector(d)
94
+ end
95
+ end
96
+
97
+ # SI base-quantity vector: L M T I Θ N J + plane angle
98
+ def dimension_vector(dim)
99
+ parts = BASE_QUANTITY_SYMBOLS.map do |el, sym|
100
+ node = dim.xpath("./#{el}").first or next
101
+ [sym, node["powerNumerator"]&.to_i,
102
+ node["powerDenominator"]&.to_i]
103
+ end
104
+ vectorize(parts)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mko
5
+ # Gem version — kept dependency-free so gemspecs can read it
6
+ # without activating the model stack. Tracks the MN 116 schema
7
+ # version: the wire contract and the gem release together.
8
+ VERSION = "1.0.0"
9
+ end
10
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mko
5
+ # Maps a projection Result onto a bundle directory (or zip). The
6
+ # on-disk mechanics live in Bundle; this class only decides which
7
+ # components a document projection produces.
8
+ module Writer
9
+ class << self
10
+ def write(result, to:, zip: false)
11
+ bundle = Bundle.open(result.document.ids.short, to)
12
+ bundle.flavor = result.flavor
13
+ add_components(bundle, result)
14
+ bundle.write_manifest
15
+ zip ? bundle.zip! : bundle.dir
16
+ end
17
+
18
+ private
19
+
20
+ def add_components(bundle, result)
21
+ bundle.add_json("document", "document.json", result.document)
22
+ # bibdata/glossary/identifiers carry the NATIVE object-model
23
+ # serializations (Relaton::Bib, Glossarist::Concept, Pubid),
24
+ # produced by Document::NativeModels at the model layer.
25
+ if result.bibdata
26
+ bundle.add_json("bibdata", "bibdata.json", result.bibdata)
27
+ end
28
+ bundle.add_json("identifiers", "identifiers.json", result.identifiers)
29
+ bundle.add_json("glossary", "glossary.json",
30
+ { "concepts" => result.glossary.map do |c|
31
+ JSON.parse(c.to_json)
32
+ end })
33
+ # bibliography.jsonl: every cited document as native objects —
34
+ # Relaton item + pubid parse — keyed to its reference unit.
35
+ bib_lines = result.bibliography.map do |e|
36
+ {
37
+ "unit" => "u:#{e.key}",
38
+ "citeas" => e.citeas,
39
+ "pubid" => e.pubid && JSON.parse(e.pubid.to_json),
40
+ "pubid_render" => e.pubid&.to_s,
41
+ "bibitem" => e.item && JSON.parse(e.item.to_json),
42
+ }
43
+ end
44
+ bundle.add_lines("bibliography", "bibliography.jsonl", bib_lines) do |l|
45
+ JSON.generate(l)
46
+ end
47
+ bundle.add_lines("units", "units.jsonl", result.units)
48
+ bundle.add_lines("edges", "edges.jsonl", result.edges)
49
+ # unitsml.jsonl: the units register, referenced by id from
50
+ # formula payloads and table columns (#55). Absent when the
51
+ # source carries no UnitsML container.
52
+ unless result.unitsml.empty?
53
+ bundle.add_lines("unitsml", "unitsml.jsonl", result.unitsml) do |e|
54
+ JSON.generate(Units.entry_hash(e))
55
+ end
56
+ end
57
+ result.assets.each { |entry| bundle.add_asset(entry) }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require "json"
5
+ require "digest"
6
+
7
+ module Metanorma
8
+ # Metanorma Knowledge Objects (MKO, MN 116) — the machine
9
+ # serialization of a Metanorma document as a bundle of typed,
10
+ # addressable knowledge objects (metanorma/metanorma#592). A derived
11
+ # projection over a typed document model: like a rendering, never a
12
+ # source format.
13
+ #
14
+ # This gem owns the FORMAT (the MN 116 contract): wire schema,
15
+ # bundle layout + manifest verification, assets, alignment, diffs,
16
+ # the generated JSON Schemas, and the reference MCP server.
17
+ # metanorma-document reopens this module with the MODEL side — the
18
+ # projection walk, collection orchestration, flavor resolution — and
19
+ # composes this gem to export.
20
+ module Mko
21
+ autoload :VERSION, "metanorma/mko/version"
22
+ autoload :Schema, "metanorma/mko/schema"
23
+ autoload :Bundle, "metanorma/mko/bundle"
24
+ autoload :Writer, "metanorma/mko/writer"
25
+ autoload :Export, "metanorma/mko/export"
26
+ autoload :Result, "metanorma/mko/result"
27
+ autoload :Assets, "metanorma/mko/assets"
28
+ autoload :Alignment, "metanorma/mko/alignment"
29
+ autoload :Diff, "metanorma/mko/diff"
30
+ autoload :Mcp, "metanorma/mko/mcp"
31
+ autoload :Units, "metanorma/mko/units"
32
+
33
+ autoload :VERSION, "metanorma/mko/version"
34
+
35
+ SCHEMA = "metanorma-mko"
36
+ SCHEMA_VERSION = VERSION
37
+
38
+ class << self
39
+ # The published wire contract, generated from the schema classes
40
+ # (single source of truth): name => JSON Schema draft 2020-12.
41
+ def json_schemas
42
+ Schema::JsonSchema.all
43
+ end
44
+
45
+ # Canonical short-id derivation (one rule, documents and
46
+ # collections alike).
47
+ def slug(canonical)
48
+ canonical.to_s.tr(" ", "-").gsub(/[^A-Za-z0-9.\-]/, "")
49
+ .squeeze("-").downcase
50
+ end
51
+ end
52
+ end
53
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: metanorma-mko
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ribose Inc.
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: lutaml-model
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.8'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.8'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rubyzip
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: unitsml
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.6'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.6'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1'
96
+ description: 'The MN 116 wire contract as code: typed unit schema, bundle layout with
97
+ manifest verification, hash-addressed assets, edition diffs, interlingual alignment,
98
+ generated JSON Schemas, and the reference MCP server. metanorma-document composes
99
+ this gem to export; polyglot consumers validate against the published schemas.'
100
+ email:
101
+ - open.source@ribose.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - LICENSE
107
+ - README.adoc
108
+ - lib/metanorma/mko.rb
109
+ - lib/metanorma/mko/alignment.rb
110
+ - lib/metanorma/mko/assets.rb
111
+ - lib/metanorma/mko/bundle.rb
112
+ - lib/metanorma/mko/diff.rb
113
+ - lib/metanorma/mko/export.rb
114
+ - lib/metanorma/mko/mcp.rb
115
+ - lib/metanorma/mko/result.rb
116
+ - lib/metanorma/mko/schema.rb
117
+ - lib/metanorma/mko/schema/collection.rb
118
+ - lib/metanorma/mko/schema/document.rb
119
+ - lib/metanorma/mko/schema/edge.rb
120
+ - lib/metanorma/mko/schema/identifiers.rb
121
+ - lib/metanorma/mko/schema/json_schema.rb
122
+ - lib/metanorma/mko/schema/manifest.rb
123
+ - lib/metanorma/mko/schema/unit.rb
124
+ - lib/metanorma/mko/units.rb
125
+ - lib/metanorma/mko/version.rb
126
+ - lib/metanorma/mko/writer.rb
127
+ homepage: https://github.com/metanorma/metanorma-mko
128
+ licenses:
129
+ - BSD-2-Clause
130
+ metadata: {}
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubygems_version: 4.0.16
146
+ specification_version: 4
147
+ summary: Metanorma Knowledge Objects (MN 116) — the machine serialization format
148
+ test_files: []