relaton-bib 1.4.0 → 2.1.7

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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/integration-tests.yml +54 -0
  3. data/.github/workflows/rake.yml +15 -0
  4. data/.github/workflows/release.yml +24 -0
  5. data/.gitignore +4 -1
  6. data/.rubocop.yml +3 -1
  7. data/CLAUDE.md +108 -0
  8. data/Gemfile +11 -0
  9. data/README.adoc +229 -314
  10. data/bin/console +1 -1
  11. data/docs/hash.adoc +132 -83
  12. data/lib/relaton/bib/converter/asciibib/to_asciibib.rb +663 -0
  13. data/lib/relaton/bib/converter/asciibib.rb +13 -0
  14. data/lib/relaton/bib/converter/bibtex/from_bibtex.rb +245 -0
  15. data/lib/relaton/bib/converter/bibtex/to_bibtex.rb +341 -0
  16. data/lib/relaton/bib/converter/bibtex.rb +23 -0
  17. data/lib/relaton/bib/converter/bibxml/from_rfcxml.rb +386 -0
  18. data/lib/relaton/bib/converter/bibxml/from_rfcxml_referencegroup.rb +71 -0
  19. data/lib/relaton/bib/converter/bibxml/to_rfcxml.rb +308 -0
  20. data/lib/relaton/bib/converter/bibxml/to_rfcxml_referencegroup.rb +52 -0
  21. data/lib/relaton/bib/converter/bibxml.rb +51 -0
  22. data/lib/relaton/bib/hash_parser_v1.rb +767 -0
  23. data/lib/relaton/bib/item_data.rb +229 -0
  24. data/lib/relaton/bib/model/abstract.rb +16 -0
  25. data/lib/relaton/bib/model/address.rb +22 -0
  26. data/lib/relaton/bib/model/affiliation.rb +16 -0
  27. data/lib/relaton/bib/model/bibdata.rb +9 -0
  28. data/lib/relaton/bib/model/bibdata_shared.rb +10 -0
  29. data/lib/relaton/bib/model/bibitem.rb +9 -0
  30. data/lib/relaton/bib/model/bibitem_shared.rb +10 -0
  31. data/lib/relaton/bib/model/contact.rb +14 -0
  32. data/lib/relaton/bib/model/contribution_info.rb +16 -0
  33. data/lib/relaton/bib/model/contributor.rb +29 -0
  34. data/lib/relaton/bib/model/copyright.rb +27 -0
  35. data/lib/relaton/bib/model/date.rb +29 -0
  36. data/lib/relaton/bib/model/depiction.rb +16 -0
  37. data/lib/relaton/bib/model/docidentifier.rb +34 -0
  38. data/lib/relaton/bib/model/doctype.rb +14 -0
  39. data/lib/relaton/bib/model/edition.rb +14 -0
  40. data/lib/relaton/bib/model/ext.rb +35 -0
  41. data/lib/relaton/bib/model/extent.rb +16 -0
  42. data/lib/relaton/bib/model/formattedref.rb +43 -0
  43. data/lib/relaton/bib/model/full_name_type.rb +64 -0
  44. data/lib/relaton/bib/model/fullname.rb +11 -0
  45. data/lib/relaton/bib/model/ics.rb +49 -0
  46. data/lib/relaton/bib/model/image.rb +28 -0
  47. data/lib/relaton/bib/model/item.rb +44 -0
  48. data/lib/relaton/bib/model/item_base.rb +20 -0
  49. data/lib/relaton/bib/model/item_shared.rb +88 -0
  50. data/lib/relaton/bib/model/keyword.rb +30 -0
  51. data/lib/relaton/bib/model/locality.rb +18 -0
  52. data/lib/relaton/bib/model/locality_stack.rb +14 -0
  53. data/lib/relaton/bib/model/localized_string.rb +48 -0
  54. data/lib/relaton/bib/model/localized_string_attrs.rb +24 -0
  55. data/lib/relaton/bib/model/logo.rb +14 -0
  56. data/lib/relaton/bib/model/medium.rb +22 -0
  57. data/lib/relaton/bib/model/note.rb +16 -0
  58. data/lib/relaton/bib/model/organization.rb +11 -0
  59. data/lib/relaton/bib/model/organization_type.rb +40 -0
  60. data/lib/relaton/bib/model/person.rb +36 -0
  61. data/lib/relaton/bib/model/phone.rb +14 -0
  62. data/lib/relaton/bib/model/place.rb +33 -0
  63. data/lib/relaton/bib/model/price.rb +14 -0
  64. data/lib/relaton/bib/model/relation.rb +41 -0
  65. data/lib/relaton/bib/model/series.rb +32 -0
  66. data/lib/relaton/bib/model/size.rb +23 -0
  67. data/lib/relaton/bib/model/source_locality_stack.rb +14 -0
  68. data/lib/relaton/bib/model/status.rb +27 -0
  69. data/lib/relaton/bib/model/structured_identifier.rb +48 -0
  70. data/lib/relaton/bib/model/subdivision.rb +16 -0
  71. data/lib/relaton/bib/model/title.rb +56 -0
  72. data/lib/relaton/bib/model/type/plain_date.rb +16 -0
  73. data/lib/relaton/bib/model/type/string_date.rb +48 -0
  74. data/lib/relaton/bib/model/uri.rb +18 -0
  75. data/lib/relaton/bib/model/validity.rb +16 -0
  76. data/lib/relaton/bib/model/version.rb +43 -0
  77. data/lib/relaton/bib/model_autoload.rb +85 -0
  78. data/lib/relaton/bib/namespace_helper.rb +21 -0
  79. data/lib/relaton/bib/sanitizer.rb +74 -0
  80. data/lib/relaton/bib/util.rb +18 -0
  81. data/lib/relaton/bib/version.rb +5 -0
  82. data/lib/relaton/bib/versions.json +35 -0
  83. data/lib/relaton/bib.rb +41 -0
  84. data/relaton-bib.gemspec +12 -16
  85. metadata +118 -129
  86. data/.github/workflows/macos.yml +0 -38
  87. data/.github/workflows/ubuntu.yml +0 -39
  88. data/.github/workflows/windows.yml +0 -41
  89. data/grammars/basicdoc.rng +0 -986
  90. data/grammars/biblio.rng +0 -1237
  91. data/grammars/isodoc.rng +0 -1613
  92. data/grammars/reqt.rng +0 -165
  93. data/lib/relaton_bib/bib_item_locality.rb +0 -107
  94. data/lib/relaton_bib/biblio_note.rb +0 -44
  95. data/lib/relaton_bib/biblio_version.rb +0 -49
  96. data/lib/relaton_bib/bibliographic_date.rb +0 -90
  97. data/lib/relaton_bib/bibliographic_item.rb +0 -726
  98. data/lib/relaton_bib/bibtex_parser.rb +0 -245
  99. data/lib/relaton_bib/classification.rb +0 -40
  100. data/lib/relaton_bib/contribution_info.rb +0 -109
  101. data/lib/relaton_bib/contributor.rb +0 -215
  102. data/lib/relaton_bib/copyright_association.rb +0 -77
  103. data/lib/relaton_bib/deep_dup.rb +0 -30
  104. data/lib/relaton_bib/document_identifier.rb +0 -95
  105. data/lib/relaton_bib/document_relation.rb +0 -94
  106. data/lib/relaton_bib/document_relation_collection.rb +0 -46
  107. data/lib/relaton_bib/document_status.rb +0 -93
  108. data/lib/relaton_bib/editorial_group.rb +0 -41
  109. data/lib/relaton_bib/formatted_ref.rb +0 -17
  110. data/lib/relaton_bib/formatted_string.rb +0 -55
  111. data/lib/relaton_bib/hash_converter.rb +0 -497
  112. data/lib/relaton_bib/hit.rb +0 -46
  113. data/lib/relaton_bib/hit_collection.rb +0 -73
  114. data/lib/relaton_bib/ics.rb +0 -37
  115. data/lib/relaton_bib/localized_string.rb +0 -93
  116. data/lib/relaton_bib/medium.rb +0 -44
  117. data/lib/relaton_bib/organization.rb +0 -135
  118. data/lib/relaton_bib/person.rb +0 -206
  119. data/lib/relaton_bib/place.rb +0 -49
  120. data/lib/relaton_bib/series.rb +0 -134
  121. data/lib/relaton_bib/structured_identifier.rb +0 -171
  122. data/lib/relaton_bib/technical_committee.rb +0 -36
  123. data/lib/relaton_bib/typed_title_string.rb +0 -96
  124. data/lib/relaton_bib/typed_uri.rb +0 -47
  125. data/lib/relaton_bib/validity.rb +0 -52
  126. data/lib/relaton_bib/version.rb +0 -3
  127. data/lib/relaton_bib/workers_pool.rb +0 -43
  128. data/lib/relaton_bib/workgroup.rb +0 -46
  129. data/lib/relaton_bib/xml_parser.rb +0 -463
  130. data/lib/relaton_bib.rb +0 -44
@@ -0,0 +1,386 @@
1
+ require "cgi"
2
+
3
+ module Relaton
4
+ module Bib
5
+ module Converter
6
+ module BibXml
7
+ class FromRfcxml # rubocop:disable Metrics/ClassLength
8
+ include NamespaceHelper
9
+
10
+ def initialize(reference)
11
+ @reference = reference
12
+ end
13
+
14
+ def transform # rubocop:disable Metrics/MethodLength
15
+ namespace::ItemData.new(
16
+ docnumber: @reference.anchor,
17
+ type: "standard",
18
+ docidentifier: docidentifiers,
19
+ status: status,
20
+ language: ["en"],
21
+ script: ["Latn"],
22
+ source: source,
23
+ title: title,
24
+ formattedref: formattedref,
25
+ abstract: abstract,
26
+ contributor: contributor + workgroup_contributors,
27
+ date: date,
28
+ series: series,
29
+ keyword: keyword,
30
+ ext: ext,
31
+ )
32
+ end
33
+
34
+ private
35
+
36
+ # --- Source ---
37
+
38
+ def source # rubocop:disable Metrics/AbcSize
39
+ s = []
40
+ if @reference.target
41
+ s << Uri.new(type: "src",
42
+ content: @reference.target)
43
+ end
44
+ (@reference.format || []).each do |fr|
45
+ s << Uri.new(type: fr.type, content: fr.target)
46
+ end
47
+ s
48
+ end
49
+
50
+ # --- Docidentifiers ---
51
+
52
+ def docidentifiers
53
+ draft_id = versioned_internet_draft_id
54
+ ids = [create_docid(@reference.anchor, primary: draft_id.nil?)]
55
+ ids << draft_id if draft_id
56
+ ids + docid_from_series_info
57
+ end
58
+
59
+ def create_docid(id, primary: false) # rubocop:disable Metrics/MethodLength
60
+ args = {}
61
+ pref, num = id_to_pref_num(id)
62
+ if RFCPREFIXES.include?(pref)
63
+ args[:content] = "#{pref} #{num.sub(/^-?0+/, '')}"
64
+ args[:type] = pubid_type(id)
65
+ elsif %w[I-D draft].include?(pref)
66
+ args[:content] = "draft-#{num}"
67
+ args[:type] = "Internet-Draft"
68
+ else
69
+ args[:content] = pref ? "#{pref} #{num}" : id
70
+ args[:type] = pubid_type(id)
71
+ end
72
+ args[:primary] = true if primary
73
+ Docidentifier.new(**args)
74
+ end
75
+
76
+ def pubid_type(id)
77
+ id_to_pref_num(id)&.first
78
+ end
79
+
80
+ PREF_NUM_RE = /^(?<pref>I-D|draft|3GPP|W3C|[A-Z]{2,})[._-]?(?<num>.+)/
81
+
82
+ def id_to_pref_num(id)
83
+ tn = PREF_NUM_RE.match id
84
+ tn && tn.to_a[1..2]
85
+ end
86
+
87
+ def versioned_internet_draft_id
88
+ si = internet_draft_series_info(@reference.front) ||
89
+ internet_draft_series_info(@reference)
90
+ return unless si
91
+
92
+ Docidentifier.new(
93
+ type: "Internet-Draft", content: si.value, primary: true,
94
+ )
95
+ end
96
+
97
+ def internet_draft_series_info(parent)
98
+ (parent.series_info || []).find { |si| si.name == "Internet-Draft" }
99
+ end
100
+
101
+ def docid_from_series_info # rubocop:disable Metrics/CyclomaticComplexity
102
+ front_si = @reference.front.series_info || []
103
+ ref_si = @reference.series_info || []
104
+ (front_si + ref_si).reduce([]) do |acc, s|
105
+ next acc unless s.name.casecmp("doi").zero?
106
+
107
+ acc << Docidentifier.new(type: "DOI", content: s.value)
108
+ end
109
+ end
110
+
111
+ # --- Status ---
112
+
113
+ def status # rubocop:disable Metrics/CyclomaticComplexity
114
+ si = @reference.front.series_info&.find(&:status) ||
115
+ @reference.series_info&.find(&:status)
116
+ return unless si
117
+
118
+ stage = Status::Stage.new(content: si.status)
119
+ Status.new(stage: stage)
120
+ end
121
+
122
+ # --- Title / Formattedref ---
123
+
124
+ def title
125
+ return [] unless @reference.front.title
126
+
127
+ [Title.new(content: @reference.front.title.content, language: "en",
128
+ script: "Latn")]
129
+ end
130
+
131
+ def formattedref
132
+ return if @reference.front.title
133
+
134
+ Formattedref.new(content: @reference.anchor)
135
+ end
136
+
137
+ # --- Abstract ---
138
+
139
+ def abstract
140
+ return [] unless @reference.front.abstract&.t
141
+
142
+ @reference.front.abstract.t.map do |t|
143
+ text = CGI.escapeHTML(t.content.join.strip)
144
+ Abstract.new(content: "<p>#{text}</p>", language: "en", script: "Latn")
145
+ end
146
+ end
147
+
148
+ # --- Contributors ---
149
+
150
+ def contributor
151
+ (@reference.front.author || []).reduce([]) do |acc, author|
152
+ p = person(author)
153
+ o = organization(author)
154
+ next acc unless p || o
155
+
156
+ args = { role: [contributor_role(author)] }
157
+ if p
158
+ args[:person] = p
159
+ else
160
+ args[:organization] = o
161
+ end
162
+ acc << Contributor.new(**args)
163
+ end
164
+ end
165
+
166
+ def contributor_role(author)
167
+ type = author.role || "author"
168
+ Contributor::Role.new(type: type)
169
+ end
170
+
171
+ # --- Person ---
172
+
173
+ def person(author)
174
+ return unless author.fullname || author.surname
175
+
176
+ Person.new(
177
+ name: person_name(author),
178
+ affiliation: person_affiliation(author),
179
+ address: person_address(author),
180
+ phone: contact_phone(author),
181
+ email: contact_email(author),
182
+ uri: contact_uri(author),
183
+ )
184
+ end
185
+
186
+ def person_name(author)
187
+ FullName.new(
188
+ completename: LocalizedString.new(content: author.fullname,
189
+ language: "en"),
190
+ formatted_initials: person_initials(author),
191
+ forename: person_forename(author),
192
+ surname: LocalizedString.new(content: author.surname,
193
+ language: "en"),
194
+ )
195
+ end
196
+
197
+ def person_initials(author)
198
+ return unless author.initials
199
+
200
+ LocalizedString.new(content: author.initials, language: "en")
201
+ end
202
+
203
+ def person_forename(author)
204
+ return [] unless author.initials
205
+
206
+ author.initials.split(/\.-?\s?|\s/).map do |i|
207
+ FullNameType::Forename.new(initial: i, language: "en")
208
+ end
209
+ end
210
+
211
+ def person_affiliation(author)
212
+ org = organization(author)
213
+ return [] unless org
214
+
215
+ [Affiliation.new(organization: org)]
216
+ end
217
+
218
+ # --- Organization ---
219
+
220
+ def organization(author) # rubocop:disable Metrics/AbcSize
221
+ org = author.organization
222
+ return if org.nil? || org.content.empty?
223
+
224
+ name = ORGNAMES[org.content.join] || org.content.join
225
+ orgname = TypedLocalizedString.new(content: name, language: "en")
226
+ abbrev = LocalizedString.new(content: org.abbrev, language: "en") if org.abbrev
227
+ Organization.new(
228
+ name: [orgname], abbreviation: abbrev,
229
+ address: org_address(author), phone: contact_phone(author),
230
+ email: contact_email(author), uri: contact_uri(author)
231
+ )
232
+ end
233
+
234
+ # --- Address ---
235
+
236
+ def person_address(author)
237
+ transform_address(author.address)
238
+ end
239
+
240
+ def org_address(author)
241
+ transform_address(author.address)
242
+ end
243
+
244
+ def transform_address(address) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength
245
+ return [] unless address&.postal
246
+
247
+ postal = address.postal
248
+ transform_address_args(
249
+ street: postal.street || [],
250
+ city: postal.city || [],
251
+ region: postal.region || [],
252
+ country: postal.country || [],
253
+ code: postal.code || [],
254
+ postal_line: postal.postal_line || [],
255
+ )
256
+ end
257
+
258
+ def transform_address_args(**args)
259
+ i = 0
260
+ addrs = []
261
+ while args.values.any? { |v| v[i] }
262
+ addrs << create_address(**args.transform_values { |v| v[i] })
263
+ i += 1
264
+ end
265
+ addrs
266
+ end
267
+
268
+ def create_address(**args) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/AbcSize,Metrics/PerceivedComplexity
269
+ street = []
270
+ street << args[:street]&.content if args[:street]
271
+ Address.new(
272
+ street: street,
273
+ city: args[:city]&.content,
274
+ state: args[:region]&.content,
275
+ country: args[:country]&.content,
276
+ postcode: args[:code]&.content,
277
+ formatted_address: args[:postal_line]&.content,
278
+ )
279
+ end
280
+
281
+ # --- Contacts ---
282
+
283
+ def contact_phone(author)
284
+ return [] unless author.address&.phone
285
+
286
+ [Phone.new(content: author.address.phone.content)]
287
+ end
288
+
289
+ def contact_email(author)
290
+ return [] unless author.address&.email
291
+
292
+ author.address.email.map(&:content)
293
+ end
294
+
295
+ def contact_uri(author)
296
+ return [] unless author.address&.uri
297
+
298
+ [Uri.new(content: author.address.uri.content)]
299
+ end
300
+
301
+ # --- Date ---
302
+
303
+ def date # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
304
+ dt = @reference.front&.date
305
+ return [] unless dt && (dt.year || dt.month || dt.day)
306
+
307
+ dparts = [dt.year, month_to_num(dt.month),
308
+ dt.day].compact.reject(&:empty?)
309
+ at = dt.content.empty? ? dparts.join("-") : dt.content
310
+ [Date.new(type: "published", at: at)]
311
+ end
312
+
313
+ def month_to_num(month)
314
+ return unless month
315
+
316
+ ::Date::MONTHNAMES.index(month.capitalize).to_s
317
+ end
318
+
319
+ # --- Series ---
320
+
321
+ def series # rubocop:disable Metrics/CyclomaticComplexity,Metrics/AbcSize
322
+ ref_si = @reference.series_info || []
323
+ front_si = @reference.front.series_info || []
324
+ (ref_si + front_si).map do |si|
325
+ next if si.name == "DOI" || si.stream || si.status
326
+
327
+ t = Title.new(content: si.name, language: "en", script: "Latn")
328
+ Series.new(title: [t], number: si.value, type: "main")
329
+ end.compact
330
+ end
331
+
332
+ # --- Keyword ---
333
+
334
+ def keyword
335
+ (@reference.front.keyword || []).map do |kw|
336
+ vocab = LocalizedString.new(content: kw.content, language: "en",
337
+ script: "Latn")
338
+ Keyword.new(vocab: vocab)
339
+ end
340
+ end
341
+
342
+ # --- Workgroup Contributors ---
343
+
344
+ def workgroup_contributors
345
+ return [] unless @reference.front.workgroup
346
+
347
+ @reference.front.workgroup.map do |wg|
348
+ Contributor.new(
349
+ role: [Contributor::Role.new(
350
+ type: "author",
351
+ description: [LocalizedMarkedUpString.new(content: "committee")],
352
+ )],
353
+ organization: Organization.new(
354
+ subdivision: [Subdivision.new(
355
+ type: "workgroup",
356
+ name: [TypedLocalizedString.new(content: wg.content)],
357
+ )],
358
+ ),
359
+ )
360
+ end
361
+ end
362
+
363
+ # --- Ext ---
364
+
365
+ def ext
366
+ dt = doctype
367
+ return unless dt
368
+
369
+ namespace::Ext.new doctype: dt, flavor: falvor
370
+ end
371
+
372
+ def doctype
373
+ type = case @reference.anchor
374
+ when /I-D/ then "internet-draft"
375
+ when /IEEE/ then "ieee"
376
+ else "rfc"
377
+ end
378
+ namespace::Doctype.new content: type
379
+ end
380
+
381
+ def falvor = "ietf"
382
+ end
383
+ end
384
+ end
385
+ end
386
+ end
@@ -0,0 +1,71 @@
1
+ module Relaton
2
+ module Bib
3
+ module Converter
4
+ module BibXml
5
+ class FromRfcxmlReferencegroup
6
+ include NamespaceHelper
7
+
8
+ def initialize(reference)
9
+ @reference = reference
10
+ end
11
+
12
+ def transform
13
+ namespace::ItemData.new(
14
+ docnumber: @reference.anchor.sub(/^\w+\./, ""),
15
+ type: "standard",
16
+ docidentifier: docidentifiers,
17
+ source: source,
18
+ relation: relation,
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def docidentifiers
25
+ [create_docid(@reference.anchor, primary: true)]
26
+ end
27
+
28
+ def create_docid(id, primary: false) # rubocop:disable Metrics/MethodLength
29
+ pref, num = id_to_pref_num(id)
30
+ if RFCPREFIXES.include?(pref)
31
+ pid = "#{pref} #{num.sub(/^-?0+/, '')}"
32
+ type = pubid_type(id)
33
+ elsif %w[I-D draft].include?(pref)
34
+ pid = "draft-#{num}"
35
+ type = "Internet-Draft"
36
+ else
37
+ pid = pref ? "#{pref} #{num}" : id
38
+ type = pubid_type(id)
39
+ end
40
+ Docidentifier.new(type: type, content: pid, primary: primary)
41
+ end
42
+
43
+ def pubid_type(id)
44
+ id_to_pref_num(id)&.first
45
+ end
46
+
47
+ def id_to_pref_num(id)
48
+ tn = FromRfcxml::PREF_NUM_RE.match id
49
+ tn && tn.to_a[1..2]
50
+ end
51
+
52
+ def source
53
+ s = []
54
+ if @reference.target
55
+ s << Uri.new(type: "src",
56
+ content: @reference.target)
57
+ end
58
+ s
59
+ end
60
+
61
+ def relation
62
+ (@reference.reference || []).map do |ref|
63
+ item = namespace::Converter::BibXml::FromRfcxml.new(ref).transform
64
+ Relation.new(type: "includes", bibitem: item)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end