pubid 2.0.0 → 2.0.2
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/parg/adobe.json +1 -1
- data/data/parg/aiee.json +1 -1
- data/data/parg/amca.json +1 -1
- data/data/parg/ansi.json +1 -1
- data/data/parg/api.json +1 -1
- data/data/parg/ashrae.json +1 -1
- data/data/parg/asme.json +1 -1
- data/data/parg/astm.json +1 -1
- data/data/parg/bipm.json +1 -1
- data/data/parg/bsi.json +1 -1
- data/data/parg/calconnect.json +1 -1
- data/data/parg/ccsds.json +1 -1
- data/data/parg/cen_cenelec.json +1 -1
- data/data/parg/cie.json +1 -1
- data/data/parg/csa.json +1 -1
- data/data/parg/doi.json +1 -1
- data/data/parg/easc.json +1 -1
- data/data/parg/ecma.json +1 -1
- data/data/parg/etsi.json +1 -1
- data/data/parg/evs.json +1 -1
- data/data/parg/gb.json +1 -1
- data/data/parg/gost.json +1 -1
- data/data/parg/iala.json +1 -1
- data/data/parg/iana.json +1 -1
- data/data/parg/idf.json +1 -1
- data/data/parg/iec.json +1 -1
- data/data/parg/ieee.json +1 -29571
- data/data/parg/ietf.json +1 -1
- data/data/parg/iho.json +1 -0
- data/data/parg/ire.json +1 -1
- data/data/parg/isbn.json +1 -1
- data/data/parg/iso.json +1 -8510
- data/data/parg/itu.json +1 -1
- data/data/parg/jcgm.json +1 -1
- data/data/parg/jis.json +1 -1
- data/data/parg/nesc.json +1 -1
- data/data/parg/nist.json +1 -9588
- data/data/parg/oasis.json +1 -1
- data/data/parg/ogc.json +1 -1
- data/data/parg/oiml.json +1 -1
- data/data/parg/omg.json +1 -1
- data/data/parg/parg.json +1 -1
- data/data/parg/plateau.json +1 -1
- data/data/parg/sae.json +1 -1
- data/data/parg/tables/typed_stages.yaml +1 -0
- data/data/parg/tgpp.json +1 -1
- data/data/parg/un.json +1 -1
- data/data/parg/w3c.json +1 -1
- data/data/parg/xsf.json +1 -1
- data/lib/pubid/adobe/identifier.rb +1 -1
- data/lib/pubid/adobe/parser.rb +2 -113
- data/lib/pubid/amca/identifiers/base.rb +1 -1
- data/lib/pubid/amca/parser.rb +1 -145
- data/lib/pubid/ansi/identifier.rb +1 -1
- data/lib/pubid/ansi/parser.rb +1 -82
- data/lib/pubid/api/builder.rb +32 -1
- data/lib/pubid/api/identifier.rb +1 -1
- data/lib/pubid/api/parser.rb +1 -131
- data/lib/pubid/ashrae/identifiers/base.rb +1 -1
- data/lib/pubid/ashrae/parser.rb +3 -710
- data/lib/pubid/asme/builder.rb +6 -4
- data/lib/pubid/asme/identifier.rb +1 -2
- data/lib/pubid/asme/parser.rb +1 -328
- data/lib/pubid/astm/builder.rb +12 -3
- data/lib/pubid/astm/identifier.rb +1 -2
- data/lib/pubid/astm/parser.rb +1 -227
- data/lib/pubid/bipm/identifier.rb +8 -1
- data/lib/pubid/bipm/parser.rb +2 -224
- data/lib/pubid/bsi/builder.rb +2 -4
- data/lib/pubid/bsi/parser.rb +1 -666
- data/lib/pubid/bsi/single_identifier.rb +38 -1
- data/lib/pubid/calconnect/identifier.rb +1 -1
- data/lib/pubid/calconnect/parser.rb +2 -40
- data/lib/pubid/ccsds/identifier.rb +1 -1
- data/lib/pubid/ccsds/parser.rb +1 -66
- data/lib/pubid/cen_cenelec/identifier.rb +16 -1
- data/lib/pubid/cen_cenelec/parser.rb +3 -136
- data/lib/pubid/cie/identifier.rb +1 -1
- data/lib/pubid/cie/parser.rb +3 -378
- data/lib/pubid/conformance.rb +1 -1
- data/lib/pubid/csa/builder.rb +41 -18
- data/lib/pubid/csa/identifier.rb +12 -4
- data/lib/pubid/csa/identifiers/amendment.rb +28 -0
- data/lib/pubid/csa/identifiers.rb +1 -0
- data/lib/pubid/csa/parser.rb +16 -397
- data/lib/pubid/doi/identifier.rb +1 -1
- data/lib/pubid/doi/parser.rb +2 -37
- data/lib/pubid/easc/builder.rb +1 -1
- data/lib/pubid/easc/identifier.rb +1 -1
- data/lib/pubid/easc/parser.rb +2 -70
- data/lib/pubid/ecma/CLAUDE.md +8 -1
- data/lib/pubid/ecma/identifier.rb +13 -8
- data/lib/pubid/ecma/parser.rb +2 -54
- data/lib/pubid/ecma/renderer.rb +3 -7
- data/lib/pubid/errors.rb +18 -13
- data/lib/pubid/etsi/builder.rb +8 -5
- data/lib/pubid/etsi/identifiers/base.rb +1 -1
- data/lib/pubid/etsi/parser.rb +1 -132
- data/lib/pubid/evs/identifier.rb +7 -0
- data/lib/pubid/evs/parser.rb +1 -8
- data/lib/pubid/evs.rb +1 -1
- data/lib/pubid/gb/identifier.rb +7 -1
- data/lib/pubid/gb/parser.rb +2 -51
- data/lib/pubid/gost/identifier.rb +9 -1
- data/lib/pubid/gost/parser.rb +4 -81
- data/lib/pubid/iala/parser.rb +2 -121
- data/lib/pubid/iana/identifier.rb +1 -1
- data/lib/pubid/iana/parser.rb +2 -24
- data/lib/pubid/identifier.rb +78 -1
- data/lib/pubid/idf/builder.rb +42 -0
- data/lib/pubid/idf/identifier.rb +1 -1
- data/lib/pubid/idf/parser.rb +1 -137
- data/lib/pubid/idf.rb +1 -2
- data/lib/pubid/iec/builder.rb +158 -50
- data/lib/pubid/iec/identifier.rb +1 -1
- data/lib/pubid/iec/parser.rb +3 -430
- data/lib/pubid/ieee/aiee/identifier.rb +2 -1
- data/lib/pubid/ieee/aiee.rb +0 -1
- data/lib/pubid/ieee/builder.rb +2 -2
- data/lib/pubid/ieee/identifiers/base.rb +1 -1
- data/lib/pubid/ieee/ire/identifier.rb +2 -1
- data/lib/pubid/ieee/ire.rb +0 -1
- data/lib/pubid/ieee/nesc.rb +0 -1
- data/lib/pubid/ieee/parser.rb +1 -1257
- data/lib/pubid/ietf/identifiers/base.rb +17 -1
- data/lib/pubid/ietf/parser.rb +2 -47
- data/lib/pubid/ietf/renderer.rb +17 -2
- data/lib/pubid/iho/CLAUDE.md +89 -0
- data/lib/pubid/iho/builder.rb +4 -1
- data/lib/pubid/iho/identifiers/base.rb +28 -2
- data/lib/pubid/iho/renderer.rb +8 -0
- data/lib/pubid/iho/urn_generator.rb +11 -1
- data/lib/pubid/iho/urn_parser.rb +21 -11
- data/lib/pubid/iho.rb +0 -1
- data/lib/pubid/isbn/identifier.rb +1 -1
- data/lib/pubid/isbn/parser.rb +2 -31
- data/lib/pubid/iso/builder.rb +1 -1
- data/lib/pubid/iso/identifiers/directives.rb +6 -3
- data/lib/pubid/iso/identifiers/guide.rb +6 -3
- data/lib/pubid/iso/identifiers/international_standard.rb +3 -1
- data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +6 -3
- data/lib/pubid/iso/normalizer.rb +1 -1
- data/lib/pubid/iso/parser.rb +1 -539
- data/lib/pubid/iso/supplement_identifier.rb +6 -3
- data/lib/pubid/iso/utilities.rb +4 -5
- data/lib/pubid/iso.rb +1 -19
- data/lib/pubid/itu/builder.rb +36 -20
- data/lib/pubid/itu/identifiers/base.rb +15 -2
- data/lib/pubid/itu/parser.rb +1 -775
- data/lib/pubid/jcgm/builder.rb +35 -0
- data/lib/pubid/jcgm/parser.rb +1 -151
- data/lib/pubid/jcgm.rb +1 -2
- data/lib/pubid/jis/builder.rb +18 -29
- data/lib/pubid/jis/identifier.rb +1 -1
- data/lib/pubid/jis/parser.rb +1 -128
- data/lib/pubid/nist/builder.rb +5 -1
- data/lib/pubid/nist/caster.rb +13 -3
- data/lib/pubid/nist/identifiers/base.rb +37 -2
- data/lib/pubid/nist/identifiers/circular.rb +2 -2
- data/lib/pubid/nist/identifiers/circular_supplement.rb +2 -2
- data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +2 -2
- data/lib/pubid/nist/identifiers/crpl_report.rb +2 -2
- data/lib/pubid/nist/identifiers/handbook.rb +2 -2
- data/lib/pubid/nist/identifiers/internal_report.rb +2 -2
- data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +2 -2
- data/lib/pubid/nist/identifiers/monograph.rb +2 -2
- data/lib/pubid/nist/identifiers/report.rb +2 -2
- data/lib/pubid/nist/parser.rb +19 -775
- data/lib/pubid/nist/router.rb +4 -1
- data/lib/pubid/nist/supplement_identifier.rb +2 -2
- data/lib/pubid/oasis/identifier.rb +1 -1
- data/lib/pubid/oasis/parser.rb +2 -16
- data/lib/pubid/ogc/identifier.rb +1 -1
- data/lib/pubid/ogc/parser.rb +2 -26
- data/lib/pubid/oiml/identifiers/certification_system.rb +7 -0
- data/lib/pubid/oiml/identifiers/dual_published.rb +2 -2
- data/lib/pubid/oiml/parser.rb +1 -303
- data/lib/pubid/oiml.rb +1 -2
- data/lib/pubid/omg/identifier.rb +6 -1
- data/lib/pubid/omg/parser.rb +2 -76
- data/lib/pubid/parg/artifact.rb +22 -3
- data/lib/pubid/parg/model.rb +98 -0
- data/lib/pubid/parg.rb +1 -0
- data/lib/pubid/parser.rb +5 -4
- data/lib/pubid/parsers/mr_string.rb +1 -1
- data/lib/pubid/plateau/parser.rb +1 -66
- data/lib/pubid/plateau.rb +1 -2
- data/lib/pubid/renderers/directives_renderer.rb +1 -1
- data/lib/pubid/renderers/iwa_renderer.rb +1 -1
- data/lib/pubid/renderers/publisher_stripper.rb +82 -0
- data/lib/pubid/renderers/supplement_renderer.rb +1 -1
- data/lib/pubid/renderers.rb +1 -0
- data/lib/pubid/sae/identifiers/base.rb +1 -1
- data/lib/pubid/sae/parser.rb +1 -48
- data/lib/pubid/tgpp/identifier.rb +1 -1
- data/lib/pubid/tgpp/parser.rb +2 -59
- data/lib/pubid/un/identifier.rb +1 -1
- data/lib/pubid/un/parser.rb +1 -18
- data/lib/pubid/version.rb +1 -1
- data/lib/pubid/w3c/identifier.rb +1 -1
- data/lib/pubid/w3c/parser.rb +2 -27
- data/lib/pubid/xsf/identifier.rb +11 -7
- data/lib/pubid/xsf/parser.rb +3 -31
- data/lib/pubid/xsf/renderer.rb +2 -4
- data/lib/pubid.rb +8 -9
- metadata +15 -25
- data/lib/pubid/ieee/aiee/parser.rb +0 -130
- data/lib/pubid/ieee/ire/parser.rb +0 -109
- data/lib/pubid/ieee/nesc/parser.rb +0 -155
- data/lib/pubid/iho/parser.rb +0 -68
- data/lib/pubid/parser/common_parse_methods.rb +0 -13
- data/lib/pubid/parser/common_parse_rules.rb +0 -56
- data/lib/pubid/parser/grammar.rb +0 -96
|
@@ -64,9 +64,25 @@ module Pubid
|
|
|
64
64
|
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
parsed = Pubid::Parg::
|
|
67
|
+
parsed = Pubid::Parg::Model.bound(:ietf, strip_prefix(identifier))
|
|
68
68
|
Builder.build(parsed)
|
|
69
69
|
end
|
|
70
|
+
|
|
71
|
+
# The external spellings `IETF RFC 8341`, `IETF I-D draft-…`,
|
|
72
|
+
# `IETF Internet-Draft draft-…`, `Internet-Draft draft-…` and
|
|
73
|
+
# `I-D draft-…` name the same document as the bare form. The publisher
|
|
74
|
+
# and the draft label are not part of the identity, so they are dropped
|
|
75
|
+
# before the baked grammar runs, and `to_s(with_publisher:)` renders
|
|
76
|
+
# them again. The lookahead keeps the strip off anything that is not
|
|
77
|
+
# followed by an IETF designation.
|
|
78
|
+
EXTERNAL_PREFIX = /
|
|
79
|
+
\A(?:IETF\s+)?(?:(?:I-D|Internet-Draft)\s+(?=draft-))?
|
|
80
|
+
(?=(?:RFC|BCP|STD|FYI)\s|draft-)
|
|
81
|
+
/x
|
|
82
|
+
|
|
83
|
+
def self.strip_prefix(identifier)
|
|
84
|
+
identifier.sub(EXTERNAL_PREFIX, "")
|
|
85
|
+
end
|
|
70
86
|
end
|
|
71
87
|
end
|
|
72
88
|
end
|
data/lib/pubid/ietf/parser.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "parslet"
|
|
4
|
-
|
|
5
3
|
module Pubid
|
|
6
4
|
module Ietf
|
|
7
|
-
#
|
|
5
|
+
# Grammar for the three IETF families. The top-level alternation is
|
|
8
6
|
# keyed by leading token, and each alternative captures the raw fields the
|
|
9
7
|
# Builder needs:
|
|
10
8
|
# * RFC -> { number: }
|
|
@@ -12,50 +10,7 @@ module Pubid
|
|
|
12
10
|
# * draft -> { draft_rest: } (everything after "draft-"; the version
|
|
13
11
|
# split is done in the Builder to keep the digit-tail
|
|
14
12
|
# heuristic in one place)
|
|
15
|
-
class Parser
|
|
16
|
-
rule(:space) { str(" ") }
|
|
17
|
-
rule(:digit) { match["0-9"] }
|
|
18
|
-
rule(:digits) { digit.repeat(1) }
|
|
19
|
-
|
|
20
|
-
# "RFC 2119", and the zero-padded, space-less spelling the RFC editor's
|
|
21
|
-
# rfc-index.xml emits ("RFC0001"). See #subseries for why.
|
|
22
|
-
rule(:rfc) { str("RFC") >> space.maybe >> digits.as(:number) }
|
|
23
|
-
|
|
24
|
-
# "BCP 3" / "STD 66" / "FYI 1".
|
|
25
|
-
#
|
|
26
|
-
# The space is optional and leading zeros are allowed because
|
|
27
|
-
# rfc-index.xml writes the sub-series membership of an RFC in the padded,
|
|
28
|
-
# space-less form -- <is-also><doc-id>STD0066</doc-id></is-also> -- and
|
|
29
|
-
# relaton builds the STD/BCP/FYI <-> RFC cross-references from exactly
|
|
30
|
-
# those elements without normalizing them itself. The Builder strips the
|
|
31
|
-
# pad, so the printed form stays the canonical "STD 66".
|
|
32
|
-
rule(:subseries) do
|
|
33
|
-
(str("BCP") | str("STD") | str("FYI")).as(:series) >>
|
|
34
|
-
space.maybe >> digits.as(:number)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Internet-Draft slug characters: lowercase, digits and the "-", "+", "_"
|
|
38
|
-
# separators, plus two historical shapes the published corpus carries --
|
|
39
|
-
# a dot inside a topic token ("draft-ietf-pilc-2.5g3g-12") and uppercase
|
|
40
|
-
# letters in protocol/organisation names ("draft-chapin-clnp-ISO8473-00").
|
|
41
|
-
# 50 of the 166,740 relaton-data-ids identifiers need those two
|
|
42
|
-
# characters; nothing else outside [a-z0-9-] occurs in the corpus.
|
|
43
|
-
#
|
|
44
|
-
# Widening the tail is unambiguous: the top-level alternation is keyed on
|
|
45
|
-
# the leading token (RFC / BCP|STD|FYI / draft-), so a wider draft tail
|
|
46
|
-
# cannot make another branch match.
|
|
47
|
-
#
|
|
48
|
-
# The whole remainder after "draft-" is captured; the Builder anchors the
|
|
49
|
-
# optional trailing "-NN" version.
|
|
50
|
-
rule(:draft_rest) { match['a-zA-Z0-9+_.\-'].repeat(1).as(:draft_rest) }
|
|
51
|
-
rule(:draft) { str("draft-") >> draft_rest }
|
|
52
|
-
|
|
53
|
-
rule(:identifier) { rfc | subseries | draft }
|
|
54
|
-
rule(:root) { identifier }
|
|
55
|
-
|
|
56
|
-
def self.parse(input)
|
|
57
|
-
new.parse(input)
|
|
58
|
-
end
|
|
13
|
+
class Parser
|
|
59
14
|
end
|
|
60
15
|
end
|
|
61
16
|
end
|
data/lib/pubid/ietf/renderer.rb
CHANGED
|
@@ -8,10 +8,25 @@ module Pubid
|
|
|
8
8
|
# "RFC 2119"
|
|
9
9
|
# "BCP 3" / "STD 66" / "FYI 1"
|
|
10
10
|
# "draft-giuliano-treedn-02" / "draft-giuliano-treedn"
|
|
11
|
+
#
|
|
12
|
+
# The default form carries no publisher, which is how IETF documents are
|
|
13
|
+
# cited. `with_publisher:` names the two forms relaton#49 asks for:
|
|
14
|
+
# true -> "IETF RFC 2119" / "IETF Internet-Draft draft-giuliano-treedn"
|
|
15
|
+
# false -> "RFC 2119" / "Internet-Draft draft-giuliano-treedn"
|
|
16
|
+
# A draft slug says nothing about its type, so both explicit forms label
|
|
17
|
+
# it; the default keeps the bare slug.
|
|
11
18
|
class Renderer < ::Pubid::Renderers::Base
|
|
12
|
-
|
|
19
|
+
INTERNET_DRAFT_LABEL = "Internet-Draft"
|
|
20
|
+
|
|
21
|
+
def render(context: nil, with_publisher: nil, **_opts)
|
|
13
22
|
require_number!(@id)
|
|
14
|
-
render_identifier(@id)
|
|
23
|
+
result = render_identifier(@id)
|
|
24
|
+
return result if with_publisher.nil?
|
|
25
|
+
|
|
26
|
+
if @id.is_a?(Identifiers::InternetDraft)
|
|
27
|
+
result = "#{INTERNET_DRAFT_LABEL} #{result}"
|
|
28
|
+
end
|
|
29
|
+
with_publisher ? "#{Identifier::PUBLISHER} #{result}" : result
|
|
15
30
|
end
|
|
16
31
|
|
|
17
32
|
private
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# IHO flavor notes
|
|
2
|
+
|
|
3
|
+
IHO (International Hydrographic Organization) identifiers: the
|
|
4
|
+
S/P/M/B/C letter series, their parg-driven parse, and the series
|
|
5
|
+
components introduced for relaton/relaton#407.
|
|
6
|
+
|
|
7
|
+
Read this before you change `lib/pubid/iho/` or `spec/pubid/iho/`. The
|
|
8
|
+
root `CLAUDE.md` keeps the cross-flavor contract that every flavor
|
|
9
|
+
obeys.
|
|
10
|
+
|
|
11
|
+
## Parsing runs on the parg artifact
|
|
12
|
+
|
|
13
|
+
There is no `parser.rb` and no Parslet anywhere in this flavor: parse
|
|
14
|
+
goes `Pubid::Parg::Backend.parse(:iho, ...)` over the baked artifact
|
|
15
|
+
`data/parg/iho.json`. The artifact is baked from the flavor grammar
|
|
16
|
+
source `grammars/iho.parg` in the pubid-grammar repository (commit
|
|
17
|
+
840b2b1d "Retire the last live Parslet parsers" ports the original
|
|
18
|
+
13-rule Parslet grammar byte-identically).
|
|
19
|
+
|
|
20
|
+
**Drift warning:** the artifact on this branch carries the
|
|
21
|
+
relaton/relaton#407 tail rules (year / volume / issue), while the
|
|
22
|
+
checked-in `grammars/iho.parg` in pubid-grammar still has only the
|
|
23
|
+
version tail. Until the updated grammar source lands in pubid-grammar,
|
|
24
|
+
the artifact is ahead of its source and not reproducible from it —
|
|
25
|
+
the same artifact-ahead-of-source state the root notes describe under
|
|
26
|
+
grammar-artifact drift. Do not re-bake from the stale source, and do
|
|
27
|
+
not hand-edit the artifact; land the grammar first.
|
|
28
|
+
|
|
29
|
+
## Series components (relaton/relaton#407)
|
|
30
|
+
|
|
31
|
+
The B-7, P-1, P-5, P-6 series are continuously updated publications
|
|
32
|
+
whose trailing component is not an a.b.c edition: P-5/P-6 (and B-7)
|
|
33
|
+
are identified by **year**, P-1 (the International Hydrographic
|
|
34
|
+
Review) by **volume** with an optional **issue**. The grammar tail
|
|
35
|
+
picks exactly one discriminator, so only one of `version` / `year` /
|
|
36
|
+
`volume` is ever present, with `issue` only beside a volume:
|
|
37
|
+
|
|
38
|
+
- `IHO P-5 2024` — year is a plain `:string` scalar, 19xx/20xx only
|
|
39
|
+
(`IHO P-5 1850` and the two-segment `IHO B-12 3.0` reject).
|
|
40
|
+
- `IHO P-1 Vol. 30 (2)` — volume/issue scalars; `Issue 2` is an input
|
|
41
|
+
alias whose canonical print is `(2)`.
|
|
42
|
+
- `IHO S-44 5.0.0` — the dotted `version` stays for the edition-bearing
|
|
43
|
+
series and legacy `1.0.0` placeholder rows.
|
|
44
|
+
|
|
45
|
+
All three are ordinary lutaml attributes mapped through the flavor's
|
|
46
|
+
`key_value` block — no converters, no hand-rolled serialization.
|
|
47
|
+
|
|
48
|
+
**Identity semantics:** the components are identity-bearing in `==` /
|
|
49
|
+
`matches?`; a bare reference (`IHO P-5`) leaves all four nil and
|
|
50
|
+
wildcards the series (`spec/pubid/partial_ref_spec.rb` locks the
|
|
51
|
+
omitted-key set). The volume/issue pair is a scalar **cluster**:
|
|
52
|
+
`exclude(:volume)` nils the issue with it, so a reference that omits
|
|
53
|
+
the issue still matches a hit that carries one — the same
|
|
54
|
+
additive-alias shape the base applies to `:year -> :date`. A lone
|
|
55
|
+
scalar `:year` needs no override; the base nils a declared `:year`
|
|
56
|
+
directly.
|
|
57
|
+
|
|
58
|
+
**`all_parts_edition_keys` needs no override:** the default
|
|
59
|
+
`%i[date year edition version]` already covers `:year` and `version`,
|
|
60
|
+
and volume/issue only ride part-less ids, which cannot join
|
|
61
|
+
`AllParts` (`validate_member!` demands a `part`). The audit case in
|
|
62
|
+
`spec/pubid/all_parts_edition_keys_audit_spec.rb` covers the
|
|
63
|
+
version-bearing S-series collapse.
|
|
64
|
+
|
|
65
|
+
**URN conventions:** a year is a bare trailing segment
|
|
66
|
+
(`urn:iho:p:5:2024`); a volume is a `vol.N` part-kind segment with an
|
|
67
|
+
optional `issue.N` after it (`urn:iho:p:1:vol.30.issue.2`). The URN
|
|
68
|
+
parser walks part-kind tokens as a run and hands the rebuilt text
|
|
69
|
+
back through the flavor parse, so a URN round-trip exercises the same
|
|
70
|
+
grammar as a string.
|
|
71
|
+
|
|
72
|
+
## Fixtures run through the classify pipeline
|
|
73
|
+
|
|
74
|
+
`spec/fixtures/iho/identifiers/full/identifiers.txt` is the input
|
|
75
|
+
corpus; `rake "validation:classify[iho]"` regenerates `pass/` and
|
|
76
|
+
`fail/` from it (the classifier deletes pass/fail first — never edit
|
|
77
|
+
them by hand, edit `full/` and reclassify). Before the relaton#407
|
|
78
|
+
branch the pass files were hand-curated with **no** `full/` tree, the
|
|
79
|
+
OASIS hazard: a classify run would have wiped 131 entries and rebuilt
|
|
80
|
+
nothing. The classifier's per-flavor class detection has an `iho`
|
|
81
|
+
case, so fail entries land in a named class file.
|
|
82
|
+
|
|
83
|
+
## relaton-side follow-ups (not this repo)
|
|
84
|
+
|
|
85
|
+
relaton/relaton#407 items 3-4 stay open on the relaton side:
|
|
86
|
+
`Relaton::Iho::Bibliography#pubid_match?`/`version_key` need the new
|
|
87
|
+
component, relaton-cli's `MachineIndex::REFINABLE_KEYS` needs
|
|
88
|
+
`year`/`volume` beside `version`/`edition`, and the pubid pin bumps
|
|
89
|
+
in step with the relaton-data-iho re-key.
|
data/lib/pubid/iho/builder.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Iho
|
|
5
|
-
# Build IHO identifier objects from
|
|
5
|
+
# Build IHO identifier objects from the parg artifact parse tree.
|
|
6
6
|
class Builder
|
|
7
7
|
def build(parsed)
|
|
8
8
|
hash = parsed.is_a?(Array) ? parsed.reduce({}, :merge) : parsed
|
|
@@ -15,6 +15,9 @@ module Pubid
|
|
|
15
15
|
annex: stringify(hash[:annex]),
|
|
16
16
|
supplement: stringify(hash[:supplement]),
|
|
17
17
|
version: stringify(hash[:version]),
|
|
18
|
+
year: stringify(hash[:year]),
|
|
19
|
+
volume: stringify(hash[:volume]),
|
|
20
|
+
issue: stringify(hash[:issue]),
|
|
18
21
|
}.compact
|
|
19
22
|
|
|
20
23
|
Pubid::Iho.identifier_klass_for_type_letter(type_letter).new(**attrs)
|
|
@@ -6,7 +6,7 @@ module Pubid
|
|
|
6
6
|
# every concrete IHO identifier (Identifiers::*) descends from it.
|
|
7
7
|
#
|
|
8
8
|
# IHO identifiers have the form:
|
|
9
|
-
# IHO {S|P|M|B|C}-{number}[ Ap. {appendix}][ Part {part}][ Annex {annex}][ Suppl {supplement}][ {version}]
|
|
9
|
+
# IHO {S|P|M|B|C}-{number}[ Ap. {appendix}][ Part {part}][ Annex {annex}][ Suppl {supplement}][ {version|year|Vol. N [issue]}]
|
|
10
10
|
#
|
|
11
11
|
# The leading IHO publisher prefix is optional on input but always
|
|
12
12
|
# emitted on output.
|
|
@@ -25,7 +25,7 @@ module Pubid
|
|
|
25
25
|
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
parsed =
|
|
28
|
+
parsed = Pubid::Parg::Backend.parse(:iho, identifier.strip)
|
|
29
29
|
Builder.build(parsed)
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -40,6 +40,17 @@ module Pubid
|
|
|
40
40
|
attribute :annex, :string
|
|
41
41
|
attribute :supplement, :string
|
|
42
42
|
attribute :version, :string
|
|
43
|
+
# Series identity components (relaton#407): the year-identified
|
|
44
|
+
# series (B-7, P-5, P-6) and the volume/issue-identified ones
|
|
45
|
+
# (P-1 and the IHR) carry no a.b.c edition — their trailing
|
|
46
|
+
# component is a year or a "Vol. N [issue]" pair instead. Plain
|
|
47
|
+
# scalars, like every other IHO attribute; the grammar's tail
|
|
48
|
+
# picks exactly one discriminator, so only one of
|
|
49
|
+
# version/year/volume is ever present, with issue only
|
|
50
|
+
# alongside volume.
|
|
51
|
+
attribute :year, :string
|
|
52
|
+
attribute :volume, :string
|
|
53
|
+
attribute :issue, :string
|
|
43
54
|
|
|
44
55
|
# Polymorphic type map for lutaml::Model key_value serialization: maps
|
|
45
56
|
# each subclass's polymorphic_name to its class name so `from_hash` can
|
|
@@ -65,10 +76,25 @@ module Pubid
|
|
|
65
76
|
map "annex", to: :annex
|
|
66
77
|
map "supplement", to: :supplement
|
|
67
78
|
map "version", to: :version
|
|
79
|
+
map "year", to: :year
|
|
80
|
+
map "volume", to: :volume
|
|
81
|
+
map "issue", to: :issue
|
|
68
82
|
end
|
|
69
83
|
|
|
70
84
|
# from_hash is the shared polymorphic dispatch on Pubid::Identifier.
|
|
71
85
|
# IHO_TYPE_MAP remains as the key_value polymorphic_map.
|
|
86
|
+
|
|
87
|
+
# The volume/issue pair is a scalar cluster: a reference that omits
|
|
88
|
+
# the issue must still match a hit that carries one ("P-1 Vol. 30"
|
|
89
|
+
# vs "P-1 Vol. 30 (2)"), so excluding the volume nils the issue
|
|
90
|
+
# with it — the same additive-alias shape the base applies to
|
|
91
|
+
# :year -> :date. A lone scalar :year needs no override here: the
|
|
92
|
+
# base nils a declared :year directly (the additive :year -> :date
|
|
93
|
+
# alias nils whichever name the flavor declares).
|
|
94
|
+
def exclude(*args)
|
|
95
|
+
args << :issue if args.include?(:volume)
|
|
96
|
+
super
|
|
97
|
+
end
|
|
72
98
|
end
|
|
73
99
|
|
|
74
100
|
end
|
data/lib/pubid/iho/renderer.rb
CHANGED
|
@@ -9,6 +9,8 @@ module Pubid
|
|
|
9
9
|
# "IHO P-1/21 1.0.0"
|
|
10
10
|
# "IHO S-100 Part 4a 1.0.0"
|
|
11
11
|
# "IHO S-65 Ap. A 1.0.0"
|
|
12
|
+
# "IHO P-5 2024" (year-identified series)
|
|
13
|
+
# "IHO P-1 Vol. 30 (2)" (volume/issue-identified series)
|
|
12
14
|
#
|
|
13
15
|
# The renderer is registered as the `:human` format in the IHO format
|
|
14
16
|
# registry and invoked via `render(format: :human)`.
|
|
@@ -22,7 +24,13 @@ module Pubid
|
|
|
22
24
|
rendered << " Part #{id.part}" if id.part
|
|
23
25
|
rendered << " Annex #{id.annex}" if id.annex
|
|
24
26
|
rendered << " Suppl #{id.supplement}" if id.supplement
|
|
27
|
+
# The grammar's tail picks exactly one discriminator: a dotted
|
|
28
|
+
# version, a year, or a volume (with an optional issue). The
|
|
29
|
+
# issue prints parenthesized; "Issue N" is an input alias only.
|
|
25
30
|
rendered << " #{id.version}" if id.version
|
|
31
|
+
rendered << " #{id.year}" if id.year
|
|
32
|
+
rendered << " Vol. #{id.volume}" if id.volume
|
|
33
|
+
rendered << " (#{id.issue})" if id.issue
|
|
26
34
|
rendered
|
|
27
35
|
end
|
|
28
36
|
end
|
|
@@ -4,8 +4,13 @@ module Pubid
|
|
|
4
4
|
module Iho
|
|
5
5
|
# Generates URN strings for IHO identifiers.
|
|
6
6
|
#
|
|
7
|
-
# Format: urn:iho:{type-letter-lowercase}:{number}[:ap.{appendix}][:part.{part}][:annex.{annex}][:suppl.{supplement}][:{version}]
|
|
7
|
+
# Format: urn:iho:{type-letter-lowercase}:{number}[:ap.{appendix}][:part.{part}][:annex.{annex}][:suppl.{supplement}][:{version|year|vol.N[.issue.N]}]
|
|
8
8
|
# Example: urn:iho:s:44:5.0.0 for "IHO S-44 5.0.0".
|
|
9
|
+
# The trailing series component follows the flavor's own conventions:
|
|
10
|
+
# a version or a year is a bare segment ("urn:iho:p:5:2024" for
|
|
11
|
+
# "IHO P-5 2024"), a volume is a vol.N part-kind segment with an
|
|
12
|
+
# optional issue.N after it ("urn:iho:p:1:vol.30.issue.2" for
|
|
13
|
+
# "IHO P-1 Vol. 30 (2)").
|
|
9
14
|
class UrnGenerator
|
|
10
15
|
attr_reader :identifier
|
|
11
16
|
|
|
@@ -21,7 +26,12 @@ module Pubid
|
|
|
21
26
|
parts << "part.#{identifier.part}" if identifier.part
|
|
22
27
|
parts << "annex.#{identifier.annex}" if identifier.annex
|
|
23
28
|
parts << "suppl.#{identifier.supplement}" if identifier.supplement
|
|
29
|
+
# The grammar's tail picks exactly one discriminator; a volume
|
|
30
|
+
# may carry an issue beside it.
|
|
31
|
+
parts << "vol.#{identifier.volume}" if identifier.volume
|
|
32
|
+
parts << "issue.#{identifier.issue}" if identifier.issue
|
|
24
33
|
parts << identifier.version.to_s if identifier.version
|
|
34
|
+
parts << identifier.year.to_s if identifier.year
|
|
25
35
|
parts.join(":")
|
|
26
36
|
end
|
|
27
37
|
end
|
data/lib/pubid/iho/urn_parser.rb
CHANGED
|
@@ -5,22 +5,28 @@ module Pubid
|
|
|
5
5
|
# Parses IHO URNs back into identifiers.
|
|
6
6
|
#
|
|
7
7
|
# UrnGenerator emits:
|
|
8
|
-
# urn:iho:{type-lower}:{number}[:{part-kind}.{part}]:{version}
|
|
8
|
+
# urn:iho:{type-lower}:{number}[:{part-kind}.{part}][:{version|year|vol.N[.issue.N]}]
|
|
9
9
|
#
|
|
10
10
|
# type-lower is the lowercase series letter (s, p, m, b, c).
|
|
11
|
-
# part-kind is the lowercase part label (part, ap, annex, suppl
|
|
12
|
-
#
|
|
11
|
+
# part-kind is the lowercase part label (part, ap, annex, suppl,
|
|
12
|
+
# vol, issue).
|
|
13
|
+
# The trailing segment is the dotted version, a bare year, or the
|
|
14
|
+
# volume/issue pair.
|
|
13
15
|
#
|
|
14
16
|
# Examples:
|
|
15
17
|
# - urn:iho:s:44:5.0.0 → IHO S-44 5.0.0
|
|
16
18
|
# - urn:iho:s:100:part.4a:1.0.0 → IHO S-100 Part 4a 1.0.0
|
|
17
19
|
# - urn:iho:s:65:ap.A:1.0.0 → IHO S-65 Ap. A 1.0.0
|
|
20
|
+
# - urn:iho:p:5:2024 → IHO P-5 2024
|
|
21
|
+
# - urn:iho:p:1:vol.30 → IHO P-1 Vol. 30
|
|
22
|
+
# - urn:iho:p:1:vol.30.issue.2 → IHO P-1 Vol. 30 (2)
|
|
18
23
|
class UrnParser < Pubid::UrnParser::Base
|
|
19
24
|
PART_LABELS = {
|
|
20
25
|
"part" => "Part",
|
|
21
26
|
"ap" => "Ap.",
|
|
22
27
|
"annex" => "Annex",
|
|
23
28
|
"suppl" => "Suppl",
|
|
29
|
+
"vol" => "Vol.",
|
|
24
30
|
}.freeze
|
|
25
31
|
|
|
26
32
|
def parse_urn(urn)
|
|
@@ -30,18 +36,22 @@ module Pubid
|
|
|
30
36
|
type_token = parts.fetch(0).upcase
|
|
31
37
|
number = parts.fetch(1)
|
|
32
38
|
idx = 2
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
text = "IHO #{type_token}-#{number}"
|
|
40
|
+
# Part-kind tokens come in any run (a volume may carry an issue
|
|
41
|
+
# beside it); "issue.N" prints as the parenthesized issue.
|
|
42
|
+
while parts[idx] && part_kind?(parts[idx])
|
|
35
43
|
kind, value = parts[idx].split(".", 2)
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
if kind.downcase == "issue"
|
|
45
|
+
text += " (#{value})"
|
|
46
|
+
else
|
|
47
|
+
label = PART_LABELS.fetch(kind.downcase) { kind.capitalize }
|
|
48
|
+
text += " #{label} #{value}"
|
|
49
|
+
end
|
|
38
50
|
idx += 1
|
|
39
51
|
end
|
|
40
|
-
|
|
52
|
+
tail = parts[idx]
|
|
41
53
|
|
|
42
|
-
text
|
|
43
|
-
text += " #{part_label}" if part_label
|
|
44
|
-
text += " #{version}" if version
|
|
54
|
+
text += " #{tail}" if tail
|
|
45
55
|
flavor_parse(text)
|
|
46
56
|
end
|
|
47
57
|
|
data/lib/pubid/iho.rb
CHANGED
|
@@ -11,7 +11,6 @@ module Pubid
|
|
|
11
11
|
autoload :Builder, "#{__dir__}/iho/builder"
|
|
12
12
|
autoload :Identifier, "#{__dir__}/iho/identifiers/base"
|
|
13
13
|
autoload :Identifiers, "#{__dir__}/iho/identifiers"
|
|
14
|
-
autoload :Parser, "#{__dir__}/iho/parser"
|
|
15
14
|
autoload :Renderer, "#{__dir__}/iho/renderer"
|
|
16
15
|
autoload :UrnGenerator, "#{__dir__}/iho/urn_generator"
|
|
17
16
|
autoload :UrnParser, "#{__dir__}/iho/urn_parser"
|
|
@@ -64,7 +64,7 @@ module Pubid
|
|
|
64
64
|
|
|
65
65
|
# @raise [Pubid::Errors::ParseError] if the string is not a valid ISBN
|
|
66
66
|
def self.build_identifier(identifier)
|
|
67
|
-
parsed = Pubid::Parg::
|
|
67
|
+
parsed = Pubid::Parg::Model.bound(:isbn, identifier)
|
|
68
68
|
Builder.build(parsed)
|
|
69
69
|
rescue ArgumentError => e
|
|
70
70
|
# The Builder validates length and check digit. Surface that as a parse
|
data/lib/pubid/isbn/parser.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "parslet"
|
|
4
|
-
|
|
5
3
|
module Pubid
|
|
6
4
|
module Isbn
|
|
7
|
-
#
|
|
5
|
+
# Grammar for ISBN identifiers per ISO 2108.
|
|
8
6
|
#
|
|
9
7
|
# Accepts:
|
|
10
8
|
# [ISBN[ |:]] {10 or 13 digits with optional hyphens}
|
|
@@ -12,34 +10,7 @@ module Pubid
|
|
|
12
10
|
# ISBN-10 final digit may be "X" (check digit value 10). Hyphens are
|
|
13
11
|
# optional and may appear in any group position; they are preserved
|
|
14
12
|
# for round-trip rendering.
|
|
15
|
-
class Parser
|
|
16
|
-
rule(:space) { str(" ") }
|
|
17
|
-
rule(:colon) { str(":") }
|
|
18
|
-
rule(:hyphen) { str("-") }
|
|
19
|
-
rule(:digit) { match("[0-9]") }
|
|
20
|
-
rule(:x_digit) { digit | str("X") }
|
|
21
|
-
|
|
22
|
-
rule(:isbn_prefix) do
|
|
23
|
-
(str("ISBN") >> (space | (space.maybe >> colon >> space.maybe))).maybe
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
rule(:digit_group) { digit.repeat(1) }
|
|
27
|
-
rule(:x_group) { x_digit.repeat(1) }
|
|
28
|
-
|
|
29
|
-
# Loose "ISBN-ish" token: digits or final X, separated by hyphens.
|
|
30
|
-
rule(:isbn_body) do
|
|
31
|
-
(digit_group >> (hyphen >> digit_group).repeat >>
|
|
32
|
-
(hyphen >> x_digit).maybe).as(:body) |
|
|
33
|
-
(digit.repeat(9, 12) >> x_digit.maybe).as(:body)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
rule(:identifier) { isbn_prefix >> isbn_body }
|
|
37
|
-
|
|
38
|
-
rule(:root) { identifier }
|
|
39
|
-
|
|
40
|
-
def self.parse(input)
|
|
41
|
-
new.parse(input)
|
|
42
|
-
end
|
|
13
|
+
class Parser
|
|
43
14
|
end
|
|
44
15
|
end
|
|
45
16
|
end
|
data/lib/pubid/iso/builder.rb
CHANGED
|
@@ -308,7 +308,7 @@ module Pubid
|
|
|
308
308
|
when :number
|
|
309
309
|
# For TC documents, number is the document number (N number)
|
|
310
310
|
# For regular identifiers, this is handled in :number_with_part
|
|
311
|
-
if value.is_a?(
|
|
311
|
+
if value.is_a?(String) ||
|
|
312
312
|
value.is_a?(Integer)
|
|
313
313
|
value.to_s
|
|
314
314
|
else
|
|
@@ -35,9 +35,12 @@ module Pubid
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def to_s(**opts)
|
|
38
|
-
context = build_rendering_context(
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
context = build_rendering_context(
|
|
39
|
+
nil, format: :human, **opts.except(:with_publisher)
|
|
40
|
+
)
|
|
41
|
+
rendered = Pubid::Renderers::DirectivesRenderer.new(self)
|
|
42
|
+
.render(context:, **opts.slice(:with_edition))
|
|
43
|
+
apply_with_publisher(rendered, :human, opts[:with_publisher])
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
# Directives use a different URN scheme: urn:iso:doc instead of urn:iso:std
|
|
@@ -91,9 +91,12 @@ module Pubid
|
|
|
91
91
|
def to_s(**opts)
|
|
92
92
|
return super if opts[:format] && opts[:format] != :human
|
|
93
93
|
|
|
94
|
-
context = build_rendering_context(
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
context = build_rendering_context(
|
|
95
|
+
nil, format: :human, **opts.except(:with_publisher)
|
|
96
|
+
)
|
|
97
|
+
rendered = Pubid::Renderers::GuideRenderer.new(self)
|
|
98
|
+
.render(context:, **opts.slice(:with_edition))
|
|
99
|
+
apply_with_publisher(rendered, :human, opts[:with_publisher])
|
|
97
100
|
end
|
|
98
101
|
end
|
|
99
102
|
end
|
|
@@ -54,7 +54,9 @@ module Pubid
|
|
|
54
54
|
code: :wds,
|
|
55
55
|
stage_code: :wds,
|
|
56
56
|
type_code: :is,
|
|
57
|
-
|
|
57
|
+
# ISO launched the "WD Study" WG consultation (stage 20.20/20.60,
|
|
58
|
+
# relaton-iso#125); its spelled-out form is an accepted input spelling.
|
|
59
|
+
abbr: ["WDS", "WD Study"],
|
|
58
60
|
name: "Working Draft Study",
|
|
59
61
|
harmonized_stages: %w[20.20 20.60],
|
|
60
62
|
),
|
|
@@ -85,9 +85,12 @@ module Pubid
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
def to_s(**opts)
|
|
88
|
-
context = build_rendering_context(
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
context = build_rendering_context(
|
|
89
|
+
nil, format: :human, **opts.except(:with_publisher)
|
|
90
|
+
)
|
|
91
|
+
rendered = Pubid::Renderers::IwaRenderer.new(self)
|
|
92
|
+
.render(context:, **opts.slice(:with_edition))
|
|
93
|
+
apply_with_publisher(rendered, :human, opts[:with_publisher])
|
|
91
94
|
end
|
|
92
95
|
end
|
|
93
96
|
end
|
data/lib/pubid/iso/normalizer.rb
CHANGED
|
@@ -71,7 +71,7 @@ module Pubid
|
|
|
71
71
|
supplement_number = match[3] # "1"
|
|
72
72
|
supplement_year = match[4] # "1987" or nil
|
|
73
73
|
|
|
74
|
-
base_parsed = Pubid::
|
|
74
|
+
base_parsed = Pubid::Parg::Backend.parse(:iso, base_str)
|
|
75
75
|
base = Pubid::Iso.builder.build(base_parsed)
|
|
76
76
|
|
|
77
77
|
addendum = Identifiers::Addendum.new
|