relaton 3.0.0.pre.alpha.1 → 3.0.0.pre.alpha.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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +57 -1
  3. data/bin/console +0 -1
  4. data/lib/relaton/3gpp/bibliography.rb +82 -7
  5. data/lib/relaton/3gpp/data_fetcher.rb +51 -3
  6. data/lib/relaton/3gpp/docidentifier.rb +114 -0
  7. data/lib/relaton/3gpp/item.rb +6 -0
  8. data/lib/relaton/3gpp/parser.rb +1 -1
  9. data/lib/relaton/3gpp/processor.rb +4 -1
  10. data/lib/relaton/3gpp.rb +5 -1
  11. data/lib/relaton/adobe/bibdata.rb +8 -0
  12. data/lib/relaton/adobe/bibitem.rb +8 -0
  13. data/lib/relaton/adobe/bibliography.rb +92 -0
  14. data/lib/relaton/adobe/docidentifier.rb +49 -0
  15. data/lib/relaton/adobe/doctype.rb +14 -0
  16. data/lib/relaton/adobe/ext.rb +32 -0
  17. data/lib/relaton/adobe/item.rb +15 -0
  18. data/lib/relaton/adobe/item_base.rb +18 -0
  19. data/lib/relaton/adobe/item_data.rb +6 -0
  20. data/lib/relaton/adobe/processor.rb +45 -0
  21. data/lib/relaton/adobe/util.rb +8 -0
  22. data/lib/relaton/adobe.rb +37 -0
  23. data/lib/relaton/bib/model/address.rb +2 -2
  24. data/lib/relaton/bib/model/docidentifier.rb +24 -9
  25. data/lib/relaton/bib/model/localized_string.rb +1 -1
  26. data/lib/relaton/bib/model/structured_identifier.rb +10 -9
  27. data/lib/relaton/bib/sanitizer.rb +202 -6
  28. data/lib/relaton/bib.rb +0 -2
  29. data/lib/relaton/bipm/bibliography.rb +159 -10
  30. data/lib/relaton/bipm/data_fetcher.rb +26 -2
  31. data/lib/relaton/bipm/data_outcomes_parser.rb +3 -4
  32. data/lib/relaton/bipm/id_parser.rb +5 -4
  33. data/lib/relaton/bipm/model/structured_identifier.rb +21 -0
  34. data/lib/relaton/bipm/processor.rb +2 -2
  35. data/lib/relaton/bipm/rawdata_bipm_metrologia/fetcher.rb +2 -4
  36. data/lib/relaton/bipm/si_brochure_parser.rb +5 -3
  37. data/lib/relaton/bipm.rb +7 -1
  38. data/lib/relaton/bsi/bibliography.rb +115 -43
  39. data/lib/relaton/bsi/hit.rb +14 -0
  40. data/lib/relaton/bsi/hit_collection.rb +15 -16
  41. data/lib/relaton/bsi/model/docidentifier.rb +99 -1
  42. data/lib/relaton/bsi/processor.rb +1 -0
  43. data/lib/relaton/calconnect/bibliography.rb +12 -14
  44. data/lib/relaton/calconnect/data_fetcher.rb +77 -9
  45. data/lib/relaton/calconnect/docidentifier.rb +80 -0
  46. data/lib/relaton/calconnect/hit_collection.rb +65 -57
  47. data/lib/relaton/calconnect/model/item.rb +7 -0
  48. data/lib/relaton/calconnect/processor.rb +7 -1
  49. data/lib/relaton/calconnect.rb +11 -1
  50. data/lib/relaton/ccsds/data/fetcher.rb +17 -12
  51. data/lib/relaton/ccsds/data/parser.rb +1 -1
  52. data/lib/relaton/ccsds/hit_collection.rb +6 -1
  53. data/lib/relaton/ccsds/model/docidentifier.rb +121 -0
  54. data/lib/relaton/ccsds/model/item.rb +2 -0
  55. data/lib/relaton/cen/bibliography.rb +75 -47
  56. data/lib/relaton/cen/hit.rb +16 -1
  57. data/lib/relaton/cen/hit_collection.rb +59 -11
  58. data/lib/relaton/cen/model/docidentifier.rb +92 -1
  59. data/lib/relaton/cen/processor.rb +13 -8
  60. data/lib/relaton/cen/scraper.rb +13 -5
  61. data/lib/relaton/cen.rb +1 -0
  62. data/lib/relaton/cie/data_fetcher.rb +215 -30
  63. data/lib/relaton/cie/processor.rb +3 -1
  64. data/lib/relaton/cie/scrapper.rb +15 -2
  65. data/lib/relaton/cie.rb +2 -1
  66. data/lib/relaton/core/data_fetcher.rb +150 -3
  67. data/lib/relaton/core/governor.rb +320 -0
  68. data/lib/relaton/core/pacer.rb +134 -0
  69. data/lib/relaton/core/processor.rb +19 -0
  70. data/lib/relaton/core/request_error.rb +14 -0
  71. data/lib/relaton/core.rb +3 -0
  72. data/lib/relaton/db/registry.rb +41 -1
  73. data/lib/relaton/doi/crossref.rb +19 -2
  74. data/lib/relaton/doi/parser.rb +109 -15
  75. data/lib/relaton/easc/bibdata.rb +8 -0
  76. data/lib/relaton/easc/bibitem.rb +8 -0
  77. data/lib/relaton/easc/bibliography.rb +95 -0
  78. data/lib/relaton/easc/docidentifier.rb +100 -0
  79. data/lib/relaton/easc/doctype.rb +14 -0
  80. data/lib/relaton/easc/ext.rb +44 -0
  81. data/lib/relaton/easc/item.rb +13 -0
  82. data/lib/relaton/easc/item_base.rb +18 -0
  83. data/lib/relaton/easc/item_data.rb +6 -0
  84. data/lib/relaton/easc/processor.rb +46 -0
  85. data/lib/relaton/easc/util.rb +8 -0
  86. data/lib/relaton/easc.rb +35 -0
  87. data/lib/relaton/ecma/bibliography.rb +93 -25
  88. data/lib/relaton/ecma/data_fetcher.rb +71 -12
  89. data/lib/relaton/ecma/docidentifier.rb +124 -0
  90. data/lib/relaton/ecma/item.rb +2 -0
  91. data/lib/relaton/ecma/memento_parser.rb +1 -1
  92. data/lib/relaton/ecma/page_fetcher.rb +15 -3
  93. data/lib/relaton/ecma/parser_common.rb +2 -2
  94. data/lib/relaton/ecma/processor.rb +4 -1
  95. data/lib/relaton/ecma/standard_parser.rb +2 -2
  96. data/lib/relaton/ecma.rb +10 -1
  97. data/lib/relaton/etsi/bibliography.rb +67 -2
  98. data/lib/relaton/etsi/data_fetcher.rb +43 -4
  99. data/lib/relaton/etsi/processor.rb +3 -1
  100. data/lib/relaton/etsi.rb +2 -1
  101. data/lib/relaton/gb/bibliography.rb +55 -29
  102. data/lib/relaton/gb/docidentifier.rb +58 -9
  103. data/lib/relaton/gb/processor.rb +3 -0
  104. data/lib/relaton/gb/scraper.rb +27 -10
  105. data/lib/relaton/gost/bibdata.rb +8 -0
  106. data/lib/relaton/gost/bibitem.rb +8 -0
  107. data/lib/relaton/gost/bibliography.rb +107 -0
  108. data/lib/relaton/gost/docidentifier.rb +80 -0
  109. data/lib/relaton/gost/doctype.rb +16 -0
  110. data/lib/relaton/gost/ext.rb +46 -0
  111. data/lib/relaton/gost/item.rb +15 -0
  112. data/lib/relaton/gost/item_base.rb +18 -0
  113. data/lib/relaton/gost/item_data.rb +6 -0
  114. data/lib/relaton/gost/processor.rb +49 -0
  115. data/lib/relaton/gost/util.rb +8 -0
  116. data/lib/relaton/gost.rb +36 -0
  117. data/lib/relaton/iala/bibdata.rb +8 -0
  118. data/lib/relaton/iala/bibitem.rb +8 -0
  119. data/lib/relaton/iala/bibliography.rb +146 -0
  120. data/lib/relaton/iala/docidentifier.rb +89 -0
  121. data/lib/relaton/iala/doctype.rb +18 -0
  122. data/lib/relaton/iala/ext.rb +32 -0
  123. data/lib/relaton/iala/item.rb +21 -0
  124. data/lib/relaton/iala/item_base.rb +18 -0
  125. data/lib/relaton/iala/item_data.rb +6 -0
  126. data/lib/relaton/iala/processor.rb +43 -0
  127. data/lib/relaton/iala/relation.rb +7 -0
  128. data/lib/relaton/iala/util.rb +8 -0
  129. data/lib/relaton/iala.rb +35 -0
  130. data/lib/relaton/iana/bibliography.rb +67 -14
  131. data/lib/relaton/iana/data_fetcher.rb +35 -5
  132. data/lib/relaton/iana/processor.rb +3 -1
  133. data/lib/relaton/iana.rb +12 -1
  134. data/lib/relaton/iec/data_fetcher.rb +7 -1
  135. data/lib/relaton/iec/hit_collection.rb +1 -1
  136. data/lib/relaton/iec/model/docidentifier.rb +9 -5
  137. data/lib/relaton/iec/model/ext.rb +2 -2
  138. data/lib/relaton/iec/processor.rb +1 -0
  139. data/lib/relaton/ieee/bibliography.rb +25 -3
  140. data/lib/relaton/ieee/data_fetcher.rb +158 -17
  141. data/lib/relaton/ieee/idams_parser.rb +18 -11
  142. data/lib/relaton/ieee/processor.rb +4 -1
  143. data/lib/relaton/ieee/rawbib_id_parser.rb +291 -86
  144. data/lib/relaton/ieee.rb +2 -1
  145. data/lib/relaton/ietf/data_fetcher.rb +295 -12
  146. data/lib/relaton/ietf/processor.rb +7 -3
  147. data/lib/relaton/ietf/rfc/entry.rb +39 -3
  148. data/lib/relaton/ietf/scraper.rb +69 -36
  149. data/lib/relaton/ietf.rb +4 -1
  150. data/lib/relaton/iho/bibliography.rb +1 -1
  151. data/lib/relaton/iho/docidentifier.rb +1 -1
  152. data/lib/relaton/index/file_io.rb +11 -11
  153. data/lib/relaton/index/file_storage.rb +6 -1
  154. data/lib/relaton/index/pool.rb +6 -1
  155. data/lib/relaton/index/shard_source.rb +201 -0
  156. data/lib/relaton/index/type.rb +63 -12
  157. data/lib/relaton/index.rb +2 -1
  158. data/lib/relaton/iso/bibliography.rb +20 -15
  159. data/lib/relaton/iso/data_fetcher.rb +3 -3
  160. data/lib/relaton/iso/data_parser.rb +17 -3
  161. data/lib/relaton/iso/hit_collection.rb +27 -15
  162. data/lib/relaton/iso/item_data.rb +22 -0
  163. data/lib/relaton/iso/model/docidentifier.rb +24 -12
  164. data/lib/relaton/iso/processor.rb +1 -0
  165. data/lib/relaton/iso/scraper.rb +19 -3
  166. data/lib/relaton/itu/bibliography.rb +9 -4
  167. data/lib/relaton/itu/data_crawler_r.rb +664 -0
  168. data/lib/relaton/itu/data_fetcher.rb +496 -50
  169. data/lib/relaton/itu/data_merge_r.rb +149 -0
  170. data/lib/relaton/itu/data_parser_r.rb +163 -89
  171. data/lib/relaton/itu/data_parser_t.rb +228 -0
  172. data/lib/relaton/itu/family_cache.rb +177 -0
  173. data/lib/relaton/itu/governor.rb +56 -0
  174. data/lib/relaton/itu/hit.rb +9 -3
  175. data/lib/relaton/itu/hit_collection.rb +258 -86
  176. data/lib/relaton/itu/model/docidentifier.rb +67 -1
  177. data/lib/relaton/itu/model/structured_identifier.rb +19 -0
  178. data/lib/relaton/itu/processor.rb +10 -4
  179. data/lib/relaton/itu/pubid.rb +27 -5
  180. data/lib/relaton/itu/recommendation_fields.rb +334 -0
  181. data/lib/relaton/itu/recommendation_parser.rb +18 -149
  182. data/lib/relaton/itu/scraper.rb +13 -3
  183. data/lib/relaton/itu.rb +2 -1
  184. data/lib/relaton/jcgm/bibdata.rb +8 -0
  185. data/lib/relaton/jcgm/bibitem.rb +8 -0
  186. data/lib/relaton/jcgm/bibliography.rb +97 -0
  187. data/lib/relaton/jcgm/data_fetcher.rb +81 -0
  188. data/lib/relaton/jcgm/docidentifier.rb +102 -0
  189. data/lib/relaton/jcgm/doctype.rb +12 -0
  190. data/lib/relaton/jcgm/ext.rb +23 -0
  191. data/lib/relaton/jcgm/item.rb +20 -0
  192. data/lib/relaton/jcgm/item_base.rb +18 -0
  193. data/lib/relaton/jcgm/item_data.rb +6 -0
  194. data/lib/relaton/jcgm/meetings_parser.rb +175 -0
  195. data/lib/relaton/jcgm/processor.rb +71 -0
  196. data/lib/relaton/jcgm/relation.rb +9 -0
  197. data/lib/relaton/jcgm/structured_identifier.rb +40 -0
  198. data/lib/relaton/jcgm/util.rb +8 -0
  199. data/lib/relaton/jcgm.rb +24 -0
  200. data/lib/relaton/jis/bibliography.rb +8 -10
  201. data/lib/relaton/jis/data_fetcher.rb +21 -19
  202. data/lib/relaton/jis/docidentifier.rb +104 -5
  203. data/lib/relaton/jis/hit.rb +18 -23
  204. data/lib/relaton/jis/hit_collection.rb +19 -18
  205. data/lib/relaton/jis/processor.rb +1 -1
  206. data/lib/relaton/jis.rb +2 -3
  207. data/lib/relaton/logger/channels/gh_issue.rb +78 -13
  208. data/lib/relaton/nist/data_fetcher.rb +63 -13
  209. data/lib/relaton/nist/docidentifier.rb +165 -0
  210. data/lib/relaton/nist/item.rb +2 -0
  211. data/lib/relaton/nist/item_base.rb +16 -0
  212. data/lib/relaton/nist/mods_parser.rb +38 -12
  213. data/lib/relaton/nist/processor.rb +2 -1
  214. data/lib/relaton/nist/relation.rb +3 -0
  215. data/lib/relaton/nist/scraper.rb +6 -3
  216. data/lib/relaton/oasis/bibliography.rb +147 -6
  217. data/lib/relaton/oasis/data_fetcher.rb +41 -5
  218. data/lib/relaton/oasis/data_parser_utils.rb +37 -3
  219. data/lib/relaton/oasis/docidentifier.rb +54 -0
  220. data/lib/relaton/oasis/item.rb +3 -0
  221. data/lib/relaton/oasis/processor.rb +7 -1
  222. data/lib/relaton/oasis.rb +14 -1
  223. data/lib/relaton/ogc/data_fetcher.rb +23 -2
  224. data/lib/relaton/ogc/docidentifier.rb +105 -0
  225. data/lib/relaton/ogc/hit_collection.rb +78 -3
  226. data/lib/relaton/ogc/processor.rb +2 -1
  227. data/lib/relaton/ogc.rb +5 -1
  228. data/lib/relaton/oiml/bibliography.rb +90 -15
  229. data/lib/relaton/oiml/docidentifier.rb +18 -3
  230. data/lib/relaton/omg/docidentifier.rb +67 -0
  231. data/lib/relaton/omg/item.rb +1 -0
  232. data/lib/relaton/omg/processor.rb +1 -0
  233. data/lib/relaton/omg/scraper.rb +61 -16
  234. data/lib/relaton/omg.rb +1 -0
  235. data/lib/relaton/plateau/bibliography.rb +10 -3
  236. data/lib/relaton/plateau/data_fetcher.rb +25 -2
  237. data/lib/relaton/plateau/handbook_parser.rb +8 -1
  238. data/lib/relaton/plateau/hit.rb +10 -2
  239. data/lib/relaton/plateau/hit_collection.rb +31 -11
  240. data/lib/relaton/plateau/processor.rb +3 -1
  241. data/lib/relaton/plateau/technical_report_parser.rb +8 -1
  242. data/lib/relaton/plateau.rb +2 -1
  243. data/lib/relaton/sdo/config.rb +34 -0
  244. data/lib/relaton/sdo/fetcher.rb +52 -0
  245. data/lib/relaton/sdo/logo.rb +95 -0
  246. data/lib/relaton/sdo/name.rb +26 -0
  247. data/lib/relaton/sdo/organization.rb +71 -0
  248. data/lib/relaton/sdo/store.rb +49 -0
  249. data/lib/relaton/sdo.rb +29 -0
  250. data/lib/relaton/version.rb +1 -1
  251. data/lib/relaton/w3c/bibliography.rb +132 -12
  252. data/lib/relaton/w3c/data_fetcher.rb +194 -16
  253. data/lib/relaton/w3c/data_parser.rb +3 -3
  254. data/lib/relaton/w3c/docidentifier.rb +48 -0
  255. data/lib/relaton/w3c/governor.rb +32 -0
  256. data/lib/relaton/w3c/item.rb +3 -0
  257. data/lib/relaton/w3c/pubid.rb +12 -0
  258. data/lib/relaton/w3c/safe_realize.rb +110 -21
  259. data/lib/relaton/w3c.rb +12 -1
  260. data/lib/relaton/xsf/bibliography.rb +61 -1
  261. data/lib/relaton/xsf/data_fetcher.rb +55 -5
  262. data/lib/relaton/xsf/docidentifier.rb +46 -0
  263. data/lib/relaton/xsf/hit_collection.rb +31 -3
  264. data/lib/relaton/xsf/item.rb +6 -0
  265. data/lib/relaton/xsf/processor.rb +1 -0
  266. data/lib/relaton/xsf.rb +5 -1
  267. data/lib/relaton.rb +42 -0
  268. metadata +135 -24
  269. data/lib/relaton/ieee/pub_id.rb +0 -161
  270. data/lib/relaton/index/id_number.rb +0 -30
@@ -101,40 +101,45 @@ module Relaton
101
101
  # @return [void]
102
102
  #
103
103
  def search_instance_translation(bib)
104
- bibid = bib.docidentifier.first.content.dup
105
- if bibid.sub!(TRRGX, "")
106
- search_relations bibid, bib
104
+ pubid = bib.docidentifier.first.pubid
105
+ return unless pubid # unparseable content: no relation to search
106
+
107
+ bibid = pubid.exclude(:language)
108
+ if bibid == pubid
109
+ search_translations bibid, bib # no language: this is an instance
107
110
  else
108
- search_translations bibid, bib
111
+ search_relations bibid, bib # has a language: this is a translation
109
112
  end
110
113
  end
111
114
 
112
115
  #
113
116
  # Search instance or translation relation
114
117
  #
115
- # @param [String] bibid instance bibitem id
118
+ # @param [Pubid::Ccsds::Identifier] bibid_pid language-less instance id
116
119
  # @param [Relaton::Ccsds::ItemData] bib instance or translation bibitem
117
120
  #
118
121
  # @return [void]
119
122
  #
120
- def search_relations(bibid, bib)
121
- bibid_pid = ::Pubid::Ccsds::Identifier.parse(bibid)
123
+ def search_relations(bibid_pid, bib)
122
124
  # search(bibid_pid) narrows candidates by number via binary search first.
123
125
  index.search(bibid_pid) do |row|
124
- id = row[:id].exclude(:language)
125
- # TODO: smiplify this line?
126
- next if id != bibid_pid || row[:id] == bib.docidentifier.first.content
126
+ # Match language-agnostically: `bibid_pid` is language-less, so a
127
+ # translated row and the instance both match. `===` cannot do this —
128
+ # pubid declares CCSDS `language` `subset_strict`, so a nil reference
129
+ # language means "has none" instead of "any". Mirrors `HitCollection`.
130
+ next unless row[:id].exclude(:language) == bibid_pid
131
+ next if row[:id] == bib.docidentifier.first.pubid # exclude the document's own row
127
132
 
128
133
  create_relations bib, row[:file]
129
134
  end
130
135
  end
131
136
 
132
- def search_translations(bibid, bib)
133
- bibid_pid = ::Pubid::Ccsds::Identifier.parse(bibid)
137
+ def search_translations(bibid_pid, bib)
134
138
  # will call create_instance_relation if
135
139
  # there are same identifiers in index but with word "Translated"
136
140
  # search(bibid_pid) narrows candidates by number via binary search first.
137
141
  index.search(bibid_pid) do |row|
142
+ # Only translated rows (`row[:id].language`), matched language-agnostically.
138
143
  next unless row[:id].language && row[:id].exclude(:language) == bibid_pid
139
144
 
140
145
  create_instance_relation bib, row[:file]
@@ -47,7 +47,7 @@ module Relaton
47
47
  end
48
48
 
49
49
  def parse_docidentifier
50
- [Bib::Docidentifier.new(content: docidentifier, type: "CCSDS", primary: true)]
50
+ [Docidentifier.new(content: docidentifier, type: "CCSDS", primary: true)]
51
51
  end
52
52
 
53
53
  def docidentifier
@@ -31,7 +31,12 @@ module Relaton
31
31
 
32
32
  def rows
33
33
  if pubid.edition
34
- index.search(pubid)
34
+ # `exact:` keeps the match exact. `Type#search` without it takes
35
+ # pubid's subset match `pubid === row`. pubid declares `language`
36
+ # `subset_strict`, so a translation no longer matches, but `suffix` is
37
+ # still a wildcard: `CCSDS 101.0-B-4` would also reach the historical
38
+ # `CCSDS 101.0-B-4-S` (260 such pairs in the index fixture).
39
+ index.search(pubid, exact: true)
35
40
  else
36
41
  # search(pubid) narrows candidates by number via binary search first.
37
42
  index.search(pubid) { |r| r[:id].exclude(:edition) == pubid }
@@ -0,0 +1,121 @@
1
+ module Relaton
2
+ module Ccsds
3
+ # CCSDS document identifier backed by a parsed Pubid::Ccsds::Identifier.
4
+ #
5
+ # Mirrors the IEC flavor (lib/relaton/iec/model/docidentifier.rb): the
6
+ # lutaml `content` stays a plain string for serialization, while the parsed
7
+ # pubid is kept in `@pubid` so the structural mutators (`remove_part!`,
8
+ # `remove_date!`, `to_all_parts!`) can edit the identifier graph and
9
+ # re-render. CCSDS components are simpler than ISO/IEC — plain strings, no
10
+ # subpart, no supplement/consolidated special-casing beyond the base chain —
11
+ # so this is the minimal Pubid-backed shape.
12
+ class Docidentifier < Bib::Docidentifier
13
+ attribute :content, :string
14
+
15
+ attr_reader :pubid
16
+
17
+ def initialize(arg = nil, **kwargs)
18
+ arg.is_a?(Hash) ? super(arg) : super(**kwargs)
19
+ # lutaml may run the content setter before `type` is assigned; re-run it
20
+ # from `initialize` so the authoritative value is parsed with type set.
21
+ raw = arg.is_a?(Hash) ? (arg["content"] || arg[:content]) : kwargs[:content]
22
+ self.content = raw if raw
23
+ end
24
+
25
+ alias_method :original_content=, :content=
26
+ alias_method :original_content, :content
27
+
28
+ def content=(value)
29
+ @pubid = nil
30
+ @raw_content = nil
31
+
32
+ parsed =
33
+ case value
34
+ when ::Pubid::Ccsds::Identifier then value
35
+ when String
36
+ begin
37
+ ::Pubid::Ccsds::Identifier.parse(value)
38
+ rescue StandardError
39
+ # Suppress while `type` is unset: lutaml runs this setter once
40
+ # during init before `type` is assigned, then `initialize` re-runs
41
+ # it — only the second pass is authoritative.
42
+ Util.warn "Failed to parse Pubid: #{value}" if type
43
+ nil
44
+ end
45
+ end
46
+
47
+ if parsed
48
+ @pubid = parsed
49
+ elsif value.is_a?(String)
50
+ @raw_content = value
51
+ end
52
+
53
+ send(:original_content=, to_s)
54
+ end
55
+
56
+ def content
57
+ return @raw_content if @raw_content
58
+ return @pubid.to_s if @pubid
59
+
60
+ original_content
61
+ end
62
+
63
+ def to_s
64
+ content.to_s
65
+ end
66
+
67
+ def to_all_parts!
68
+ return if !@pubid || @pubid.all_parts?
69
+
70
+ # `#exclude` (no args) rebuilds a full independent copy, including
71
+ # the base chain of a supplement/corrigendum — `remove_attr!` walks
72
+ # and mutates that whole chain in place, so even a shallow `.dup`
73
+ # would still lose the original part/date to that mutation.
74
+ original = @pubid.exclude
75
+ remove_part!
76
+ remove_date!
77
+ # `content` is live-derived from `@pubid` (see above), and the wrapped
78
+ # all-parts identifier renders a "(all parts)" marker pubid-ccsds
79
+ # itself never has — so freeze the already-stripped rendering into
80
+ # `@raw_content` (checked first by `content`) before wrapping.
81
+ @raw_content = to_s
82
+ # Wrap the ORIGINAL (unstripped) pubid, not the part/date-stripped
83
+ # working copy above — `identifiers` should hold the real identifier
84
+ # this all-parts reference was derived from.
85
+ @pubid = original.to_all_parts
86
+ end
87
+
88
+ def remove_part!
89
+ remove_attr!(:part)
90
+ end
91
+
92
+ def remove_date!
93
+ remove_attr!(:date)
94
+ end
95
+
96
+ private
97
+
98
+ def remove_attr!(attr)
99
+ return unless @pubid
100
+
101
+ clear_attr_on(@pubid, attr)
102
+ # Supplements/corrigenda wrap a base identifier; a plain Base returns
103
+ # nil, so the walk clears the attr down the whole chain and terminates.
104
+ node = @pubid.base
105
+ while node
106
+ clear_attr_on(node, attr)
107
+ node = node.base
108
+ end
109
+ refresh_content!
110
+ end
111
+
112
+ def clear_attr_on(pubid, attr)
113
+ pubid.send("#{attr}=", nil) if pubid.respond_to?("#{attr}=")
114
+ end
115
+
116
+ def refresh_content!
117
+ send(:original_content=, to_s)
118
+ end
119
+ end
120
+ end
121
+ end
@@ -1,5 +1,6 @@
1
1
  require "relaton/bib"
2
2
  require_relative "../item_data"
3
+ require_relative "docidentifier"
3
4
  require_relative "ext"
4
5
 
5
6
  module Relaton
@@ -7,6 +8,7 @@ module Relaton
7
8
  class Item < Bib::Item
8
9
  model ItemData
9
10
 
11
+ attribute :docidentifier, Docidentifier, collection: true, initialize_empty: true
10
12
  attribute :ext, Ext
11
13
  end
12
14
  end
@@ -6,91 +6,119 @@ module Relaton
6
6
  class Bibliography
7
7
  class << self
8
8
  # @param text [String]
9
- # @return [RelatonCen::HitCollection]
9
+ # @return [Relaton::Cen::HitCollection]
10
10
  def search(text, year = nil)
11
- # /^C?EN\s(?<code>.+)/ =~ text
12
11
  HitCollection.new(text, year).search
13
12
  rescue Mechanize::ResponseCodeError, Net::ReadTimeout => e
14
13
  raise Relaton::RequestError, e.message
15
14
  end
16
15
 
16
+ #
17
+ # Parses a printed CEN/CENELEC reference.
18
+ #
19
+ # Deliberately does NOT rescue. An unrecognized query reference must
20
+ # raise, so that relaton-cli can print `"…" is not a recognized
21
+ # standards identifier`; returning nil would make "malformed" look like
22
+ # "not found". Only the DATA side rescues — see `Hit#pubid`, which
23
+ # drops a catalogue code the grammar cannot read (`prEN 13306 rev`).
24
+ #
25
+ # @param ref [String] a printed reference, e.g. "EN 285:2015+A1"
26
+ #
27
+ # @return [Pubid::CenCenelec::Identifier]
28
+ # @raise [Pubid::Errors::ParseError]
29
+ #
30
+ def parse(ref)
31
+ ::Pubid::CenCenelec::Identifier.parse ref
32
+ end
33
+
17
34
  #
18
35
  # @param code [String] the CEN standard Code to look up
19
36
  # @param year [String] the year the standard was published (optional)
20
37
  # @param opts [Hash] options
21
38
  # @option opts [Boolean] :keep_year don't upate reference
22
39
  #
23
- # @return [RelatonBib::BibliographicItem, nil]
40
+ # @return [Relaton::Cen::ItemData, nil]
24
41
  #
25
42
  def get(code, year = nil, opts = {})
26
- code_parts = code_to_parts code
27
- year ||= code_parts[:year] if code_parts
43
+ # An empty string is no reference, not a malformed one, so it stays a
44
+ # plain nil rather than a parse failure. Mirrors the guard
45
+ # HitCollection#search already carries.
46
+ return if code.nil? || code.strip.empty?
47
+
48
+ query = parse code
49
+ year ||= publication_year query
28
50
 
29
- bib_get(code, year, opts)
51
+ bib_get code, year, opts, query
30
52
  end
31
53
 
54
+ private
55
+
32
56
  #
33
- # Decopmposes a CEN standard code into its parts.
57
+ # The year of the document the reference names: the base document's
58
+ # year for a supplement, and the adopted document's year for an adopted
59
+ # norm (an `AdoptedEuropeanNorm` carries no year of its own, so `#root`
60
+ # is the accessor that answers for every form).
34
61
  #
35
- # @param [String] code the CEN standard code to decompose
62
+ # @param id [Pubid::CenCenelec::Identifier]
36
63
  #
37
- # @return [MatchData] the decomposition of the code
64
+ # @return [String, nil]
38
65
  #
39
- def code_to_parts(code)
40
- %r{^
41
- (?<code>[^:-]+)(?:-(?<part>\d+))?
42
- (?::(?<year>\d{4}))?
43
- (?:\+(?<amd>[A-Z]\d+)(?:(?<amy>\d{4}))?)?
44
- (?:\/(?<ac>AC\d+:\d{4}))?
45
- }x.match code
66
+ def publication_year(id)
67
+ id.root.year&.to_s
46
68
  end
47
69
 
48
- private
70
+ # A component is absent from a reference when excluding it changes
71
+ # nothing. This replaces the old capture-group tests, and works for the
72
+ # forms that hold the component on a nested identifier (an adopted norm
73
+ # keeps its part on the adopted ISO document).
74
+ #
75
+ # @return [Boolean]
76
+ def absent?(id, *keys)
77
+ id.exclude(*keys) == id
78
+ end
49
79
 
50
- def fetch_ref_err(code, year, missed_years) # rubocop:disable Metrics/MethodLength
51
- # id = year ? "#{code}:#{year}" : code
52
- # Util.warn "WARNING: No match found online for `#{id}`. " \
53
- # "The code must be exactly like it is on the standards website."
80
+ def fetch_ref_err(_code, year, missed_years)
54
81
  unless missed_years.empty?
55
82
  Util.info "There was no match for `#{year}`, though there " \
56
83
  "were matches found for `#{missed_years.join('`, `')}`."
57
84
  end
58
- # if /\d-\d/.match? code
59
- # warn "[relaton-cen] The provided document part may not exist, or "\
60
- # "the document may no longer be published in parts."
61
- # else
62
- # warn "[relaton-cen] If you wanted to cite all document parts for "\
63
- # "the reference, use \"#{code} (all parts)\".\nIf the document "\
64
- # "is not a standard, use its document type abbreviation (TS, TR, "]
65
- # "PAS, Guide)."
66
- # end
67
85
  nil
68
86
  end
69
87
 
70
- # @param code [String]
71
- # @return [RelatonCen::HitCollection]
72
- def search_filter(code) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
73
- parts = code_to_parts code
74
- result = search(code)
75
- result.select! do |i|
76
- pts = code_to_parts i.hit[:code]
77
- parts[:code] == pts[:code] &&
78
- (!parts[:part] || parts[:part] == pts[:part]) &&
79
- (!parts[:year] || parts[:year] == pts[:year]) &&
80
- parts[:amd] == pts[:amd] && (!parts[:amy] || parts[:amy] == pts[:amy])
81
- end
88
+ #
89
+ # Selects the portal hits that denote the same document as the query.
90
+ #
91
+ # The search text stays the caller's raw reference — that is
92
+ # search-engine input, not identifier parsing — while the SELECTION is
93
+ # pubid's. A hit whose code the grammar cannot read is dropped rather
94
+ # than aborting the search.
95
+ #
96
+ # The selection is pubid's subset match `query === hit.pubid`. A part,
97
+ # a year or a supplement year that the query omits matches any value.
98
+ # pubid declares the CEN `type`, `stage` and `typed_stage` strict, so
99
+ # `EN 1325` matches neither `prEN 1325` nor `CEN/TS 1325`. The class
100
+ # must be identical, so a base reference never answers with its own
101
+ # amendment's record, and a supplement reference never answers with
102
+ # its base document.
103
+ #
104
+ # @param code [String] the raw reference, as the portal form wants it
105
+ # @param query [Pubid::CenCenelec::Identifier]
106
+ #
107
+ # @return [Relaton::Cen::HitCollection]
108
+ #
109
+ def search_filter(code, query)
110
+ search(code).select! { |hit| hit.pubid && query === hit.pubid }
82
111
  end
83
112
 
84
113
  # Sort through the results from Isobib, fetching them three at a time,
85
114
  # and return the first result that matches the code,
86
115
  # matches the year (if provided), and which # has a title (amendments do not).
87
116
  # Only expects the first page of results to be populated.
88
- # Does not match corrigenda etc (e.g. ISO 3166-1:2006/Cor 1:2007)
89
117
  # If no match, returns any years which caused mismatch, for error reporting
90
118
  def isobib_results_filter(result, year)
91
119
  missed_years = []
92
120
  result.each do |r|
93
- /:(?<pyear>\d{4})/ =~ r.hit[:code]
121
+ pyear = r.pubid && publication_year(r.pubid)
94
122
  if !year || year == pyear
95
123
  ret = r.item
96
124
  return { ret: ret } if ret
@@ -101,10 +129,10 @@ module Relaton
101
129
  { years: missed_years }
102
130
  end
103
131
 
104
- def bib_get(code, year, opts) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength
105
- ref = year.nil? || code.match?(/:\d{4}/) ? code : "#{code}:#{year}"
132
+ def bib_get(code, year, opts, query) # rubocop:disable Metrics/MethodLength
133
+ ref = year && absent?(query, :year) ? "#{code}:#{year}" : code
106
134
  Util.info "Fetching from standards.cencenelec.eu ...", key: ref
107
- result = search_filter(code) || return
135
+ result = search_filter(code, query)
108
136
  ret = isobib_results_filter(result, year)
109
137
  if ret[:ret]
110
138
  bib = year || opts[:keep_year] ? ret[:ret] : ret[:ret].to_most_recent_reference
@@ -7,10 +7,25 @@ module Relaton
7
7
  attr_writer :item
8
8
 
9
9
  # Parse page.
10
- # @return [IsoRelatonBib::IsoBibliographicItem]
10
+ # @return [Relaton::Cen::ItemData]
11
11
  def item
12
12
  @fetch ||= Scraper.parse_page self
13
13
  end
14
+
15
+ # The hit's code parsed with pubid, memoized because the filter, the sort
16
+ # and the scraper all read it. `nil` when pubid cannot parse the code: the
17
+ # portal lists draft revisions such as `prEN 13306 rev`, and one such row
18
+ # must not abort the search. (A malformed *query* raises; see
19
+ # `Bibliography.parse`.)
20
+ #
21
+ # @return [Pubid::CenCenelec::Identifier, nil]
22
+ def pubid
23
+ return @pubid if defined?(@pubid)
24
+
25
+ @pubid = Bibliography.parse hit[:code]
26
+ rescue StandardError
27
+ @pubid = nil
28
+ end
14
29
  end
15
30
  end
16
31
  end
@@ -39,20 +39,68 @@ module Relaton
39
39
  private
40
40
 
41
41
  def sort
42
- @array.sort! do |a, b|
43
- ap = Bibliography.code_to_parts a.hit[:code]
44
- bp = Bibliography.code_to_parts b.hit[:code]
45
- s = ap[:code] <=> bp[:code]
46
- s = ap[:part].to_s <=> bp[:part].to_s if s.zero?
47
- s = bp[:year].to_s <=> ap[:year].to_s if s.zero?
48
- s = ap[:amd].to_s <=> bp[:amd].to_s if s.zero?
49
- s = ap[:amy].to_s <=> bp[:amy].to_s if s.zero?
50
- s = ap[:ac].to_s <=> bp[:ac].to_s if s.zero?
51
- s
52
- end
42
+ @array.sort_by! { |hit| sort_key hit }
53
43
  self
54
44
  end
55
45
 
46
+ #
47
+ # The order the `code_to_parts` comparison gave, read from pubid instead:
48
+ # the document family ascending, then the part ascending with a part-less
49
+ # hit first, then the year DESCENDING with a year-less hit last, then the
50
+ # supplement, so a base document precedes its own amendments and
51
+ # corrigenda. A hit pubid cannot parse sorts last.
52
+ #
53
+ # `#root` is the accessor that answers for every form: an adopted norm
54
+ # keeps its number, part and year on the adopted ISO document.
55
+ #
56
+ # @param hit [Relaton::Cen::Hit]
57
+ #
58
+ # @return [Array]
59
+ #
60
+ def sort_key(hit)
61
+ id = hit.pubid
62
+ return [1, "", [], 0, ["", "", ""]] unless id
63
+
64
+ root = id.root
65
+ [0, family(id), part_segments(root), -root.year.to_s.to_i,
66
+ supplement_key(id)]
67
+ end
68
+
69
+ # The document family: the base document with its part and year dropped,
70
+ # e.g. `EN 13250:2000/A1:2005` -> `EN 13250`. Read from the base document
71
+ # rather than from `#root`, so that `EN ISO 1234` and `CEN ISO/TS 1234`
72
+ # stay apart.
73
+ def family(id)
74
+ id.base_document.exclude(:year, :part, :subpart).to_s
75
+ end
76
+
77
+ # Part segments as integers, so `-10` sorts after `-2`. pubid holds a
78
+ # sub-part inside `part` (`61375-2-3` gives `"2-3"`).
79
+ def part_segments(root)
80
+ root.part.to_s.split("-").map(&:to_i)
81
+ end
82
+
83
+ def supplement_key(id)
84
+ sup = supplements(id).first
85
+ return ["", "", ""] unless sup
86
+
87
+ [sup.supplement_type.to_s, sup.number.to_s, sup.year.to_s]
88
+ end
89
+
90
+ # A consolidated identifier (`EN 285:2015+A1:2021`) holds its base
91
+ # document and its supplements in `#identifiers`, and answers none of the
92
+ # supplement accessors itself; an amendment or corrigendum IS the
93
+ # supplement.
94
+ def supplements(id)
95
+ if id.respond_to? :identifiers
96
+ id.identifiers.drop(1)
97
+ elsif id.respond_to? :supplement_type
98
+ [id]
99
+ else
100
+ []
101
+ end
102
+ end
103
+
56
104
  # @param resp [Mechanize::Page]
57
105
  # @return [Array<RelatonCen::Hit>]
58
106
  def hits(resp)
@@ -1,8 +1,99 @@
1
1
  module Relaton
2
2
  module Cen
3
+ # A CEN/CENELEC document identifier backed by `Pubid::CenCenelec`.
4
+ #
5
+ # `content` stays a plain string, so serialization is unchanged; the parsed
6
+ # identifier lives beside it in `@pubid` and drives the three mutators.
7
+ # Follows the IALA shape (`lib/relaton/iala/docidentifier.rb`), which
8
+ # `lib/relaton/bib/model/docidentifier.rb` names as the reference. CEN has
9
+ # no relaton-iso dependency, so BSI's `Iso::Type::Pubid` lutaml type does
10
+ # not fit here.
3
11
  class Docidentifier < Bib::Docidentifier
12
+ # @return [Pubid::CenCenelec::Identifier, nil] nil when the content is
13
+ # not a CEN identifier, or the grammar cannot read it
14
+ attr_reader :pubid
15
+
16
+ # Capture the inherited (LocalizedMarkedUpString) content setter before
17
+ # overriding #content=, so #refresh_content! writes the re-rendered string
18
+ # back WITHOUT re-parsing it and discarding the mutation.
19
+ alias_method :store_content, :content=
20
+
21
+ def content=(value)
22
+ super
23
+ return unless value
24
+
25
+ @pubid = begin
26
+ # `pubid` is required lazily because deserialization reaches this
27
+ # class without the flavor entry file having been loaded. LoadError
28
+ # degrades to a plain string; StandardError covers a non-CEN value
29
+ # (an ISBN) and a code the grammar rejects (`prEN 13306 rev`), both
30
+ # of which are DATA and must not raise. A malformed *query* raises —
31
+ # see Bibliography.parse.
32
+ require "pubid"
33
+ ::Pubid::CenCenelec::Identifier.parse(value)
34
+ rescue LoadError, StandardError
35
+ nil
36
+ end
37
+ end
38
+
39
+ # Drops the LAST year, and only that one.
40
+ #
41
+ # On a supplement the identifier names the supplement, so its own year is
42
+ # the last one: `EN 13250:2000/A1:2005` is amendment A1 of 2005, and the
43
+ # `2000` belongs to the base document it amends. Excluding that base year
44
+ # would strip the base document's identity rather than a date. Where there
45
+ # is no supplement year the document's own year is the base year.
46
+ #
47
+ # This also closes a case the old `/:\d{4}$/` regex missed:
48
+ # `EN 61375-2-3:2015/AC:2016-11` does not end in a bare year, so the regex
49
+ # stripped nothing, while `exclude(:supplement_year)` resets the
50
+ # supplement's year and month together.
4
51
  def remove_date!
5
- self.content.sub!(/:\d{4}$/, "")
52
+ return unless @pubid
53
+
54
+ stripped = @pubid.exclude(:supplement_year)
55
+ stripped = @pubid.exclude(:year) if stripped == @pubid
56
+ replace_pubid stripped
57
+ end
58
+
59
+ def remove_part!
60
+ return unless @pubid
61
+
62
+ replace_pubid @pubid.exclude(:part, :subpart)
63
+ end
64
+
65
+ # Strips the part and the date, then wraps `@pubid` in pubid's `AllParts`.
66
+ #
67
+ # `content` stays the plain part-and-date-stripped string —
68
+ # `Pubid::CenCenelec::Renderer` has no "(all parts)" marker of its own,
69
+ # and `content` is cached from the pre-wrap pubid on purpose (see
70
+ # below). `#pubid` itself, read directly, now answers
71
+ # `all_parts? == true` and renders WITH pubid's own generic marker
72
+ # (`#to_s`), since it's the wrapper. Same trade-off as
73
+ # `Relaton::Bsi::Docidentifier` and `Relaton::Iala::Docidentifier`.
74
+ def to_all_parts!
75
+ return if !@pubid || @pubid.all_parts?
76
+
77
+ # `#exclude` (no args) rebuilds a full independent copy. `remove_part!`
78
+ # / `remove_date!` happen to replace `@pubid` rather than mutate it in
79
+ # place here, but this stays correct even if that changes.
80
+ original = @pubid.exclude
81
+ remove_part!
82
+ remove_date!
83
+ @pubid = original.to_all_parts
84
+ end
85
+
86
+ private
87
+
88
+ # `Pubid#exclude` returns a COPY (the BSI shape), so the new identifier
89
+ # replaces the old one; it does not mutate in place as IALA's setters do.
90
+ def replace_pubid(new_pubid)
91
+ @pubid = new_pubid
92
+ refresh_content!
93
+ end
94
+
95
+ def refresh_content!
96
+ store_content(@pubid.to_s) if @pubid
6
97
  end
7
98
  end
8
99
  end
@@ -1,4 +1,4 @@
1
- require_relative "../cen"
1
+ require "relaton/core/processor"
2
2
 
3
3
  module Relaton
4
4
  module Cen
@@ -6,6 +6,7 @@ module Relaton
6
6
  def initialize # rubocop:disable Lint/MissingSuper
7
7
  @short = :relaton_cen
8
8
  @prefix = "CEN"
9
+ @pubid_flavor = :CenCenelec # global prefixes from Pubid::CenCenelec.prefixes
9
10
  @defaultprefix = %r{^(C?EN|ENV|CWA|HD|CR)[\s/]}
10
11
  @idtype = "CEN"
11
12
  end
@@ -13,27 +14,31 @@ module Relaton
13
14
  # @param code [String]
14
15
  # @param date [String, NilClass] year
15
16
  # @param opts [Hash]
16
- # @return [RelatonBib::BibliographicItem]
17
+ # @return [Relaton::Cen::ItemData]
17
18
  def get(code, date, opts)
18
- Bibliography.get(code, date, opts)
19
+ require_relative "../cen"
20
+ ::Relaton::Cen::Bibliography.get(code, date, opts)
19
21
  end
20
22
 
21
23
  # @param xml [String]
22
- # @return [RelatonBib::BibliographicItem]
24
+ # @return [Relaton::Cen::ItemData]
23
25
  def from_xml(xml)
24
- Item.from_xml xml
26
+ require_relative "../cen"
27
+ ::Relaton::Cen::Item.from_xml xml
25
28
  end
26
29
 
27
30
  # @param yaml [String]
28
- # @return [RelatonBib::BibliographicItem]
31
+ # @return [Relaton::Cen::ItemData]
29
32
  def from_yaml(yaml)
30
- Item.from_yaml yaml
33
+ require_relative "../cen"
34
+ ::Relaton::Cen::Item.from_yaml yaml
31
35
  end
32
36
 
33
37
  # Returns hash of XML grammar
34
38
  # @return [String]
35
39
  def grammar_hash
36
- @grammar_hash ||= Cen.grammar_hash
40
+ require_relative "../cen"
41
+ @grammar_hash ||= ::Relaton::Cen.grammar_hash
37
42
  end
38
43
  end
39
44
  end