relaton 3.0.0.pre.alpha.17 → 3.0.0.pre.alpha.18

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/data/equivalence/adoption-chains.yaml +38 -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/series.rb +1 -1
  9. data/lib/relaton/bipm/CLAUDE.md +173 -0
  10. data/lib/relaton/bipm/README.adoc +352 -0
  11. data/lib/relaton/bipm/data_fetcher.rb +3 -2
  12. data/lib/relaton/bsi/CLAUDE.md +35 -0
  13. data/lib/relaton/bsi/README.adoc +112 -0
  14. data/lib/relaton/calconnect/CLAUDE.md +177 -0
  15. data/lib/relaton/calconnect/README.adoc +140 -0
  16. data/lib/relaton/ccsds/CLAUDE.md +74 -0
  17. data/lib/relaton/ccsds/README.adoc +154 -0
  18. data/lib/relaton/cen/CLAUDE.md +219 -0
  19. data/lib/relaton/cen/README.adoc +155 -0
  20. data/lib/relaton/cie/CLAUDE.md +90 -0
  21. data/lib/relaton/cie/README.adoc +121 -0
  22. data/lib/relaton/core/CLAUDE.md +140 -0
  23. data/lib/relaton/core/README.adoc +64 -0
  24. data/lib/relaton/core/data_fetcher.rb +50 -3
  25. data/lib/relaton/db/CLAUDE.md +373 -0
  26. data/lib/relaton/db.rb +19 -1
  27. data/lib/relaton/doi/CLAUDE.md +105 -0
  28. data/lib/relaton/doi/README.adoc +89 -0
  29. data/lib/relaton/easc/CLAUDE.md +85 -0
  30. data/lib/relaton/ecma/CLAUDE.md +217 -0
  31. data/lib/relaton/ecma/README.adoc +178 -0
  32. data/lib/relaton/equivalence.rb +89 -0
  33. data/lib/relaton/etsi/CLAUDE.md +219 -0
  34. data/lib/relaton/etsi/README.adoc +137 -0
  35. data/lib/relaton/evs/CLAUDE.md +120 -0
  36. data/lib/relaton/evs/README.adoc +27 -0
  37. data/lib/relaton/gb/CLAUDE.md +47 -0
  38. data/lib/relaton/gb/README.adoc +169 -0
  39. data/lib/relaton/gost/CLAUDE.md +100 -0
  40. data/lib/relaton/iala/CLAUDE.md +97 -0
  41. data/lib/relaton/iana/CLAUDE.md +105 -0
  42. data/lib/relaton/iana/README.adoc +107 -0
  43. data/lib/relaton/iec/CLAUDE.md +13 -0
  44. data/lib/relaton/iec/README.adoc +280 -0
  45. data/lib/relaton/iec/data_fetcher.rb +24 -8
  46. data/lib/relaton/iec/project_fetcher.rb +48 -0
  47. data/lib/relaton/iec/project_parser.rb +111 -0
  48. data/lib/relaton/iec.rb +5 -0
  49. data/lib/relaton/ieee/CLAUDE.md +176 -0
  50. data/lib/relaton/ieee/README.adoc +131 -0
  51. data/lib/relaton/ietf/CLAUDE.md +357 -0
  52. data/lib/relaton/ietf/README.adoc +167 -0
  53. data/lib/relaton/iho/CLAUDE.md +70 -0
  54. data/lib/relaton/iho/README.adoc +119 -0
  55. data/lib/relaton/index/CLAUDE.md +207 -0
  56. data/lib/relaton/index/README.adoc +221 -0
  57. data/lib/relaton/index/dataset_lock.rb +122 -0
  58. data/lib/relaton/index/family.rb +63 -0
  59. data/lib/relaton/index/type.rb +32 -0
  60. data/lib/relaton/index.rb +3 -0
  61. data/lib/relaton/isbn/CLAUDE.md +47 -0
  62. data/lib/relaton/isbn/README.adoc +91 -0
  63. data/lib/relaton/iso/CLAUDE.md +38 -0
  64. data/lib/relaton/iso/README.adoc +370 -0
  65. data/lib/relaton/iso/data_fetcher.rb +16 -1
  66. data/lib/relaton/itu/CLAUDE.md +685 -0
  67. data/lib/relaton/itu/README.adoc +188 -0
  68. data/lib/relaton/jcgm/CLAUDE.md +107 -0
  69. data/lib/relaton/jis/CLAUDE.md +64 -0
  70. data/lib/relaton/jis/README.adoc +167 -0
  71. data/lib/relaton/logger/CLAUDE.md +37 -0
  72. data/lib/relaton/logger/README.adoc +120 -0
  73. data/lib/relaton/nist/CLAUDE.md +180 -0
  74. data/lib/relaton/nist/README.adoc +389 -0
  75. data/lib/relaton/nist/bibliography.rb +17 -1
  76. data/lib/relaton/nist/data_fetcher.rb +60 -4
  77. data/lib/relaton/nist/hit_collection.rb +121 -59
  78. data/lib/relaton/nist/scraper.rb +21 -7
  79. data/lib/relaton/oasis/CLAUDE.md +251 -0
  80. data/lib/relaton/oasis/README.adoc +132 -0
  81. data/lib/relaton/ogc/CLAUDE.md +242 -0
  82. data/lib/relaton/ogc/README.adoc +134 -0
  83. data/lib/relaton/oiml/CLAUDE.md +47 -0
  84. data/lib/relaton/oiml/README.adoc +70 -0
  85. data/lib/relaton/omg/CLAUDE.md +137 -0
  86. data/lib/relaton/omg/README.adoc +101 -0
  87. data/lib/relaton/plateau/CLAUDE.md +126 -0
  88. data/lib/relaton/plateau/README.adoc +139 -0
  89. data/lib/relaton/sdo/CLAUDE.md +81 -0
  90. data/lib/relaton/un/CLAUDE.md +91 -0
  91. data/lib/relaton/un/README.adoc +125 -0
  92. data/lib/relaton/version.rb +1 -1
  93. data/lib/relaton/w3c/CLAUDE.md +369 -0
  94. data/lib/relaton/w3c/README.adoc +140 -0
  95. data/lib/relaton/w3c/data_fetcher.rb +1 -1
  96. data/lib/relaton/xsf/CLAUDE.md +189 -0
  97. data/lib/relaton/xsf/README.adoc +127 -0
  98. data/lib/relaton.rb +1 -0
  99. metadata +83 -2
@@ -0,0 +1,169 @@
1
+ = Relaton::Gb
2
+
3
+ Relaton::Gb is a Ruby gem that searches and fetches Chinese GB standards.
4
+ The standards scraped form:
5
+
6
+ * GB standards - http://www.std.gov.cn/gb/search
7
+ * Social organization standards - http://www.ttbz.org.cn
8
+ * Sector standards - http://www.std.gov.cn/hb
9
+
10
+ NOTE: Unlike the ISO website, the GB websites require the year of publication to be included in the search.
11
+ So a standard will not be found successfully through this gem unless the year is included in its document
12
+ identifier: for example, not `CN(GB/T 1.1)`, but `CN(GB/T 1.1-2009)`.
13
+
14
+ == Usage
15
+
16
+ === Search document
17
+
18
+ [source,ruby]
19
+ ----
20
+ require 'relaton/gb'
21
+ => true
22
+
23
+ hit_collection = Relaton::Gb::Bibliography.search "GB/T 20223-2006"
24
+ [relaton-gb] INFO: (GB/T 20223-2006) Fetching from openstd.samr.gov.cn ...
25
+ => <Relaton::Gb::HitCollection:0x00000000001350 @ref= @fetched=false>
26
+
27
+ hit_collection.first
28
+ => <Relaton::Gb::Hit:0x00000000001850 @fullIdentifier="" @docref="GB/T 20223-2006">
29
+ ----
30
+
31
+ === Scrape document form collection
32
+
33
+ [source,ruby]
34
+ ----
35
+ hit_collection.first.item
36
+ => #<Relaton::Gb::ItemData:0x000000012913b968
37
+
38
+ hit_collection.first.item.title.first
39
+ => #<Relaton::Bib::Title:0x00000001290f2bc8
40
+ @content="棉短绒",
41
+ @format=nil,
42
+ @language="zh",
43
+ @locale=nil,
44
+ @script="Hans",
45
+ @type="title-main">
46
+
47
+ hit_collection.first.item.ext.gbtype
48
+ => #<Relaton::Gb::GbType:0x00000001259687c0
49
+ @mandate=["recommended"],
50
+ @prefix="GB_national",
51
+ @scope=["national"],
52
+ @topic=["other"]>
53
+
54
+ hit_collection.first.item.ext.ccs
55
+ => [#<Relaton::Gb::CCS:0x000000012913f608 @code="B32">]
56
+
57
+ hit_collection.first.item.date
58
+ => [#<Relaton::Bib::Date:0x0000000129111dc0
59
+ @at=#<Relaton::Bib::StringDate::Value:0x00000001291111e0 @value="2006-03-10">,
60
+ @from=nil,
61
+ @text=nil,
62
+ @to=nil,
63
+ @type=["published"]>]
64
+
65
+ hit_collection.first.item.date.first.at.to_s
66
+ => "2006-03-10"
67
+
68
+ hit_collection.first.item.date.first.at.to_date
69
+ => #<Date: 2006-03-10 ((2453805j,0s,0n),+0s,2299161j)>
70
+ ----
71
+
72
+ === Get document by identifier
73
+
74
+ [source,ruby]
75
+ ----
76
+ Relaton::Gb::Bibliography.get "GB/T 20223-2006"
77
+ [relaton-gb] INFO: (GB/T 20223-2006) Fetching from openstd.samr.gov.cn ...
78
+ [relaton-gb] INFO: (GB/T 20223-2006) Found: `GB/T 20223-2006`
79
+ => #<Relaton::Gb::ItemData:0x0000000129dd16a0
80
+ ----
81
+
82
+ === Get document by identifier and year
83
+
84
+ [source,ruby]
85
+ ----
86
+ Relaton::Gb::Bibliography.get "GB/T 20223", "2006"
87
+ [relaton-gb] INFO: (GB/T 20223-2006) Fetching from openstd.samr.gov.cn ...
88
+ [relaton-gb] INFO: (GB/T 20223-2006) Found: `GB/T 20223-2006`
89
+ => #<Relaton::Gb::ItemData:0x00000001299ffdd8
90
+ ----
91
+
92
+ === Create bibliographic item from YAML
93
+ [source,ruby]
94
+ ----
95
+
96
+ Relaton::Gb::Item.from_yaml File.read('spec/fixtures/item.yaml')
97
+ => #<Relaton::Gb::ItemData:0x00000001292d85c8
98
+ ----
99
+
100
+ === Serialization
101
+
102
+ [source,ruby]
103
+ ----
104
+ hit_collection.first.item.to_xml
105
+ => "<bibitem id="GBT202232006" type="standard" schema-version="v1.5.6">
106
+ <fetched>2026-01-26</fetched>
107
+ <title language="zh" script="Hans" type="title-main">棉短绒</title>
108
+ <title language="zh" script="Hans" type="main">棉短绒</title>
109
+ <title language="en" script="Latn" type="title-main">Cotton linter</title>
110
+ <title language="en" script="Latn" type="main">Cotton linter</title>
111
+ <uri type="src">http://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=083B48FA72DBD3B9BDE74507BC31736A</uri>
112
+ <docidentifier type="Chinese Standard" primary="true">GB/T 20223-2006</docidentifier>
113
+ ...
114
+ </bibitem>"
115
+ ----
116
+
117
+ With `bibdata: true` option XML output is wrapped with `bibdata` element and `ext` element added.
118
+
119
+ [source,ruby]
120
+ ----
121
+ hit_collection.first.item.to_xml bibdata: true
122
+ => "<bibdata type="standard" schema-version="v1.5.6">
123
+ <fetched>2026-01-26</fetched>
124
+ <title language="zh" script="Hans" type="title-main">棉短绒</title>
125
+ <title language="zh" script="Hans" type="main">棉短绒</title>
126
+ <title language="en" script="Latn" type="title-main">Cotton linter</title>
127
+ <title language="en" script="Latn" type="main">Cotton linter</title>
128
+ <uri type="src">http://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=083B48FA72DBD3B9BDE74507BC31736A</uri>
129
+ <docidentifier type="Chinese Standard" primary="true">GB/T 20223-2006</docidentifier>
130
+ ...
131
+ <ext schema-version="v1.1.2">
132
+ <doctype>standard</doctype>
133
+ ...
134
+ </ext>
135
+ </bibdata>"
136
+ ----
137
+
138
+ === Typed source links
139
+
140
+ Each GB document has `src` type source link.
141
+
142
+ [source,ruby]
143
+ ----
144
+ hit_collection.first.item.source
145
+ => #<Relaton::Bib::Uri:0x0000000129112bf8
146
+ @content="http://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=083B48FA72DBD3B9BDE74507BC31736A",
147
+ @language=nil,
148
+ @locale=nil,
149
+ @script=nil,
150
+ @type="src">
151
+ ----
152
+
153
+ === Logging
154
+
155
+ Relaton::Gb 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.
156
+
157
+ == Development
158
+
159
+ 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.
160
+
161
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
162
+
163
+ == Contributing
164
+
165
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/gdbib.
166
+
167
+ == License
168
+
169
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,100 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with the
4
+ `Relaton::Gost` flavor.
5
+
6
+ ## Project Overview
7
+
8
+ `Relaton::Gost` retrieves **GOST** standards — the interstate/national standards of the
9
+ former-Soviet space (Государственный стандарт), published by Rosstandart and the
10
+ Euro-Asian Council for Standardization (EASC/МГС). It is **index-backed** (no scraping):
11
+ it searches a pre-built **pubid-structured** index via `Relaton::Index` and fetches
12
+ per-document YAML from the `relaton/relaton-data-gost` GitHub repo. The model layer mirrors
13
+ the `Relaton::Easc` sibling; the pubid-backed retrieval mirrors `Relaton::Oiml`.
14
+
15
+ ## Architecture
16
+
17
+ Namespace: `Relaton::Gost`. Retrieval flow:
18
+
19
+ 1. **Bibliography** (`bibliography.rb`) — `get(code, year, opts)` parses the citation with
20
+ `Pubid::Gost.parse`, looks it up in the **pubid-structured** index, fetches the matching
21
+ `row[:file]` YAML over `Net::HTTP` (`Item.from_yaml`, raising `Relaton::RequestError` on
22
+ non-200), and stamps `fetched`. `search` picks the **latest edition** (`max_by { year }`)
23
+ among number-matched candidates; `get` then strips the year for an **undated** citation via
24
+ `to_most_recent_reference` (so `GOST 1.0` renders undated but resolves the newest edition),
25
+ while a dated citation (`GOST 1.0-92`) or explicit `year` pins that edition. Mirrors
26
+ `lib/relaton/oiml/bibliography.rb`.
27
+ 2. **Index** — `Relaton::Index.find_or_create(:gost, url: "#{ENDPOINT}index-v2.zip",
28
+ file: "index-v2.yaml", pubid_class: ::Pubid::Gost::Identifier)`. Each row's `:id` is a
29
+ `_type: pubid:gost:{interstate,national}-standard` hash that `Relaton::Index` rebuilds into
30
+ a `Pubid::Gost` identifier via `from_hash`; passing the parsed pubid to `#search` lets it
31
+ binary-search candidates by number (see `Bibliography#rows`). `INDEXFILE =
32
+ "index-v2"` is in `lib/relaton/gost.rb`.
33
+ 3. **Item / ItemData / Ext** (`item.rb`, `item_data.rb`, `ext.rb`) — `Item` extends
34
+ `Bib::Item`, re-declares `ext` to the typed `Gost::Ext` and `docidentifier` to the pubid
35
+ `Gost::Docidentifier`. `Ext`'s GOST fields (`urn`, `webpage`, `ics_code`, `developer`,
36
+ `keywords` [collection], `pages`, `designation_original`) round-trip natively through YAML
37
+ **and** XML (mapped in both `xml` and `key_value`). (The live dataset uses `urn`/`webpage`/
38
+ `ics_code`/`pages`/`designation_original`; `developer`/`keywords` are declared but unused.)
39
+ 4. **Doctype** (`doctype.rb`) — `TYPES = %w[interstate national preliminary methodological]`
40
+ (GOST, GOST R, PST, OD categories). Carried in the inherited `content` attribute.
41
+ 5. **Docidentifier** (`docidentifier.rb`) — wraps a `Pubid::Gost` identifier (`attr_reader
42
+ :pubid`); `content=` re-parses to keep `@pubid` in sync, and `remove_date!` nil-s the pubid
43
+ `year` and re-renders `content` (GOST carries the edition as `year`, not `date`) so
44
+ `to_most_recent_reference` yields `GOST R 34.12`. Mirrors `Oiml::Docidentifier`.
45
+ 6. **Processor** (`processor.rb`) — registry integration; `@short = :relaton_gost`,
46
+ `@prefix = "GOST"`, `@idtype = "GOST"`, and `@defaultprefix = %r{^(?:GOST|ГОСТ)\b}` so **both
47
+ the Latin `GOST` and Cyrillic `ГОСТ` surface forms route here** (the `\b` stops it swallowing
48
+ longer tokens). `remove_index_file` passes `url: true` and `file:` only — the delete never
49
+ reads the index, so it needs no `pubid_class` (see `lib/relaton/index/CLAUDE.md`). Every method touching a
50
+ flavor constant lazy-`require_relative`s `../gost` first (the lazy-registry invariant;
51
+ `spec/relaton/lazy_loading_spec.rb` guards it).
52
+
53
+ There are no scrapers — everything comes from the curated index + GitHub YAML.
54
+
55
+ ## Pubid
56
+
57
+ The flavor is **pubid-backed** via `Pubid::Gost` (metanorma/pubid#108), on the `pubid` `main`
58
+ branch the root `Gemfile` already pins (for JCGM). `Bibliography#rows` selects a dated
59
+ citation with `index.search(query, exact: true)` (pubid `==`) and an undated one with the
60
+ default subset match `query === row`: the omitted year matches any value, so it matches every
61
+ edition sharing subtype + number. `===` requires the same class, so interstate and national
62
+ stay distinct, and `1.1` does not match `1.10`. pubid declares `copublisher` strict for GOST
63
+ (pubid#408), so an omitted copublisher means "none". Measured over the full
64
+ `relaton-data-gost` index (37,508 rows): `===` and the old `matches?(…, ignore: [:year])` agree
65
+ on all 94,279 pairs. The
66
+ `relaton-data-gost` `index-v2.yaml` was built with this **same** pubid, so the rows deserialize
67
+ (a mismatched pubid would make `Relaton::Index` reject the whole index — cf. the root
68
+ `CLAUDE.md` JCGM note). The pinned release `2.0.0.pre.alpha.13` carries `Pubid::Gost` and
69
+ `subset_strict`; any lower pin must too.
70
+
71
+ ## Dataset
72
+
73
+ `relaton/relaton-data-gost` is the live data repo behind `ENDPOINT` (a real pubid-structured
74
+ `index-v2.yaml` + `data/*.yaml`). The specs run fully offline against a small committed subset
75
+ (see Testing).
76
+
77
+ ## Testing
78
+
79
+ `spec/gost/` — self-contained, run via `bundle exec rake spec:gost` (or `cd spec/gost &&
80
+ bundle exec rspec -I . .`). Coverage:
81
+
82
+ - `ext_spec.rb`, `item_spec.rb` — YAML round-trip of the typed ext fields.
83
+ - `bibliography_spec.rb` — retrieval against real records copied from `relaton-data-gost`:
84
+ the pubid-structured index fixture `fixtures/index-v2.zip` is pre-loaded into the
85
+ `Relaton::Index` pool in `before(:suite)` (with `pubid_class`), and `fixtures/data/*.yaml`
86
+ are served by WebMock (`support/webmock.rb`) — **no live network**. Covers dated/undated/
87
+ Cyrillic lookups, undated→latest-edition, year-pinning, not-found (`nil`), and non-200
88
+ (`Relaton::RequestError`). Fixture docs: interstate `GOST 1.0` (editions 2015 + 92) and
89
+ national `GOST R 34.12-2015`.
90
+ - `processor_spec.rb` — processor shape + Latin/Cyrillic prefix routing.
91
+
92
+ To refresh the fixture, copy the wanted rows from `relaton-data-gost/index-v2.yaml` into
93
+ `fixtures/index-v2.yaml`, re-zip it to `fixtures/index-v2.zip`, and copy the matching
94
+ `data/*.yaml` files.
95
+
96
+ There is **no RelaxNG grammar** for GOST and no XML-schema validation spec: the metanorma
97
+ document model (`metanorma-model-iso/grammars/`) defines no GOST grammar, so there is nothing
98
+ to copy into `grammar/` and nothing to validate `to_xml` against. This matches the sibling
99
+ `easc` flavor (also grammar-less). If an official GOST grammar is ever added upstream, copy the
100
+ `<flavor>{,-compile}.rng` pair into `grammar/` and add a Jing-validation spec then.
@@ -0,0 +1,97 @@
1
+ # CLAUDE.md
2
+
3
+ ## Architecture
4
+
5
+ **Pubid-backed docidentifier.** `Relaton::Iala::Docidentifier`
6
+ (`docidentifier.rb`, `< Bib::Docidentifier`) parses its `content` into a
7
+ `Pubid::Iala::Identifier` kept in `@pubid`, while the lutaml `content` attribute
8
+ stays a plain string for serialization. Parsing is **soft**: `content=` lazily
9
+ `require "pubid"` + `require "pubid/iala"` (pubid core must load first for
10
+ `Pubid::PrefixesSupport`) and rescues `LoadError`/`StandardError`, so a missing
11
+ pubid gem or non-IALA/unparseable content leaves `@pubid` nil rather than raising.
12
+
13
+ It implements the base class's abstract `remove_part!` / `remove_date!` /
14
+ `to_all_parts!` by mutating the pubid and re-rendering via `refresh_content!`
15
+ (which writes through the aliased `store_content` — the inherited string setter —
16
+ so in-place mutations aren't clobbered by a re-parse). IALA's identifier models
17
+ only `publisher`/`number`/`edition`/`language`, so the mapping is IALA-specific:
18
+
19
+ - **`remove_date!` → clears `edition`.** IALA carries no date component; `edition`
20
+ is its version discriminator, so this yields the version-agnostic ("most
21
+ recent") reference, e.g. `IALA S1070 Ed 2.0` → `IALA S1070`.
22
+ - **`remove_part!` → clears the (unused) `part`/`subpart` attributes.** A no-op
23
+ for the rendered string today: IALA folds any subpart inline into `number`
24
+ (e.g. `0103-1`), so the part is not a separable component. Implemented anyway
25
+ so it never raises, and it starts working automatically if pubid-iala ever
26
+ models `part` separately.
27
+ - **`to_all_parts!` → `remove_part!` + `remove_date!`, then wraps `@pubid` in
28
+ pubid's `AllParts`.** `content` stays the edition-stripped string — IALA's
29
+ own renderer has no "(all parts)" marker, and `content` is cached from the
30
+ pre-wrap pubid on purpose. `#pubid` itself, read directly, now answers
31
+ `all_parts? == true` and renders WITH pubid's generic marker (`#to_s`),
32
+ since it's the wrapper. Mirrors `lib/relaton/iec/model/docidentifier.rb`.
33
+
34
+ All three no-op safely when `@pubid` is nil, so `Bib::ItemData#to_all_parts` /
35
+ `#to_most_recent_reference` never raise on IALA items.
36
+
37
+ **Index (`index-v2`, pubid-keyed).** `INDEXFILE` is the pubid-backed
38
+ `index-v2` published by `relaton-data-iala`: 701 rows of
39
+ `_type: pubid:iala:*` with `number`/`edition`/`language`. The consumer
40
+ (`Bibliography#index`) passes
41
+ `pubid_class: ::Pubid::Iala::Identifier` — that is what makes `Relaton::Index`
42
+ deserialize the rows into identifiers, sort them by `id.root.number`, and let
43
+ `Type#search` bsearch. Omitting it leaves the rows as raw hashes and
44
+ `FileIO#sorted` false, so every lookup scans all 701 rows, silently. That was
45
+ the state before this flavor adopted the W3C pattern; the data repo already
46
+ published a pubid-shaped `index-v2`, only the consumer was unwired.
47
+ `Processor#remove_index_file` passes `url: true` and `file:` only: the delete
48
+ never reads the index (see `lib/relaton/index/CLAUDE.md`).
49
+
50
+ `Bibliography#best_match` follows the ETSI/W3C idiom:
51
+
52
+ - **Pass the pubid, not the string.** `Type#search_candidates` narrows only when
53
+ the argument is not a `String`, so passing the reference text — which this
54
+ flavor used to do — disables the binary search however the index was built.
55
+ - **Selection is pubid's asymmetric subset match** (`Pubid::SubsetMatch`):
56
+ `pubid === r[:id]`, the reference on the left. A component the reference omits
57
+ matches any value, so a bare `IALA M0001` finds the `Ed 9.0 (E)` row, while
58
+ `IALA R0106 Ed 2.1 (F)` reaches only its own. `edition` and `language` are
59
+ IALA's only optional components.
60
+ - **The document type is never a wildcard.** It is the identifier's class, and
61
+ `===` requires the same class. 17 of the 309 index numbers are shared by two
62
+ types (`R1001` and `C1001` are different documents), so they stay apart. An
63
+ `Annex` row carries its base identifier, which `===` compares with its own
64
+ subset match.
65
+ - **Newest edition wins, deterministically.** Editions are dotted versions and
66
+ must be compared segment-by-segment as integers: `"10.0"` is newer than
67
+ `"9.0"` but loses as a string. No published edition has a segment above 9
68
+ today, so this is latent — the same bug already fixed for ETSI. Ties break on
69
+ the edition text and then the file path, because the index sort is not stable.
70
+ Among rows of one edition the language-neutral record wins: it is the base
71
+ document, the others are translations.
72
+ - **An unparseable reference raises.** `parse_ref` lets
73
+ `Pubid::Errors::ParseError` propagate, and the full-scan substring fallback is
74
+ gone with it: a substring match answered an ambiguous reference with whichever
75
+ row sorted first, instead of telling the caller the reference is not an
76
+ identifier.
77
+
78
+ `require "pubid"` sits in `lib/relaton/iala.rb` (the IANA/IHO form), so pubid
79
+ loads with the flavor. `Processor#remove_index_file` names no pubid class, so the
80
+ cold `Db#clear` path that `spec/relaton/lazy_loading_spec.rb` guards does not
81
+ depend on it. `Docidentifier` keeps its own
82
+ lazy, `LoadError`-rescuing require: it is reached by deserialization, which must
83
+ not depend on the flavor entry file having been loaded.
84
+
85
+ ## Testing
86
+
87
+ - `bundle exec rake spec:iala` — run the IALA suite.
88
+ - Specs live in `spec/iala/relaton/iala/` and run self-contained (see the root
89
+ `CLAUDE.md` Testing section).
90
+ - `spec/iala/fixtures/index-v2.zip` is a 19-row curated subset of the published
91
+ index, copied row-for-row so the stored shapes stay verbatim. It covers the
92
+ matching rules above: a multi-edition document (`R0101`, editions `2` and
93
+ `3.0`), a four-language document (`R0106`), a number shared by two types
94
+ (`R1001` / `C1001`), a sub-part folded into the number (`C0103-1`), and the
95
+ language-neutral/translation split. `spec/iala/support/webmock.rb` seeds it
96
+ into the `Relaton::Index` pool **with `pubid_class:`** — without that the
97
+ suite would pass while exercising something the runtime never does.
@@ -0,0 +1,105 @@
1
+ # CLAUDE.md
2
+
3
+ ## Development
4
+
5
+ - `bundle install` — install dependencies
6
+ - `bundle exec rake spec:iana` — run this flavor's tests
7
+ (rubocop is configured via the root `.rubocop.yml` but is not in the bundle)
8
+
9
+ ## What this flavor is
10
+
11
+ IANA entries are protocol **registries**, not numbered standards. An identifier
12
+ is a hierarchical registry slug capped at one "/":
13
+
14
+ ```
15
+ IANA _6lowpan-parameters
16
+ IANA _6lowpan-parameters/lowpan_nhc
17
+ ```
18
+
19
+ `Parser#docnumber` builds the bare slug (`registry[/sub-registry]`) by joining a
20
+ nested registry's XML `id` to its parent's; `Core::DataFetcher#output_file`
21
+ turns that into the document filename (`data/_6lowpan-parameters-lowpan_nhc.yaml`).
22
+
23
+ `output_file` collapses `/` and `-` to the same character, so two distinct
24
+ registries can want one file — `rpki/signed-objects` and `rpki-signed-objects`
25
+ do, the only such pair in the 3405 published rows. `save_doc` therefore writes
26
+ through `Core::DataFetcher#unique_output_file`, which hands the second one a
27
+ digest-suffixed path instead of overwriting the first; the warning stays so the
28
+ clash is visible in the crawl log. Before that fix the corpus had 3405 index
29
+ rows over 3404 files, and one id resolved to the wrong document.
30
+
31
+ ## Index (`index-v2`, pubid-keyed)
32
+
33
+ `INDEXFILE = "index-v2"` (`lib/relaton/iana.rb`). Rows carry
34
+ `Pubid::Iana::Identifiers::Registry` identifiers, serialized as:
35
+
36
+ ```yaml
37
+ - :id:
38
+ _type: pubid:iana:registry
39
+ sub_registry: lowpan_nhc # absent for a top-level registry
40
+ number: _6lowpan-parameters
41
+ :file: data/_6lowpan-parameters-lowpan_nhc.yaml
42
+ ```
43
+
44
+ `number` holds the **top-level** slug — not a document number, because an IANA
45
+ identifier has none (digits only ever appear glued inside a slug token:
46
+ `sip-parameters/sip-parameters-13`, `idna-tables-11.0.0`). It is the index key:
47
+ `Relaton::Index::Type#candidates_by_number` bsearches on `id.root.number.to_s`,
48
+ so a registry and all of its sub-registries share one bucket (655 buckets over
49
+ the 3405 published rows; median 3, max 73 — `pcep`) and `Bibliography#pubid_match?`
50
+ separates them. `registry` is a **derived reader** over `number`, so it is not a
51
+ serialized key.
52
+
53
+ The pair that must stay in step, both passing
54
+ `pubid_class: ::Pubid::Iana::Identifier`:
55
+
56
+ | role | file |
57
+ |---|---|
58
+ | producer | `data_fetcher.rb` — `#index` + the guarded `#add_to_index` |
59
+ | consumer | `bibliography.rb` — `#index`, `#parse_ref`, `#pubid_match?` |
60
+
61
+ `processor.rb#remove_index_file` passes `url: true` and `file:` only: the delete
62
+ never reads the index (see `lib/relaton/index/CLAUDE.md`).
63
+
64
+ `#add_to_index` rescues and warns rather than raising: `Relaton::Index` rejects
65
+ the **whole** index if one row fails to deserialize, so an unparseable slug has
66
+ to be skipped at write time instead of aborting the crawl.
67
+
68
+ ### Why a stale index is loud, not silent
69
+
70
+ A row serialized before `number` existed (`{_type, registry, sub_registry}`)
71
+ deserializes with `number` **nil and no error at all**: lutaml ignores unknown
72
+ keys, and `Index::FileIO#id_supported?` skips its `to_hash`/`from_hash`
73
+ validation for a concrete subclass — which every IANA id is. pubid therefore
74
+ fails at *render* time instead: `Identifier#require_number!` raises
75
+ `ArgumentError` ("… regenerate the index") from `to_s`, `to_urn` and
76
+ `to_mr_string`. **If you see that error the index is stale — rebuild it, don't
77
+ work around it.** `spec/iana/relaton/iana_spec.rb`'s "the pubid index-v2
78
+ fixture" block is the tripwire for the fixture.
79
+
80
+ ### Migration note (v1 → v2)
81
+
82
+ `index-v1` was keyed on the bare slug string, so `Index::Type#search` matched by
83
+ **substring** and `min_by { |i| i[:id] }` picked the shortest hit — `IANA rpki`
84
+ also matched `rpki/signed-objects`. v2 matches the identifier exactly, so a
85
+ partial reference no longer resolves.
86
+
87
+ This flavor no longer produces `index-v1`. `relaton-data-iana`'s own
88
+ `crawler.rb` derives it from `index-v2` (a plain YAML transform:
89
+ `sub_registry ? "#{number}/#{sub_registry}" : number`) so released gem lines
90
+ keep resolving.
91
+
92
+ ## Testing
93
+
94
+ - **Framework:** RSpec with VCR cassettes and WebMock.
95
+ - **Index fixture:** `spec/iana/fixtures/index-v2.zip` (all 3405 published rows)
96
+ is deserialized through `pubid_class` once per process and **re-seeded into
97
+ the `Relaton::Index` pool before every example** — see
98
+ `spec/iana/support/webmock.rb` for why (the producer-side `find_or_create`
99
+ evicts it otherwise). Its `actual?` override claims only the consumer
100
+ (`url:`) lookup.
101
+ - **VCR cassettes:** `spec/iana/vcr_cassettes/` — index downloads are ignored by
102
+ VCR (`support/vcr.rb`), since the fixture answers them.
103
+ - The XML round-trip specs validate against `grammar/relaton-iana-compile.rng`
104
+ with Jing, which needs a **Java runtime**; without one they fail with
105
+ `Jing::ExecutionError`, unrelated to any code change.
@@ -0,0 +1,107 @@
1
+ = Relaton::Iana
2
+
3
+ Relaton::Iana 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 IANA Standards and access such metadata through the `Realaton::Iana::ItemData` object.
6
+
7
+ == Usage
8
+
9
+ === Search for a standard using keywords
10
+
11
+ [source,ruby]
12
+ ----
13
+ require 'relaton/iana'
14
+ => true
15
+
16
+ item = Relaton::Iana::Bibliography.get "IANA service-names-port-numbers"
17
+ [relaton-iana] INFO: (IANA service-names-port-numbers) Fetching from Relaton repository ...
18
+ [relaton-iana] INFO: (IANA service-names-port-numbers) Found: `IANA service-names-port-numbers`
19
+ => #<Relaton::Iana::ItemData:0x0000000110a8ed80
20
+ ...
21
+ ----
22
+
23
+ === XML serialization
24
+
25
+ [source,ruby]
26
+ ----
27
+ item.to_xml
28
+ => "<bibitem id="IANAservicenamesportnumbers" type="standard" schema-version="v1.5.6">
29
+ <fetched>2026-01-27</fetched>
30
+ <title language="en" script="Latn">Service Name and Transport Protocol Port Number Registry</title>
31
+ <uri type="self">https://www.iana.org/assignments/service-names-port-numbers</uri>
32
+ <docidentifier type="IANA" primary="true">IANA service-names-port-numbers</docidentifier>
33
+ ...
34
+ </bibitem>"
35
+ ----
36
+
37
+ === Typed links
38
+
39
+ Each IANA document has `src` type source link.
40
+
41
+ [source,ruby]
42
+ ----
43
+ item.source.first.type
44
+ => "src"
45
+
46
+ item.source.first.content
47
+ => "https://www.iana.org/assignments/service-names-port-numbers"
48
+ ----
49
+
50
+ === Create bibliographic item from XML
51
+ [source,ruby]
52
+ ----
53
+ Relaton::Iana::Item.from_xml File.read('spec/fixtures/auto-response-parameters.xml')
54
+ => #<Relaton::Iana::ItemData:0x0000000124776198
55
+ ...
56
+ ----
57
+
58
+ === Create bibliographic item from YAML
59
+ [source,ruby]
60
+ ----
61
+ Relaton::Iana::Item.from_yaml File.read('spec/fixtures/item.yaml')
62
+ => #<Relaton::Iana::ItemData:0x0000000124e1c588
63
+ ...
64
+ ----
65
+
66
+ === Fetch data
67
+
68
+ There is an IANA dataset https://github.com/ietf-ribose/iana-registries which can be converted into RelatonXML/BibXML/BibYAML formats.
69
+
70
+ The method `Relaton::Iana::DataFetcher.fetch(output: "data", format: "yaml")` converts all the documents from the dataset and saves them to the `./data` folder in YAML format.
71
+ Arguments:
72
+
73
+ - `output` - folder to save documents (default './data').
74
+ - `format` - the format in which the documents are saved. Possible formats are: `yaml`, `xml`, `bibxml` (default `yaml`).
75
+
76
+ [source,ruby]
77
+ ----
78
+ Relaton::Iana::DataFetcher.fetch
79
+ Started at: 2021-11-19 13:32:05 +0100
80
+ Stopped at: 2021-11-19 13:34:40 +0100
81
+ Done in: 155 sec.
82
+ => nil
83
+ ----
84
+
85
+ This method uses GitHub search API to fetch the documents. The search API has a https://docs.github.com/en/rest/reference/search#rate-limit[rate limitaion] of 30 requests per minute for authenticated users and 10 requests per minute for unauthenticated users. Usually, the rate limit is not reached because the search API is used only for the file list fetching. But if you run the method multiple times in a short period, the rate limit may be reached. In this case, the method will wait until the rate limit is reset.
86
+
87
+ You can use a personal access token (PTA) to increase the rate limit. To do this, add your PTA to the `GITHUB_TOKEN` environment variable.
88
+
89
+ === Logging
90
+
91
+ Relaton::Iana 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.
92
+
93
+ == Development
94
+
95
+ 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.
96
+
97
+ The index test fixture is `spec/iana/fixtures/index-v2.zip` — the pubid-keyed `index-v2` published by the https://github.com/relaton/relaton-data-iana[relaton-data-iana] repository (branch `v2`). To refresh it, download the live `index-v2.zip` and replace the fixture.
98
+
99
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
100
+
101
+ == Contributing
102
+
103
+ Bug reports and pull requests are welcome on GitHub at https://github.com/relaton/relaton_w3c.
104
+
105
+ == License
106
+
107
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,13 @@
1
+ # CLAUDE.md
2
+
3
+ ## Development
4
+
5
+ - `bundle install` — install dependencies
6
+ - `bundle exec rake spec` — run tests
7
+ - `bundle exec rubocop` — lint
8
+
9
+ ## Testing
10
+
11
+ - **Framework:** RSpec with VCR cassettes and WebMock
12
+ - **Index fixture:** `spec/fixtures/index-v2.zip` is pre-loaded into `Relaton::Index` pool in `before(:suite)` (configured in `spec/support/webmock.rb`). Run `rake spec:update_index` to refresh from relaton-data-iec. The fixture is the lean pubid-v2 `to_hash` index (`_type`-tagged rows); rows are deserialized via `Pubid::Iec::Identifier.from_hash` and validated by round-trip in relaton-index (no `id_keys` allowlist).
13
+ - **VCR cassettes:** `spec/vcr_cassettes/` — index download requests are ignored by VCR (handled by fixture).