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.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/data/parg/adobe.json +1 -1
  3. data/data/parg/aiee.json +1 -1
  4. data/data/parg/amca.json +1 -1
  5. data/data/parg/ansi.json +1 -1
  6. data/data/parg/api.json +1 -1
  7. data/data/parg/ashrae.json +1 -1
  8. data/data/parg/asme.json +1 -1
  9. data/data/parg/astm.json +1 -1
  10. data/data/parg/bipm.json +1 -1
  11. data/data/parg/bsi.json +1 -1
  12. data/data/parg/calconnect.json +1 -1
  13. data/data/parg/ccsds.json +1 -1
  14. data/data/parg/cen_cenelec.json +1 -1
  15. data/data/parg/cie.json +1 -1
  16. data/data/parg/csa.json +1 -1
  17. data/data/parg/doi.json +1 -1
  18. data/data/parg/easc.json +1 -1
  19. data/data/parg/ecma.json +1 -1
  20. data/data/parg/etsi.json +1 -1
  21. data/data/parg/evs.json +1 -1
  22. data/data/parg/gb.json +1 -1
  23. data/data/parg/gost.json +1 -1
  24. data/data/parg/iala.json +1 -1
  25. data/data/parg/iana.json +1 -1
  26. data/data/parg/idf.json +1 -1
  27. data/data/parg/iec.json +1 -1
  28. data/data/parg/ieee.json +1 -29571
  29. data/data/parg/ietf.json +1 -1
  30. data/data/parg/iho.json +1 -0
  31. data/data/parg/ire.json +1 -1
  32. data/data/parg/isbn.json +1 -1
  33. data/data/parg/iso.json +1 -8510
  34. data/data/parg/itu.json +1 -1
  35. data/data/parg/jcgm.json +1 -1
  36. data/data/parg/jis.json +1 -1
  37. data/data/parg/nesc.json +1 -1
  38. data/data/parg/nist.json +1 -9588
  39. data/data/parg/oasis.json +1 -1
  40. data/data/parg/ogc.json +1 -1
  41. data/data/parg/oiml.json +1 -1
  42. data/data/parg/omg.json +1 -1
  43. data/data/parg/parg.json +1 -1
  44. data/data/parg/plateau.json +1 -1
  45. data/data/parg/sae.json +1 -1
  46. data/data/parg/tables/typed_stages.yaml +1 -0
  47. data/data/parg/tgpp.json +1 -1
  48. data/data/parg/un.json +1 -1
  49. data/data/parg/w3c.json +1 -1
  50. data/data/parg/xsf.json +1 -1
  51. data/lib/pubid/adobe/identifier.rb +1 -1
  52. data/lib/pubid/adobe/parser.rb +2 -113
  53. data/lib/pubid/amca/identifiers/base.rb +1 -1
  54. data/lib/pubid/amca/parser.rb +1 -145
  55. data/lib/pubid/ansi/identifier.rb +1 -1
  56. data/lib/pubid/ansi/parser.rb +1 -82
  57. data/lib/pubid/api/builder.rb +32 -1
  58. data/lib/pubid/api/identifier.rb +1 -1
  59. data/lib/pubid/api/parser.rb +1 -131
  60. data/lib/pubid/ashrae/identifiers/base.rb +1 -1
  61. data/lib/pubid/ashrae/parser.rb +3 -710
  62. data/lib/pubid/asme/builder.rb +6 -4
  63. data/lib/pubid/asme/identifier.rb +1 -2
  64. data/lib/pubid/asme/parser.rb +1 -328
  65. data/lib/pubid/astm/builder.rb +12 -3
  66. data/lib/pubid/astm/identifier.rb +1 -2
  67. data/lib/pubid/astm/parser.rb +1 -227
  68. data/lib/pubid/bipm/identifier.rb +8 -1
  69. data/lib/pubid/bipm/parser.rb +2 -224
  70. data/lib/pubid/bsi/builder.rb +2 -4
  71. data/lib/pubid/bsi/parser.rb +1 -666
  72. data/lib/pubid/bsi/single_identifier.rb +38 -1
  73. data/lib/pubid/calconnect/identifier.rb +1 -1
  74. data/lib/pubid/calconnect/parser.rb +2 -40
  75. data/lib/pubid/ccsds/identifier.rb +1 -1
  76. data/lib/pubid/ccsds/parser.rb +1 -66
  77. data/lib/pubid/cen_cenelec/identifier.rb +16 -1
  78. data/lib/pubid/cen_cenelec/parser.rb +3 -136
  79. data/lib/pubid/cie/identifier.rb +1 -1
  80. data/lib/pubid/cie/parser.rb +3 -378
  81. data/lib/pubid/conformance.rb +1 -1
  82. data/lib/pubid/csa/builder.rb +41 -18
  83. data/lib/pubid/csa/identifier.rb +12 -4
  84. data/lib/pubid/csa/identifiers/amendment.rb +28 -0
  85. data/lib/pubid/csa/identifiers.rb +1 -0
  86. data/lib/pubid/csa/parser.rb +16 -397
  87. data/lib/pubid/doi/identifier.rb +1 -1
  88. data/lib/pubid/doi/parser.rb +2 -37
  89. data/lib/pubid/easc/builder.rb +1 -1
  90. data/lib/pubid/easc/identifier.rb +1 -1
  91. data/lib/pubid/easc/parser.rb +2 -70
  92. data/lib/pubid/ecma/CLAUDE.md +8 -1
  93. data/lib/pubid/ecma/identifier.rb +13 -8
  94. data/lib/pubid/ecma/parser.rb +2 -54
  95. data/lib/pubid/ecma/renderer.rb +3 -7
  96. data/lib/pubid/errors.rb +18 -13
  97. data/lib/pubid/etsi/builder.rb +8 -5
  98. data/lib/pubid/etsi/identifiers/base.rb +1 -1
  99. data/lib/pubid/etsi/parser.rb +1 -132
  100. data/lib/pubid/evs/identifier.rb +7 -0
  101. data/lib/pubid/evs/parser.rb +1 -8
  102. data/lib/pubid/evs.rb +1 -1
  103. data/lib/pubid/gb/identifier.rb +7 -1
  104. data/lib/pubid/gb/parser.rb +2 -51
  105. data/lib/pubid/gost/identifier.rb +9 -1
  106. data/lib/pubid/gost/parser.rb +4 -81
  107. data/lib/pubid/iala/parser.rb +2 -121
  108. data/lib/pubid/iana/identifier.rb +1 -1
  109. data/lib/pubid/iana/parser.rb +2 -24
  110. data/lib/pubid/identifier.rb +78 -1
  111. data/lib/pubid/idf/builder.rb +42 -0
  112. data/lib/pubid/idf/identifier.rb +1 -1
  113. data/lib/pubid/idf/parser.rb +1 -137
  114. data/lib/pubid/idf.rb +1 -2
  115. data/lib/pubid/iec/builder.rb +158 -50
  116. data/lib/pubid/iec/identifier.rb +1 -1
  117. data/lib/pubid/iec/parser.rb +3 -430
  118. data/lib/pubid/ieee/aiee/identifier.rb +2 -1
  119. data/lib/pubid/ieee/aiee.rb +0 -1
  120. data/lib/pubid/ieee/builder.rb +2 -2
  121. data/lib/pubid/ieee/identifiers/base.rb +1 -1
  122. data/lib/pubid/ieee/ire/identifier.rb +2 -1
  123. data/lib/pubid/ieee/ire.rb +0 -1
  124. data/lib/pubid/ieee/nesc.rb +0 -1
  125. data/lib/pubid/ieee/parser.rb +1 -1257
  126. data/lib/pubid/ietf/identifiers/base.rb +17 -1
  127. data/lib/pubid/ietf/parser.rb +2 -47
  128. data/lib/pubid/ietf/renderer.rb +17 -2
  129. data/lib/pubid/iho/CLAUDE.md +89 -0
  130. data/lib/pubid/iho/builder.rb +4 -1
  131. data/lib/pubid/iho/identifiers/base.rb +28 -2
  132. data/lib/pubid/iho/renderer.rb +8 -0
  133. data/lib/pubid/iho/urn_generator.rb +11 -1
  134. data/lib/pubid/iho/urn_parser.rb +21 -11
  135. data/lib/pubid/iho.rb +0 -1
  136. data/lib/pubid/isbn/identifier.rb +1 -1
  137. data/lib/pubid/isbn/parser.rb +2 -31
  138. data/lib/pubid/iso/builder.rb +1 -1
  139. data/lib/pubid/iso/identifiers/directives.rb +6 -3
  140. data/lib/pubid/iso/identifiers/guide.rb +6 -3
  141. data/lib/pubid/iso/identifiers/international_standard.rb +3 -1
  142. data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +6 -3
  143. data/lib/pubid/iso/normalizer.rb +1 -1
  144. data/lib/pubid/iso/parser.rb +1 -539
  145. data/lib/pubid/iso/supplement_identifier.rb +6 -3
  146. data/lib/pubid/iso/utilities.rb +4 -5
  147. data/lib/pubid/iso.rb +1 -19
  148. data/lib/pubid/itu/builder.rb +36 -20
  149. data/lib/pubid/itu/identifiers/base.rb +15 -2
  150. data/lib/pubid/itu/parser.rb +1 -775
  151. data/lib/pubid/jcgm/builder.rb +35 -0
  152. data/lib/pubid/jcgm/parser.rb +1 -151
  153. data/lib/pubid/jcgm.rb +1 -2
  154. data/lib/pubid/jis/builder.rb +18 -29
  155. data/lib/pubid/jis/identifier.rb +1 -1
  156. data/lib/pubid/jis/parser.rb +1 -128
  157. data/lib/pubid/nist/builder.rb +5 -1
  158. data/lib/pubid/nist/caster.rb +13 -3
  159. data/lib/pubid/nist/identifiers/base.rb +37 -2
  160. data/lib/pubid/nist/identifiers/circular.rb +2 -2
  161. data/lib/pubid/nist/identifiers/circular_supplement.rb +2 -2
  162. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +2 -2
  163. data/lib/pubid/nist/identifiers/crpl_report.rb +2 -2
  164. data/lib/pubid/nist/identifiers/handbook.rb +2 -2
  165. data/lib/pubid/nist/identifiers/internal_report.rb +2 -2
  166. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +2 -2
  167. data/lib/pubid/nist/identifiers/monograph.rb +2 -2
  168. data/lib/pubid/nist/identifiers/report.rb +2 -2
  169. data/lib/pubid/nist/parser.rb +19 -775
  170. data/lib/pubid/nist/router.rb +4 -1
  171. data/lib/pubid/nist/supplement_identifier.rb +2 -2
  172. data/lib/pubid/oasis/identifier.rb +1 -1
  173. data/lib/pubid/oasis/parser.rb +2 -16
  174. data/lib/pubid/ogc/identifier.rb +1 -1
  175. data/lib/pubid/ogc/parser.rb +2 -26
  176. data/lib/pubid/oiml/identifiers/certification_system.rb +7 -0
  177. data/lib/pubid/oiml/identifiers/dual_published.rb +2 -2
  178. data/lib/pubid/oiml/parser.rb +1 -303
  179. data/lib/pubid/oiml.rb +1 -2
  180. data/lib/pubid/omg/identifier.rb +6 -1
  181. data/lib/pubid/omg/parser.rb +2 -76
  182. data/lib/pubid/parg/artifact.rb +22 -3
  183. data/lib/pubid/parg/model.rb +98 -0
  184. data/lib/pubid/parg.rb +1 -0
  185. data/lib/pubid/parser.rb +5 -4
  186. data/lib/pubid/parsers/mr_string.rb +1 -1
  187. data/lib/pubid/plateau/parser.rb +1 -66
  188. data/lib/pubid/plateau.rb +1 -2
  189. data/lib/pubid/renderers/directives_renderer.rb +1 -1
  190. data/lib/pubid/renderers/iwa_renderer.rb +1 -1
  191. data/lib/pubid/renderers/publisher_stripper.rb +82 -0
  192. data/lib/pubid/renderers/supplement_renderer.rb +1 -1
  193. data/lib/pubid/renderers.rb +1 -0
  194. data/lib/pubid/sae/identifiers/base.rb +1 -1
  195. data/lib/pubid/sae/parser.rb +1 -48
  196. data/lib/pubid/tgpp/identifier.rb +1 -1
  197. data/lib/pubid/tgpp/parser.rb +2 -59
  198. data/lib/pubid/un/identifier.rb +1 -1
  199. data/lib/pubid/un/parser.rb +1 -18
  200. data/lib/pubid/version.rb +1 -1
  201. data/lib/pubid/w3c/identifier.rb +1 -1
  202. data/lib/pubid/w3c/parser.rb +2 -27
  203. data/lib/pubid/xsf/identifier.rb +11 -7
  204. data/lib/pubid/xsf/parser.rb +3 -31
  205. data/lib/pubid/xsf/renderer.rb +2 -4
  206. data/lib/pubid.rb +8 -9
  207. metadata +15 -25
  208. data/lib/pubid/ieee/aiee/parser.rb +0 -130
  209. data/lib/pubid/ieee/ire/parser.rb +0 -109
  210. data/lib/pubid/ieee/nesc/parser.rb +0 -155
  211. data/lib/pubid/iho/parser.rb +0 -68
  212. data/lib/pubid/parser/common_parse_methods.rb +0 -13
  213. data/lib/pubid/parser/common_parse_rules.rb +0 -56
  214. data/lib/pubid/parser/grammar.rb +0 -96
@@ -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
- first_num = parsed_hash[:first_number]
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
- annotated = format.is_a?(Hash) ? format[:annotated] : nil
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, annotated: annotated)
38
+ annotate_plain_render(rendered, **flags)
39
39
  end
40
40
 
41
41
  private
@@ -146,7 +146,7 @@ module Pubid
146
146
  raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
147
147
  end
148
148
 
149
- parsed = Pubid::Parg::Backend.parse(:oasis, identifier)
149
+ parsed = Pubid::Parg::Model.bound(:oasis, identifier)
150
150
  Builder.build(parsed)
151
151
  end
152
152
 
@@ -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
- # Parslet grammar for printed OASIS identifiers, e.g.
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 < ::Pubid::Parser::Grammar
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
@@ -85,7 +85,7 @@ module Pubid
85
85
  raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
86
86
  end
87
87
 
88
- Builder.build(Pubid::Parg::Backend.parse(:ogc, identifier))
88
+ Builder.build(Pubid::Parg::Model.bound(:ogc, identifier))
89
89
  end
90
90
  end
91
91
  end
@@ -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
- # Parslet grammar for OGC identifiers.
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 < ::Pubid::Parser::Grammar
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 Parslet::ParseFailed for a
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 Parslet::ParseFailed, Pubid::Errors::InvalidInputError
167
+ rescue Pubid::Errors::ParseError, Pubid::Errors::InvalidInputError
168
168
  nil
169
169
  end
170
170
  private_class_method :parse_side
@@ -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 < ::Pubid::Parser::Grammar
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
@@ -34,10 +34,9 @@ module Pubid
34
34
  return dual if dual
35
35
  end
36
36
 
37
- parser = Parser.new
38
37
  builder = Builder.new
39
38
 
40
- parsed = Pubid::Parg::Backend.parse(:oiml, identifier)
39
+ parsed = Pubid::Parg::Model.bound(:oiml, identifier)
41
40
  builder.build(parsed)
42
41
  end
43
42
 
@@ -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::Backend.parse(:omg, identifier)
61
+ parsed = Pubid::Parg::Model.bound(:omg, identifier)
57
62
  Builder.build(parsed)
58
63
  end
59
64
  end
@@ -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
- # Parslet grammar for OMG specification identifiers.
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 < ::Pubid::Parser::Grammar
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
@@ -21,13 +21,19 @@ module Pubid
21
21
  def for(flavor)
22
22
  @artifacts[flavor] ||=
23
23
  begin
24
- path = File.join(DATA_DIR, "#{flavor}.json")
25
- new(Parsanol::PARG::Artifact.load(path, tables_dir: TABLES_DIR))
24
+ new(Parsanol::PARG::Artifact.load(path_for(flavor),
25
+ tables_dir: TABLES_DIR))
26
26
  rescue Parsanol::PARG::Error => e
27
27
  raise Pubid::Errors::ParseError,
28
- "PG artifact #{path} rejected: #{e.message}"
28
+ "PG artifact #{path_for(flavor)} rejected: #{e.message}"
29
29
  end
30
30
  end
31
+
32
+ # The vendored artifact's path. PG::Lutaml.register loads the file
33
+ # itself, so model registration needs the path, not the wrapper.
34
+ def path_for(flavor)
35
+ File.join(DATA_DIR, "#{flavor}.json")
36
+ end
31
37
  end
32
38
 
33
39
  def initialize(artifact)
@@ -38,6 +44,19 @@ module Pubid
38
44
  @artifact.parse(entry, input)
39
45
  end
40
46
 
47
+ # The artifact tree with the entry's declared bindings applied —
48
+ # the builder-ready attribute hash the grammar's bindings block
49
+ # declares, types cast at the engine.
50
+ def parse_and_bind(entry, input)
51
+ @artifact.parse_and_bind(entry, input)
52
+ end
53
+
54
+ # The bindings the grammar declared for +entry+, empty when it
55
+ # declares none.
56
+ def bindings(entry)
57
+ @artifact.envelope.dig("entries", entry, "bindings") || []
58
+ end
59
+
41
60
  def checksum
42
61
  @artifact.envelope["checksum"]
43
62
  end