relaton 3.0.0.pre.alpha.17 → 3.0.0.pre.alpha.19

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.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/data/equivalence/adoption-chains.yaml +59 -0
  3. data/lib/relaton/3gpp/CLAUDE.md +187 -0
  4. data/lib/relaton/3gpp/README.adoc +149 -0
  5. data/lib/relaton/adobe/CLAUDE.md +104 -0
  6. data/lib/relaton/bib/CLAUDE.md +130 -0
  7. data/lib/relaton/bib/README.adoc +330 -0
  8. data/lib/relaton/bib/model/item_shared.rb +5 -1
  9. data/lib/relaton/bib/model/series.rb +1 -1
  10. data/lib/relaton/bipm/CLAUDE.md +173 -0
  11. data/lib/relaton/bipm/README.adoc +352 -0
  12. data/lib/relaton/bipm/data_fetcher.rb +3 -2
  13. data/lib/relaton/bsi/CLAUDE.md +35 -0
  14. data/lib/relaton/bsi/README.adoc +112 -0
  15. data/lib/relaton/calconnect/CLAUDE.md +177 -0
  16. data/lib/relaton/calconnect/README.adoc +140 -0
  17. data/lib/relaton/ccsds/CLAUDE.md +74 -0
  18. data/lib/relaton/ccsds/README.adoc +154 -0
  19. data/lib/relaton/cen/CLAUDE.md +219 -0
  20. data/lib/relaton/cen/README.adoc +155 -0
  21. data/lib/relaton/cie/CLAUDE.md +90 -0
  22. data/lib/relaton/cie/README.adoc +121 -0
  23. data/lib/relaton/core/CLAUDE.md +140 -0
  24. data/lib/relaton/core/README.adoc +64 -0
  25. data/lib/relaton/core/data_fetcher.rb +142 -3
  26. data/lib/relaton/db/CLAUDE.md +373 -0
  27. data/lib/relaton/db.rb +21 -1
  28. data/lib/relaton/doi/CLAUDE.md +105 -0
  29. data/lib/relaton/doi/README.adoc +89 -0
  30. data/lib/relaton/easc/CLAUDE.md +85 -0
  31. data/lib/relaton/ecma/CLAUDE.md +217 -0
  32. data/lib/relaton/ecma/README.adoc +178 -0
  33. data/lib/relaton/equivalence.rb +117 -0
  34. data/lib/relaton/etsi/CLAUDE.md +219 -0
  35. data/lib/relaton/etsi/README.adoc +137 -0
  36. data/lib/relaton/evs/CLAUDE.md +120 -0
  37. data/lib/relaton/evs/README.adoc +27 -0
  38. data/lib/relaton/gb/CLAUDE.md +47 -0
  39. data/lib/relaton/gb/README.adoc +169 -0
  40. data/lib/relaton/gost/CLAUDE.md +100 -0
  41. data/lib/relaton/iala/CLAUDE.md +97 -0
  42. data/lib/relaton/iana/CLAUDE.md +105 -0
  43. data/lib/relaton/iana/README.adoc +107 -0
  44. data/lib/relaton/iec/CLAUDE.md +13 -0
  45. data/lib/relaton/iec/README.adoc +280 -0
  46. data/lib/relaton/iec/data_fetcher.rb +24 -8
  47. data/lib/relaton/iec/project_fetcher.rb +48 -0
  48. data/lib/relaton/iec/project_parser.rb +111 -0
  49. data/lib/relaton/iec.rb +5 -0
  50. data/lib/relaton/ieee/CLAUDE.md +176 -0
  51. data/lib/relaton/ieee/README.adoc +131 -0
  52. data/lib/relaton/ietf/CLAUDE.md +357 -0
  53. data/lib/relaton/ietf/README.adoc +167 -0
  54. data/lib/relaton/iho/CLAUDE.md +70 -0
  55. data/lib/relaton/iho/README.adoc +119 -0
  56. data/lib/relaton/index/CLAUDE.md +207 -0
  57. data/lib/relaton/index/README.adoc +221 -0
  58. data/lib/relaton/index/dataset_lock.rb +122 -0
  59. data/lib/relaton/index/family.rb +63 -0
  60. data/lib/relaton/index/type.rb +32 -0
  61. data/lib/relaton/index.rb +3 -0
  62. data/lib/relaton/isbn/CLAUDE.md +47 -0
  63. data/lib/relaton/isbn/README.adoc +91 -0
  64. data/lib/relaton/iso/CLAUDE.md +38 -0
  65. data/lib/relaton/iso/README.adoc +370 -0
  66. data/lib/relaton/iso/data_fetcher.rb +16 -1
  67. data/lib/relaton/itu/CLAUDE.md +685 -0
  68. data/lib/relaton/itu/README.adoc +188 -0
  69. data/lib/relaton/jcgm/CLAUDE.md +107 -0
  70. data/lib/relaton/jis/CLAUDE.md +64 -0
  71. data/lib/relaton/jis/README.adoc +167 -0
  72. data/lib/relaton/logger/CLAUDE.md +37 -0
  73. data/lib/relaton/logger/README.adoc +120 -0
  74. data/lib/relaton/nist/CLAUDE.md +180 -0
  75. data/lib/relaton/nist/README.adoc +389 -0
  76. data/lib/relaton/nist/bibliography.rb +17 -1
  77. data/lib/relaton/nist/data_fetcher.rb +60 -4
  78. data/lib/relaton/nist/hit_collection.rb +121 -59
  79. data/lib/relaton/nist/scraper.rb +21 -7
  80. data/lib/relaton/oasis/CLAUDE.md +251 -0
  81. data/lib/relaton/oasis/README.adoc +132 -0
  82. data/lib/relaton/ogc/CLAUDE.md +242 -0
  83. data/lib/relaton/ogc/README.adoc +134 -0
  84. data/lib/relaton/ogc/data_fetcher.rb +9 -3
  85. data/lib/relaton/oiml/CLAUDE.md +47 -0
  86. data/lib/relaton/oiml/README.adoc +70 -0
  87. data/lib/relaton/omg/CLAUDE.md +137 -0
  88. data/lib/relaton/omg/README.adoc +101 -0
  89. data/lib/relaton/plateau/CLAUDE.md +126 -0
  90. data/lib/relaton/plateau/README.adoc +139 -0
  91. data/lib/relaton/sdo/CLAUDE.md +81 -0
  92. data/lib/relaton/un/CLAUDE.md +91 -0
  93. data/lib/relaton/un/README.adoc +125 -0
  94. data/lib/relaton/version.rb +1 -1
  95. data/lib/relaton/w3c/CLAUDE.md +369 -0
  96. data/lib/relaton/w3c/README.adoc +140 -0
  97. data/lib/relaton/w3c/data_fetcher.rb +1 -1
  98. data/lib/relaton/xsf/CLAUDE.md +189 -0
  99. data/lib/relaton/xsf/README.adoc +127 -0
  100. data/lib/relaton.rb +1 -0
  101. metadata +85 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dd9570bd9dfabbff679d3b42be8135aecf3e0414ebae11691a1c259a7ef19c3
4
- data.tar.gz: 1152eddfe84df0d84d219279e7ab60dce5436c079f8dae51b1908b7434911eec
3
+ metadata.gz: eee3114e443e307b96ca18a96dff2a48dcb4d5b5dae424fd2ecb3ca6e57202be
4
+ data.tar.gz: 7208a6c3f058fca1874370a24bfe89d03516d5c4d66477d8c4160c4517717c44
5
5
  SHA512:
6
- metadata.gz: 3b76f06708d9e9f72ccb9a14a257d4a4c1ea052fe2f9fa5e07c437a50e43534d65587c8145c92d436e722fabe5084ea65f4d5e105e926d0a578763b91d43b0dd
7
- data.tar.gz: 62812f52faf3ac20cf0e21e610c0251a38266ebb2a37dd3e5c2e7dce7315b274f8e78a8c0ba17d4fac17cdd7372552a519bce90cd8da02ea46d82c91270fc43d
6
+ metadata.gz: f8c13c3bbdbe96caa0874d0b0c4671c05fa4c6652070372b0fc55992ac3c87834a6e03bda4a7fe17945d31db33b5f1a5a99d927d8d1d203d8238a7e837c29d77
7
+ data.tar.gz: 3e5e49f9fdd82e5abe32bc4afb35b2baf075bc1b10aee9aad0e663f4361a0486069ff94cf3d4c066d17ccf4f32dacfac5eccad6eb1534008978d39734577dc84
@@ -0,0 +1,59 @@
1
+ # Cross-SDO equivalence as data: adoption-chain patterns and explicit
2
+ # equivalences the resolver consults on a miss (TODO.sota layer 4).
3
+ #
4
+ # chain patterns: ordered most-specific-first rewrites. A code matching the
5
+ # first group resolves to each rewrite in turn (the canonical form last).
6
+ # The CEN/BSI/DIN/NF adoption ladder: a national adoption cites the EN, the
7
+ # EN cites the ISO — so a miss on "BS EN ISO 1388" falls back through
8
+ # "EN ISO 1388" to "ISO 1388".
9
+ chains:
10
+ - match: '^(?:BS |DIN |NF )?EN (ISO[ /].+)$'
11
+ resolve:
12
+ - 'EN \1'
13
+ - '\1'
14
+ - match: '^(?:BS |DIN |NF )(ISO[ /].+)$'
15
+ resolve:
16
+ - '\1'
17
+ - match: '^BS EN (.+)$'
18
+ resolve:
19
+ - 'EN \1'
20
+ - '\1'
21
+ - match: '^DIN EN (.+)$'
22
+ resolve:
23
+ - 'EN \1'
24
+ - '\1'
25
+ - match: '^NF EN (.+)$'
26
+ resolve:
27
+ - 'EN \1'
28
+ - '\1'
29
+
30
+ # Adoption ladders: the reverse edges. A code matching `pattern` is a base
31
+ # document; `forms` are its national/regional adoptions, ordered
32
+ # outermost-last, so the resolver can walk a chain from either end
33
+ # (TODO.sota: adoption chains resolvable end-to-end, bidirectional).
34
+ adoptions:
35
+ - pattern: '^(ISO[ /].+)$'
36
+ forms:
37
+ - 'EN \1'
38
+ - 'BS EN \1'
39
+ - 'DIN EN \1'
40
+ - 'NF EN \1'
41
+
42
+ # Explicit equivalences: every form in `equivalents` resolves to
43
+ # `canonical` (and the canonical resolves to itself). Used where no pattern
44
+ # expresses the relationship (joint publications, historical adoptions).
45
+ equivalences:
46
+ - canonical: ISO/IEC 17025
47
+ equivalents:
48
+ - EN ISO/IEC 17025
49
+ - BS EN ISO/IEC 17025
50
+ - DIN EN ISO/IEC 17025
51
+ # Joint publication: the same document under a different publisher
52
+ # ordering (Law 3.1) — the direction each corpus prints is the data.
53
+ - canonical: ISO/IEC 14515-1
54
+ equivalents:
55
+ - IEC/ISO 14515-1
56
+ # Variant spelling: the prefixed form of a bare reference (Law 3.3).
57
+ - canonical: RFC 7200
58
+ equivalents:
59
+ - IETF RFC 7200
@@ -0,0 +1,187 @@
1
+ # CLAUDE.md
2
+
3
+ ## Architecture
4
+
5
+ **Pubid-backed docidentifier.** `Relaton::ThreeGpp::Docidentifier`
6
+ (`docidentifier.rb`, `< Bib::Docidentifier`) parses its `content` into a
7
+ `Pubid::Tgpp::Identifier` kept in `@pubid`, while the lutaml `content`
8
+ attribute stays a plain **string** — `ItemData#create_id` calls
9
+ `docid.content.sub(...)`, so the ISO `Type::Pubid` route (which makes `content`
10
+ return an object) would break it. Parsing is **soft**: `content=` lazily
11
+ `require "pubid"` and rescues `LoadError`/`StandardError`, so a missing pubid
12
+ gem or non-3GPP content leaves `@pubid` nil rather than raising.
13
+
14
+ Before this class existed, 3GPP inherited `Bib::Docidentifier`, whose
15
+ `remove_part!` / `remove_date!` / `to_all_parts!` each raise
16
+ `NotImplementedError` — and `Bib::ItemData` broadcasts all three to every
17
+ docidentifier, so `#to_all_parts` and `#to_most_recent_reference` **raised on
18
+ every 3GPP item**. The mapping is 3GPP-specific:
19
+
20
+ - **`remove_part!` → clears `parts`.** A real, separable component here
21
+ (unlike IALA's, which is a no-op): `TS 29.198-04-1` → `TS 29.198`. `parts`
22
+ is a collection, so it is emptied rather than nil'd — `Identifier#code`
23
+ renders through `parts.map`.
24
+ - **`remove_date!` → clears `release` AND `version`.** 3GPP carries no date;
25
+ those two are its version discriminators — the same pair that
26
+ `Bibliography#best_match` lets a reference omit — so clearing both yields the
27
+ version-agnostic ("most recent") reference `TS 23.207`.
28
+ - **`to_all_parts!` → both, then wraps `@pubid` in pubid's `AllParts`.**
29
+ `content` stays the plain stripped id — the Tgpp renderer has no
30
+ "(all parts)" marker, and `content` is cached from the pre-wrap pubid on
31
+ purpose. `#pubid` itself, read directly, now answers `all_parts? == true`
32
+ and renders WITH pubid's generic marker (`#to_s`), since it's the wrapper.
33
+
34
+ All three no-op safely when `@pubid` is nil, so `ItemData#to_all_parts` and
35
+ `#to_most_recent_reference` never raise.
36
+
37
+ **The `with_publisher` trap.** `Pubid::Tgpp::Identifier#to_s` defaults to
38
+ **omitting** the `3GPP ` token — deliberately, so it reproduces the index id
39
+ (`TS 23.207:REL-4/4.0.0`). The stored docidentifier `content` *keeps* the
40
+ prefix (`Parser#parse_docid` builds `"3GPP #{number}"`), while `docnumber` does
41
+ not. So `Docidentifier#refresh_content!` renders
42
+ `to_s(with_publisher: true)`; without it every mutation would silently strip
43
+ the prefix. This is the one place 3GPP diverges from the IALA template, whose
44
+ `refresh_content!` is a bare `to_s`.
45
+
46
+ **Index (`index-v2`, pubid-keyed).** `INDEXFILE` is the pubid-backed
47
+ `index-v2`: 88,464 rows of `_type: pubid:3gpp:{technical-specification,
48
+ technical-report}` with `number`/`suffix`/`parts`/`release`/`version`. The
49
+ consumer (`Bibliography#index`) and the producer (`DataFetcher#index`) pass
50
+ `pubid_class: ::Pubid::Tgpp::Identifier`. That is what makes `Relaton::Index`
51
+ deserialize the rows into identifiers, sort them by `id.root.number`, and let
52
+ `Type#search` bsearch. Omitting it on the **producer** side writes v1-shaped
53
+ rows under a v2 name, silently (`FileIO#save` only calls `to_hash` when the
54
+ value is an instance of `pubid_class`); omitting it on the **consumer** side
55
+ leaves the rows raw hashes with `FileIO#sorted` false, so every lookup scans
56
+ all 88,464 rows. Measured on the live corpus: 3,767 number buckets, largest
57
+ 979; a `TS 23.207` lookup narrows to **37 of 88,464**.
58
+ `Processor#remove_index_file` passes `url: true` and `file:` only: the delete
59
+ never reads the index (see `lib/relaton/index/CLAUDE.md`).
60
+
61
+ `Bibliography#best_match` follows the ETSI/W3C/IALA idiom:
62
+
63
+ - **Pass the pubid, not the string.** `Type#search_candidates` narrows only
64
+ when the argument is not a `String`, so passing the reference text — which
65
+ this flavor used to do — disables the binary search however the index was
66
+ built. `pubid_class:` alone fixes nothing; both had to change together.
67
+ - **Select with the subset match.** `best_match` calls `index.search(pubid)`
68
+ with no block, so `Index::Type#search` selects with pubid's `pubid === row`.
69
+ `release` and `version` are 3GPP's only optional components: one that the
70
+ reference omits matches any value, so a bare `3GPP TS 23.207` finds the
71
+ `REL-19/19.0.0` row. Measured over the full `relaton-data-3gpp` index
72
+ (88,464 rows): `===` and the old `matches?(…, ignore: %i[release version])`
73
+ agree on all 31,305,864 pairs.
74
+ - **`suffix` and `parts` are strict.** They are part of the document code
75
+ (`TS 29.198-04-1`, `TR 00.01U`), not qualifiers, so pubid declares them
76
+ `subset_strict` for 3GPP (pubid#408): `TS 29.198` does not match
77
+ `TS 29.198-04-1`, and `TR 00.01` does not match `TR 00.01U`.
78
+ - **Neither is the document type.** It is the identifier's class, and `===`
79
+ requires the same class, so `TS 23.207` and `TR 23.207` never match. (No
80
+ document code in the corpus is actually carried by both types — measured
81
+ over all 88,464 rows — so the specs pin this with a negative lookup rather
82
+ than a fixture pair.)
83
+ - **A part-less row must deserialize with `parts == []`.** A parsed identifier
84
+ has always had `parts == []`; one deserialized from an index row used to
85
+ have `parts == nil`. With `parts` strict, nil and empty are the same value,
86
+ but the old attribute-wise `matches?` made every part-less row silently fail
87
+ to match its own reference — 366 of the 387 fixture rows. Fixed upstream in
88
+ pubid (`242ad5b`). If part-less lookups ever start returning nil again,
89
+ check the installed pubid before the flavor.
90
+ - **An unrecognized reference raises.** Like ISO and ETSI, the parse error
91
+ propagates out of `search`, so relaton-cli renders `"…" is not a recognized
92
+ standards identifier` (`gems/relaton-cli/lib/relaton/cli/command.rb:324`,
93
+ `subcommand_collection.rb:134`) and API callers rescue it themselves. The
94
+ parse happens **outside** the method-level rescue, whose list is transport
95
+ errors only, so it is never relabelled as a `Relaton::RequestError`.
96
+
97
+ ### Ordering: highest version wins
98
+
99
+ `min_by { |r| r[:id] }` compared raw strings, so `3GPP TS 23.207` (37 rows)
100
+ returned `REL-10/10.0.0` because `"1" < "4"` — neither the newest nor the
101
+ oldest, just whichever sorted first as text. The key is now the version,
102
+ compared **segment by segment as integers** (`19.0.0` beats `4.0.0`; `5.10.0`
103
+ beats `5.9.0`, which a text comparison gets backwards).
104
+
105
+ Scored against each document's own `date[0].at` over 870 documents in 59
106
+ multi-row groups:
107
+
108
+ | Key | Picks the newest published document |
109
+ |---|---|
110
+ | version segments, numeric | 51/59 — **86%** |
111
+ | release rank, then version | 51/59 — **86%** |
112
+ | the old `min_by { r[:id] }` | 1/59 — **2%** |
113
+
114
+ **Do not add a release key on top.** The two candidate keys picked the same row
115
+ in all 59 sampled groups and disagree on only **9 of 3,982** multi-row groups
116
+ corpus-wide (99.77% identical) — all draft TRs carried into a later release at
117
+ a lower version, e.g. `TR 23.873`, where the release answer is not clearly
118
+ better. The cost would be a hand-maintained 27-token table whose order is
119
+ **non-monotonic**: the chronological sequence is `Ph1, Ph2, REL-96, REL-97,
120
+ REL-98, REL-99, Release 2000, UMTS, REL-4 … REL-21`, so every numeric read of
121
+ the token puts REL-99 above REL-19.
122
+
123
+ Ties break on the rendered id and then the file path, because the index sort is
124
+ not stable.
125
+
126
+ **Known limit — do not try to "fix" the ordering back.** "Newest release" and
127
+ "newest publication" are different documents in ~8% of groups, because 3GPP
128
+ keeps revising old releases:
129
+
130
+ ```
131
+ TS 04.08 newest published: REL-98/7.21.0 2004-01-05
132
+ highest release: REL-99/8.0.0 2000-06-30
133
+ ```
134
+
135
+ A version key returns the second. No key computable from the identifier alone
136
+ returns the first: index rows carry only `{id, file}` and `Pubid::Tgpp` has no
137
+ date attribute. Closing that gap would need a pubid attribute, an index schema
138
+ change and a crawler change.
139
+
140
+ **Producer side.** `DataFetcher#save_doc` indexes
141
+ `docidentifier.detect(&:primary).pubid` through `#index_primary` — the pubid is
142
+ already parsed on the model, so nothing re-parses `docnumber`, and
143
+ `pubid.to_s` (no publisher token) reproduces the old string key byte-for-byte,
144
+ so the index key did not move. `docnumber` still names the output file. An id
145
+ pubid cannot rebuild is recorded in `@errors` (the inherited `report_errors`
146
+ logs a String value as the message) and the row is **skipped** rather than
147
+ indexed unparsed — `Relaton::Index` rejects the whole index on one bad row. The
148
+ data file is still written, so the document is unindexed, never lost.
149
+
150
+ `require "pubid"` sits in `lib/relaton/3gpp.rb` (the IANA/IHO/IALA form), so
151
+ pubid loads with the flavor. `Processor#remove_index_file` names no pubid
152
+ class, so the cold `Db#clear` path that `spec/relaton/lazy_loading_spec.rb`
153
+ guards does not depend on it. `Processor` also sets
154
+ `@pubid_flavor = :Tgpp`, so `Core::Processor#prefixes` reads
155
+ `Pubid::Tgpp.prefixes` (`["3GPP"]`).
156
+
157
+ ## Testing
158
+
159
+ - `bundle exec rake spec:3gpp` — run the 3GPP suite.
160
+ - Specs live in `spec/3gpp/relaton/3gpp/` and run self-contained (see the root
161
+ `CLAUDE.md` Testing section).
162
+ - **Index fixture.** `spec/3gpp/fixtures/index-v2.zip` is a curated subset of
163
+ the published index — never the full 88,464 rows, and never hand-written.
164
+ `bundle exec rake spec:update_index_3gpp` regenerates it;
165
+ `tasks/index_fixture_3gpp.rb` names each document group and the rule it pins
166
+ (`TS 23.207` version ordering, `TS 05.05` legacy release tokens, `TS 04.08`
167
+ the known limit, `TS 29.198-04-1` parts, `TR 00.01U` suffix, `TS 29.215` the
168
+ release-less row). Rows are copied **verbatim** from `relaton-data-3gpp`'s
169
+ published `index-v2.zip`, so the stored shapes are exactly what `DataFetcher`
170
+ writes and what the runtime deserializes. `#build` refuses a v1 source
171
+ outright — string ids under the v2 name would give a fixture
172
+ `Relaton::Index` rejects wholesale — so the failure names its cause instead
173
+ of surfacing as an unrelated deserialization error.
174
+
175
+ Re-cutting always produces a different **binary** even when the rows are
176
+ unchanged, because a zip stores mtimes. Compare the rows, not the file: if
177
+ the row set is identical, there is nothing to commit.
178
+ - `spec/3gpp/support/webmock.rb` seeds the fixture into the `Relaton::Index`
179
+ pool **with `pubid_class:`** — without it the suite would pass while
180
+ exercising something the runtime never does. It re-seeds in `before(:each)`,
181
+ not only `before(:suite)`, because `DataFetcher#index` asks for the same type
182
+ with `file:` but no `url:` and `Pool#type` then replaces the entry.
183
+ `Pool#type` upcases its key, so `Bibliography`'s `"3GPP"` and
184
+ `DataFetcher`'s `"3gpp"` are the same pooled entry, `:"3GPP"`.
185
+ - **VCR cassettes:** `spec/3gpp/vcr_cassettes/`. Index downloads are ignored by
186
+ VCR (matched as `index-v\d+\.zip`, so the next `INDEXFILE` bump does not
187
+ start recording them) and served from the fixture instead.
@@ -0,0 +1,149 @@
1
+ = Relaton3gpp
2
+
3
+ Relaton3gpp is a Ruby gem that implements the https://github.com/metanorma/metanorma-model-iso#iso-bibliographic-item[IsoBibliographicItem model].
4
+
5
+ You can use it to retrieve metadata of W3C Standards from https://w3.org, and access such metadata through the `W3cBibliographicItem` object.
6
+
7
+ == Usage
8
+
9
+ === Search for a standard using keywords
10
+
11
+ [source,ruby]
12
+ ----
13
+ require "relaton_3gpp"
14
+ => true
15
+
16
+ item = Relaton3gpp::Bibliography.get "3GPP TR 00.01U:UMTS/3.0.0"
17
+ [relaton-3gpp] (3GPP TR 00.01U:UMTS/3.0.0) Fetching repository ...
18
+ [relaton-3gpp] (3GPP TR 00.01U:UMTS/3.0.0) Found: `3GPP TR 00.01U:UMTS/3.0.0`
19
+ => #<Relaton3gpp::BibliographicItem:0x00007f92d94264e0
20
+ ...
21
+ ----
22
+
23
+ === XML serialization
24
+
25
+ [source,ruby]
26
+ ----
27
+ item.to_xml
28
+ => "<bibitem id="3GPPTR00.01U-UMTS/3.0.0" type="standard" schema-version="v1.2.1">
29
+ <fetched>2022-12-02</fetched>
30
+ <title type="main" format="text/plain">Work programme for the standardization of Universal Mobile Telecommunications System (UMTS)</title>
31
+ <uri type="src">http://www.3gpp.org/ftp/Specs/archive/00_series/00.01U/0001U-300.zip</uri>
32
+ <docidentifier type="3GPP" primary="true">3GPP TR 00.01U:UMTS/3.0.0</docidentifier>
33
+ <docnumber>TR 00.01U:UMTS/3.0.0</docnumber>
34
+ ...
35
+ </bibitem>"
36
+ ----
37
+
38
+ With argument `bibdata: true` it outputs XML wrapped by `bibdata` element and adds flavor `ext` element.
39
+
40
+ [source,ruby]
41
+ ----
42
+ item.to_xml bibdata: true
43
+ => "<bibdata type="standard" schema-version="v1.2.1">
44
+ <fetched>2022-12-02</fetched>
45
+ <title type="main" format="text/plain">Work programme for the standardization of Universal Mobile Telecommunications System (UMTS)</title>
46
+ <uri type="src">http://www.3gpp.org/ftp/Specs/archive/00_series/00.01U/0001U-300.zip</uri>
47
+ <docidentifier type="3GPP" primary="true">3GPP TR 00.01U:UMTS/3.0.0</docidentifier>
48
+ <docnumber>TR 00.01U:UMTS/3.0.0</docnumber>
49
+ ...
50
+ <ext schema-version="v1.0.4">
51
+ <doctype>TR</doctype>
52
+ <editorialgroup>
53
+ <technical-committee type="prime">SMG5</technical-committee>
54
+ </editorialgroup>
55
+ <radiotechnology>3G</radiotechnology>
56
+ <release>
57
+ <version2G>3</version2G>
58
+ <version3G>3</version3G>
59
+ <defunct>true</defunct>
60
+ <freeze-meeting>SMG-28</freeze-meeting>
61
+ <freeze-stage1-meeting>SMG-28</freeze-stage1-meeting>
62
+ <freeze-stage2-meeting>SMG-28</freeze-stage2-meeting>
63
+ <freeze-stage3-meeting>SMG-28</freeze-stage3-meeting>
64
+ <close-meeting>SP-28</close-meeting>
65
+ <project-end>1999-02-12</project-end>
66
+ </release>
67
+ </ext>
68
+ </ext>
69
+ </bibdata>"
70
+ ----
71
+
72
+ === Typed links
73
+
74
+ Some 3GPP documents have `src` type link.
75
+
76
+ [source,ruby]
77
+ ----
78
+ item.link.first.type
79
+ => "src"
80
+
81
+ item.link.first.content
82
+ ==> #<Addressable::URI:0x320 URI:http://www.3gpp.org/ftp/Specs/archive/00_series/00.01U/0001U-300.zip>
83
+ ----
84
+
85
+ === Create bibliographic item from XML
86
+ [source,ruby]
87
+ ----
88
+ Relaton3gpp::XMLParser.from_xml File.read('spec/fixtures/bibdata.xml')
89
+ => #<Relaton3gpp::BibliographicItem:0x00007fcf5f21af30
90
+ ...
91
+ ----
92
+
93
+ === Create bibliographic item from YAML
94
+ [source,ruby]
95
+ ----
96
+ hash = YAML.load_file 'spec/fixtures/bib.yaml'
97
+ => {"schema-version"=>"v1.2.1",
98
+ "id"=>"3GPPTR00.01U-UMTS/3.0.0",
99
+ ...
100
+
101
+ bib_hash = Relaton3gpp::HashConverter.hash_to_bib hash
102
+ => {:"schema-version"=>"v1.2.1",
103
+ :id=>"3GPPTR00.01U-UMTS/3.0.0",
104
+ ...
105
+
106
+ Relaton3gpp::BibliographicItem.new(**bib_hash)
107
+ => #<Relaton3gpp::BibliographicItem:0x00007f92d953cbe0
108
+ ...
109
+ ----
110
+
111
+ === Fetch data
112
+
113
+ There is a 3GPP dataset ftp://www.3gpp.org/Information/Databases/[latest *.csv] which can be converted into RelatonXML/BibXML/BibYAML formats.
114
+
115
+ The method `Relaton3GPP::DataFetcher.fetch(output: "data", format: "yaml")` converts all the documents from the dataset and save them to the `./data` folder in YAML format.
116
+ Arguments:
117
+
118
+ - `source` - a data source name. Possible values are: `status-smg-3GPP` and `status-smg-3GPP-force`. The first one only downloads a dataset if it's updated. The second one forces this gem to download the latest dataset.
119
+ - `output` - folder to save documents (default './data').
120
+ - `format` - a format in which the documents are saved. Possible formats are: `yaml`, `xml`, `bibxml` (default `yaml`).
121
+
122
+ [source,ruby]
123
+ ----
124
+ Relaton3GPP::DataFetcher.fetch "status-smg-3GPP" output: "data", format: "yaml"
125
+ Started at: 2021-12-10 19:58:46 +0100
126
+ Stopped at: 2021-12-10 20:08:03 +0100
127
+ Done in: 557 sec.
128
+ => nil
129
+ ----
130
+
131
+ === Logging
132
+
133
+ Relaton3gpp uses the relaton-logger gem for logging. By default, it logs to STDOUT. To change the log levels and add other loggers, read the https://github.com/relaton/relaton-logger#usage[relaton-logger] documentation.
134
+
135
+ == Development
136
+
137
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
138
+
139
+ To update the index test fixture (used by tests), run `rake spec:update_index_3gpp`. This downloads the published index from the https://github.com/relaton/relaton-data-3gpp[relaton-data-3gpp] repository, keeps a curated subset of it, and writes `spec/3gpp/fixtures/index-v2.zip`. The full index has ~88,000 rows, so the fixture is deliberately a subset; `tasks/index_fixture_3gpp.rb` documents which document groups it keeps and why.
140
+
141
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
142
+
143
+ == Contributing
144
+
145
+ Bug reports and pull requests are welcome on GitHub at https://github.com/relaton/relaton_3gpp.
146
+
147
+ == License
148
+
149
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,104 @@
1
+ # CLAUDE.md
2
+
3
+ Dev notes for the `Relaton::Adobe` flavor. Excluded from the packaged gem via
4
+ the gemspec `files` glob (dev docs only). See the root `CLAUDE.md` for the
5
+ cross-flavor conventions.
6
+
7
+ ## What Adobe is
8
+
9
+ `Relaton::Adobe` covers Adobe technical publications: **font tech notes** (the
10
+ `adobe-type-tools/font-tech-notes` repo, numbered `ATN<NNNN>` / "Adobe Technical
11
+ Note #<number>") and **named publications** cited by PDF-Association standards
12
+ (Adobe Glyph List, PostScript Language, the Adobe-Japan1/GB1/CNS1/Korea1/KR-9
13
+ character collections, etc.). Records live in the `relaton-data-adobe` data repo.
14
+
15
+ The flavor mirrors the shape of `Relaton::Iala` — a thin model layer over
16
+ `Relaton::Bib` plus a `Bibliography` retrieval front-end.
17
+
18
+ ## Two doctypes
19
+
20
+ `Doctype::TYPES` (`doctype.rb`) is `%w[tech-note publication]`, carried in the
21
+ inherited `content` attribute:
22
+
23
+ - **`tech-note`** — numbered notes; `tech_note_number` + `source_repo_path` set.
24
+ - **`publication`** — slug-keyed named publications; `publication_slug` set.
25
+
26
+ ## Typed Ext (`ext.rb`)
27
+
28
+ `Relaton::Adobe::Ext < Bib::Ext` adds Adobe-specific structured metadata so it
29
+ round-trips natively through **both** XML and YAML (no per-repo merge hacks):
30
+ `urn`, `webpage`, `tech_note_number`, `source_repo_path`, `publication_slug`.
31
+ Each is declared in both the `xml` and `key_value` mapping blocks. The elements
32
+ land directly under `<ext>` (alongside the inherited `doctype`/`flavor`); the
33
+ RelaxNG grammar (`grammar/relaton-adobe.rng`) extends `BibDataExtensionType`
34
+ (`combine="interleave"`) with the five optional elements and restricts
35
+ `DocumentType` to the two doctypes. `spec/adobe/relaton/adobe/xml_spec.rb`
36
+ Jing-validates the produced XML against it.
37
+
38
+ ## Docidentifier (pubid-backed, mirrors OIML)
39
+
40
+ `Relaton::Adobe::Docidentifier < Bib::Docidentifier` **soft-parses** its `content`
41
+ into a `Pubid::Adobe` identifier held in `@pubid` (a `TechNote` or `Publication`),
42
+ exactly like `Relaton::Oiml::Docidentifier`:
43
+
44
+ - `content=` runs `::Pubid::Adobe.parse` and rescues — a non-canonical string
45
+ (e.g. a title-cased `"Adobe Glyph List"`, vs the canonical
46
+ `"Adobe Publication adobe-glyph-list"`) leaves `@pubid` nil while the plain
47
+ `content` string still round-trips.
48
+ - The base class's abstract `remove_part!` / `remove_date!` / `to_all_parts!`
49
+ (which otherwise raise `NotImplementedError`) are implemented as **safe no-ops**
50
+ over the pubid: Adobe identifiers carry no part/date/all-parts component, so
51
+ these keep `Bib::ItemData#to_most_recent_reference` / `#to_all_parts` from
52
+ raising and never change the rendered `content`.
53
+
54
+ `require "pubid"` (in `adobe.rb`) exposes `Pubid::Adobe`. pubid is already a
55
+ gemspec dependency (shared with iso/oiml/…), so no new dep was added.
56
+
57
+ ## Retrieval flow (pubid-backed, mirrors OIML)
58
+
59
+ `Processor#get` (`processor.rb`) → `Bibliography.get` → `Bibliography.search`
60
+ (`bibliography.rb`), modelled on `lib/relaton/oiml/bibliography.rb`:
61
+
62
+ 1. Parse the reference with `::Pubid::Adobe.parse` (`pubid_for`); an unparseable
63
+ ref is a **graceful miss** (logged, returns nil) rather than a raised Parslet
64
+ error — it only reached here because it matched the flavor prefix regex.
65
+ 2. Look up in the `relaton-data-adobe` `index-v2` via
66
+ `Relaton::Index.find_or_create(:adobe, …, pubid_class: ::Pubid::Adobe::Identifier)`,
67
+ so each row's `:id` is a `Pubid::Adobe` identifier. **Tech notes narrow by
68
+ number** (binary search); **publications have no number**, so `search(nil)`
69
+ scans and the block filters. `pubid_match?` compares `row_id.to_s == query.to_s`
70
+ — the human render is a stable identity key (drops a tech note's filename
71
+ slug so `ATN5014` matches a slug-carrying record; includes a publication's
72
+ slug+version). Unlike OIML there is **no edition-year selection** — each
73
+ reference maps to a single record.
74
+ 3. `Net::HTTP.get_response` for the row's `:file` under `ENDPOINT`
75
+ (`raw.githubusercontent.com/relaton/relaton-data-adobe/main/`).
76
+ 4. `Relaton::Adobe::Item.from_yaml` → an `ItemData`, date-stamped via `fetched=`.
77
+
78
+ Network/HTTP errors become `Relaton::RequestError`. **Do not** delegate `get`
79
+ back into `Relaton::Db#fetch` — that both mis-arities (`Db#fetch` is 1..3 args)
80
+ and re-enters the dispatcher, so it can never resolve.
81
+
82
+ The `Processor` registers `short = :relaton_adobe`, `prefix = "Adobe"`,
83
+ `defaultprefix = /^(?:Adobe|ATN)/` so both `Adobe Technical Note #5014` and
84
+ `ATN5014` route here.
85
+
86
+ ## Status / follow-ups
87
+
88
+ - `Pubid::Adobe` (identifier flavor: `TechNote` + `Publication`, prefixes
89
+ `["Adobe", "ATN"]`) is on pubid `main`, the version this repo pins, and is now
90
+ wired into both `Docidentifier` and `Bibliography`.
91
+ - `relaton-data-adobe` (dataset) is not yet published; retrieval is wired and
92
+ unit-tested offline (webmock + a stubbed pubid-class index), but a live
93
+ end-to-end fetch waits on the data repo. When it lands, confirm rows serialize
94
+ as `_type: pubid:adobe:{tech-note,publication}` (the `ADOBE_TYPE_MAP` pubid
95
+ deserializes via `pubid_class`).
96
+
97
+ ## Testing
98
+
99
+ - `bundle exec rake spec:adobe` — run the Adobe suite.
100
+ - Specs live in `spec/adobe/relaton/adobe/` and run self-contained (see the root
101
+ `CLAUDE.md` Testing section). `xml_spec.rb` covers schema validation;
102
+ `docidentifier_spec.rb` covers pubid parsing + the mutation hooks;
103
+ `bibliography_spec.rb` covers the offline retrieval path (tech-note narrowing,
104
+ publication scan, ATN-alias identity, graceful miss).
@@ -0,0 +1,130 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ RelatonBib is a Ruby gem that implements the [BibliographicItem model](https://github.com/metanorma/relaton-models#bibliography-uml-models) for bibliographic reference management. It's part of the Relaton ecosystem and serves as the base library for other Relaton gems.
8
+
9
+ ## Common Commands
10
+
11
+ ```bash
12
+ # Install dependencies
13
+ bundle install
14
+
15
+ # Run all tests
16
+ bundle exec rake spec
17
+
18
+ # Run a single test file
19
+ bundle exec rspec spec/relaton_bib/bibliographic_item_spec.rb
20
+
21
+ # Run a specific test by line number
22
+ bundle exec rspec spec/relaton_bib/bibliographic_item_spec.rb:42
23
+
24
+ # Run linting
25
+ bundle exec rubocop
26
+
27
+ # Auto-fix linting issues
28
+ bundle exec rubocop -a
29
+
30
+ # Interactive console
31
+ bin/console
32
+ ```
33
+
34
+ ## Architecture
35
+
36
+ ### Serialization Layer (lutaml-model)
37
+
38
+ The codebase uses [lutaml-model](https://github.com/lutaml/lutaml-model) for serialization. Each model class in `lib/relaton/bib/model/` inherits from `Lutaml::Model::Serializable` and declares attributes with XML/YAML/JSON mappings.
39
+
40
+ ### Core Classes
41
+
42
+ **`Relaton::Bib::Item`** ([lib/relaton/bib/model/item.rb](lib/relaton/bib/model/item.rb)) - The main serialization class defining all bibliographic attributes and their XML mappings. Uses `ItemData` as its underlying model.
43
+
44
+ **`Relaton::Bib::ItemData`** ([lib/relaton/bib/item_data.rb](lib/relaton/bib/item_data.rb)) - The data container class that holds bibliographic data and provides conversion methods (`to_xml`, `to_yaml`, `to_bibtex`, `to_rfcxml`). This separation allows the same data to be serialized differently (bibitem vs bibdata).
45
+
46
+ **`Relaton::Bib::Bibitem`** and **`Relaton::Bib::Bibdata`** - Variants of `Item` with different XML root elements and attributes (bibitem excludes `ext`, bibdata excludes `id`).
47
+
48
+ ### Component Models
49
+
50
+ Each bibliographic attribute has its own class in `lib/relaton/bib/model/`:
51
+
52
+ - `Title`, `LocalizedString`, `LocalizedMarkedUpString` - text with language/script
53
+ - `Contributor`, `Person`, `Organization` - contributors and affiliations
54
+ - `Docidentifier` - document IDs (DOI, ISBN, etc.)
55
+ - `Date` - publication dates with custom `StringDate` type
56
+ - `Relation` - related documents (circular reference with Item)
57
+ - `Ext` - extension data (doctype, ICS codes, structured identifiers)
58
+
59
+ ### Sanitizing marked-up content
60
+
61
+ **`Relaton::Bib::Sanitizer`** ([lib/relaton/bib/sanitizer.rb](lib/relaton/bib/sanitizer.rb))
62
+ strips markup outside the basicdoc set. `LocalizedMarkedUpString#content=` calls it on
63
+ assignment, so every `Title`, `Abstract`, `Note`, `Formattedref` and `Docidentifier` in
64
+ every flavor passes through it. Two properties are load-bearing:
65
+
66
+ - **It does not give up on an undeclared namespace prefix.** Third-party markup prefixes
67
+ its elements — Crossref returns JATS as `<jats:p><jats:italic>x</jats:italic></jats:p>`,
68
+ sometimes with an `xlink:` prefix on an attribute — and Relaton never declares the prefix.
69
+ Nokogiri reports an undeclared prefix as a parse error, so the old
70
+ `return content if fragment.errors.any?` returned exactly the markup that most needed
71
+ sanitizing. The undeclared prefix then reached the bibdata XML, relaton-render's
72
+ `sanitise_citations_input_string` returned nil, and isodoc raised
73
+ `NoMethodError: undefined method '[]' for nil` (metanorma-pdfa#99). `parse_with_prefixes`
74
+ now declares each prefix on a wrapper element, parses, and removes **only the wrapper's
75
+ own** declarations, matched by identity rather than by URI — `remove_namespaces!` would
76
+ also strip a namespace the content declares itself, such as the MathML `xmlns` inside an
77
+ opaque `<stem>`. `wrapper_name` renames the wrapper while the content contains it, so the
78
+ content cannot close it early — in **one** scan (`NS_WRAPPER_RX`), because growing the
79
+ name and re-testing with `include?` is quadratic, the same trap the root `CLAUDE.md`
80
+ records for relaton-cli's `strip_tags`. An undeclared prefix inside `<stem>` goes as well
81
+ (`<mml:math>` → `<math>`): an undeclared prefix in the output is the failure being fixed.
82
+ Un-prefixing an attribute renames it, so `drop_attribute_namespaces` **drops** a prefixed
83
+ attribute whose plain name the element already carries — keeping both would emit
84
+ `target="a" target="b"`, which Nokogiri rejects as `Attribute target redefined`, and that
85
+ is the very unparseable output this path exists to prevent. Test the collision with
86
+ `plain_attribute?`, not `Node#attribute`, which matches on the name alone and so finds the
87
+ prefixed attribute itself. The guard for genuinely malformed input stays — unbalanced tags,
88
+ a stray `<`, and text that only looks like a prefixed tag are all returned untouched.
89
+ - **It does not indent.** `SAVE_OPTS` drops Nokogiri's `FORMAT` default, so
90
+ `sanitize("<p><em>x</em></p>")` returns the input rather than `"<p>\n <em>x</em>\n</p>"`.
91
+ Don't reintroduce a bare `to_xml`.
92
+
93
+ ### Parsing
94
+
95
+ - **`HashParserV1`** ([lib/relaton/bib/hash_parser_v1.rb](lib/relaton/bib/hash_parser_v1.rb)) - Converts legacy Hash/YAML format to `ItemData`
96
+ - **`Converter::BibXml`** ([lib/relaton/bib/converter/bibxml.rb](lib/relaton/bib/converter/bibxml.rb)) - Parses RFC BibXML format via `Relaton::Bib::Converter::BibXml.to_item`
97
+ - **lutaml-model native** - `Item.from_xml`, `Item.from_yaml`, `Item.from_json` handle current format
98
+
99
+ ### Rendering
100
+
101
+ - **`Renderer::BibtexBuilder`** - Converts `ItemData` to BibTeX format
102
+ - **`Converter::BibXml`** - Converts `ItemData` to RFC XML format via `Relaton::Bib::Converter::BibXml.to_xml`
103
+ - **lutaml-model native** - `to_xml`, `to_yaml`, `to_json` via the serialization classes
104
+
105
+ ### Usage Pattern
106
+
107
+ ```ruby
108
+ # Parse from YAML
109
+ item = Relaton::Bib::Item.from_yaml(yaml_string)
110
+
111
+ # Parse from XML
112
+ item = Relaton::Bib::Bibitem.from_xml(xml_string)
113
+ item = Relaton::Bib::Bibdata.from_xml(xml_string)
114
+
115
+ # Parse from RFC XML
116
+ item = Relaton::Bib::Converter::BibXml.to_item(xml_string)
117
+
118
+ # Convert to different formats (returns string)
119
+ item.to_xml # as <bibitem>
120
+ item.to_xml(bibdata: true) # as <bibdata>
121
+ item.to_yaml
122
+ item.to_bibtex
123
+ item.to_rfcxml
124
+ ```
125
+
126
+ ## Code Style
127
+
128
+ - Follows Ribose OSS Ruby style guide (inherited via `.rubocop.yml`)
129
+ - Target Ruby version: 3.1+
130
+ - Uses YARD documentation comments