pubid 2.0.1 → 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/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/iana.json +1 -1
- data/data/parg/idf.json +1 -1
- data/data/parg/iec.json +1 -1
- data/data/parg/iho.json +1 -0
- data/data/parg/isbn.json +1 -1
- data/data/parg/itu.json +1 -1
- data/data/parg/jcgm.json +1 -1
- data/data/parg/jis.json +1 -1
- data/data/parg/nist.json +1 -1
- data/data/parg/oiml.json +1 -1
- data/data/parg/omg.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/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_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/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 +6 -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/nist/router.rb
CHANGED
|
@@ -104,7 +104,10 @@ module Pubid
|
|
|
104
104
|
|
|
105
105
|
# Check for CS variants (works for both compound "NBS CS" and simple "CS")
|
|
106
106
|
if series == "CS"
|
|
107
|
-
|
|
107
|
+
# The composite branch of the split first_number wrapper (the
|
|
108
|
+
# grammar's v#n# branch) arrives under :first_number_composite.
|
|
109
|
+
first_num = parsed_hash[:first_number] ||
|
|
110
|
+
parsed_hash[:first_number_composite]
|
|
108
111
|
|
|
109
112
|
# Check for CSM (monthly) - v#n# pattern inside first_number hash
|
|
110
113
|
if first_num.is_a?(Hash) && first_num[:volume_number] && first_num[:issue_number]
|
|
@@ -30,12 +30,12 @@ module Pubid
|
|
|
30
30
|
# and `to_s` wraps its single result. `super` is not reachable from a
|
|
31
31
|
# private method, so it is handed in as a block.
|
|
32
32
|
def to_s(format = :short)
|
|
33
|
-
|
|
33
|
+
flags = plain_render_flags(format)
|
|
34
34
|
format = format[:format] || :short if format.is_a?(Hash)
|
|
35
35
|
|
|
36
36
|
rendered = render_plain(format) { super(format) }
|
|
37
37
|
|
|
38
|
-
annotate_plain_render(rendered,
|
|
38
|
+
annotate_plain_render(rendered, **flags)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
private
|
data/lib/pubid/oasis/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 Oasis
|
|
7
|
-
#
|
|
5
|
+
# Grammar for printed OASIS identifiers, e.g.
|
|
8
6
|
# "OASIS OSLC-CoreShapes-3.0-PS01-Pt8"
|
|
9
7
|
# "OASIS amqp-core"
|
|
10
8
|
#
|
|
@@ -16,19 +14,7 @@ module Pubid
|
|
|
16
14
|
# Builder with order-independent fragment classification. Keeping
|
|
17
15
|
# decomposition out of the PEG avoids brittle backtracking and guarantees a
|
|
18
16
|
# lossless round-trip.
|
|
19
|
-
class Parser
|
|
20
|
-
rule(:prefix) { str("OASIS") >> str(" ") }
|
|
21
|
-
|
|
22
|
-
# The whole slug, captured verbatim (any character, incl. spaces / "]").
|
|
23
|
-
rule(:slug) { any.repeat(1).as(:original) }
|
|
24
|
-
|
|
25
|
-
rule(:identifier) { prefix >> slug }
|
|
26
|
-
|
|
27
|
-
rule(:root) { identifier }
|
|
28
|
-
|
|
29
|
-
def self.parse(input)
|
|
30
|
-
new.parse(input)
|
|
31
|
-
end
|
|
17
|
+
class Parser
|
|
32
18
|
end
|
|
33
19
|
end
|
|
34
20
|
end
|
data/lib/pubid/ogc/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 Ogc
|
|
7
|
-
#
|
|
5
|
+
# Grammar for OGC identifiers.
|
|
8
6
|
#
|
|
9
7
|
# Recognizes `<yy>-<nnn>` optionally followed by a revision suffix that
|
|
10
8
|
# begins with a letter (r/c/a/R…) and may carry a trailing letter
|
|
@@ -12,29 +10,7 @@ module Pubid
|
|
|
12
10
|
# leniency, though the canonical printed form omits it.
|
|
13
11
|
#
|
|
14
12
|
# Examples: "25-023", "24-032r1", "01-009a", "04-095c1", "OGC 24-032r1".
|
|
15
|
-
class Parser
|
|
16
|
-
rule(:space) { match["\\s"].repeat(1) }
|
|
17
|
-
rule(:digits) { match["0-9"].repeat(1) }
|
|
18
|
-
|
|
19
|
-
# Optional leading publisher token, e.g. "OGC " — consumed, not captured.
|
|
20
|
-
rule(:publisher) { (str("OGC") >> space).maybe }
|
|
21
|
-
|
|
22
|
-
# Revision suffix: a run of letters/digits that starts after the numeric
|
|
23
|
-
# part (which greedily consumes all digits, so the suffix begins with a
|
|
24
|
-
# separator letter). Kept as a single token for the builder to normalize.
|
|
25
|
-
rule(:revision) { match["A-Za-z0-9"].repeat(1).as(:revision) }
|
|
26
|
-
|
|
27
|
-
rule(:identifier) do
|
|
28
|
-
publisher >>
|
|
29
|
-
digits.as(:year) >> str("-") >> digits.as(:number) >>
|
|
30
|
-
revision.maybe >> space.maybe
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
root(:identifier)
|
|
34
|
-
|
|
35
|
-
def self.parse(input)
|
|
36
|
-
new.parse(input)
|
|
37
|
-
end
|
|
13
|
+
class Parser
|
|
38
14
|
end
|
|
39
15
|
end
|
|
40
16
|
end
|
|
@@ -28,6 +28,13 @@ module Pubid
|
|
|
28
28
|
def type_string
|
|
29
29
|
"CS"
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
# `OIML-CS` names the OIML Certification System as one unit, so
|
|
33
|
+
# `with_publisher: false` drops it whole (`PD-05 Edition 6`) and never
|
|
34
|
+
# leaves a bare `CS`.
|
|
35
|
+
def publisher_prefixes
|
|
36
|
+
["#{publisher}-#{type_string}", "#{publisher} #{type_string}"]
|
|
37
|
+
end
|
|
31
38
|
end
|
|
32
39
|
end
|
|
33
40
|
end
|
|
@@ -127,7 +127,7 @@ module Pubid
|
|
|
127
127
|
# which starts with an OIML prefix), parses each side through its
|
|
128
128
|
# own flavor and returns the wrapper. Returns nil for anything else,
|
|
129
129
|
# so the caller (Pubid::Oiml.parse) falls through to the ordinary
|
|
130
|
-
# grammar, which raises the standard
|
|
130
|
+
# grammar, which raises the standard Pubid::Errors::ParseError for a
|
|
131
131
|
# string containing "|" (the grammar defines no rule for it).
|
|
132
132
|
def self.build(identifier)
|
|
133
133
|
sides = split_sides(identifier)
|
|
@@ -164,7 +164,7 @@ module Pubid
|
|
|
164
164
|
|
|
165
165
|
def self.parse_side(str, oiml:)
|
|
166
166
|
oiml ? Oiml.parse(str) : ::Pubid.parse(str)
|
|
167
|
-
rescue
|
|
167
|
+
rescue Pubid::Errors::ParseError, Pubid::Errors::InvalidInputError
|
|
168
168
|
nil
|
|
169
169
|
end
|
|
170
170
|
private_class_method :parse_side
|
data/lib/pubid/oiml/parser.rb
CHANGED
|
@@ -1,310 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "parslet"
|
|
4
|
-
|
|
5
3
|
module Pubid
|
|
6
4
|
module Oiml
|
|
7
|
-
class Parser
|
|
8
|
-
include ::Pubid::Parser::CommonParseRules
|
|
9
|
-
include ::Pubid::Parser::CommonParseMethods
|
|
10
|
-
|
|
11
|
-
root :identifier
|
|
12
|
-
|
|
13
|
-
# Additional basic rules not in CommonParseRules
|
|
14
|
-
rule(:colon) { str(":") }
|
|
15
|
-
rule(:lparen) { str("(") }
|
|
16
|
-
rule(:rparen) { str(")") }
|
|
17
|
-
rule(:slash) { str("/") }
|
|
18
|
-
|
|
19
|
-
# Main identifier pattern - check supplements first
|
|
20
|
-
rule(:identifier) do
|
|
21
|
-
amendment_identifier | amendment_short | annex_letter_identifier |
|
|
22
|
-
annex_identifier | plus_supplement_identifier |
|
|
23
|
-
trailing_supplement_identifier | cs_identifier |
|
|
24
|
-
bulletin_identifier | base
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Publisher - always "OIML"
|
|
28
|
-
rule(:publisher) { str("OIML").as(:publisher) >> space }
|
|
29
|
-
|
|
30
|
-
# Document type - single letter. Strict family set: OIML publishes
|
|
31
|
-
# R D B G E V S documents (the estate grammar's family letters);
|
|
32
|
-
# any other letter is a rejection, not a flavor.
|
|
33
|
-
rule(:doc_type) { match("[BDEGRSV]").as(:type) >> space }
|
|
34
|
-
|
|
35
|
-
# Bulletin locator — structured form. Year optionally followed by
|
|
36
|
-
# 2-digit issue and 2-digit sequence:
|
|
37
|
-
# "OIML Bulletin", "OIML Bulletin 1960",
|
|
38
|
-
# "OIML Bulletin 1960-03", "OIML Bulletin 1960-03-01"
|
|
39
|
-
# All four shapes appear as primary docids in relaton-data-oiml.
|
|
40
|
-
rule(:bulletin_date) do
|
|
41
|
-
space >> year_digits.as(:year) >>
|
|
42
|
-
(dash >> two_digits.as(:issue)).maybe >>
|
|
43
|
-
(dash >> two_digits.as(:sequence)).maybe
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Two-digit zero-padded number (used for issue / sequence).
|
|
47
|
-
rule(:two_digits) { match('\d').repeat(2, 2) }
|
|
48
|
-
|
|
49
|
-
# Roman numeral token composed of I,V,X,L,C,D,M (uppercase, matching
|
|
50
|
-
# OIML's print convention). Captured so the builder can cross-check
|
|
51
|
-
# the declared volume against the year implied by the article_id.
|
|
52
|
-
rule(:roman_numeral) { match("[IVXLCDM]").repeat(1).as(:volume_roman) }
|
|
53
|
-
|
|
54
|
-
# Bulletin locator — citation form. The format OIML prints on the
|
|
55
|
-
# article page is: "LXVII(2) 20260211" where LXVII is the volume in
|
|
56
|
-
# roman numerals, (2) is the issue in arabic without zero padding,
|
|
57
|
-
# and 20260211 is the 8-digit oiml.org article id (YYYYNNSS).
|
|
58
|
-
rule(:bulletin_citation) do
|
|
59
|
-
space >> roman_numeral >>
|
|
60
|
-
lparen >> digits.as(:issue_arabic) >> rparen >>
|
|
61
|
-
space >> match('\d').repeat(8, 8).as(:article_id)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Bulletin identifier — no code; the locator (when present) is the
|
|
65
|
-
# (year, issue, sequence) tuple drawn from either the structured
|
|
66
|
-
# YYYY-II-SS form or the citation VOLUME(ISSUE) ARTID form. Both
|
|
67
|
-
# decode to the same record. Tried before base because
|
|
68
|
-
# "Bulletin" is a word that the single-letter doc_type rule cannot
|
|
69
|
-
# match.
|
|
70
|
-
rule(:bulletin_identifier) do
|
|
71
|
-
publisher >> str("Bulletin").as(:type) >>
|
|
72
|
-
(bulletin_citation | bulletin_date).maybe >>
|
|
73
|
-
language_portion.maybe.as(:language)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Number with optional part and subpart
|
|
77
|
-
rule(:number_only) { digits.as(:number) }
|
|
78
|
-
|
|
79
|
-
# Part number, optionally a slashed multi-part continuation captured
|
|
80
|
-
# verbatim, e.g. "1/-2" in "OIML R 46-1/-2:2012".
|
|
81
|
-
rule(:part_number) do
|
|
82
|
-
dash >> (digits >> (slash >> dash >> digits).repeat).as(:part)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
rule(:subpart_number) { dash >> digits.as(:subpart) }
|
|
86
|
-
|
|
87
|
-
# Free-form named/lettered code suffix glued after the numeric part:
|
|
88
|
-
# -GUM 1, -special, -sup, -erratum, -A, -ISO3930, -Amend, -Amended_2012
|
|
89
|
-
# plus the space-separated "Brochure" label. "GUM <n>" is tried first so
|
|
90
|
-
# its trailing number isn't lost to the bare-word alternative. The
|
|
91
|
-
# trailing `(_?digits)*` captures joint codes ("ISO3930") and the
|
|
92
|
-
# "Amended_2012" amendment label (the hand-off treats these as part
|
|
93
|
-
# suffixes, preserved verbatim for round-trip).
|
|
94
|
-
rule(:named_suffix) do
|
|
95
|
-
(dash >> (
|
|
96
|
-
(str("GUM") >> space >> digits) |
|
|
97
|
-
(match("[A-Za-z]").repeat(1) >> (str("_").maybe >> digits).repeat)
|
|
98
|
-
).as(:code_suffix)) |
|
|
99
|
-
(space >> str("Brochure").as(:code_suffix) >> str("").as(:space_suffix))
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
rule(:full_number) do
|
|
103
|
-
(number_only >> part_number >> subpart_number >> named_suffix.maybe) |
|
|
104
|
-
(number_only >> part_number >> named_suffix.maybe) |
|
|
105
|
-
(number_only >> named_suffix.maybe)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Edition number - ordinal numbers
|
|
109
|
-
rule(:edition_number) do
|
|
110
|
-
(
|
|
111
|
-
str("6th") | str("5th") | str("4th") | str("3rd") |
|
|
112
|
-
str("2nd") | str("1st") |
|
|
113
|
-
(digits >> (str("th") | str("nd") | str("rd") | str("st")))
|
|
114
|
-
).as(:edition)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Edition text - uppercase or lowercase
|
|
118
|
-
rule(:edition_text) { str("Edition") | str("edition") }
|
|
119
|
-
|
|
120
|
-
# Edition portion - handles "6th Edition 2015" or "Edition 2013" or ", edition 1992"
|
|
121
|
-
rule(:edition_portion) do
|
|
122
|
-
(
|
|
123
|
-
(str(", ") | space) >>
|
|
124
|
-
edition_number.maybe >> space.maybe >> edition_text >> space.maybe >> year_digits.as(:year)
|
|
125
|
-
).as(:edition_format) # Wrap entire match to capture that Edition was used
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Date - year after colon, the VIM's paren-year form ("OIML V 2-200
|
|
129
|
-
# (2000)" — space + (YYYY) instead of :YYYY, the shape #342 flags as
|
|
130
|
-
# the one real-world spelling that did not parse), OR edition portion
|
|
131
|
-
# (with optional space before year)
|
|
132
|
-
rule(:date) do
|
|
133
|
-
edition_portion |
|
|
134
|
-
(space.maybe >> colon >> space.maybe >> year_digits.as(:year)) |
|
|
135
|
-
(space.maybe >> lparen >> year_digits.as(:year) >> rparen)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# Draft stage - WD or CD with optional iteration
|
|
139
|
-
rule(:stage_iteration) do
|
|
140
|
-
(
|
|
141
|
-
(digits >> str(".") >> digits) | # 3.1
|
|
142
|
-
digits # 1, 2, 3
|
|
143
|
-
).as(:iteration)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
rule(:stage_abbr) do
|
|
147
|
-
(str("WD") | str("CD")).as(:stage)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
rule(:draft_stage) do
|
|
151
|
-
space >> stage_iteration.maybe >> stage_abbr
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Language codes
|
|
155
|
-
#
|
|
156
|
-
# relaton-data-oiml encodes the publication language with its own code
|
|
157
|
-
# set (lib/oiml_fetcher.rb DOCID_LANG_CODE): single uppercase letters
|
|
158
|
-
# E F D R S C A U X, plus two-letter PO/PT/PE/SR. The two-letter OIML
|
|
159
|
-
# codes must be tried before the single-letter rule so e.g. "PE" isn't
|
|
160
|
-
# left with a dangling "E".
|
|
161
|
-
rule(:lang_single) do
|
|
162
|
-
match("[EFRXDSCAU]")
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
rule(:lang_multi_oiml) do
|
|
166
|
-
str("PO") | str("PT") | str("PE") | str("SR")
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
rule(:lang_multi) do
|
|
170
|
-
match("[a-z]").repeat(2, 2) # Two letters: en, fr, etc.
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# Full-word language markers as OIML prints them ("(Fra)", "(Eng)"),
|
|
174
|
-
# three or more letters, any case. Kept verbatim on the identifier;
|
|
175
|
-
# the URN lowercases.
|
|
176
|
-
rule(:lang_word) do
|
|
177
|
-
match("[A-Za-z]").repeat(3)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
rule(:language_code) do
|
|
181
|
-
(
|
|
182
|
-
(lang_single >> slash >> lang_single) | # E/F
|
|
183
|
-
lang_multi_oiml | # PO, PT, PE, SR
|
|
184
|
-
lang_word | # Fra, eng, rus — before
|
|
185
|
-
# the letter rules: a
|
|
186
|
-
# committed "F" of "(Fra)"
|
|
187
|
-
# or "fr" of "(fra)" would
|
|
188
|
-
# never fall through
|
|
189
|
-
lang_single | # E, F, D, R, S, C, A, U, X
|
|
190
|
-
lang_multi # en, fr
|
|
191
|
-
).as(:language)
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
rule(:language_with_space) do
|
|
195
|
-
space >> lparen >> language_code >> rparen >> str("").as(:space_before_lang)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
rule(:language_without_space) do
|
|
199
|
-
lparen >> language_code >> rparen
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
rule(:language_portion) do
|
|
203
|
-
language_with_space | language_without_space
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Amendment identifier - "Amendment (YYYY) to BASE"
|
|
207
|
-
rule(:amendment_identifier) do
|
|
208
|
-
str("Amendment") >> space >> lparen >> year_digits.as(:year) >> rparen >>
|
|
209
|
-
space >> str("to") >> space >>
|
|
210
|
-
base_without_language.as(:base) >>
|
|
211
|
-
language_portion.maybe.as(:language)
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# Short amendment forms - "OIML TYPE NUMBER Amendment Edition YYYY" or "Amendment: YYYY"
|
|
215
|
-
rule(:amendment_short) do
|
|
216
|
-
publisher >>
|
|
217
|
-
doc_type >>
|
|
218
|
-
full_number.as(:base_code) >>
|
|
219
|
-
space >> str("Amendment").as(:amd_marker) >>
|
|
220
|
-
(
|
|
221
|
-
(space >> edition_text >> space.maybe >> year_digits.as(:year)).as(:edition_format) |
|
|
222
|
-
(colon >> space.maybe >> year_digits.as(:year))
|
|
223
|
-
) >>
|
|
224
|
-
language_portion.maybe.as(:language)
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# Trailing supplement word - "BASE Amendment" / "BASE Errata" where the
|
|
228
|
-
# publication year stays on the base (e.g. "OIML R 138:2009 Amendment").
|
|
229
|
-
# amendment_short is tried first; it only matches when a year follows the
|
|
230
|
-
# word, so the no-year trailing form falls through to here.
|
|
231
|
-
rule(:trailing_supplement_identifier) do
|
|
232
|
-
base_without_language.as(:base) >>
|
|
233
|
-
space >> (str("Amendment") | str("Errata")).as(:trailing_marker) >>
|
|
234
|
-
(space >> digits.as(:number)).maybe >>
|
|
235
|
-
language_portion.maybe.as(:language)
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
# OIML-CS certification-system documents. Two head spellings
|
|
239
|
-
# ("OIML-CS" / "OIML CS") and two family-number separators
|
|
240
|
-
# ("PD-05" / "PD 05"), an "Edition N" instead of a year, and an
|
|
241
|
-
# optional parenthesized trailing amendment - "(Amendment 1)".
|
|
242
|
-
rule(:cs_identifier) do
|
|
243
|
-
str("OIML").as(:publisher) >>
|
|
244
|
-
(dash >> str("CS") | space >> str("CS")).as(:cs_series) >> space >>
|
|
245
|
-
(str("PD") | str("OD") | str("CID")).as(:cs_family) >>
|
|
246
|
-
(dash | space).as(:cs_separator) >>
|
|
247
|
-
digits.as(:number) >>
|
|
248
|
-
space >> str("Edition") >> space >> digits.as(:edition) >>
|
|
249
|
-
(space >> lparen >> str("Amendment") >> space >>
|
|
250
|
-
digits.as(:cs_amendment) >> rparen).maybe
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# Plus-joined supplement - "BASE:YEAR+Supplement:YEAR" form where both
|
|
254
|
-
# the base and the supplement carry their own year. Used for amendments
|
|
255
|
-
# and errata to dated bases (e.g. "OIML B 10:2011+Amendment:2012").
|
|
256
|
-
# Annexes already encode the year-on-base intent via their own model.
|
|
257
|
-
rule(:plus_supplement_identifier) do
|
|
258
|
-
base_without_language.as(:base) >>
|
|
259
|
-
str("+") >>
|
|
260
|
-
(str("Amendment") | str("Errata")).as(:plus_marker) >>
|
|
261
|
-
(colon >> year_digits.as(:year)).maybe >>
|
|
262
|
-
language_portion.maybe.as(:language)
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
# Annex identifier - "BASE Annexes Edition YYYY" / "BASE Annexes:YYYY" /
|
|
266
|
-
# "BASE:YYYY Annexes" (year on the base, no annex year).
|
|
267
|
-
rule(:annex_identifier) do
|
|
268
|
-
base_without_language.as(:base) >>
|
|
269
|
-
space >> str("Annexes").as(:annex_marker) >>
|
|
270
|
-
(
|
|
271
|
-
(space >> edition_text >> space >> year_digits.as(:year)).as(:edition_format) |
|
|
272
|
-
(colon >> year_digits.as(:year))
|
|
273
|
-
).maybe >>
|
|
274
|
-
language_portion.maybe.as(:language)
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
# Annex letter or letter range, e.g. "A" or "B-C".
|
|
278
|
-
rule(:annex_letter_value) do
|
|
279
|
-
(match("[A-Z]") >> (dash >> match("[A-Z]")).maybe).as(:annex_letter)
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
# Annex with letter - "BASE Annex A Edition YYYY" / "BASE:YYYY Annex B-C"
|
|
283
|
-
rule(:annex_letter_identifier) do
|
|
284
|
-
base_without_language.as(:base) >>
|
|
285
|
-
space >> str("Annex") >> space >> annex_letter_value >>
|
|
286
|
-
((space >> edition_text >> space >> year_digits.as(:year)) | (colon >> year_digits.as(:year))).maybe >>
|
|
287
|
-
language_portion.maybe.as(:language)
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
# Base identifier without language (for use in supplements)
|
|
291
|
-
rule(:base_without_language) do
|
|
292
|
-
publisher >>
|
|
293
|
-
doc_type >>
|
|
294
|
-
full_number >>
|
|
295
|
-
date.maybe >>
|
|
296
|
-
draft_stage.maybe
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
# Base identifier for recursion and standalone parsing
|
|
300
|
-
rule(:base) do
|
|
301
|
-
publisher >>
|
|
302
|
-
doc_type >>
|
|
303
|
-
full_number >>
|
|
304
|
-
date.maybe >>
|
|
305
|
-
draft_stage.maybe >>
|
|
306
|
-
language_portion.maybe
|
|
307
|
-
end
|
|
5
|
+
class Parser
|
|
308
6
|
end
|
|
309
7
|
end
|
|
310
8
|
end
|
data/lib/pubid/oiml.rb
CHANGED
data/lib/pubid/omg/identifier.rb
CHANGED
|
@@ -43,6 +43,11 @@ module Pubid
|
|
|
43
43
|
render(format: :human, **opts)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
# OMG keeps its publisher in the PUBLISHER constant, not the attribute.
|
|
47
|
+
def publisher_prefixes
|
|
48
|
+
[PUBLISHER]
|
|
49
|
+
end
|
|
50
|
+
|
|
46
51
|
def self.parse(identifier)
|
|
47
52
|
unless identifier.is_a?(String)
|
|
48
53
|
raise Pubid::Errors::InvalidInputError,
|
|
@@ -53,7 +58,7 @@ module Pubid
|
|
|
53
58
|
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
54
59
|
end
|
|
55
60
|
|
|
56
|
-
parsed = Pubid::Parg::
|
|
61
|
+
parsed = Pubid::Parg::Model.bound(:omg, identifier)
|
|
57
62
|
Builder.build(parsed)
|
|
58
63
|
end
|
|
59
64
|
end
|
data/lib/pubid/omg/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 Omg
|
|
7
|
-
#
|
|
5
|
+
# Grammar for OMG specification identifiers.
|
|
8
6
|
#
|
|
9
7
|
# Accepts:
|
|
10
8
|
# OMG {ACRONYM}[ {VERSION}][ {PART}]
|
|
@@ -15,79 +13,7 @@ module Pubid
|
|
|
15
13
|
# "2.5.1", "5 beta 3", "2.5 beta".
|
|
16
14
|
# PART is the volume or format segment, e.g. "Superstructure", "PDF". A
|
|
17
15
|
# space or a slash separates it.
|
|
18
|
-
class Parser
|
|
19
|
-
rule(:space) { str(" ") }
|
|
20
|
-
|
|
21
|
-
# Acronym: the URL segment OMG gives the specification, kept verbatim,
|
|
22
|
-
# because a consumer builds the URL from it. It starts with a letter,
|
|
23
|
-
# which may be lower case ("smartant"). Letters, digits and "+" follow
|
|
24
|
-
# ("SysML", "AMI4CCM", "VSIPL++"). A hyphen or a slash joins further
|
|
25
|
-
# segments ("DDS-PSM-Cxx", "EDMC-FIBO/BE"). Each segment is non-empty,
|
|
26
|
-
# so a hyphen or a slash never ends the acronym.
|
|
27
|
-
#
|
|
28
|
-
# The slash in "EDMC-FIBO/BE" belongs to the acronym: OMG serves the
|
|
29
|
-
# document at /spec/EDMC-FIBO/BE/. A slash after the version separates
|
|
30
|
-
# the document part instead (see the identifier rule).
|
|
31
|
-
rule(:acronym_char) { match("[A-Za-z0-9+]") }
|
|
32
|
-
|
|
33
|
-
rule(:acronym) do
|
|
34
|
-
(match("[A-Za-z]") >> acronym_char.repeat >>
|
|
35
|
-
(match("[-/]") >> acronym_char.repeat(1)).repeat).as(:acronym)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Version: digits with optional dots, optionally followed by " beta" and
|
|
39
|
-
# an optional beta number. OMG writes the label both ways: the document
|
|
40
|
-
# at /spec/UML/2.5/Beta1 gives its own version as "2.5 beta", and DDS 1.4
|
|
41
|
-
# supersedes /spec/DDS/1.4/Beta2.
|
|
42
|
-
#
|
|
43
|
-
# The beta number must stay optional here. The document part below would
|
|
44
|
-
# otherwise swallow a bare "beta" and report the version as "2.5" — a
|
|
45
|
-
# silent wrong answer, not a parse failure.
|
|
46
|
-
#
|
|
47
|
-
# Both halves of the beta label end at a word boundary. Parslet never
|
|
48
|
-
# backtracks into a `.maybe` that already succeeded, so an unanchored
|
|
49
|
-
# literal makes the version eat the front of a document part and then
|
|
50
|
-
# reject the whole identifier: "OMG DDS 1.4 beta2" and
|
|
51
|
-
# "OMG DDS 1.4 betawave" would raise instead of reading the part.
|
|
52
|
-
rule(:word_boundary) { match("[A-Za-z0-9]").absent? }
|
|
53
|
-
|
|
54
|
-
rule(:beta) do
|
|
55
|
-
str(" beta") >> word_boundary >>
|
|
56
|
-
(space >> match("[0-9]").repeat(1) >> word_boundary).maybe
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
rule(:version) do
|
|
60
|
-
(match("[0-9]").repeat(1) >>
|
|
61
|
-
(str(".") >> match("[0-9]").repeat(1)).repeat >>
|
|
62
|
-
beta.maybe).as(:version)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# After the version, OMG separates the document part with either a
|
|
66
|
-
# space or a slash. The renderer prints a space, so the two spellings of
|
|
67
|
-
# one document stay equal.
|
|
68
|
-
rule(:part_separator) { space | str("/") }
|
|
69
|
-
|
|
70
|
-
# Document part: the volume or format segment OMG puts after the
|
|
71
|
-
# version. UML 2.1.1 is two documents, Superstructure and
|
|
72
|
-
# Infrastructure, and the URL carries the same segment
|
|
73
|
-
# (/spec/UML/2.1.1/Superstructure). A format name (/spec/DDS/1.4/PDF)
|
|
74
|
-
# occupies the same position.
|
|
75
|
-
rule(:part) { match("[A-Za-z0-9]").repeat(1).as(:part) }
|
|
76
|
-
|
|
77
|
-
# Only a space separates a part that follows the acronym directly. The
|
|
78
|
-
# acronym rule takes a slash there, so a slash separator is legal only
|
|
79
|
-
# after a version.
|
|
80
|
-
rule(:identifier) do
|
|
81
|
-
str("OMG") >> space >> acronym >>
|
|
82
|
-
((space >> version >> (part_separator >> part).maybe) |
|
|
83
|
-
(space >> part)).maybe
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
rule(:root) { identifier }
|
|
87
|
-
|
|
88
|
-
def self.parse(input)
|
|
89
|
-
new.parse(input)
|
|
90
|
-
end
|
|
16
|
+
class Parser
|
|
91
17
|
end
|
|
92
18
|
end
|
|
93
19
|
end
|
data/lib/pubid/parser.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "pubid"
|
|
4
3
|
module Pubid
|
|
5
4
|
module Parser
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
# A trailing "(all parts)" marks the reference as the whole document.
|
|
6
|
+
# NIST's parse path strips it before preprocessing and carries the
|
|
7
|
+
# marker into the tree (:all_parts) for the builder to wrap; the
|
|
8
|
+
# shared Builder::Base reads the same marker for every other flavor.
|
|
9
|
+
ALL_PARTS_SUFFIX = "(all parts)".freeze
|
|
9
10
|
end
|
|
10
11
|
end
|
|
@@ -116,7 +116,7 @@ module Pubid
|
|
|
116
116
|
# failure — the class the cross-flavor contract requires, and the one
|
|
117
117
|
# `Pubid.parse` treats as "try the next flavor".
|
|
118
118
|
if identifier_string == mr_string && flavor_module == Pubid::Iso
|
|
119
|
-
raise
|
|
119
|
+
raise Pubid::Errors::ParseError,
|
|
120
120
|
"#{mr_string.inspect} is not an MR string"
|
|
121
121
|
end
|
|
122
122
|
|
data/lib/pubid/plateau/parser.rb
CHANGED
|
@@ -2,72 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Plateau
|
|
5
|
-
class Parser
|
|
6
|
-
rule(:space) { str(" ") }
|
|
7
|
-
rule(:dash) { str("-") }
|
|
8
|
-
rule(:hash) { str("#") }
|
|
9
|
-
|
|
10
|
-
rule(:digit) { match["0-9"] }
|
|
11
|
-
rule(:digits) { digit.repeat(1) }
|
|
12
|
-
|
|
13
|
-
# PLATEAU
|
|
14
|
-
rule(:publisher) { str("PLATEAU") }
|
|
15
|
-
|
|
16
|
-
# Handbook or Technical Report
|
|
17
|
-
rule(:doc_type) do
|
|
18
|
-
(str("Handbook") | str("Technical Report")).as(:type)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Number: #00, #01, #46, etc.
|
|
22
|
-
rule(:number) { hash >> digits.as(:number) }
|
|
23
|
-
|
|
24
|
-
# Annex: -1, -2, etc. Legacy Latin references use "_" as the sub-number
|
|
25
|
-
# separator (e.g. "#46_1"); both render as the canonical dash.
|
|
26
|
-
rule(:annex) { (dash | str("_")) >> digits.as(:annex) }
|
|
27
|
-
|
|
28
|
-
# Edition (Handbook only). Canonical form is 第X.Y版; legacy Latin
|
|
29
|
-
# references write the bare X.Y (e.g. "PLATEAU Handbook #00 1.0"). Both
|
|
30
|
-
# capture the same numeric :edition slice, so they build the same object
|
|
31
|
-
# and render to the canonical 第X.Y版.
|
|
32
|
-
rule(:edition_part) do
|
|
33
|
-
str("第") >>
|
|
34
|
-
(digits >> str(".") >> digits).as(:edition) >>
|
|
35
|
-
str("版")
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
rule(:edition_latin) { (digits >> str(".") >> digits).as(:edition) }
|
|
39
|
-
|
|
40
|
-
rule(:edition) { space >> (edition_part | edition_latin) }
|
|
41
|
-
|
|
42
|
-
# Annex supplement: "Annex A", "Annex B", etc.
|
|
43
|
-
rule(:annex_letter) { match["A-Z"].as(:annex_letter) }
|
|
44
|
-
rule(:annex_supplement) do
|
|
45
|
-
space >> str("Annex") >> space >> annex_letter
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Full identifier patterns
|
|
49
|
-
rule(:handbook) do
|
|
50
|
-
publisher >> space >> doc_type >> space >>
|
|
51
|
-
number >> annex.maybe >> edition.maybe
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Technical Report has no edition. A legacy Latin TR reference carrying a
|
|
55
|
-
# trailing edition (e.g. "#00 1.0") is matched by the `handbook` rule
|
|
56
|
-
# (doc_type matches either keyword, tried first); the builder dispatches
|
|
57
|
-
# on :type and drops the captured edition for Technical Report.
|
|
58
|
-
rule(:technical_report) do
|
|
59
|
-
publisher >> space >> doc_type >> space >>
|
|
60
|
-
number >> annex.maybe
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Annex identifier (supplement)
|
|
64
|
-
rule(:annex_identifier) do
|
|
65
|
-
(handbook | technical_report).as(:base) >> annex_supplement
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
rule(:identifier) { annex_identifier | handbook | technical_report }
|
|
69
|
-
|
|
70
|
-
rule(:root) { identifier }
|
|
5
|
+
class Parser
|
|
71
6
|
end
|
|
72
7
|
end
|
|
73
8
|
end
|