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