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.
- checksums.yaml +4 -4
- data/data/equivalence/adoption-chains.yaml +38 -0
- data/lib/relaton/3gpp/CLAUDE.md +187 -0
- data/lib/relaton/3gpp/README.adoc +149 -0
- data/lib/relaton/adobe/CLAUDE.md +104 -0
- data/lib/relaton/bib/CLAUDE.md +130 -0
- data/lib/relaton/bib/README.adoc +330 -0
- data/lib/relaton/bib/model/series.rb +1 -1
- data/lib/relaton/bipm/CLAUDE.md +173 -0
- data/lib/relaton/bipm/README.adoc +352 -0
- data/lib/relaton/bipm/data_fetcher.rb +3 -2
- data/lib/relaton/bsi/CLAUDE.md +35 -0
- data/lib/relaton/bsi/README.adoc +112 -0
- data/lib/relaton/calconnect/CLAUDE.md +177 -0
- data/lib/relaton/calconnect/README.adoc +140 -0
- data/lib/relaton/ccsds/CLAUDE.md +74 -0
- data/lib/relaton/ccsds/README.adoc +154 -0
- data/lib/relaton/cen/CLAUDE.md +219 -0
- data/lib/relaton/cen/README.adoc +155 -0
- data/lib/relaton/cie/CLAUDE.md +90 -0
- data/lib/relaton/cie/README.adoc +121 -0
- data/lib/relaton/core/CLAUDE.md +140 -0
- data/lib/relaton/core/README.adoc +64 -0
- data/lib/relaton/core/data_fetcher.rb +50 -3
- data/lib/relaton/db/CLAUDE.md +373 -0
- data/lib/relaton/db.rb +19 -1
- data/lib/relaton/doi/CLAUDE.md +105 -0
- data/lib/relaton/doi/README.adoc +89 -0
- data/lib/relaton/easc/CLAUDE.md +85 -0
- data/lib/relaton/ecma/CLAUDE.md +217 -0
- data/lib/relaton/ecma/README.adoc +178 -0
- data/lib/relaton/equivalence.rb +89 -0
- data/lib/relaton/etsi/CLAUDE.md +219 -0
- data/lib/relaton/etsi/README.adoc +137 -0
- data/lib/relaton/evs/CLAUDE.md +120 -0
- data/lib/relaton/evs/README.adoc +27 -0
- data/lib/relaton/gb/CLAUDE.md +47 -0
- data/lib/relaton/gb/README.adoc +169 -0
- data/lib/relaton/gost/CLAUDE.md +100 -0
- data/lib/relaton/iala/CLAUDE.md +97 -0
- data/lib/relaton/iana/CLAUDE.md +105 -0
- data/lib/relaton/iana/README.adoc +107 -0
- data/lib/relaton/iec/CLAUDE.md +13 -0
- data/lib/relaton/iec/README.adoc +280 -0
- data/lib/relaton/iec/data_fetcher.rb +24 -8
- data/lib/relaton/iec/project_fetcher.rb +48 -0
- data/lib/relaton/iec/project_parser.rb +111 -0
- data/lib/relaton/iec.rb +5 -0
- data/lib/relaton/ieee/CLAUDE.md +176 -0
- data/lib/relaton/ieee/README.adoc +131 -0
- data/lib/relaton/ietf/CLAUDE.md +357 -0
- data/lib/relaton/ietf/README.adoc +167 -0
- data/lib/relaton/iho/CLAUDE.md +70 -0
- data/lib/relaton/iho/README.adoc +119 -0
- data/lib/relaton/index/CLAUDE.md +207 -0
- data/lib/relaton/index/README.adoc +221 -0
- data/lib/relaton/index/dataset_lock.rb +122 -0
- data/lib/relaton/index/family.rb +63 -0
- data/lib/relaton/index/type.rb +32 -0
- data/lib/relaton/index.rb +3 -0
- data/lib/relaton/isbn/CLAUDE.md +47 -0
- data/lib/relaton/isbn/README.adoc +91 -0
- data/lib/relaton/iso/CLAUDE.md +38 -0
- data/lib/relaton/iso/README.adoc +370 -0
- data/lib/relaton/iso/data_fetcher.rb +16 -1
- data/lib/relaton/itu/CLAUDE.md +685 -0
- data/lib/relaton/itu/README.adoc +188 -0
- data/lib/relaton/jcgm/CLAUDE.md +107 -0
- data/lib/relaton/jis/CLAUDE.md +64 -0
- data/lib/relaton/jis/README.adoc +167 -0
- data/lib/relaton/logger/CLAUDE.md +37 -0
- data/lib/relaton/logger/README.adoc +120 -0
- data/lib/relaton/nist/CLAUDE.md +180 -0
- data/lib/relaton/nist/README.adoc +389 -0
- data/lib/relaton/nist/bibliography.rb +17 -1
- data/lib/relaton/nist/data_fetcher.rb +60 -4
- data/lib/relaton/nist/hit_collection.rb +121 -59
- data/lib/relaton/nist/scraper.rb +21 -7
- data/lib/relaton/oasis/CLAUDE.md +251 -0
- data/lib/relaton/oasis/README.adoc +132 -0
- data/lib/relaton/ogc/CLAUDE.md +242 -0
- data/lib/relaton/ogc/README.adoc +134 -0
- data/lib/relaton/oiml/CLAUDE.md +47 -0
- data/lib/relaton/oiml/README.adoc +70 -0
- data/lib/relaton/omg/CLAUDE.md +137 -0
- data/lib/relaton/omg/README.adoc +101 -0
- data/lib/relaton/plateau/CLAUDE.md +126 -0
- data/lib/relaton/plateau/README.adoc +139 -0
- data/lib/relaton/sdo/CLAUDE.md +81 -0
- data/lib/relaton/un/CLAUDE.md +91 -0
- data/lib/relaton/un/README.adoc +125 -0
- data/lib/relaton/version.rb +1 -1
- data/lib/relaton/w3c/CLAUDE.md +369 -0
- data/lib/relaton/w3c/README.adoc +140 -0
- data/lib/relaton/w3c/data_fetcher.rb +1 -1
- data/lib/relaton/xsf/CLAUDE.md +189 -0
- data/lib/relaton/xsf/README.adoc +127 -0
- data/lib/relaton.rb +1 -0
- 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).
|