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
@@ -0,0 +1,177 @@
1
+ # CLAUDE.md
2
+
3
+ ## Development
4
+
5
+ - `bundle install` — install dependencies
6
+ - `bundle exec rake spec:calconnect` — run this flavor's suite
7
+ - `bundle exec rubocop` — lint
8
+
9
+ The root `Gemfile` git-pins pubid. Pick it up with **`bundle update pubid`**,
10
+ never `bundle install` — an already-locked git source does not refloat.
11
+
12
+ ## Index
13
+
14
+ ### The gem builds and reads `index-v2`; the data repo owns `index-v1`
15
+
16
+ `INDEXFILE` is the pubid-backed `index-v2`: rows are
17
+ `Pubid::Calconnect::Identifier` hashes
18
+ (`_type: pubid:calconnect:standard`, with `series`/`number`/`year`, plus
19
+ `month`/`day` for the one fully dated row). The producer
20
+ (`DataFetcher#index`) and the consumer (`HitCollection#index`) pass
21
+ `pubid_class: ::Pubid::Calconnect::Identifier`. Omitting it on the
22
+ **producer** writes v1-shaped rows under a v2 name, silently (`FileIO#save`
23
+ calls `to_hash` only for instances of `pubid_class`); omitting it on the
24
+ **consumer** leaves the rows raw hashes with `FileIO#sorted` false, so every
25
+ lookup scans all 191 rows. `Processor#remove_index_file` passes `url: true` and
26
+ `file:` only: the delete never reads the index (see
27
+ `lib/relaton/index/CLAUDE.md`).
28
+
29
+ **`index-v1` is not this gem's concern at all.** It exists only for released
30
+ relaton **v2** clients, and `relaton-data-calconnect` owns it end to end:
31
+ `build_index_v1.rb` rebuilds it there from each crawled document's own primary
32
+ `docidentifier` string out of `data/` — unlike relaton-data-{ecma,w3c,iana,bipm},
33
+ it does **not** derive it from the v2 rows. CalConnect can do that and they
34
+ cannot, because a v1 row id is simply the document's printed id, with no edition
35
+ or volume to split back out. The upshot for this side is that their v1 never
36
+ depends on pubid, so nothing here can break it and there is no v1 fixture and no
37
+ v1 assertion in this suite.
38
+
39
+ ### Lookup
40
+
41
+ `HitCollection#search_index` follows the ETSI/W3C/ECMA idiom:
42
+
43
+ - **Pass the pubid, not the string.** `Type#search_candidates` narrows only when
44
+ the argument is not a `String`, and a block alone never narrows — so the plain
45
+ string this flavor used to pass disabled the binary search however the index
46
+ was built. `pubid_class:` alone fixes nothing; both had to change together.
47
+ - **Select with the subset match.** `search_index` calls
48
+ `index.search(pubid)` with no block, so `Index::Type#search` selects with
49
+ pubid's `pubid === row`. A date that the reference omits matches any value.
50
+ pubid declares `series` strict for CalConnect (pubid#408): the series keeps
51
+ `CC/CD 51016` and `CC/WD 51016` apart, and its *absence* is a value too, so
52
+ `CC 36010` does not match `CC/WD 36010`. Measured over the 191-row fixture:
53
+ `===` and the old `matches?(…, ignore: %i[year])` agree on all 406 pairs.
54
+ - **An unparseable reference finds nothing**, with a warning, and is a miss
55
+ rather than an error — it must never surface as a `Relaton::RequestError`.
56
+
57
+ **This ended a silently ambiguous substring scan.** The old string search
58
+ matched any row *containing* the reference: `CC/DIR 1000` answered with all five
59
+ `CC/DIR 1000x` documents and `CC/A 1` with every `CC/A 1xxx`. A number now
60
+ matches exactly, and a leading zero is significant (`CC/A 0001` is not
61
+ `CC/A 1`).
62
+
63
+ **Ordering is newest-first, and it has to be explicit.** The index is sorted by
64
+ number, so rows sharing a number arrive in no meaningful order; without
65
+ `#recency_key` a bare `CC/S 0601` would answer with an arbitrary one of the 2005
66
+ and 2006 documents. Date segments are compared as integers and negated for
67
+ descending order, with `row[:file]` breaking ties because the index sort is not
68
+ stable.
69
+
70
+ `Bibliography.get` no longer regex-splits the reference before searching: pubid
71
+ parses `CC/DIR 10005:2019` whole, so a dated reference narrows in the index
72
+ itself. The `year` **argument** still needs filtering afterwards, because an
73
+ undated reference reaches every year of the document — that is why
74
+ `bib_results_filter` stays, and why it now reads the year off `row[:id].date`
75
+ instead of scraping a rendered string (the old `/:(\d{4})$/` would raise
76
+ `TypeError` against a pubid).
77
+
78
+ ### Why this flavor needs no render flags
79
+
80
+ CalConnect is the simple end of the pubid migrations, and the reason is worth
81
+ stating so nobody adds machinery it does not need. `Pubid::Calconnect` renders
82
+ the publisher by default and models no edition and no volume, so the index key
83
+ and the document's own printed id are **the same string**: `CC/DIR 10005:2019`
84
+ in the row, `CC/DIR 10005:2019` in the record. There is nothing to opt in to
85
+ (contrast `Relaton::ThreeGpp::Docidentifier`, which must pass
86
+ `with_publisher: true`) and nothing to opt out of (contrast
87
+ `Relaton::Ecma::Docidentifier`, which must suppress the edition and the volume).
88
+ `DataFetcher#index_id` therefore returns the docidentifier's pubid untouched —
89
+ no `dup`, no mutation.
90
+
91
+ The number is a plain `String`, not a `Components::Code`, and deliberately so:
92
+ it preserves leading zeros (`0001`, `0514`) and keeps a sub-number in one token
93
+ (`0812-1`, `0707.1`). CalConnect models **no part** — those are numbers, not a
94
+ number plus a part — which is why `Docidentifier#remove_part!` and
95
+ `#to_all_parts!` are no-ops. Do not "fix" them by splitting the number.
96
+
97
+ `Docidentifier` follows the IHO/W3C shape: `content=` calls `super` first, so
98
+ `content` stays the source string verbatim. That is load-bearing beyond
99
+ convention — `ItemData#create_id` derives the record's `id` from
100
+ `content.gsub(/\W+/, "")`, so a re-render would move every published id.
101
+ `#remove_date!` is the one real mutator (`CC/DIR 10005:2019` ->
102
+ `CC/DIR 10005`); it writes back through `store_content`, never `content=`,
103
+ because a re-parse would rebuild `@pubid` and discard the mutation.
104
+
105
+ ### An unparseable docid is recorded, not just warned
106
+
107
+ `Docidentifier#parse` logs at `Util.error` and leaves `#pubid` nil — it must not
108
+ raise, or an already-published record would stop deserializing.
109
+ `DataFetcher#add_to_index` then records
110
+ `@errors[docid] = "Unparseable primary id … was not indexed (…)"`, which the
111
+ inherited `Core::DataFetcher#report_errors` logs and `Logger::Channels::GhIssue`
112
+ turns into a GitHub issue at the end of the crawl. (`report_errors` treats a
113
+ **String** value as the message; the boolean form the parsers use means "this
114
+ field failed for every record" and is the wrong shape here.)
115
+
116
+ The row is skipped rather than indexed unparsed, because `Relaton::Index`
117
+ rejects the **whole** index if one row fails to deserialize and its sort calls
118
+ `.root.number` on every id. `#write_doc` writes the file **before** indexing, so
119
+ the document is unindexed, never lost.
120
+
121
+ ### Measured against the live published corpus
122
+
123
+ All 191 rows of the published index (2026-09-08): every id parses; `to_s`
124
+ reproduces the published string **exactly**; `from_hash(to_hash)` round-trips
125
+ all 191; they render **191 distinct keys**, so nothing collapses under
126
+ `add_or_update`; `root.number` is empty for none. Driving all 191 documents of
127
+ `relaton-data-calconnect`'s `data/` through `write_doc` produces 191 rows with 0
128
+ errors and reads back as 191 identifiers with `FileIO#sorted` true.
129
+ `spec/calconnect/relaton/calconnect/pubid_contract_spec.rb` pins these
130
+ properties offline against the committed `index-v2` fixture.
131
+
132
+ No pubid change was needed for this migration. `Pubid::Calconnect` was written
133
+ for it.
134
+
135
+ ## Testing
136
+
137
+ - **Framework:** RSpec with VCR cassettes and WebMock.
138
+ - **Index fixture:** `spec/calconnect/fixtures/index-v2.zip` — the whole
139
+ published index (191 rows), copied verbatim, so the stored shapes are exactly
140
+ what the runtime deserializes. Refresh it with
141
+ `bundle exec rake spec:update_index_calconnect`
142
+ (`tasks/index_fixture_calconnect.rb`); `#build` refuses a source that is not a
143
+ pubid index-v2 rather than writing a fixture `Relaton::Index` would reject
144
+ wholesale.
145
+ - It is seeded into the `Relaton::Index` pool by
146
+ `spec/calconnect/support/webmock.rb`, built **with `pubid_class:`** — without
147
+ which the rows stay raw hashes, `FileIO#sorted` stays false, and
148
+ `Type#search` silently stops narrowing, so the suite would pass while
149
+ exercising something the runtime never does. It is written to a temp file and
150
+ read through `FileIO`, not stuffed into `@index` directly, because the
151
+ deserialize and the sort both happen on that read path.
152
+ - There is **no `index-v1` fixture**, deliberately: `index-v1` is a released
153
+ relaton v2 artifact that this gem neither produces nor reads (see the index
154
+ section). Don't reintroduce one.
155
+ - **The `before(:each)` re-seed is not belt-and-braces.** `Index::Pool#type`
156
+ replaces the pooled entry whenever `actual?` says no, and `DataFetcher#index`
157
+ asks for the same `:CC` slot with a *different* file (`index-v2.yaml`). With a
158
+ `before(:suite)` seed alone the fetcher evicts the fixture, and every later
159
+ example goes to the network for real — which is exactly how six examples used
160
+ to die with `Errno::EPERM` on `~/.relaton/cc/index-v1.yaml`. The pool key is
161
+ `type.upcase.to_sym`, so the producer and the consumer share one slot.
162
+ (The ECMA/OGC pattern.)
163
+ - **VCR cassettes:** `spec/calconnect/vcr_cassettes/` — index downloads are
164
+ ignored by VCR, matched on the constant so a version bump cannot leave the
165
+ filter naming the old file and let a real download through.
166
+ - `pubid_contract_spec.rb` checks the whole fixture corpus for the properties
167
+ the index key depends on: every row deserializes, one distinct rendered key
168
+ per row, no empty bsearch key, a `to_hash`/`from_hash` round trip, and every
169
+ rendered key parsing back to the same identifier. Re-run it whenever the
170
+ fixture is refreshed.
171
+ - `hit_collection_spec.rb` covers the runtime search path — narrowing,
172
+ ordering, and the misses. It was an empty `describe` block before this
173
+ migration, so the lookup had no direct coverage at all.
174
+ - The `bibdata`/`bibitem` round-trip examples and two `gets` examples validate
175
+ XML with Jing, which needs a **JVM on PATH**. Without one they fail with
176
+ `Jing::ExecutionError`, which is environmental, not a code defect — check
177
+ `java -version` before chasing it.
@@ -0,0 +1,140 @@
1
+ = Relaton::Calconnect
2
+
3
+ Relaton::Calconnect 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 Calconnect Standards from https://standards.calconnect.org, and access such metadata through the `CcBibliographicItem` object.
6
+
7
+ == Usage
8
+
9
+ === Search for a standard using keywords
10
+
11
+ [source,ruby]
12
+ ----
13
+ require 'relaton/calconnect'
14
+ => true
15
+
16
+ hits = Relaton::Calconnect::Bibliography.search("CC/DIR 10005:2019")
17
+ => <Relaton::Calconnect::HitCollection:0x00000000002df0 @ref=CC/DIR 10005:2019 @fetched=false>
18
+
19
+ item = hits[0].item
20
+ => #<Relaton::Calconnect::ItemData:0x000000011104c998
21
+ ...
22
+ ----
23
+
24
+ === XML serialization
25
+
26
+ [source,ruby]
27
+ ----
28
+ item.to_xml
29
+ => "<bibitem id="CC-DIR10005-2019" type="standard" schema-version="v1.5.6">
30
+ <title language="en">Guidelines for the implementation of the CalConnect patent policy</title>
31
+ <docidentifier type="CalConnect" primary="true">CC/DIR 10005:2019</docidentifier>
32
+ ...
33
+ </bibitem>"
34
+ ----
35
+ With argument `bibdata: true` it outputs XML wrapped by `bibdata` element and adds flavour `ext` element.
36
+ [source,ruby]
37
+ ----
38
+ item.to_xml bibdata: true
39
+ => "<bibdata type="standard" schema-version="v1.5.6">
40
+ <title language="en">Guidelines for the implementation of the CalConnect patent policy</title>
41
+ <docidentifier type="CalConnect" primary="true">CC/DIR 10005:2019</docidentifier>
42
+ ...
43
+ <ext schema-version="v1.1.2">
44
+ <doctype abbreviation="DIR">directive</doctype>
45
+ <flavor>calconnect</flavor>
46
+ </ext>
47
+ </bibdata>
48
+ ----
49
+
50
+ === Typed links
51
+
52
+ Each Calconnect document has link types:
53
+
54
+ * `xml` - XML format
55
+ * `pdf` - PDF format
56
+ * `doc` - Microsoft Word document format
57
+ * `html` - HTML format
58
+ * `rxl` - RelatonXML format
59
+
60
+ [source,ruby]
61
+ ----
62
+ item.source
63
+ => [#<Relaton::Bib::Uri:0x00007ff2d4b009e0 @content=#<Addressable::URI:0x668 URI:https://standards.calconnect.org/csd/cc-10005.xml>, @type="xml">,
64
+ #<Relaton::Bib::Uri:0x00007ff2d4b00378 @content=#<Addressable::URI:0x67c URI:https://standards.calconnect.org/csd/cc-10005.pdf>, @type="pdf">,
65
+ #<Relaton::Bib::Uri:0x00007ff2d4b00058 @content=#<Addressable::URI:0x690 URI:https://standards.calconnect.org/csd/cc-10005.doc>, @type="doc">,
66
+ #<Relaton::Bib::Uri:0x00007ff2d4b23d28 @content=#<Addressable::URI:0x6a4 URI:https://standards.calconnect.org/csd/cc-10005.html>, @type="html">,
67
+ #<Relaton::Bib::Uri:0x00007ff2d4b23a08 @content=#<Addressable::URI:0x6b8 URI:https://standards.calconnect.org/csd/cc-10005.rxl>, @type="rxl">]
68
+ ----
69
+
70
+ === Get document by code and year
71
+ [source,ruby]
72
+ ----
73
+ Relaton::Calconnect::Bibliography.get "CC/DIR 10005", "2019"
74
+ [relaton-calconnect] INFO: (CC/DIR 10005) Fetching from Relaton repository ...
75
+ [relaton-calconnect] INFO: (CC/DIR 10005) Found: `CC/DIR 10005:2019`
76
+ => #<Relaton::Calconnect::ItemData:0x0000000111944b18
77
+ ...
78
+ ----
79
+
80
+ === Create bibliographic item from XML
81
+ [source,ruby]
82
+ ----
83
+ Relaton::Calconnect::Item.from_xml File.read('spec/fixtures/cc_dir_10005_2019.xml')
84
+ => #<Relaton::Calconnect::ItemData:0x000000010ec81040
85
+ ...
86
+ ----
87
+
88
+ === Create bibliographic item from YAML
89
+ [source,ruby]
90
+ ----
91
+ Relaton::Calconnect::Item.from_yaml File.read('spec/fixtures/item.yaml')
92
+ => #<Relaton::Calconnect::ItemData:0x000000010fe5e600
93
+ ...
94
+ ----
95
+
96
+ === Fetch data
97
+
98
+ This gem uses the https://raw.githubusercontent.com/usnistgov/NIST-Tech-Pubs/nist-pages/xml/allrecords.xml dataset as one of the data sources.
99
+
100
+ The method `RelatonCalconnect::DataFetcher.fetch(output: "data", format: "yaml")` fetches all the documents from the dataset and saves them to the `./data` folder in YAML format.
101
+ Arguments:
102
+
103
+ - `output` - folder to save documents (default './data').
104
+ - `format` - the format in which the documents are saved. Possible formats are: `yaml`, `xml`, `bibxml` (default `yaml`).
105
+
106
+ [source,ruby]
107
+ ----
108
+ require "relaton/calconnect/data_fetcher"
109
+ Relaton::Calconnect::DataFetcher.fetch
110
+ Started at: 2021-09-09 16:03:51 +0200
111
+ Stopped at: 2021-09-09 16:04:12 +0200
112
+ Done in: 20 sec.
113
+ => nil
114
+ ----
115
+
116
+ === Logging
117
+
118
+ RelatonCalconnect 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.
119
+
120
+ == Development
121
+
122
+ 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.
123
+
124
+ == Index
125
+
126
+ Lookups read a pubid-backed `index-v2`, whose rows are `Pubid::Calconnect::Identifier` objects serialized as `_type: pubid:calconnect:standard`. A reference is parsed into an identifier and matched against the index structurally, so a number matches exactly rather than as a substring: `CC/DIR 1000` finds nothing, `CC/DIR 10005` finds the one document, and `CC/A 0001` is not `CC/A 1`. An undated reference reaches every year of a document and the most recent is returned first.
127
+
128
+ The legacy `index-v1` is no longer produced here; https://github.com/relaton/relaton-data-calconnect[relaton-data-calconnect] rebuilds it from each crawled document's own docidentifier for released relaton v2 clients.
129
+
130
+ To refresh the index test fixture, run `rake spec:update_index_calconnect`. This downloads the published `index-v2.zip` from the https://github.com/relaton/relaton-data-calconnect[relaton-data-calconnect] repository.
131
+
132
+ 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).
133
+
134
+ == Contributing
135
+
136
+ Bug reports and pull requests are welcome on GitHub at https://github.com/calconnect/relaton_calconnect.
137
+
138
+ == License
139
+
140
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,74 @@
1
+ # CLAUDE.md
2
+
3
+ ## Architecture
4
+
5
+ **Pubid-backed docidentifier.** `Relaton::Ccsds::Docidentifier`
6
+ (`model/docidentifier.rb`, `< Bib::Docidentifier`) parses its `content` into a
7
+ `Pubid::Ccsds::Identifier` kept in `@pubid`, while the lutaml `content` attribute
8
+ stays a plain string for serialization. It implements the base class's abstract
9
+ `remove_part!` / `remove_date!` / `to_all_parts!` by mutating the pubid graph
10
+ (walking the `base` chain for supplements/corrigenda) and re-rendering
11
+ via `refresh_content!`. Unparseable content falls back to the raw string. It is
12
+ wired into `model/item.rb` (`attribute :docidentifier, Docidentifier`) so
13
+ `from_xml`/`from_yaml` and `DataParser#parse_docidentifier` yield this class.
14
+ `remove_date!` is effectively a no-op today — CCSDS ids carry no date component —
15
+ but is implemented for parity with ISO/IEC. `to_all_parts!` drops the part
16
+ component (e.g. `CCSDS 121.0-B-3` → `CCSDS 121-B-3`), freezes that rendering
17
+ into `@raw_content` (since `content` is live-derived from `@pubid`), then
18
+ wraps `@pubid` in pubid's `AllParts`. pubid-ccsds has no dedicated `AllParts`
19
+ subclass — unlike pubid-iso — so the wrapper is the generic
20
+ `Pubid::AllPartsIdentifier`: `content` stays the plain part-stripped id, but
21
+ `#pubid` itself, read directly, now answers `all_parts? == true` and renders
22
+ WITH pubid's generic "(all parts)" marker, since it's the wrapper. This
23
+ mirrors `lib/relaton/iec/model/docidentifier.rb`; the shared skeleton is
24
+ intentionally duplicated per flavor for now (to be hoisted into
25
+ `Bib::Docidentifier` once every flavor's id is Pubid-backed).
26
+
27
+ **Translation relations use pubid's subset match.**
28
+ `Data::Fetcher#search_instance_translation` reads the document's **already
29
+ parsed** id from `bib.docidentifier.first.pubid` (not `content`, which it would
30
+ re-parse), derives the language-less reference with `pubid.exclude(:language)`,
31
+ and picks the branch by `bibid == pubid` — no language means an instance
32
+ (`#search_translations`), a language means a translation (`#search_relations`).
33
+ This is more correct than the old `TRRGX` string test: `content` returns
34
+ `@pubid.to_s`, which reorders components, so a translated corrigendum
35
+ (`CCSDS 320.0-B-1-S - German Translated Cor. 1`) put the language mid-string and
36
+ the `$`-anchored `TRRGX` missed it; `exclude(:language)` strips the language down
37
+ the whole base chain. `TRRGX` still serves `translation_relation_types` and
38
+ `DataParser#relation_type`, which match raw relation-id strings.
39
+ `#search_relations` and `#search_translations` receive that pubid and select
40
+ index rows with `row[:id].exclude(:language) == bibid_pid` — a
41
+ language-agnostic match, so a translated row and the instance both match while
42
+ another edition of the same number does not. This does **not** use `===`:
43
+ pubid declares CCSDS `language` `subset_strict`, so a nil reference language
44
+ means "has none" (a wildcard would be wrong for `HitCollection`, where a
45
+ language-less user reference must not reach a translation). `exclude(:language)`
46
+ drops the language from the row on both sides, mirroring
47
+ `HitCollection#rows` (`r[:id].exclude(:edition) == pubid`).
48
+
49
+ `HitCollection#rows` keeps `index.search(pubid, exact: true)` for an edition
50
+ reference, although `language` is strict now. CCSDS `suffix` is still a nil
51
+ wildcard in pubid, so the bare `index.search(pubid)` makes `CCSDS 101.0-B-4`
52
+ also return the historical `CCSDS 101.0-B-4-S` (260 such pairs in the index
53
+ fixture). The `hit_collection_spec` "historical and a translated sibling"
54
+ example guards this. Drop `exact: true` only after pubid declares `suffix`
55
+ `subset_strict` too.
56
+
57
+ `#search_relations` excludes the document's own row with
58
+ `row[:id] == bib.docidentifier.first.pubid` — pubid to pubid. It compared a
59
+ `Pubid::Ccsds::Identifier` with a `String` (`.content`) before and was always
60
+ false, so a re-crawl over an index that already held the document gave it a
61
+ relation to itself. Covered by the `#search_relations` "excludes the document's
62
+ own row" example.
63
+
64
+ ## Development
65
+
66
+ - `bundle install` — install dependencies
67
+ - `bundle exec rake spec` — run tests
68
+ - `bundle exec rubocop` — lint
69
+
70
+ ## Testing
71
+
72
+ - **Framework:** RSpec with VCR cassettes and WebMock
73
+ - **Index fixture:** `spec/fixtures/index-v1.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-ccsds.
74
+ - **VCR cassettes:** `spec/vcr_cassettes/` — index download requests are ignored by VCR (handled by fixture).
@@ -0,0 +1,154 @@
1
+ = Relaton::Ccsds
2
+
3
+ RelatonCcsds is a Ruby gem that implements the https://github.com/relaton/relaton-models#bibliographic-item[BibliographicItem model].
4
+
5
+ You can use it to retrieve metadata of CCSDS Standards from https://public.ccsds.org/Publications/AllPubs.aspx, and access such metadata through the `RelatonCcsds::BibliographicItem` object.
6
+
7
+ == Usage
8
+
9
+ === Search for a standard using keywords
10
+
11
+ [source,ruby]
12
+ ----
13
+ require 'relaton/ccsds'
14
+ => true
15
+
16
+ hits = Relaton::Ccsds::Bibliography.search("CCSDS 230.2-G-1")
17
+ => <Relaton::Ccsds::HitCollection:0x000000000016b0 @ref=CCSDS 230.2-G-1 @fetched=false>
18
+
19
+ item = hits[0].item
20
+ => #<Relaton::Ccsds::ItemData:0x0000000125ae1318
21
+ ...
22
+
23
+ item.docidentifier[0].content
24
+ => "CCSDS 230.2-G-1"
25
+
26
+ item = Relaton::Ccsds::Bibliography.get("CCSDS 230.2-G-1")
27
+ [relaton-ccsds] INFO: (CCSDS 230.2-G-1) Fetching from Relaton repository ...
28
+ [relaton-ccsds] INFO: (CCSDS 230.2-G-1) Found: `CCSDS 230.2-G-1`.
29
+ => #<Relaton::Ccsds::ItemData:0x0000000125deba40
30
+ ...
31
+ ----
32
+
33
+ === XML serialization
34
+
35
+ [source,ruby]
36
+ ----
37
+ item.to_xml
38
+ => "<bibitem id="CCSDS2302G1" schema-version="v1.5.6">
39
+ <fetched>2025-12-03</fetched>
40
+ <title language="en" script="Latn">Next Generation Uplink</title>
41
+ <uri type="src">https://ccsds.org/publications/ccsdsallpubs/entry/3224/</uri>
42
+ <uri type="pdf">https://ccsds.org/wp-content/uploads/gravity_forms/5-448e85c647331d9cbaf66c096458bdd5/2025/01//230x2g1.pdf</uri>
43
+ <docidentifier type="CCSDS" primary="true">CCSDS 230.2-G-1</docidentifier>
44
+ ...
45
+ </bibitem>"
46
+ ----
47
+ With argument `bibdata: true` it outputs XML wrapped by `bibdata` element and adds flavour `ext` element.
48
+ [source,ruby]
49
+ ----
50
+ item.to_xml bibdata: true
51
+ => "<bibdata schema-version="v1.5.6">
52
+ <fetched>2025-12-03</fetched>
53
+ <title language="en" script="Latn">Next Generation Uplink</title>
54
+ <uri type="src">https://ccsds.org/publications/ccsdsallpubs/entry/3224/</uri>
55
+ ...
56
+ <ext>
57
+ <doctype>report</doctype>
58
+ <technology-area>Space Link Services Area</technology-area>
59
+ </ext>
60
+ </bibdata>"
61
+ ----
62
+
63
+ === Typed links
64
+
65
+ All the CCSDS documents have PDF links. Some of them have additional DOC links. The gem provides a way to access these links through the `RelatonBib::TypedUri` object.
66
+
67
+ [source,ruby]
68
+ ----
69
+ item.source
70
+ => [#<Relaton::Bib::Uri:0x0000000126722b90 @content="https://ccsds.org/publications/ccsdsallpubs/entry/3224/", @language=nil, @locale=nil, @script=nil, @type="src">,
71
+ #<Relaton::Bib::Uri:0x0000000126722690
72
+ @content="https://ccsds.org/wp-content/uploads/gravity_forms/5-448e85c647331d9cbaf66c096458bdd5/2025/01//230x2g1.pdf",
73
+ @language=nil,
74
+ @locale=nil,
75
+ @script=nil,
76
+ @type="pdf">]
77
+
78
+ Relaton::Ccsds::Bibliography.get("CCSDS 720.6-Y-1").source
79
+ [relaton-ccsds] (CCSDS 720.6-Y-1) Fetching from Relaton repository ...
80
+ [relaton-ccsds] (CCSDS 720.6-Y-1) Found: `CCSDS 720.6-Y-1`.
81
+ => [#<Relaton::Bib::Uri:0x00000001268cfba0 @content="https://ccsds.org/publications/ccsdsallpubs/entry/3160/", @language=nil, @locale=nil, @script=nil, @type="src">,
82
+ #<Relaton::Bib::Uri:0x00000001268cf650
83
+ @content="https://ccsds.org/wp-content/uploads/gravity_forms/5-448e85c647331d9cbaf66c096458bdd5/2025/01//720x6y1.doc",
84
+ @language=nil,
85
+ @locale=nil,
86
+ @script=nil,
87
+ @type="doc">,
88
+ #<Relaton::Bib::Uri:0x00000001268cf100
89
+ @content="https://ccsds.org/wp-content/uploads/gravity_forms/5-448e85c647331d9cbaf66c096458bdd5/2025/01//720x6y1.pdf",
90
+ @language=nil,
91
+ @locale=nil,
92
+ @script=nil,
93
+ @type="pdf">]
94
+ ----
95
+
96
+ === Create bibliographic item from XML
97
+ [source,ruby]
98
+ ----
99
+ Relaton::Ccsds::Item.from_xml File.read('spec/fixtures/ccsds_230_2-g-1.xml')
100
+ => #<Relaton::Ccsds::ItemData:0x0000000127b46680
101
+ ...
102
+ ----
103
+
104
+ === Create bibliographic item from YAML
105
+ [source,ruby]
106
+ ----
107
+ yaml = File.read 'spec/fixtures/ccsds_230_2-g-1.yaml'
108
+ => ---\nschema-version: v1.2.9\nid: CCSDS230.2-G-1 ...
109
+
110
+ Relaton::Ccsds::Item.from_yaml yaml
111
+ => #<Relaton::Ccsds::ItemData:0x0000000122e4caa0
112
+ ...
113
+ ----
114
+
115
+ === Fetch data
116
+
117
+ This gem uses the https://ccsds.org/publications/ccsdsallpubs/ page as a data source.
118
+
119
+ The method `Relaton::Ccsds::DataFetcher.fetch(output: "data", format: "yaml")` fetches all the documents from the data source and saves them to the `./data` folder in YAML format.
120
+ Arguments:
121
+
122
+ - `output` - folder to save documents (default './data').
123
+ - `format` - the format in which the documents are saved. Possible formats are: `yaml`, `xml`, `bibxml` (default `yaml`).
124
+
125
+ [source,ruby]
126
+ ----
127
+ require 'relaton/ccsds/data/fetcher'
128
+ Relaton::Ccsds::DataFetcher.fetch
129
+ Started at: 2023-08-25 22:02:06 -0400
130
+ (data/CCSDS-720-4-Y-1.yaml) file already exists. Trying to merge links ...
131
+ ...
132
+ Done in: 40 sec.
133
+ => nil
134
+ ----
135
+
136
+ === Logging
137
+
138
+ RelatonCcsds 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.
139
+
140
+ == Development
141
+
142
+ 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.
143
+
144
+ To update the index test fixture (used by tests), run `rake spec:update_index`. This downloads the latest `index-v1.zip` from the https://github.com/relaton/relaton-data-ccsds[relaton-data-ccsds] repository.
145
+
146
+ 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).
147
+
148
+ == Contributing
149
+
150
+ Bug reports and pull requests are welcome on GitHub at https://github.com/relaton/relaton-ccsds.
151
+
152
+ == License
153
+
154
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).