pubid 2.0.1 → 2.0.3
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/ieee/update_codes.yaml +2 -2
- data/data/parg/adobe.json +1 -1
- data/data/parg/aiee.json +1 -1
- data/data/parg/aiee.parg +99 -9
- 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/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/ecma.json +1 -1
- data/data/parg/etsi.json +1 -1
- data/data/parg/evs.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 -1
- data/data/parg/iho.json +1 -0
- data/data/parg/ire.json +1 -1
- data/data/parg/ire.parg +51 -12
- data/data/parg/isbn.json +1 -1
- data/data/parg/iso.json +1 -1
- data/data/parg/iso.parg +751 -0
- 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/nesc.parg +97 -12
- data/data/parg/nist.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/tgpp.json +1 -1
- data/data/parg/un.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/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/parser.rb +2 -37
- data/lib/pubid/easc/builder.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 +6 -0
- 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/builder.rb +48 -39
- data/lib/pubid/iala/identifier.rb +1 -1
- 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/CLAUDE.md +2 -0
- data/lib/pubid/ieee/aiee/identifier.rb +2 -1
- data/lib/pubid/ieee/aiee.rb +0 -1
- data/lib/pubid/ieee/builder.rb +177 -30
- data/lib/pubid/ieee/components/code.rb +46 -7
- data/lib/pubid/ieee/identifiers/base.rb +5 -2
- data/lib/pubid/ieee/identifiers/joint_development.rb +7 -3
- data/lib/pubid/ieee/ire/identifier.rb +2 -1
- data/lib/pubid/ieee/ire.rb +0 -1
- data/lib/pubid/ieee/nesc/builder.rb +1 -1
- data/lib/pubid/ieee/nesc.rb +0 -1
- data/lib/pubid/ieee/parser.rb +1 -1257
- data/lib/pubid/ieee/project_renderer.rb +5 -1
- data/lib/pubid/ieee/renderer.rb +4 -2
- data/lib/pubid/ieee/urn_generator.rb +4 -1
- 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 +98 -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 +14 -1
- data/lib/pubid/iho/urn_parser.rb +45 -32
- 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/CLAUDE.md +66 -0
- data/lib/pubid/iso/builder.rb +229 -15
- data/lib/pubid/iso/identifier.rb +22 -12
- data/lib/pubid/iso/identifiers/directives.rb +6 -3
- data/lib/pubid/iso/identifiers/guide.rb +6 -3
- data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +6 -3
- data/lib/pubid/iso/normalizer.rb +5 -4
- data/lib/pubid/iso/parser.rb +1 -539
- data/lib/pubid/iso/supplement_identifier.rb +6 -3
- data/lib/pubid/iso/urn_parser.rb +13 -8
- 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/CLAUDE.md +36 -0
- data/lib/pubid/jis/builder.rb +30 -41
- data/lib/pubid/jis/identifier.rb +12 -2
- data/lib/pubid/jis/identifiers/welding_engineering_standard.rb +35 -0
- data/lib/pubid/jis/identifiers.rb +2 -0
- data/lib/pubid/jis/parser.rb +1 -128
- data/lib/pubid/jis/renderer.rb +1 -1
- data/lib/pubid/jis/supplement_identifier.rb +5 -5
- data/lib/pubid/jis/urn_parser.rb +2 -1
- data/lib/pubid/jis.rb +3 -2
- 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/parser.rb +2 -16
- 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/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/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/parser.rb +2 -27
- data/lib/pubid/xsf/identifier.rb +10 -6
- data/lib/pubid/xsf/parser.rb +3 -31
- data/lib/pubid/xsf/renderer.rb +2 -4
- data/lib/pubid.rb +8 -9
- metadata +9 -23
- 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
data/lib/pubid/iala/builder.rb
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Iala
|
|
5
|
-
# Builds an IALA identifier object from the
|
|
5
|
+
# Builds an IALA identifier object from the artifact's bound map —
|
|
6
|
+
# the flat attribute hash the grammar's bindings block declares
|
|
7
|
+
# (typed at the engine, one key per capture).
|
|
6
8
|
class Builder
|
|
7
9
|
def build(parsed)
|
|
8
|
-
|
|
10
|
+
return build_annex(parsed) if parsed[:annex_marker]
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type_letter = stringify(hash[:type_letter])&.upcase
|
|
12
|
+
type_letter = stringify(parsed[:type_letter])&.upcase
|
|
13
13
|
klass = Iala.identifier_klass_for_type_letter(type_letter)
|
|
14
14
|
|
|
15
15
|
attrs = {
|
|
16
|
-
number:
|
|
17
|
-
edition:
|
|
18
|
-
language: stringify(
|
|
16
|
+
number: build_number(parsed, klass),
|
|
17
|
+
edition: stringify(parsed[:edition_value]),
|
|
18
|
+
language: stringify(parsed[:language])&.upcase,
|
|
19
19
|
}.compact
|
|
20
20
|
|
|
21
21
|
klass.new(**attrs)
|
|
@@ -27,31 +27,34 @@ module Pubid
|
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
-
# Build the Annex wrapper from the
|
|
31
|
-
#
|
|
32
|
-
#
|
|
30
|
+
# Build the Annex wrapper from the bound map. The base composite in
|
|
31
|
+
# the grammar stays unbound: its captures (type_letter, doc_number,
|
|
32
|
+
# …) are the only occurrence of each name on the annex path, so the
|
|
33
|
+
# harvest hoists them to the top level and this branch reads the
|
|
34
|
+
# same flat keys as the plain identifier.
|
|
33
35
|
def build_annex(hash)
|
|
34
|
-
|
|
35
|
-
base_type = stringify(base_hash[:type_letter])&.upcase
|
|
36
|
+
base_type = stringify(hash[:type_letter])&.upcase
|
|
36
37
|
base_klass = Iala.identifier_klass_for_type_letter(base_type)
|
|
37
|
-
base = base_klass.new(number: build_number(
|
|
38
|
+
base = base_klass.new(number: build_number(hash, base_klass))
|
|
38
39
|
|
|
39
40
|
attrs = {
|
|
40
41
|
base: base,
|
|
41
|
-
annex_form:
|
|
42
|
-
letter:
|
|
43
|
-
edition:
|
|
44
|
-
language:
|
|
42
|
+
annex_form: stringify(hash[:annex_marker]),
|
|
43
|
+
letter: stringify(hash[:annex_letter]),
|
|
44
|
+
edition: stringify(hash[:edition_value]),
|
|
45
|
+
language: stringify(hash[:language])&.upcase,
|
|
45
46
|
}.compact
|
|
46
47
|
|
|
47
48
|
Identifiers::Annex.new(**attrs)
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
# Compose the full number string from the
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
51
|
+
# Compose the full number string from the bound map's three number
|
|
52
|
+
# columns: the leading doc_number, the dotted continuation segments
|
|
53
|
+
# (doc_number_dots, each `{dotted_segment: "01"}`) and the dash
|
|
54
|
+
# subpart runs (subpart, each `{subpart_number: "1"}`). GA01.13 →
|
|
55
|
+
# "01.13", L2.1.11 → "2.1.11", C0103-1 → "0103-1", L2.7.1-2 →
|
|
56
|
+
# "2.7.1-2". Padding is class-driven: the resolved identifier class
|
|
57
|
+
# declares its canonical IALA cover-page widths via
|
|
55
58
|
# `canonical_number_width` / `canonical_dotted_segment_width`, so a
|
|
56
59
|
# new type declares its padding on its own class instead of editing
|
|
57
60
|
# this builder's case statement.
|
|
@@ -61,34 +64,40 @@ module Pubid
|
|
|
61
64
|
|
|
62
65
|
base = canonical_base(base, klass)
|
|
63
66
|
|
|
64
|
-
dots =
|
|
65
|
-
|
|
67
|
+
dots = canonical_dots(
|
|
68
|
+
number_segments(hash[:doc_number_dots], :dotted_segment), klass
|
|
69
|
+
)
|
|
70
|
+
dots_str = dots.empty? ? "" : ".#{dots}"
|
|
71
|
+
|
|
72
|
+
subparts = number_segments(hash[:subpart], :subpart_number)
|
|
73
|
+
subpart_str = subparts.empty? ? "" : "-#{subparts.join('-')}"
|
|
66
74
|
|
|
67
|
-
subpart_str = hash[:subpart] ? "-#{subpart_to_s(hash[:subpart])}" : ""
|
|
68
|
-
subpart_str = "" if subpart_str == "-"
|
|
69
75
|
"#{base}#{dots_str}#{subpart_str}"
|
|
70
76
|
end
|
|
71
77
|
|
|
78
|
+
# The string values of a bound repetition column: doc_number_dots
|
|
79
|
+
# carries `{dotted_segment: …}` elements, subpart carries
|
|
80
|
+
# `{subpart_number: …}` elements.
|
|
81
|
+
def number_segments(column, key)
|
|
82
|
+
Array(column).filter_map do |element|
|
|
83
|
+
stringify(element[key]) if element.is_a?(Hash)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
72
87
|
def canonical_base(base, klass)
|
|
73
88
|
width = klass&.canonical_number_width
|
|
74
89
|
width ? base.rjust(width, "0") : base
|
|
75
90
|
end
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
# Pad each dotted continuation segment to the class's canonical
|
|
93
|
+
# width; nil width preserves the segments verbatim (the L series).
|
|
94
|
+
def canonical_dots(segments, klass)
|
|
95
|
+
return "" if segments.empty?
|
|
79
96
|
|
|
80
97
|
width = klass&.canonical_dotted_segment_width
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
str.split(".", -1).map { |seg| seg.empty? ? seg : seg.rjust(width, "0") }.join(".")
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# The subpart capture may be an array of {subpart_number: "1"},
|
|
87
|
-
# {subpart_number: "9"}, … (repeated matches). Join with "-".
|
|
88
|
-
def subpart_to_s(subpart)
|
|
89
|
-
return stringify(subpart).to_s unless subpart.is_a?(Array)
|
|
98
|
+
return segments.join(".") unless width
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
segments.map { |segment| segment.rjust(width, "0") }.join(".")
|
|
92
101
|
end
|
|
93
102
|
|
|
94
103
|
def stringify(value)
|
data/lib/pubid/iala/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 Iala
|
|
7
|
-
#
|
|
5
|
+
# Grammar for IALA identifiers.
|
|
8
6
|
#
|
|
9
7
|
# Accepts every shape that appears on the IALA website or cover page:
|
|
10
8
|
#
|
|
@@ -15,124 +13,7 @@ module Pubid
|
|
|
15
13
|
# R1016:ed2.0(F)
|
|
16
14
|
# C0103-1
|
|
17
15
|
# C0103-1 Ed 3.0
|
|
18
|
-
class Parser
|
|
19
|
-
include ::Pubid::Parser::CommonParseRules
|
|
20
|
-
|
|
21
|
-
root :identifier
|
|
22
|
-
|
|
23
|
-
rule(:space) { str(" ") }
|
|
24
|
-
rule(:space?) { space.maybe }
|
|
25
|
-
rule(:dash) { str("-") }
|
|
26
|
-
rule(:colon) { str(":") }
|
|
27
|
-
rule(:dot) { str(".") }
|
|
28
|
-
rule(:lparen) { str("(") }
|
|
29
|
-
rule(:rparen) { str(")") }
|
|
30
|
-
|
|
31
|
-
# Type prefix — IALA document classes. GA must be tried before G so it
|
|
32
|
-
# isn't left with a dangling "A". Captured to route to the right
|
|
33
|
-
# Identifiers::* subclass.
|
|
34
|
-
rule(:type_letter) do
|
|
35
|
-
(str("GA") | str("S") | str("R") | str("G") | str("M") |
|
|
36
|
-
str("C") | str("A") | str("L") | str("X") | str("P")).as(:type_letter)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Document number — one or more decimal digits. The dataset historically
|
|
40
|
-
# zero-pads to 4 (S1070, R0126, M0001) but the canonical form going
|
|
41
|
-
# forward is unpadded (M1, GA1.1). Accept any length so both shapes
|
|
42
|
-
# round-trip; the number attribute preserves whatever was parsed.
|
|
43
|
-
rule(:doc_number) do
|
|
44
|
-
match("[0-9]").repeat(1).as(:doc_number)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Dotted continuation that some prefixes use as part of the number:
|
|
48
|
-
# GA01.01 (series.index), L2.1.11 (series.sub.item). Captured into the
|
|
49
|
-
# same :doc_number atom so the number attribute stays a single string
|
|
50
|
-
# (e.g. "01.01", "2.1.11") and the existing builder needs no changes.
|
|
51
|
-
rule(:dotted_number) do
|
|
52
|
-
dot >> match("[0-9]").repeat(1)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
rule(:number_with_dots) do
|
|
56
|
-
doc_number >> dotted_number.repeat.as(:doc_number_dots)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Numeric sub-part suffix(es): "-1", "-9-10", "-11". The catalogue
|
|
60
|
-
# uses ranges like R0124-9-10 & 11 — that compound is captured
|
|
61
|
-
# verbatim and resolved by the caller, not here.
|
|
62
|
-
rule(:subpart) do
|
|
63
|
-
(dash >> digits.as(:subpart_number)).repeat(1).as(:subpart)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
rule(:code) do
|
|
67
|
-
type_letter >> number_with_dots >> subpart.maybe
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Edition forms observed on covers and listing pages:
|
|
71
|
-
# " Ed 2.0" — cover-page human form
|
|
72
|
-
# ":ed2.0" — listing-page compact form
|
|
73
|
-
rule(:edition_human) do
|
|
74
|
-
(space >> str("Ed") >> space >> edition_value).as(:edition)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
rule(:edition_compact) do
|
|
78
|
-
(colon >> str("ed") >> edition_value).as(:edition)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
rule(:edition_value) do
|
|
82
|
-
(digits >> (dot >> digits).repeat).as(:edition_value)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
rule(:edition) do
|
|
86
|
-
edition_human | edition_compact
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Language is a single uppercase letter inside parens: (E), (F), …
|
|
90
|
-
rule(:language) do
|
|
91
|
-
(space? >> lparen >>
|
|
92
|
-
(str("E") | str("F") | str("S") | str("C") | str("A") | str("R")).as(:language) >>
|
|
93
|
-
rparen).as(:language_group)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Optional "IALA " prefix.
|
|
97
|
-
rule(:publisher) do
|
|
98
|
-
(str("IALA") >> space).maybe
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
rule(:identifier) do
|
|
102
|
-
annex_identifier | (publisher >> code >> edition.maybe >> language.maybe)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Annex to a base publication. Wraps the base code and adds an
|
|
106
|
-
# "Annex" / "ANNEX" marker (case preserved verbatim), an optional
|
|
107
|
-
# single-letter suffix (A/B/C/D), then the usual edition + language.
|
|
108
|
-
# Tried before the flat `identifier` so the annex marker isn't left
|
|
109
|
-
# dangling after a successful base-code match.
|
|
110
|
-
#
|
|
111
|
-
# The letter suffix uses a negative lookahead for a following lowercase
|
|
112
|
-
# letter so the "E" in "Ed 1" isn't mis-captured as annex letter "E".
|
|
113
|
-
rule(:annex_identifier) do
|
|
114
|
-
(publisher >> type_letter >> number_with_dots >> subpart.maybe).as(:base) >>
|
|
115
|
-
space >> (str("Annex") | str("ANNEX")).as(:annex_marker) >>
|
|
116
|
-
(space >> match("[A-Z]").as(:annex_letter) >> match("[a-z]").absent?).maybe >>
|
|
117
|
-
edition.maybe >> language.maybe
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Parse a string and return the raw parslet tree.
|
|
121
|
-
# @param string [String]
|
|
122
|
-
# @return [Hash]
|
|
123
|
-
def self.parse(string)
|
|
124
|
-
unless string.is_a?(String)
|
|
125
|
-
raise ::Pubid::Errors::InvalidInputError,
|
|
126
|
-
::Pubid::INPUT_NOT_A_STRING_MESSAGE
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
if string.length > ::Pubid::MAX_INPUT_LENGTH
|
|
130
|
-
raise ::Pubid::Errors::InvalidInputError,
|
|
131
|
-
::Pubid::INPUT_TOO_LONG_MESSAGE
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
new.parse(string.strip)
|
|
135
|
-
end
|
|
16
|
+
class Parser
|
|
136
17
|
end
|
|
137
18
|
end
|
|
138
19
|
end
|
|
@@ -132,7 +132,7 @@ module Pubid
|
|
|
132
132
|
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
-
parsed = Pubid::Parg::
|
|
135
|
+
parsed = Pubid::Parg::Model.bound(:iana, identifier)
|
|
136
136
|
Builder.build(parsed)
|
|
137
137
|
end
|
|
138
138
|
end
|
data/lib/pubid/iana/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 Iana
|
|
7
|
-
#
|
|
5
|
+
# Grammar for IANA registry identifiers.
|
|
8
6
|
#
|
|
9
7
|
# Accepts the authoritative printed form "IANA <slug>[/<sub-slug>]" as well
|
|
10
8
|
# as a bare index-key slug (no "IANA " prefix). The optional prefix is
|
|
@@ -12,27 +10,7 @@ module Pubid
|
|
|
12
10
|
#
|
|
13
11
|
# Slugs use only [a-zA-Z0-9._-] and contain at most one "/", so a plain
|
|
14
12
|
# character-class repeat parses them; there is no ReDoS surface.
|
|
15
|
-
class Parser
|
|
16
|
-
# A single registry slug: letters, digits, dot, underscore, hyphen.
|
|
17
|
-
rule(:slug) { match['a-zA-Z0-9._\-'].repeat(1) }
|
|
18
|
-
|
|
19
|
-
# Optional publisher prefix. The trailing space is required, so a
|
|
20
|
-
# space-free slug never triggers it; PEG `.maybe` backtracks the whole
|
|
21
|
-
# sequence if the space is absent (e.g. a slug that happens to start
|
|
22
|
-
# "IANA...").
|
|
23
|
-
rule(:iana_prefix) { (str("IANA") >> str(" ")).maybe }
|
|
24
|
-
|
|
25
|
-
rule(:identifier) do
|
|
26
|
-
iana_prefix >>
|
|
27
|
-
slug.as(:registry) >>
|
|
28
|
-
(str("/") >> slug.as(:sub_registry)).maybe
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
rule(:root) { identifier }
|
|
32
|
-
|
|
33
|
-
def self.parse(input)
|
|
34
|
-
new.parse(input)
|
|
35
|
-
end
|
|
13
|
+
class Parser
|
|
36
14
|
end
|
|
37
15
|
end
|
|
38
16
|
end
|
data/lib/pubid/identifier.rb
CHANGED
|
@@ -817,14 +817,86 @@ module Pubid
|
|
|
817
817
|
# consumed by the renderer, not the rendering context — and some flavors
|
|
818
818
|
# override build_rendering_context with a strict signature, so strip them
|
|
819
819
|
# here rather than widening that signature.
|
|
820
|
-
|
|
820
|
+
# `:with_publisher` is a render-time flag too. It reaches the renderer
|
|
821
|
+
# only for the human format and only when given, so a renderer that
|
|
822
|
+
# reads it (BIPM, NIST) sees it and the default call is unchanged.
|
|
823
|
+
with_publisher = opts[:with_publisher]
|
|
824
|
+
ctx_opts = opts.except(:trademark, :with_volume, :with_publisher)
|
|
821
825
|
context = build_rendering_context(renderer, format:, **ctx_opts)
|
|
822
826
|
render_opts = opts.slice(:with_edition, :trademark, :with_volume)
|
|
827
|
+
if format == :human && !with_publisher.nil?
|
|
828
|
+
render_opts[:with_publisher] = with_publisher
|
|
829
|
+
end
|
|
823
830
|
result = renderer.new(self).render(context:, **render_opts)
|
|
831
|
+
result = apply_with_publisher(result, format, with_publisher)
|
|
824
832
|
|
|
825
833
|
annotate_rendered(result, format, ctx_opts[:annotated], context)
|
|
826
834
|
end
|
|
827
835
|
|
|
836
|
+
# The publisher tokens this identifier's human rendering can start with,
|
|
837
|
+
# for `to_s(with_publisher: false)` ({Renderers::PublisherStripper}).
|
|
838
|
+
#
|
|
839
|
+
# The default is the rendered publisher, and the publisher joined by `/`
|
|
840
|
+
# to its copublishers in either order, so a co-published `ISO/IEC 27001`
|
|
841
|
+
# or `ANSI/AMCA 210-16` drops the whole group. Copublishers come from the
|
|
842
|
+
# shared `copublishers` collection or from a flavor's own `copublisher`
|
|
843
|
+
# (a String or an Array of them: AMCA, ASHRAE, IEEE). A wrapper with no
|
|
844
|
+
# publisher of its own prints the document it wraps, so it asks `base`.
|
|
845
|
+
#
|
|
846
|
+
# A flavor overrides this where the generic answer is wrong: where the
|
|
847
|
+
# leading token is a series that belongs to the designation (`BS 8611`,
|
|
848
|
+
# `GB/T 20223`, `ECMA-262`), the answer is `[]`; where the publisher is
|
|
849
|
+
# welded to another token (`ITU-T`), the answer is the welded group.
|
|
850
|
+
#
|
|
851
|
+
# @return [Array<String>]
|
|
852
|
+
def publisher_prefixes
|
|
853
|
+
pubs = publisher_texts(:publisher)
|
|
854
|
+
copubs = publisher_texts(:copublishers) + publisher_texts(:copublisher)
|
|
855
|
+
# A supplement can name its own publisher while the co-published group
|
|
856
|
+
# lives on the document it supplements, so `base` is always asked too.
|
|
857
|
+
# The stripper tries the longest prefix first, so order does not matter.
|
|
858
|
+
publisher_groups(pubs, copubs) + base_publisher_prefixes
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
# Each publisher alone, and the `/`-joined group in either order.
|
|
862
|
+
def publisher_groups(pubs, copubs)
|
|
863
|
+
return pubs + copubs if pubs.empty? || copubs.empty?
|
|
864
|
+
|
|
865
|
+
[[*pubs, *copubs].join("/"), [*copubs, *pubs].join("/"), *pubs, *copubs]
|
|
866
|
+
end
|
|
867
|
+
private :publisher_groups
|
|
868
|
+
|
|
869
|
+
# The printed form of a publisher-like attribute: a component, a String,
|
|
870
|
+
# or an Array of either (IEEE `DualPublished` returns an Array).
|
|
871
|
+
def publisher_texts(name)
|
|
872
|
+
return [] unless respond_to?(name)
|
|
873
|
+
|
|
874
|
+
value = public_send(name)
|
|
875
|
+
values = value.is_a?(Array) ? value : [value]
|
|
876
|
+
values.compact.map { |v| v.to_s.strip }.reject(&:empty?)
|
|
877
|
+
end
|
|
878
|
+
private :publisher_texts
|
|
879
|
+
|
|
880
|
+
def base_publisher_prefixes
|
|
881
|
+
nested = respond_to?(:base) ? base : nil
|
|
882
|
+
return [] unless nested.is_a?(::Pubid::Identifier) && !nested.equal?(self)
|
|
883
|
+
|
|
884
|
+
nested.publisher_prefixes
|
|
885
|
+
end
|
|
886
|
+
private :base_publisher_prefixes
|
|
887
|
+
|
|
888
|
+
# `with_publisher: false` on the human format drops the leading publisher
|
|
889
|
+
# token. Every other value, and every other format, leaves the string as
|
|
890
|
+
# the renderer produced it — `true` is the default rendering for every
|
|
891
|
+
# flavor whose default carries the prefix, and a flavor whose default
|
|
892
|
+
# omits it (3GPP, OGC, IETF) handles `true` itself.
|
|
893
|
+
def apply_with_publisher(result, format, with_publisher)
|
|
894
|
+
return result unless with_publisher == false && format == :human
|
|
895
|
+
|
|
896
|
+
Renderers::PublisherStripper.new(self).strip(result)
|
|
897
|
+
end
|
|
898
|
+
private :apply_with_publisher
|
|
899
|
+
|
|
828
900
|
# Apply semantic spans when the renderer did not.
|
|
829
901
|
#
|
|
830
902
|
# `context.annotated` reaches all 39 flavor renderers and all 39 ignore it,
|
|
@@ -867,7 +939,12 @@ module Pubid
|
|
|
867
939
|
# @param rendered [String] the plain rendering this method produced
|
|
868
940
|
# @param opts [Hash] the render options it was called with
|
|
869
941
|
# @return [String] annotated when `annotated:` was asked for, else as-is
|
|
942
|
+
#
|
|
943
|
+
# `with_publisher: false` is applied here too, before the annotation, so a
|
|
944
|
+
# hand-composed `to_s` drops its publisher token the same way {#render}
|
|
945
|
+
# does.
|
|
870
946
|
def annotate_plain_render(rendered, **opts)
|
|
947
|
+
rendered = apply_with_publisher(rendered, :human, opts[:with_publisher])
|
|
871
948
|
annotate_rendered(rendered, :human, opts[:annotated], nil)
|
|
872
949
|
end
|
|
873
950
|
protected :annotate_plain_render
|
data/lib/pubid/idf/builder.rb
CHANGED
|
@@ -13,6 +13,12 @@ module Pubid
|
|
|
13
13
|
}.freeze
|
|
14
14
|
|
|
15
15
|
def build(parsed_hash)
|
|
16
|
+
parsed_hash = split_supplement_base(parsed_hash)
|
|
17
|
+
# The Parslet tree carried a zero-width type_with_stage (an empty
|
|
18
|
+
# capture when no type is spelled) whose cast drove the default
|
|
19
|
+
# stage; the artifact tree omits the key entirely — restore the
|
|
20
|
+
# default input.
|
|
21
|
+
parsed_hash[:type_with_stage] ||= ""
|
|
16
22
|
all_parts = parsed_hash.delete(:all_parts)
|
|
17
23
|
typed_stage = Pubid::Idf.locate_stage(parsed_hash[:type_with_stage])
|
|
18
24
|
identifier = Pubid::Idf.locate_type(typed_stage.type_code).new
|
|
@@ -32,8 +38,44 @@ module Pubid
|
|
|
32
38
|
all_parts ? identifier.to_all_parts : identifier
|
|
33
39
|
end
|
|
34
40
|
|
|
41
|
+
# The bound grammar flattens the supplement's base wrapper (the
|
|
42
|
+
# bindings cannot nest): the supplement layer keeps the canonical
|
|
43
|
+
# number_with_part/date/type_with_stage captures and the base
|
|
44
|
+
# portion captures under base_* names. Rebuild the nested shape
|
|
45
|
+
# the builder has always consumed — the base_* keys rename onto
|
|
46
|
+
# their canonical counterparts and the shared publisher capture
|
|
47
|
+
# belongs to the base (the tree backend captured it inside the
|
|
48
|
+
# base subtree, so the supplement itself carries no publisher).
|
|
49
|
+
BASE_KEY_MAP = {
|
|
50
|
+
base_number_with_part: :number_with_part,
|
|
51
|
+
base_date: :date,
|
|
52
|
+
base_type_with_stage: :type_with_stage,
|
|
53
|
+
base_stage_iteration: :stage_iteration,
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
35
56
|
private
|
|
36
57
|
|
|
58
|
+
def split_supplement_base(parsed_hash)
|
|
59
|
+
unless BASE_KEY_MAP.keys.any? { |k| parsed_hash.key?(k) }
|
|
60
|
+
return parsed_hash
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
base_hash = {}
|
|
64
|
+
BASE_KEY_MAP.each do |bound_key, tree_key|
|
|
65
|
+
next unless parsed_hash.key?(bound_key)
|
|
66
|
+
|
|
67
|
+
base_hash[tree_key] = parsed_hash.delete(bound_key)
|
|
68
|
+
end
|
|
69
|
+
if parsed_hash[:publisher]
|
|
70
|
+
base_hash[:publisher] = parsed_hash.delete(:publisher)
|
|
71
|
+
end
|
|
72
|
+
# Keep the base as a raw hash: the shared assign_attributes loop
|
|
73
|
+
# routes :base through cast, which recursively builds it — the
|
|
74
|
+
# same path the tree backend's nested subtree took.
|
|
75
|
+
parsed_hash[:base] = base_hash
|
|
76
|
+
parsed_hash
|
|
77
|
+
end
|
|
78
|
+
|
|
37
79
|
def default_identifier_class
|
|
38
80
|
Identifiers::InternationalStandard
|
|
39
81
|
end
|
data/lib/pubid/idf/identifier.rb
CHANGED
data/lib/pubid/idf/parser.rb
CHANGED
|
@@ -1,144 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "parslet"
|
|
4
|
-
|
|
5
3
|
module Pubid
|
|
6
4
|
module Idf
|
|
7
|
-
class Parser
|
|
8
|
-
include ::Pubid::Parser::CommonParseRules
|
|
9
|
-
include ::Pubid::Parser::CommonParseMethods
|
|
10
|
-
|
|
11
|
-
# We need to sort by length to match longest first because that's how Parslet works
|
|
12
|
-
TYPED_STAGES = Pubid::Idf.all_typed_stages
|
|
13
|
-
.map(&:abbr).flatten.sort_by(&:length).reverse
|
|
14
|
-
TYPED_STAGES_SUPPLEMENTS = Pubid::Idf.all_typed_stages
|
|
15
|
-
.select { |ts| %w[amd cor].include?(ts.type_code.to_s) }
|
|
16
|
-
.map(&:abbr).flatten.sort_by(&:length).reverse
|
|
17
|
-
|
|
18
|
-
root :identifier
|
|
19
|
-
|
|
20
|
-
rule(:identifier) do
|
|
21
|
-
supplement_identifier |
|
|
22
|
-
identifier_publisher
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# IDF 125A:1988
|
|
26
|
-
rule(:prefix_sole_publisher) do
|
|
27
|
-
str("IDF").as(:publisher)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
rule(:number_with_part) do
|
|
31
|
-
(number >> part_and_subpart.maybe).as(:number_with_part)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# IDF identifiers can contain alphanumeric characters
|
|
35
|
-
# IDF 125A:1988
|
|
36
|
-
rule(:number) do
|
|
37
|
-
match('\w').repeat(1, 5)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
rule(:date) do
|
|
41
|
-
(year_digits >>
|
|
42
|
-
(dash >> month_digits).maybe >>
|
|
43
|
-
(dash >> day_digits).maybe
|
|
44
|
-
).as(:date)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
rule(:part_and_subpart) do
|
|
48
|
-
# IDF 80-1:2001
|
|
49
|
-
# the "space?" needed to handle "ISO/TS 10303- 1751:2014"
|
|
50
|
-
dash >> space? >>
|
|
51
|
-
# matches a part
|
|
52
|
-
match('\w').repeat >>
|
|
53
|
-
# matches a subpart, e.g. "A01" or "1-2" (yes we treat {5}-{1-1} as part and subpart)
|
|
54
|
-
(dash >> match('\w').repeat).repeat.maybe
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
rule(:all_parts) do
|
|
58
|
-
str("(all parts)").as(:all_parts)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
rule(:type_with_stage) do
|
|
62
|
-
# IDF/RM 82:2004
|
|
63
|
-
# IDF/RM 254:2022
|
|
64
|
-
array_to_str(TYPED_STAGES).as(:type_with_stage) >>
|
|
65
|
-
(match('\d').as(:stage_iteration) >> space).maybe
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
rule(:language) do
|
|
69
|
-
# IDF/RM 82:2004(en,fr)
|
|
70
|
-
str("(") >>
|
|
71
|
-
(
|
|
72
|
-
# parse 2-char language codes: ru,en,fr,de,ar,es
|
|
73
|
-
(match["a-z"].repeat(1) >> str(",").maybe) |
|
|
74
|
-
# parse single language codes: R/E/F
|
|
75
|
-
(match["EFARDS"] >> str("/").maybe)
|
|
76
|
-
).repeat.as(:languages) >>
|
|
77
|
-
str(")")
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
rule(:stage_iteration) do
|
|
81
|
-
# "ISO 11243.1"
|
|
82
|
-
str(".") >> match('\d').as(:stage_iteration)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
rule(:second_part) do
|
|
86
|
-
number_with_part >>
|
|
87
|
-
stage_iteration.maybe >>
|
|
88
|
-
space? >> (
|
|
89
|
-
# :2005-02
|
|
90
|
-
str(":") >> date
|
|
91
|
-
).maybe
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
rule(:third_part) do
|
|
95
|
-
# End of the identifier, we can have language, or all parts
|
|
96
|
-
space? >> language.maybe >> all_parts.maybe
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# prefix sole publishers with type and stage
|
|
100
|
-
# IDF/RM 82:2004
|
|
101
|
-
rule(:identifier_publisher) do
|
|
102
|
-
identifier_publisher_no_third >> third_part
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
rule(:identifier_publisher_no_third) do
|
|
106
|
-
prefix_sole_publisher >> space? >> str("/").maybe >>
|
|
107
|
-
type_with_stage.maybe >>
|
|
108
|
-
space.maybe >>
|
|
109
|
-
second_part
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
rule(:supplement_type_with_stage) do
|
|
113
|
-
# "AMD" or "COR"
|
|
114
|
-
array_to_str(TYPED_STAGES_SUPPLEMENTS).as(:type_with_stage)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# IDF 148-1:2008 / COR 1:2009
|
|
118
|
-
# IDF 140-1:2007 / AMD1:2012 (no space between AMD and 1)
|
|
119
|
-
rule(:supplement_identifier_no_third) do
|
|
120
|
-
identifier_publisher_no_third.as(:base) >>
|
|
121
|
-
space? >> str("/") >> space? >>
|
|
122
|
-
supplement_type_with_stage >>
|
|
123
|
-
space.maybe >> second_part
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
rule(:supplement_identifier) do
|
|
127
|
-
supplement_identifier_no_third >> third_part
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# # ISO/IEC 13818-1:2015/Amd 3:2016/Cor 1:2017
|
|
131
|
-
# # ISO/IEC 19794-7:2014/Amd 1:2015/CD Cor 1
|
|
132
|
-
# rule(:supplement_supplement_identifier) do
|
|
133
|
-
# supplement_identifier_no_third.as(:base) >>
|
|
134
|
-
# str("/") >> supplement_type_with_stage >>
|
|
135
|
-
# # digits.as(:stage_iteration).maybe >>
|
|
136
|
-
# space? >> second_part >> third_part
|
|
137
|
-
# end
|
|
138
|
-
|
|
139
|
-
rule(:dash) do
|
|
140
|
-
str("-") | str("‑") | str("‐")
|
|
141
|
-
end
|
|
5
|
+
class Parser
|
|
142
6
|
end
|
|
143
7
|
end
|
|
144
8
|
end
|
data/lib/pubid/idf.rb
CHANGED
|
@@ -28,10 +28,9 @@ module Pubid
|
|
|
28
28
|
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
parser = Parser.new
|
|
32
31
|
builder = Builder.new
|
|
33
32
|
|
|
34
|
-
parsed =
|
|
33
|
+
parsed = Pubid::Parg::Model.bound(:idf, identifier)
|
|
35
34
|
builder.build(parsed)
|
|
36
35
|
end
|
|
37
36
|
|