pubid 2.0.0.pre.alpha.12 → 2.0.0.pre.alpha.13
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.
- checksums.yaml +4 -4
- data/README.adoc +43 -1
- data/data/ieee/update_codes.yaml +17 -4
- data/data/nist/update_codes.yaml +7 -3
- data/lib/pubid/adobe/builder.rb +2 -0
- data/lib/pubid/all_parts.rb +201 -0
- data/lib/pubid/all_parts_identifier.rb +19 -0
- data/lib/pubid/amca/CLAUDE.md +47 -0
- data/lib/pubid/amca/builder.rb +3 -5
- data/lib/pubid/amca/identifiers/base.rb +10 -0
- data/lib/pubid/amca/identifiers/publication.rb +13 -0
- data/lib/pubid/amca/parser.rb +2 -1
- data/lib/pubid/amca/renderer.rb +22 -33
- data/lib/pubid/amca/urn_generator.rb +21 -2
- data/lib/pubid/amca/urn_parser.rb +36 -10
- data/lib/pubid/ansi/builder.rb +6 -0
- data/lib/pubid/api/CLAUDE.md +23 -0
- data/lib/pubid/api/builder.rb +2 -0
- data/lib/pubid/ashrae/CLAUDE.md +13 -0
- data/lib/pubid/ashrae/builder.rb +58 -14
- data/lib/pubid/ashrae/identifiers/errata.rb +14 -2
- data/lib/pubid/ashrae/identifiers/interpretation.rb +2 -10
- data/lib/pubid/ashrae/parser.rb +62 -28
- data/lib/pubid/ashrae/renderer.rb +32 -1
- data/lib/pubid/ashrae/urn_generator.rb +32 -9
- data/lib/pubid/asme/CLAUDE.md +25 -0
- data/lib/pubid/asme/builder.rb +16 -9
- data/lib/pubid/asme/components/code.rb +2 -0
- data/lib/pubid/asme/identifiers/standard.rb +6 -1
- data/lib/pubid/asme/parser.rb +41 -14
- data/lib/pubid/astm/CLAUDE.md +9 -0
- data/lib/pubid/astm/builder.rb +2 -0
- data/lib/pubid/astm/components/code.rb +2 -0
- data/lib/pubid/astm/parser.rb +4 -1
- data/lib/pubid/bipm/CLAUDE.md +11 -0
- data/lib/pubid/bipm/builder.rb +2 -0
- data/lib/pubid/bsi/CLAUDE.md +93 -0
- data/lib/pubid/bsi/builder.rb +13 -11
- data/lib/pubid/bsi/identifiers/addendum_document.rb +2 -0
- data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +6 -54
- data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +5 -22
- data/lib/pubid/bsi/identifiers/amendment.rb +36 -12
- data/lib/pubid/bsi/identifiers/bundled_identifier.rb +2 -0
- data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +23 -26
- data/lib/pubid/bsi/identifiers/corrigendum.rb +29 -12
- data/lib/pubid/bsi/identifiers/expert_commentary.rb +6 -7
- data/lib/pubid/bsi/identifiers/national_annex.rb +18 -20
- data/lib/pubid/bsi/identifiers/root_identity.rb +31 -0
- data/lib/pubid/bsi/identifiers/set.rb +2 -0
- data/lib/pubid/bsi/identifiers/supplement_document.rb +2 -0
- data/lib/pubid/bsi/identifiers.rb +1 -0
- data/lib/pubid/bsi/parser.rb +8 -8
- data/lib/pubid/bsi/renderer.rb +20 -20
- data/lib/pubid/bsi/urn_generator.rb +28 -18
- data/lib/pubid/builder/base.rb +27 -0
- data/lib/pubid/calconnect/builder.rb +2 -0
- data/lib/pubid/calconnect/identifier.rb +4 -0
- data/lib/pubid/ccsds/builder.rb +2 -0
- data/lib/pubid/ccsds/identifier.rb +8 -0
- data/lib/pubid/cen_cenelec/CLAUDE.md +59 -0
- data/lib/pubid/cen_cenelec/builder.rb +6 -1
- data/lib/pubid/cen_cenelec/identifier.rb +10 -27
- data/lib/pubid/cen_cenelec/identifiers/amendment.rb +3 -10
- data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +3 -10
- data/lib/pubid/cen_cenelec/parser.rb +11 -3
- data/lib/pubid/cie/CLAUDE.md +58 -0
- data/lib/pubid/cie/builder.rb +2 -0
- data/lib/pubid/cie/components/language.rb +2 -0
- data/lib/pubid/components/adoption.rb +2 -0
- data/lib/pubid/components/code.rb +2 -0
- data/lib/pubid/components/date.rb +8 -6
- data/lib/pubid/components/edition.rb +2 -0
- data/lib/pubid/components/iteration.rb +2 -0
- data/lib/pubid/components/language.rb +2 -0
- data/lib/pubid/components/locality.rb +2 -0
- data/lib/pubid/components/publisher.rb +2 -0
- data/lib/pubid/components/relationship.rb +2 -0
- data/lib/pubid/components/stage.rb +2 -0
- data/lib/pubid/components/supplement.rb +2 -0
- data/lib/pubid/components/type.rb +2 -0
- data/lib/pubid/components/typed_stage.rb +8 -0
- data/lib/pubid/csa/CLAUDE.md +41 -0
- data/lib/pubid/csa/builder.rb +2 -0
- data/lib/pubid/csa/identifier.rb +13 -1
- data/lib/pubid/csa/renderer.rb +12 -12
- data/lib/pubid/csa/single_identifier.rb +17 -0
- data/lib/pubid/doi/builder.rb +2 -0
- data/lib/pubid/easc/builder.rb +2 -0
- data/lib/pubid/ecma/CLAUDE.md +28 -0
- data/lib/pubid/ecma/builder.rb +2 -0
- data/lib/pubid/ecma/identifier.rb +7 -0
- data/lib/pubid/etsi/CLAUDE.md +34 -0
- data/lib/pubid/etsi/builder.rb +2 -0
- data/lib/pubid/etsi/components/code.rb +6 -0
- data/lib/pubid/etsi/components/version.rb +2 -0
- data/lib/pubid/etsi/identifiers/etsi_standard.rb +7 -0
- data/lib/pubid/evs/CLAUDE.md +58 -0
- data/lib/pubid/evs/builder.rb +2 -0
- data/lib/pubid/gb/CLAUDE.md +140 -0
- data/lib/pubid/gb/builder.rb +7 -2
- data/lib/pubid/gb/identifier.rb +5 -3
- data/lib/pubid/gb/identifiers/all_parts.rb +17 -0
- data/lib/pubid/gb/identifiers.rb +1 -0
- data/lib/pubid/gb/renderer.rb +0 -1
- data/lib/pubid/gost/CLAUDE.md +64 -0
- data/lib/pubid/gost/builder.rb +3 -1
- data/lib/pubid/gost/identifier.rb +5 -0
- data/lib/pubid/iala/CLAUDE.md +82 -0
- data/lib/pubid/iala/builder.rb +2 -0
- data/lib/pubid/iana/CLAUDE.md +7 -0
- data/lib/pubid/iana/builder.rb +2 -0
- data/lib/pubid/identifier.rb +161 -17
- data/lib/pubid/idf/builder.rb +6 -1
- data/lib/pubid/idf/identifier.rb +5 -0
- data/lib/pubid/idf/identifiers/all_parts.rb +17 -0
- data/lib/pubid/idf/identifiers.rb +1 -0
- data/lib/pubid/iec/CLAUDE.md +31 -0
- data/lib/pubid/iec/builder.rb +7 -1
- data/lib/pubid/iec/components/consolidated_amendment.rb +4 -0
- data/lib/pubid/iec/components/sheet.rb +2 -0
- data/lib/pubid/iec/components/trf_info.rb +2 -0
- data/lib/pubid/iec/components/vap_suffix.rb +2 -0
- data/lib/pubid/iec/identifier.rb +7 -2
- data/lib/pubid/iec/identifiers/all_parts.rb +19 -0
- data/lib/pubid/iec/identifiers.rb +1 -0
- data/lib/pubid/iec/renderer.rb +0 -1
- data/lib/pubid/iec/urn_generator.rb +9 -1
- data/lib/pubid/iec/urn_parser.rb +3 -2
- data/lib/pubid/ieee/CLAUDE.md +97 -0
- data/lib/pubid/ieee/builder.rb +134 -5
- data/lib/pubid/ieee/components/code.rb +2 -0
- data/lib/pubid/ieee/components/draft.rb +35 -2
- data/lib/pubid/ieee/components/typed_stage.rb +2 -0
- data/lib/pubid/ieee/identifiers/base.rb +20 -0
- data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +9 -0
- data/lib/pubid/ieee/identifiers/joint_development.rb +17 -10
- data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +8 -1
- data/lib/pubid/ieee/parser.rb +134 -21
- data/lib/pubid/ieee/renderer.rb +40 -7
- data/lib/pubid/ieee/urn_generator.rb +31 -0
- data/lib/pubid/ietf/CLAUDE.md +7 -0
- data/lib/pubid/ietf/builder.rb +2 -0
- data/lib/pubid/iho/builder.rb +2 -0
- data/lib/pubid/isbn/builder.rb +2 -0
- data/lib/pubid/iso/CLAUDE.md +47 -0
- data/lib/pubid/iso/builder.rb +19 -5
- data/lib/pubid/iso/components/publisher.rb +2 -0
- data/lib/pubid/iso/identifier.rb +6 -14
- data/lib/pubid/iso/identifiers/all_parts.rb +19 -0
- data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -2
- data/lib/pubid/iso/identifiers.rb +1 -0
- data/lib/pubid/iso/rendering_style.rb +0 -1
- data/lib/pubid/itu/CLAUDE.md +69 -0
- data/lib/pubid/itu/builder.rb +2 -0
- data/lib/pubid/itu/components/code.rb +2 -0
- data/lib/pubid/itu/components/designation.rb +2 -0
- data/lib/pubid/itu/components/sector.rb +2 -0
- data/lib/pubid/itu/components/series.rb +2 -0
- data/lib/pubid/itu/identifiers/supplement.rb +15 -0
- data/lib/pubid/jcgm/CLAUDE.md +7 -0
- data/lib/pubid/jcgm/builder.rb +2 -0
- data/lib/pubid/jcgm/components/publisher.rb +2 -0
- data/lib/pubid/jis/builder.rb +5 -1
- data/lib/pubid/jis/identifier.rb +5 -17
- data/lib/pubid/jis/identifiers/all_parts.rb +19 -0
- data/lib/pubid/jis/identifiers.rb +1 -0
- data/lib/pubid/jis/renderer.rb +0 -2
- data/lib/pubid/jis/urn_generator.rb +0 -1
- data/lib/pubid/nist/CLAUDE.md +56 -0
- data/lib/pubid/nist/builder.rb +2 -0
- data/lib/pubid/nist/components/edition.rb +2 -0
- data/lib/pubid/nist/components/issue_number.rb +2 -0
- data/lib/pubid/nist/components/part.rb +2 -0
- data/lib/pubid/nist/components/stage.rb +2 -0
- data/lib/pubid/nist/components/supplement.rb +2 -0
- data/lib/pubid/nist/components/translation.rb +2 -0
- data/lib/pubid/nist/components/update.rb +2 -0
- data/lib/pubid/nist/components/version.rb +2 -0
- data/lib/pubid/nist/components/volume.rb +2 -0
- data/lib/pubid/nist/identifiers/base.rb +24 -3
- data/lib/pubid/nist/parser.rb +15 -2
- data/lib/pubid/nist/preprocessor.rb +53 -2
- data/lib/pubid/oasis/CLAUDE.md +19 -0
- data/lib/pubid/oasis/builder.rb +2 -0
- data/lib/pubid/oasis/identifier.rb +19 -0
- data/lib/pubid/ogc/CLAUDE.md +34 -0
- data/lib/pubid/ogc/builder.rb +2 -0
- data/lib/pubid/ogc/identifier.rb +11 -0
- data/lib/pubid/oiml/CLAUDE.md +189 -0
- data/lib/pubid/oiml/builder.rb +20 -0
- data/lib/pubid/oiml/components/code.rb +6 -0
- data/lib/pubid/oiml/identifier.rb +13 -0
- data/lib/pubid/oiml/identifiers/annex.rb +4 -0
- data/lib/pubid/oiml/identifiers/certification_system.rb +34 -0
- data/lib/pubid/oiml/identifiers/code_number.rb +8 -0
- data/lib/pubid/oiml/identifiers/dual_published.rb +174 -0
- data/lib/pubid/oiml/identifiers.rb +2 -0
- data/lib/pubid/oiml/parser.rb +35 -4
- data/lib/pubid/oiml/renderer.rb +23 -1
- data/lib/pubid/oiml/single_identifier.rb +4 -0
- data/lib/pubid/oiml/supplement_identifier.rb +7 -0
- data/lib/pubid/oiml/urn_generator.rb +28 -0
- data/lib/pubid/oiml.rb +5 -0
- data/lib/pubid/omg/CLAUDE.md +15 -0
- data/lib/pubid/omg/builder.rb +2 -0
- data/lib/pubid/parser/grammar.rb +23 -0
- data/lib/pubid/plateau/builder.rb +2 -0
- data/lib/pubid/plateau/identifiers/base.rb +4 -0
- data/lib/pubid/plateau/supplement_identifier.rb +14 -2
- data/lib/pubid/plateau/urn_generator.rb +7 -1
- data/lib/pubid/renderers/human_readable.rb +0 -1
- data/lib/pubid/sae/builder.rb +2 -0
- data/lib/pubid/sae/components/date.rb +2 -0
- data/lib/pubid/sae/components/type.rb +2 -0
- data/lib/pubid/subset_match.rb +197 -0
- data/lib/pubid/tgpp/CLAUDE.md +43 -0
- data/lib/pubid/tgpp/builder.rb +2 -0
- data/lib/pubid/tgpp/identifier.rb +14 -0
- data/lib/pubid/type_resolver.rb +14 -2
- data/lib/pubid/un/builder.rb +2 -0
- data/lib/pubid/version.rb +1 -1
- data/lib/pubid/w3c/CLAUDE.md +7 -0
- data/lib/pubid/w3c/builder.rb +2 -0
- data/lib/pubid/xsf/CLAUDE.md +11 -0
- data/lib/pubid/xsf/builder.rb +2 -0
- data/lib/pubid.rb +16 -3
- metadata +44 -2
|
@@ -15,6 +15,10 @@ module Pubid
|
|
|
15
15
|
map "year_on_base", to: :year_on_base
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# A nil `letter` means the plural "Annexes" reference, not "any
|
|
19
|
+
# annex": it must not match a candidate naming a specific one.
|
|
20
|
+
subset_strict :letter
|
|
21
|
+
|
|
18
22
|
def supplement_type
|
|
19
23
|
letter ? "Annex #{letter}" : "Annexes"
|
|
20
24
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pubid
|
|
4
|
+
module Oiml
|
|
5
|
+
module Identifiers
|
|
6
|
+
# OIML-CS certification-system documents ("OIML-CS PD-05 Edition 6").
|
|
7
|
+
# The number keeps its printed zero padding ("05"); family is the
|
|
8
|
+
# PD/OD/CID document class; the print states "Edition N" instead of a
|
|
9
|
+
# year, and may carry a parenthesized trailing amendment.
|
|
10
|
+
class CertificationSystem < SingleIdentifier
|
|
11
|
+
attribute :number, :string
|
|
12
|
+
attribute :family, :string
|
|
13
|
+
attribute :amendment, :string
|
|
14
|
+
# "PD 05" prints space-separated where "PD-05" is dash-joined.
|
|
15
|
+
attribute :space_separator, :boolean, default: false
|
|
16
|
+
|
|
17
|
+
key_value do
|
|
18
|
+
map "number", to: :number
|
|
19
|
+
map "family", to: :family
|
|
20
|
+
map "amendment", to: :amendment
|
|
21
|
+
map "space_separator", to: :space_separator
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.subset_ignored_attributes
|
|
25
|
+
super + [:space_separator]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def type_string
|
|
29
|
+
"CS"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -34,6 +34,7 @@ module Pubid
|
|
|
34
34
|
def self.included(base)
|
|
35
35
|
install_attributes(base)
|
|
36
36
|
install_mappings(base)
|
|
37
|
+
install_subset_rules(base)
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def self.install_attributes(base)
|
|
@@ -53,6 +54,13 @@ module Pubid
|
|
|
53
54
|
base.attribute :space_suffix, :boolean, default: -> { false }
|
|
54
55
|
end
|
|
55
56
|
|
|
57
|
+
# A nil `part`, `subpart` or `suffix` means the document has none of
|
|
58
|
+
# them: `OIML R 138` is not `OIML R 138-Amend`, which is its
|
|
59
|
+
# amendment. Declared on each leaf, beside the columns it governs.
|
|
60
|
+
def self.install_subset_rules(base)
|
|
61
|
+
base.subset_strict :part, :subpart, :suffix
|
|
62
|
+
end
|
|
63
|
+
|
|
56
64
|
# Merged by lutaml with the block SingleIdentifier declares, which
|
|
57
65
|
# carries publisher/year/edition/stage/iteration.
|
|
58
66
|
def self.install_mappings(base)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pubid
|
|
4
|
+
module Oiml
|
|
5
|
+
module Identifiers
|
|
6
|
+
# OIML sometimes co-publishes a document jointly with another SDO
|
|
7
|
+
# (ISO confirmed so far, per pubid issue #437). The printed reference
|
|
8
|
+
# carries both identifiers, joined by a bare "|":
|
|
9
|
+
#
|
|
10
|
+
# ISO 4064-1:2024|OIML R 49-1:2024
|
|
11
|
+
#
|
|
12
|
+
# A sibling of SingleIdentifier/SupplementIdentifier — like
|
|
13
|
+
# SupplementIdentifier, it inherits NOTHING from SingleIdentifier and
|
|
14
|
+
# must delegate every identity-bearing reader (code/type/stage/
|
|
15
|
+
# iteration/publisher, the mr_* MR-slug hooks, #root) itself. Without
|
|
16
|
+
# the mr_* delegations, #to_mr_string would silently come out "" (the
|
|
17
|
+
# same filename-collision gap already documented for
|
|
18
|
+
# Amendment/Errata/Annex in lib/pubid/oiml/CLAUDE.md) — this class closes
|
|
19
|
+
# that gap from day one instead of repeating it.
|
|
20
|
+
#
|
|
21
|
+
# `first`/`second` hold the two sides in their original left-to-right
|
|
22
|
+
# print order, each a real, independently-typed `::Pubid::Identifier`
|
|
23
|
+
# (never an attr_accessor — see IEEE's CsaDualPublished for the bug
|
|
24
|
+
# that pattern causes: to_hash/from_hash/#exclude silently drop it).
|
|
25
|
+
# Either side may be OIML; `#oiml_identifier`/`#external_identifier`
|
|
26
|
+
# pick the OIML-typed one out of the pair regardless of order.
|
|
27
|
+
class DualPublished < Identifier
|
|
28
|
+
attribute :first, ::Pubid::Identifier, polymorphic: true
|
|
29
|
+
attribute :second, ::Pubid::Identifier, polymorphic: true
|
|
30
|
+
|
|
31
|
+
# Declared locally (rather than left to the inherited default)
|
|
32
|
+
# because Oiml::Identifier's shared key_value block maps "language"
|
|
33
|
+
# and "parsed_format" to real attributes — without a local
|
|
34
|
+
# declaration here, to_hash/from_hash would call a getter/setter
|
|
35
|
+
# this class never defines. Mirrors SingleIdentifier/
|
|
36
|
+
# SupplementIdentifier, which do the same for the same reason.
|
|
37
|
+
attribute :language, :string
|
|
38
|
+
attribute :parsed_format, :string, default: -> { "short" }
|
|
39
|
+
|
|
40
|
+
key_value do
|
|
41
|
+
map "first", with: { to: :first_to_kv, from: :first_from_kv }
|
|
42
|
+
map "second", with: { to: :second_to_kv, from: :second_from_kv }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Either side may belong to any flavor, so (de)serialization goes
|
|
46
|
+
# through the generic top-level Pubid.from_hash/#to_hash, not the
|
|
47
|
+
# OIML-scoped Identifier.from_hash (which only resolves OIML types).
|
|
48
|
+
def first_to_kv(model, doc)
|
|
49
|
+
value = model.first
|
|
50
|
+
return unless value
|
|
51
|
+
|
|
52
|
+
doc.add_child(
|
|
53
|
+
Lutaml::KeyValue::DataModel::Element.new("first", value.to_hash),
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def first_from_kv(model, value)
|
|
58
|
+
model.first = ::Pubid.from_hash(value) if value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def second_to_kv(model, doc)
|
|
62
|
+
value = model.second
|
|
63
|
+
return unless value
|
|
64
|
+
|
|
65
|
+
doc.add_child(
|
|
66
|
+
Lutaml::KeyValue::DataModel::Element.new("second", value.to_hash),
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def second_from_kv(model, value)
|
|
71
|
+
model.second = ::Pubid.from_hash(value) if value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The member that belongs to THIS flavor, wherever print order put
|
|
75
|
+
# it. relaton-index sorts on this side's root.number.
|
|
76
|
+
def oiml_identifier
|
|
77
|
+
[first, second].find { |member| member.is_a?(Oiml::Identifier) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The other SDO's identifier for the same document.
|
|
81
|
+
def external_identifier
|
|
82
|
+
[first, second].find { |member| !member.is_a?(Oiml::Identifier) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def root
|
|
86
|
+
oiml_identifier ? oiml_identifier.root : self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def code
|
|
90
|
+
oiml_identifier&.code
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def type
|
|
94
|
+
oiml_identifier&.type
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def stage
|
|
98
|
+
oiml_identifier&.stage
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def iteration
|
|
102
|
+
oiml_identifier&.iteration
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def publisher
|
|
106
|
+
oiml_identifier&.publisher
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def mr_publisher
|
|
110
|
+
oiml_identifier&.mr_publisher
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def mr_type
|
|
114
|
+
oiml_identifier&.mr_type
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def mr_number_with_part
|
|
118
|
+
oiml_identifier&.mr_number_with_part
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def mr_year
|
|
122
|
+
oiml_identifier&.mr_year
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Splits `identifier` on "|" and, if it looks like an OIML dual-
|
|
126
|
+
# published reference (exactly two non-empty sides, exactly one of
|
|
127
|
+
# which starts with an OIML prefix), parses each side through its
|
|
128
|
+
# own flavor and returns the wrapper. Returns nil for anything else,
|
|
129
|
+
# so the caller (Pubid::Oiml.parse) falls through to the ordinary
|
|
130
|
+
# grammar, which raises the standard Parslet::ParseFailed for a
|
|
131
|
+
# string containing "|" (the grammar defines no rule for it).
|
|
132
|
+
def self.build(identifier)
|
|
133
|
+
sides = split_sides(identifier)
|
|
134
|
+
return nil unless sides
|
|
135
|
+
|
|
136
|
+
left, right = parse_sides(*sides)
|
|
137
|
+
return nil unless left && right
|
|
138
|
+
|
|
139
|
+
new(first: left, second: right)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Two non-empty, stripped sides, or nil if `identifier` isn't
|
|
143
|
+
# shaped like "A|B".
|
|
144
|
+
def self.split_sides(identifier)
|
|
145
|
+
parts = identifier.split("|").map(&:strip)
|
|
146
|
+
return nil unless parts.length == 2
|
|
147
|
+
return nil if parts.any?(&:empty?)
|
|
148
|
+
|
|
149
|
+
parts
|
|
150
|
+
end
|
|
151
|
+
private_class_method :split_sides
|
|
152
|
+
|
|
153
|
+
# Parses each side through its own flavor, provided exactly one side
|
|
154
|
+
# looks like OIML (by prefix, not position). [nil, nil] otherwise.
|
|
155
|
+
def self.parse_sides(left, right)
|
|
156
|
+
left_is_oiml = Oiml::PREFIXES.any? { |p| left.start_with?(p) }
|
|
157
|
+
right_is_oiml = Oiml::PREFIXES.any? { |p| right.start_with?(p) }
|
|
158
|
+
return [nil, nil] if left_is_oiml == right_is_oiml
|
|
159
|
+
|
|
160
|
+
[parse_side(left, oiml: left_is_oiml),
|
|
161
|
+
parse_side(right, oiml: right_is_oiml)]
|
|
162
|
+
end
|
|
163
|
+
private_class_method :parse_sides
|
|
164
|
+
|
|
165
|
+
def self.parse_side(str, oiml:)
|
|
166
|
+
oiml ? Oiml.parse(str) : ::Pubid.parse(str)
|
|
167
|
+
rescue Parslet::ParseFailed, Pubid::Errors::InvalidInputError
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
private_class_method :parse_side
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -7,8 +7,10 @@ module Pubid
|
|
|
7
7
|
autoload :Annex, "#{__dir__}/identifiers/annex"
|
|
8
8
|
autoload :BasicPublication, "#{__dir__}/identifiers/basic_publication"
|
|
9
9
|
autoload :Bulletin, "#{__dir__}/identifiers/bulletin"
|
|
10
|
+
autoload :CertificationSystem, "#{__dir__}/identifiers/certification_system"
|
|
10
11
|
autoload :CodeNumber, "#{__dir__}/identifiers/code_number"
|
|
11
12
|
autoload :Document, "#{__dir__}/identifiers/document"
|
|
13
|
+
autoload :DualPublished, "#{__dir__}/identifiers/dual_published"
|
|
12
14
|
autoload :Errata, "#{__dir__}/identifiers/errata"
|
|
13
15
|
autoload :ExpertReport, "#{__dir__}/identifiers/expert_report"
|
|
14
16
|
autoload :Guide, "#{__dir__}/identifiers/guide"
|
data/lib/pubid/oiml/parser.rb
CHANGED
|
@@ -20,14 +20,17 @@ module Pubid
|
|
|
20
20
|
rule(:identifier) do
|
|
21
21
|
amendment_identifier | amendment_short | annex_letter_identifier |
|
|
22
22
|
annex_identifier | plus_supplement_identifier |
|
|
23
|
-
trailing_supplement_identifier |
|
|
23
|
+
trailing_supplement_identifier | cs_identifier |
|
|
24
|
+
bulletin_identifier | base
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
# Publisher - always "OIML"
|
|
27
28
|
rule(:publisher) { str("OIML").as(:publisher) >> space }
|
|
28
29
|
|
|
29
|
-
# Document type - single letter
|
|
30
|
-
|
|
30
|
+
# Document type - single letter. Strict family set: OIML publishes
|
|
31
|
+
# R D B G E V S documents (the estate grammar's family letters);
|
|
32
|
+
# any other letter is a rejection, not a flavor.
|
|
33
|
+
rule(:doc_type) { match("[BDEGRSV]").as(:type) >> space }
|
|
31
34
|
|
|
32
35
|
# Bulletin locator — structured form. Year optionally followed by
|
|
33
36
|
# 2-digit issue and 2-digit sequence:
|
|
@@ -128,7 +131,7 @@ module Pubid
|
|
|
128
131
|
# (with optional space before year)
|
|
129
132
|
rule(:date) do
|
|
130
133
|
edition_portion |
|
|
131
|
-
(colon >> space.maybe >> year_digits.as(:year)) |
|
|
134
|
+
(space.maybe >> colon >> space.maybe >> year_digits.as(:year)) |
|
|
132
135
|
(space.maybe >> lparen >> year_digits.as(:year) >> rparen)
|
|
133
136
|
end
|
|
134
137
|
|
|
@@ -167,10 +170,22 @@ module Pubid
|
|
|
167
170
|
match("[a-z]").repeat(2, 2) # Two letters: en, fr, etc.
|
|
168
171
|
end
|
|
169
172
|
|
|
173
|
+
# Full-word language markers as OIML prints them ("(Fra)", "(Eng)"),
|
|
174
|
+
# three or more letters, any case. Kept verbatim on the identifier;
|
|
175
|
+
# the URN lowercases.
|
|
176
|
+
rule(:lang_word) do
|
|
177
|
+
match("[A-Za-z]").repeat(3)
|
|
178
|
+
end
|
|
179
|
+
|
|
170
180
|
rule(:language_code) do
|
|
171
181
|
(
|
|
172
182
|
(lang_single >> slash >> lang_single) | # E/F
|
|
173
183
|
lang_multi_oiml | # PO, PT, PE, SR
|
|
184
|
+
lang_word | # Fra, eng, rus — before
|
|
185
|
+
# the letter rules: a
|
|
186
|
+
# committed "F" of "(Fra)"
|
|
187
|
+
# or "fr" of "(fra)" would
|
|
188
|
+
# never fall through
|
|
174
189
|
lang_single | # E, F, D, R, S, C, A, U, X
|
|
175
190
|
lang_multi # en, fr
|
|
176
191
|
).as(:language)
|
|
@@ -216,9 +231,25 @@ module Pubid
|
|
|
216
231
|
rule(:trailing_supplement_identifier) do
|
|
217
232
|
base_without_language.as(:base) >>
|
|
218
233
|
space >> (str("Amendment") | str("Errata")).as(:trailing_marker) >>
|
|
234
|
+
(space >> digits.as(:number)).maybe >>
|
|
219
235
|
language_portion.maybe.as(:language)
|
|
220
236
|
end
|
|
221
237
|
|
|
238
|
+
# OIML-CS certification-system documents. Two head spellings
|
|
239
|
+
# ("OIML-CS" / "OIML CS") and two family-number separators
|
|
240
|
+
# ("PD-05" / "PD 05"), an "Edition N" instead of a year, and an
|
|
241
|
+
# optional parenthesized trailing amendment - "(Amendment 1)".
|
|
242
|
+
rule(:cs_identifier) do
|
|
243
|
+
str("OIML").as(:publisher) >>
|
|
244
|
+
(dash >> str("CS") | space >> str("CS")).as(:cs_series) >> space >>
|
|
245
|
+
(str("PD") | str("OD") | str("CID")).as(:cs_family) >>
|
|
246
|
+
(dash | space).as(:cs_separator) >>
|
|
247
|
+
digits.as(:number) >>
|
|
248
|
+
space >> str("Edition") >> space >> digits.as(:edition) >>
|
|
249
|
+
(space >> lparen >> str("Amendment") >> space >>
|
|
250
|
+
digits.as(:cs_amendment) >> rparen).maybe
|
|
251
|
+
end
|
|
252
|
+
|
|
222
253
|
# Plus-joined supplement - "BASE:YEAR+Supplement:YEAR" form where both
|
|
223
254
|
# the base and the supplement carry their own year. Used for amendments
|
|
224
255
|
# and errata to dated bases (e.g. "OIML B 10:2011+Amendment:2012").
|
data/lib/pubid/oiml/renderer.rb
CHANGED
|
@@ -18,10 +18,14 @@ module Pubid
|
|
|
18
18
|
@context = context
|
|
19
19
|
|
|
20
20
|
case id
|
|
21
|
+
when Identifiers::DualPublished
|
|
22
|
+
render_dual_published(id)
|
|
21
23
|
when Identifiers::Annex
|
|
22
24
|
render_annex(id)
|
|
23
25
|
when Identifiers::Bulletin
|
|
24
26
|
render_bulletin(id)
|
|
27
|
+
when Identifiers::CertificationSystem
|
|
28
|
+
render_cs(id)
|
|
25
29
|
when SupplementIdentifier
|
|
26
30
|
render_supplement(id)
|
|
27
31
|
when SingleIdentifier
|
|
@@ -33,6 +37,16 @@ module Pubid
|
|
|
33
37
|
|
|
34
38
|
private
|
|
35
39
|
|
|
40
|
+
# Certification-system document: "OIML-CS PD-05 Edition 6 (Amendment 1)".
|
|
41
|
+
# The family-number separator keeps the parsed spelling; OIML CS
|
|
42
|
+
# documents state an edition instead of a year.
|
|
43
|
+
def render_cs(id)
|
|
44
|
+
result = "#{id.publisher}-CS #{id.family}" \
|
|
45
|
+
"#{id.space_separator ? ' ' : '-'}#{id.number} Edition #{id.edition}"
|
|
46
|
+
result += " (Amendment #{id.amendment})" if id.amendment
|
|
47
|
+
result
|
|
48
|
+
end
|
|
49
|
+
|
|
36
50
|
# Render the Bulletin in the requested or parsed form. Default is the
|
|
37
51
|
# structured "YYYY-II-SS" form (the dataset's primary docid). The
|
|
38
52
|
# citation form ("LXVII(2) 20260211") is emitted when the user asks
|
|
@@ -93,6 +107,12 @@ module Pubid
|
|
|
93
107
|
str.sub(/\s*\([^)]+\)\s*$/, "").strip
|
|
94
108
|
end
|
|
95
109
|
|
|
110
|
+
# Bare "|", original left-to-right print order, e.g.
|
|
111
|
+
# "ISO 4064-1:2024|OIML R 49-1:2024".
|
|
112
|
+
def render_dual_published(id)
|
|
113
|
+
"#{id.first}|#{id.second}"
|
|
114
|
+
end
|
|
115
|
+
|
|
96
116
|
def render_single(id)
|
|
97
117
|
format = effective_format(id)
|
|
98
118
|
|
|
@@ -147,10 +167,12 @@ module Pubid
|
|
|
147
167
|
|
|
148
168
|
# Trailing-word shorthand: "BASE Amendment" / "BASE Errata" with the
|
|
149
169
|
# publication year kept on the base identifier. The word comes from the
|
|
150
|
-
# concrete supplement class
|
|
170
|
+
# concrete supplement class; an ordinal, when printed ("Amendment 1"),
|
|
171
|
+
# follows it.
|
|
151
172
|
if id.trailing
|
|
152
173
|
base_str = strip_language(id.base.to_s)
|
|
153
174
|
result = "#{base_str} #{id.supplement_type}"
|
|
175
|
+
result += " #{id.number}" if id.number
|
|
154
176
|
result += " (#{id.language})" if id.language
|
|
155
177
|
return result
|
|
156
178
|
end
|
|
@@ -14,6 +14,10 @@ module Pubid
|
|
|
14
14
|
"short"
|
|
15
15
|
} # Track parsed format
|
|
16
16
|
|
|
17
|
+
# A nil `language` means the document states none: `OIML R 126:2015
|
|
18
|
+
# Errata` is not `OIML R 126:2015 Errata (E)`, its English edition.
|
|
19
|
+
subset_strict :language
|
|
20
|
+
|
|
17
21
|
# Serialization delta on top of Oiml::Identifier's shared block. The
|
|
18
22
|
# `date` (year) component is flattened to a top-level key rather than a
|
|
19
23
|
# nested hash, mirroring ISO (lib/pubid/iso/identifier.rb). `type` is
|
|
@@ -7,8 +7,14 @@ module Pubid
|
|
|
7
7
|
# These wrap a base identifier like ISO amendments
|
|
8
8
|
attribute :base, Oiml::Identifier, polymorphic: true
|
|
9
9
|
attribute :year, :string
|
|
10
|
+
# Ordinal of the trailing-word form ("OIML R 138:2009 Amendment 1").
|
|
11
|
+
attribute :number, :string
|
|
10
12
|
attribute :language, :string
|
|
11
13
|
|
|
14
|
+
# A nil `language` means the document states none, mirroring
|
|
15
|
+
# SingleIdentifier's rule.
|
|
16
|
+
subset_strict :language
|
|
17
|
+
|
|
12
18
|
# Delegate the document code to the wrapped standard, mirroring
|
|
13
19
|
# Pubid::Etsi::Identifiers::SupplementIdentifier#code.
|
|
14
20
|
#
|
|
@@ -54,6 +60,7 @@ module Pubid
|
|
|
54
60
|
map "base",
|
|
55
61
|
with: { to: :base_to_kv, from: :base_from_kv }
|
|
56
62
|
map "year", to: :year
|
|
63
|
+
map "number", to: :number
|
|
57
64
|
map "trailing", to: :trailing
|
|
58
65
|
map "joined", to: :joined
|
|
59
66
|
end
|
|
@@ -27,6 +27,25 @@ module Pubid
|
|
|
27
27
|
identifier.language&.to_s&.downcase
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# DualPublished declares no `date` attribute of its own (it lives on
|
|
31
|
+
# whichever side is OIML), so the shared Base#urn_year — which gates
|
|
32
|
+
# on `identifier.class.attributes.key?(:date)` — would silently drop
|
|
33
|
+
# the year. Read it through the OIML side instead.
|
|
34
|
+
def urn_year
|
|
35
|
+
if identifier.is_a?(Identifiers::DualPublished)
|
|
36
|
+
oiml_date = identifier.oiml_identifier&.date
|
|
37
|
+
return oiml_date&.year&.to_s
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
year = super
|
|
41
|
+
return year if year
|
|
42
|
+
|
|
43
|
+
# The trailing-word form keeps the publication year on the base
|
|
44
|
+
# ("R 138:2009 Amendment 1"); the supplement itself carries only the
|
|
45
|
+
# ordinal.
|
|
46
|
+
identifier.base&.date&.year&.to_s if identifier.is_a?(SupplementIdentifier)
|
|
47
|
+
end
|
|
48
|
+
|
|
30
49
|
def generate
|
|
31
50
|
# Bulletin issues carry no code; the (year, issue, sequence) tuple
|
|
32
51
|
# is the locator. URNs are canonical regardless of how the input was
|
|
@@ -39,6 +58,15 @@ module Pubid
|
|
|
39
58
|
return parts.join(":")
|
|
40
59
|
end
|
|
41
60
|
|
|
61
|
+
# Certification-system documents: the family-number pair is the
|
|
62
|
+
# document identity ("cs:pd-05"); the printed edition and trailing
|
|
63
|
+
# amendment are print states, not URN segments.
|
|
64
|
+
if identifier.is_a?(Identifiers::CertificationSystem)
|
|
65
|
+
parts = ["urn", "oiml", "cs",
|
|
66
|
+
"#{identifier.family.downcase}-#{identifier.number}"]
|
|
67
|
+
return parts.join(":")
|
|
68
|
+
end
|
|
69
|
+
|
|
42
70
|
parts = ["urn", "oiml"]
|
|
43
71
|
parts << urn_type
|
|
44
72
|
parts << urn_number if urn_number
|
data/lib/pubid/oiml.rb
CHANGED
|
@@ -29,6 +29,11 @@ module Pubid
|
|
|
29
29
|
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
if identifier.include?("|")
|
|
33
|
+
dual = Identifiers::DualPublished.build(identifier)
|
|
34
|
+
return dual if dual
|
|
35
|
+
end
|
|
36
|
+
|
|
32
37
|
parser = Parser.new
|
|
33
38
|
builder = Builder.new
|
|
34
39
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# OMG flavor notes
|
|
2
|
+
|
|
3
|
+
OMG document parts, the separator that normalizes, the acronym charset and its slash, and the bare `beta`.
|
|
4
|
+
|
|
5
|
+
These notes were part of the root `CLAUDE.md`. Read them before you change `lib/pubid/omg/` or `spec/pubid/omg/`. The root file keeps the cross-flavor contract that every flavor obeys.
|
|
6
|
+
|
|
7
|
+
- **The document part reuses the inherited `part`, retyped to `:string`**: an OMG identifier is `OMG <ACRONYM>[ <VERSION>][ <PART>]`, and the third component is a real OMG form, not an invention. OMG published UML 2.1.1 as **two** documents and its URLs carry the segment (`/spec/UML/2.1.1/Superstructure`, `/spec/UML/2.1.1/Infrastructure`); the same position also holds a format name (`/spec/DDS/1.4/PDF`). Both go into one optional component. The attribute is the **`part` that `::Pubid::Identifier` already declares** as a `Components::Code`, retyped to `:string` on `Pubid::Omg::Identifier` — the tranche-1 shape (ansi/api/idf/jcgm/bsi/cen_cenelec). Three things follow from reusing the inherited name rather than inventing a `document_part`: relaton gets `remove_part!` as a plain `exclude(:part)` with no OMG-specific knowledge, `Renderers::Annotator::TOKENS` already carries `[:part, "part"]` so annotation costs nothing, and there is no second, permanently-nil `part` on every identifier. The **placement** is what makes the retype safe: it sits once on `Pubid::Omg::Identifier`, which every OMG identifier inherits from and whose class body lives in one file (`lib/pubid/omg/identifier.rb`) and is never reopened — the condition the root `CLAUDE.md` states for a base-level declaration. Never move it onto `Identifiers::Specification`, and never add a second declaration there: a redeclaration moves the generated accessor's `owner`, which the retype tripwire specs of other flavors treat as a delegation.
|
|
8
|
+
- **The separator normalizes, and that is a matching decision, not a cosmetic one**: OMG writes the part behind either a space or a slash, so `Parser#part_separator` is `space | str("/")` and the parser takes both. The renderer prints **only a space**, so `OMG DDS 1.4/PDF` is a *normalizing* parse rendering `OMG DDS 1.4 PDF`. The alternative — a `part_separator` sibling attribute in the CSA `year_format` shape — round-trips both spellings byte for byte but makes them **not `==`**, and `#matches?` is `exclude(*ignore) == other.exclude(*ignore)`, so a relaton index lookup between the two spellings would return nothing with no error. That is the silent failure mode the root file records as the costliest here, and it is not worth a separator. `spec/pubid/omg/identifier_spec.rb` asserts the normalization **and** the equality, so a future attempt to preserve the separator turns both red. The slash forms therefore cannot live in `spec/fixtures/omg/pass/`, whose spec demands a byte-exact round-trip; they are pinned in the identifier spec instead, with a comment in the fixture file saying why.
|
|
9
|
+
- **The acronym is the URL segment, so it takes every character OMG puts there — and a slash before the version belongs to it.** A consumer (relaton) builds `https://www.omg.org/spec/<acronym>/` from the parsed acronym, so the acronym must be verbatim. The old rule `[A-Z][A-Za-z0-9]*` rejected **30 of the 270** acronyms the catalog names: 25 with a hyphen (`DDS-XTypes`, `IDL4-CPP`), 4 with a slash (`EDMC-FIBO/BE`), `VSIPL++` and the lower-case `smartant`. `Parser#acronym` now starts with any letter, takes letters, digits and `+`, and joins further non-empty segments with `-` or `/`, so a trailing hyphen or slash is never consumed. **The slash is the design decision.** The document-part bullet above made a slash separate the part, and `OMG EDMC-FIBO/BE` could then read as acronym `EDMC-FIBO` with part `BE` — which builds the URL of the wrong page, silently. The rule now is: **before the version a slash is part of the acronym, after the version it separates the part** (`OMG DDS 1.4/PDF` is unchanged; `OMG EDMC-FIBO/BE 1.1/PDF` reads both). `Parser#identifier` spells that out — a part that follows the acronym directly takes only a space. The cost is that `OMG UML/Superstructure` now reads as acronym `UML/Superstructure`; no spec, fixture or relaton corpus row used that spelling, and OMG writes the space. A fixed list of the four FIBO domains was the alternative and was rejected: it drifts when OMG adds a domain. **Known limit — a two-word title still parses.** The grammar cannot tell an acronym from a word, so `OMG Real-Time Extension` reads as acronym `Real-Time`, part `Extension`. This is not new: on `main` before this change, `OMG Model Driven` already read as acronym `Model`, part `Driven`; the wider charset only adds hyphenated and lower-case first words. A longer title (`OMG Model Driven Architecture Guide rev. 2.0`) still raises, because the grammar has no place for a third word. `spec/pubid/omg/identifier_spec.rb` carries all 30 catalog acronyms as a frozen, network-free list. To re-check against the live catalog (270 acronyms, 0 rejections on 2026-09-14): `curl -s https://www.omg.org/spec/ | ruby -e 'puts STDIN.read.scan(%r{spec/(.+?)/About-[^"]*"}).flatten.uniq'`, then `Pubid::Omg.parse("OMG #{acronym}")` for each. (hand-off: `metanorma__pubid__omg-acronym-charset`.)
|
|
10
|
+
- **The bare `beta` is not optional polish — the document part makes it load-bearing.** `parser.rb` used to demand `" beta "` followed by at least one digit. Once an optional third token is legal, a PEG grammar parses `OMG UML 2.5 beta` as version `2.5` plus a **document part named `beta`** — a silent wrong answer, not a parse failure, and exactly the shape that costs nothing until a consumer compares two identifiers. So the beta number is `.maybe`, and the version rule consumes the label whole. **This is what OMG actually publishes, checked against the source**: `https://www.omg.org/spec/UML/2.5/Beta1/` gives its own version as **`2.5 beta`**, the unnumbered form, while DDS 1.4 supersedes `https://www.omg.org/spec/DDS/1.4/Beta2`. Both spellings are real. The regression guard is the example asserting that `OMG UML 2.5 beta` leaves `part` **nil**; the round-trip example alone would pass either way.
|
|
11
|
+
- **Both halves of the beta label need a word boundary, and the failure mode is a rejection rather than a misparse.** Parslet never backtracks into a `.maybe` that already succeeded. With an unanchored `str(" beta")`, `OMG DDS 1.4 beta2` and `OMG DDS 1.4 betawave` made the version rule commit to `" beta"`, fail to find the beta number, and leave `2`/`wave` with no separator in front of it — so the **whole identifier raised**, not merely parsed oddly. The same trap sits behind the beta number: without a guard, `OMG UML 2.5 beta 1x` consumed `" 1"` and then choked on `x`. `Parser#beta` therefore ends each half with `word_boundary` (`match("[A-Za-z0-9]").absent?`), which costs nothing — a genuine bare `beta` is followed by the end of input or a space, and a genuine beta number by the same. All three inputs now read the tail as a document part. A code review found the first case; the second and third came out of probing the fix. The lesson generalizes to any flavor adding an optional trailing token after an optional literal-suffixed one: **anchor the literal, or the earlier rule eats the later one's first word and the identifier is rejected**.
|
|
12
|
+
- **Known limit — `Beta2` reads as a document part.** OMG's URL spelling glues the label and the number and capitalizes (`.../1.4/Beta2`), so `OMG DDS 1.4 Beta2` parses as version `1.4` with the part `"Beta2"`. It round-trips and it is not wrong enough to chase: no reference in the relaton corpus uses that spelling, the version rule follows the lowercase, space-separated form the relaton regex accepted, and widening it would need a rule that tells `Beta2` from a genuine volume name. Recorded rather than fixed.
|
|
13
|
+
- **`root.number` is nil for every OMG identifier, and so is the MR slug. Neither moved here, and nothing depends on either yet.** OMG models `acronym`/`version`/`part` and never sets the `number` it inherits, so the index key the root file requires of every leaf is empty and `to_mr_string` is `""` for every identifier — both were already true before this branch and both are unchanged by it (the base `mr_number_with_part` reads `number`, not `part`, so adding the part does not populate the slug). OMG has **no index and no `relaton-data-omg`** — it is scraped from `www.omg.org/spec` with Mechanize — and the relaton migration hand-off explicitly forbids adding one, so the binary-search degradation the root file describes cannot bite today. Do not "fix" it by mirroring `acronym` into `number` without checking the renderer first: that is the precondition the BIPM entry names, and the NIST attempt recorded in the root file is what happens when it does not hold.
|
|
14
|
+
- **The fixture corpus is hand-written.** OMG has no `spec/fixtures/omg/identifiers/full/` tree and no entry in `spec/fixtures/classify_fixtures.rb`, so `validation:classify` never rewrites `pass/` or `fail/` and the root file's rule against hand-editing them does not apply. `spec/pubid/omg/fixtures_spec.rb` skips `#` lines, so the files carry comments.
|
|
15
|
+
- **relaton note**: `relaton/relaton` moves its OMG flavor off the hand-written regex at `lib/relaton/omg/scraper.rb:19` onto this flavor. Both blockers it named are fixed. Its probe expected `OMG DDS 1.4/PDF` to round-trip byte for byte; it now sees a normalizing parse, which is harmless there because the flavor reads `acronym` and `version` off the parsed object and keeps the document part out of the request URL. (hand-offs: `metanorma__pubid__omg-document-part-and-bare-beta`, `relaton__relaton__omg-pubid-migration`.)
|
data/lib/pubid/omg/builder.rb
CHANGED
data/lib/pubid/parser/grammar.rb
CHANGED
|
@@ -21,10 +21,22 @@ module Pubid
|
|
|
21
21
|
# bypasses this and raises a bare `Parslet::ParseFailed`. Nothing in the
|
|
22
22
|
# gem does that.
|
|
23
23
|
class Grammar < ::Parslet::Parser
|
|
24
|
+
# A trailing "(all parts)" marks the reference as the whole document.
|
|
25
|
+
# The flavor grammars that carry their own rule consume it inside
|
|
26
|
+
# parslet; this shared strip gives every other flavor the same read:
|
|
27
|
+
# the suffix never reaches the flavor grammar, and the parsed tree
|
|
28
|
+
# carries :all_parts for the builder to wrap (see Builder::Base).
|
|
29
|
+
ALL_PARTS_SUFFIX = "(all parts)".freeze
|
|
30
|
+
|
|
24
31
|
# @param io [String, IO]
|
|
25
32
|
# @param options [Hash] passed through to parslet
|
|
26
33
|
# @raise [Pubid::Errors::ParseError]
|
|
27
34
|
def parse(io, options = {})
|
|
35
|
+
if io.is_a?(String) && io.end_with?(ALL_PARTS_SUFFIX)
|
|
36
|
+
base = io.sub(/\s*\(all parts\)\s*\z/, "")
|
|
37
|
+
return mark_all_parts(super(base, options))
|
|
38
|
+
end
|
|
39
|
+
|
|
28
40
|
super
|
|
29
41
|
rescue ::Pubid::Errors::ParseError
|
|
30
42
|
# A nested grammar already wrapped it. Keep the inner flavor and input.
|
|
@@ -35,6 +47,17 @@ module Pubid
|
|
|
35
47
|
|
|
36
48
|
private
|
|
37
49
|
|
|
50
|
+
# Carry the stripped suffix into the tree. Parslet tops are a Hash or
|
|
51
|
+
# an Array of Hashes; the marker joins either shape, and every builder
|
|
52
|
+
# (Builder::Base and the standalone ones) routes it to #to_all_parts.
|
|
53
|
+
def mark_all_parts(tree)
|
|
54
|
+
case tree
|
|
55
|
+
when Hash then tree.merge(all_parts: true)
|
|
56
|
+
when Array then tree.map { |t| t.merge(all_parts: true) }
|
|
57
|
+
else tree
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
38
61
|
# @param error [Parslet::ParseFailed]
|
|
39
62
|
# @param io [String, IO] what was handed to {#parse}
|
|
40
63
|
# @return [Pubid::Errors::ParseError]
|
|
@@ -14,6 +14,10 @@ module Pubid
|
|
|
14
14
|
attribute :number, :integer
|
|
15
15
|
attribute :annex, :integer, default: -> {}
|
|
16
16
|
|
|
17
|
+
# A nil `annex` means the document has none: PLATEAU Handbook #10 is
|
|
18
|
+
# not its annex, PLATEAU Handbook #10-1.
|
|
19
|
+
subset_strict :annex
|
|
20
|
+
|
|
17
21
|
# Stored as a plain string (always "PLATEAU") so it round-trips through
|
|
18
22
|
# to_hash/from_hash. Was a `def publisher` method, which made lutaml
|
|
19
23
|
# serialize a String against the Components::Publisher attribute.
|
|
@@ -9,9 +9,16 @@ module Pubid
|
|
|
9
9
|
class SupplementIdentifier < Pubid::Identifier
|
|
10
10
|
attribute :base, Identifier
|
|
11
11
|
attribute :letter, :string, default: -> {}
|
|
12
|
+
# Stored as a plain string (always "PLATEAU") so it round-trips through
|
|
13
|
+
# to_hash/from_hash. Was a `def publisher` method, which made lutaml
|
|
14
|
+
# serialize a String against the Components::Publisher attribute
|
|
15
|
+
# (pubid/pubid#407) — the same fix as Identifiers::Base.
|
|
16
|
+
attribute :publisher, :string, default: -> { "PLATEAU" }
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
# The UrnGenerator reads type_string on every identifier; the annex
|
|
19
|
+
# supplement's own type makes its "an" URN branch reachable.
|
|
20
|
+
def type_string
|
|
21
|
+
"Annex"
|
|
15
22
|
end
|
|
16
23
|
|
|
17
24
|
# Subclasses must implement supplement_string
|
|
@@ -22,6 +29,11 @@ module Pubid
|
|
|
22
29
|
# Override base_hash to extract edition, type, and annex from base
|
|
23
30
|
def base_hash
|
|
24
31
|
hash = super
|
|
32
|
+
# The base document's number: without it from_hash cannot
|
|
33
|
+
# reconstruct the wrapped identifier (pubid/pubid#407).
|
|
34
|
+
if base.class.attributes.key?(:number) && base.number
|
|
35
|
+
hash[:number] = base.number
|
|
36
|
+
end
|
|
25
37
|
# For Plateau supplements, edition comes from the base identifier
|
|
26
38
|
if base.class.attributes.key?(:edition) && base.edition
|
|
27
39
|
hash[:edition] = base.edition
|
|
@@ -20,7 +20,13 @@ module Pubid
|
|
|
20
20
|
|
|
21
21
|
parts << format("%02d", identifier.number) if identifier.number
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
if identifier.class.attributes.key?(:annex)
|
|
24
|
+
parts << format("%02d", identifier.annex) if identifier.annex
|
|
25
|
+
# Annex supplements carry a letter, not an annex number
|
|
26
|
+
# (pubid/pubid#407).
|
|
27
|
+
elsif identifier.class.attributes.key?(:letter) && identifier.letter
|
|
28
|
+
parts << identifier.letter.to_s.downcase
|
|
29
|
+
end
|
|
24
30
|
|
|
25
31
|
parts.join(":")
|
|
26
32
|
end
|
data/lib/pubid/sae/builder.rb
CHANGED