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,308 @@
1
+ require "cgi"
2
+
3
+ module Relaton
4
+ module Bib
5
+ module Converter
6
+ module BibXml
7
+ class ToRfcxml
8
+ def initialize(item, include_keywords: true)
9
+ @item = item
10
+ @include_keywords = include_keywords
11
+ end
12
+
13
+ def transform
14
+ model = ::Rfcxml::V3::Reference.new
15
+ model.anchor = @item.docnumber || derive_anchor
16
+ model.target = create_target
17
+ model.front = create_front
18
+ model.format = create_format
19
+ model
20
+ end
21
+
22
+ private
23
+
24
+ def derive_anchor
25
+ di = @item.docidentifier.detect(&:primary) || @item.docidentifier[0]
26
+ di&.content&.to_s&.gsub(" ", ".")
27
+ end
28
+
29
+ def create_target
30
+ target = @item.source.detect { |l| l.type.casecmp("src").zero? } ||
31
+ @item.source.detect { |l| l.type.casecmp("doi").zero? }
32
+ return unless target
33
+
34
+ target.content.to_s
35
+ end
36
+
37
+ def create_front # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
38
+ front = ::Rfcxml::V3::Front.new
39
+ front.title = Rfcxml::V3::Title.new(content: @item.title[0].content) if @item.title.any?
40
+ front.series_info = create_seriesinfo
41
+ front.author = create_authors
42
+ front.date = create_date
43
+ front.workgroup = create_workgroup
44
+ front.keyword = create_keyword if @include_keywords
45
+ front.abstract = create_abstract
46
+ front
47
+ end
48
+
49
+ def create_seriesinfo
50
+ docidentifier_to_seriesinfo + series_to_seriesinfo
51
+ end
52
+
53
+ def docidentifier_to_seriesinfo
54
+ @item.docidentifier.each_with_object([]) do |di, si|
55
+ if di.type == "DOI" && di.scope != "trademark"
56
+ si << Rfcxml::V3::SeriesInfo.new(name: di.type,
57
+ value: di.content)
58
+ end
59
+ end
60
+ end
61
+
62
+ def series_to_seriesinfo # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
63
+ @item.series.select do |s|
64
+ s.title.reject { |t| t.content == "DOI" }.any?
65
+ end.uniq do |s|
66
+ s.title.find { |t| t.content != "DOI" }.content
67
+ end.each_with_object([]) do |s, si|
68
+ title = s.title.find { |t| t.content != "DOI" }
69
+ si << Rfcxml::V3::SeriesInfo.new(name: title.content,
70
+ value: s.number)
71
+ end
72
+ end
73
+
74
+ def create_authors # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
75
+ @item.contributor.reject { |c| committee_contributor?(c) }.map do |contrib|
76
+ role = "editor" if contrib.role.detect { |r| r.type == "editor" }
77
+ Rfcxml::V3::Author.new(
78
+ role: role,
79
+ initials: person_initials(contrib.person),
80
+ surname: person_surname(contrib.person),
81
+ fullname: person_fullname(contrib.person),
82
+ organization: create_organization(contrib),
83
+ address: create_address(contrib),
84
+ )
85
+ end
86
+ end
87
+
88
+ def person_fullname(person) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
89
+ return unless person
90
+
91
+ if person.name.completename
92
+ person.name.completename.content
93
+ elsif person.name.forename.any?
94
+ parts = person.name.forename.map { |n| n.content || n.initial }
95
+ parts << person.name.surname.content if person.name.surname
96
+ parts.join(" ")
97
+ end
98
+ end
99
+
100
+ def person_initials(person) # rubocop:disable Metrics/AbcSize
101
+ return unless person
102
+
103
+ if person.name.formatted_initials
104
+ person.name.formatted_initials.content
105
+ elsif person.name.forename.any?
106
+ person.name.forename.map do |f|
107
+ "#{f.initial || f.content[0]}."
108
+ end.join " "
109
+ end
110
+ end
111
+
112
+ def person_surname(person)
113
+ return unless person
114
+
115
+ person.name.surname&.content
116
+ end
117
+
118
+ def create_organization(contrib) # rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/AbcSize
119
+ org = contrib_org(contrib)
120
+ return unless org
121
+
122
+ abbrev = org.abbreviation&.content
123
+ orgname = resolve_orgname(org, abbrev)
124
+ Rfcxml::V3::Organization.new(
125
+ content: [orgname], abbrev: abbrev,
126
+ )
127
+ end
128
+
129
+ def contrib_org(contrib)
130
+ contrib.organization ||
131
+ (contrib.person && contrib.person.affiliation[0]&.organization)
132
+ end
133
+
134
+ def resolve_orgname(org, abbrev)
135
+ orgname = org.name&.first&.content
136
+ if ORGNAMES.key?(abbrev) then abbrev
137
+ else ORGNAMES.key(orgname) || orgname || abbrev
138
+ end
139
+ end
140
+
141
+ def create_address(contrib) # rubocop:disable Metrics/AbcSize
142
+ entity = contrib.person || contrib.organization
143
+ return unless entity.address.any? || entity.phone.any? ||
144
+ entity.email.any? || entity.uri.any?
145
+
146
+ Rfcxml::V3::Address.new(
147
+ postal: address_postal(entity),
148
+ phone: address_phone(entity),
149
+ email: address_email(entity),
150
+ uri: address_uri(entity),
151
+ )
152
+ end
153
+
154
+ def address_postal(entity)
155
+ args = address_postal_args(entity)
156
+ return unless args.values.any?(&:any?)
157
+
158
+ Rfcxml::V3::Postal.new(**args)
159
+ end
160
+
161
+ def address_postal_args(entity)
162
+ {
163
+ city: address_cities(entity),
164
+ code: address_postcodes(entity),
165
+ country: address_countries(entity),
166
+ region: address_states(entity),
167
+ street: address_streets(entity),
168
+ postal_line: address_postal_lines(entity),
169
+ }
170
+ end
171
+
172
+ def address_cities(entity)
173
+ entity.address.each_with_object([]) do |addr, cities|
174
+ next unless addr.city
175
+
176
+ cities << Rfcxml::V3::City.new(content: addr.city)
177
+ end
178
+ end
179
+
180
+ def address_postcodes(entity)
181
+ entity.address.each_with_object([]) do |addr, codes|
182
+ next unless addr.postcode
183
+
184
+ codes << Rfcxml::V3::Code.new(content: addr.postcode)
185
+ end
186
+ end
187
+
188
+ def address_countries(entity)
189
+ entity.address.each_with_object([]) do |addr, countries|
190
+ next unless addr.country
191
+
192
+ countries << Rfcxml::V3::Country.new(content: addr.country)
193
+ end
194
+ end
195
+
196
+ def address_states(entity)
197
+ entity.address.each_with_object([]) do |addr, states|
198
+ next unless addr.state
199
+
200
+ states << Rfcxml::V3::Region.new(content: addr.state)
201
+ end
202
+ end
203
+
204
+ def address_streets(entity)
205
+ entity.address.each_with_object([]) do |address, streets|
206
+ address.street.each do |street|
207
+ streets << Rfcxml::V3::Street.new(content: street)
208
+ end
209
+ end
210
+ end
211
+
212
+ def address_postal_lines(entity)
213
+ entity.address.each_with_object([]) do |addr, plines|
214
+ next unless addr.formatted_address
215
+
216
+ plines << Rfcxml::V3::PostalLine.new(
217
+ content: addr.formatted_address,
218
+ )
219
+ end
220
+ end
221
+
222
+ def address_phone(entity)
223
+ return unless entity.phone.any?
224
+
225
+ Rfcxml::V3::Phone.new(content: entity.phone[0].content)
226
+ end
227
+
228
+ def address_email(entity)
229
+ entity.email.each_with_object([]) do |email, emails|
230
+ emails << Rfcxml::V3::Email.new(content: email)
231
+ end
232
+ end
233
+
234
+ def address_uri(entity)
235
+ return unless entity.uri.any?
236
+
237
+ Rfcxml::V3::Uri.new(content: entity.uri[0].content)
238
+ end
239
+
240
+ def create_date # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
241
+ pub_date = @item.date.detect { |d| d.type == "published" }
242
+ return unless pub_date
243
+
244
+ args = {}
245
+ [pub_date.at, pub_date.from, pub_date.to].compact.each do |d|
246
+ year, month, day = d.split("-")
247
+ args[:year] ||= year
248
+ args[:month] ||= ::Date::MONTHNAMES[month.to_i]
249
+ args[:day] ||= day
250
+ end
251
+ Rfcxml::V3::Date.new(**args)
252
+ end
253
+
254
+ def committee_contributor?(contrib)
255
+ contrib.role.any? do |r|
256
+ r.type == "author" &&
257
+ r.description.any? { |d| d.content == "committee" }
258
+ end
259
+ end
260
+
261
+ def create_workgroup
262
+ @item.contributor.each_with_object([]) do |contrib, wgs|
263
+ next unless committee_contributor?(contrib)
264
+
265
+ contrib.organization&.subdivision&.each do |sd|
266
+ sd.name.each do |n|
267
+ wgs << Rfcxml::V3::Workgroup.new(content: n.content)
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ def create_keyword
274
+ @item.keyword.inject([]) do |a, k|
275
+ if k.vocab
276
+ a + [Rfcxml::V3::Keyword.new(content: k.vocab.content)]
277
+ else
278
+ a + k.taxon.map { |t| Rfcxml::V3::Keyword.new(content: t.content) }
279
+ end
280
+ end
281
+ end
282
+
283
+ def create_abstract
284
+ return unless @item.abstract.any?
285
+
286
+ content = @item.abstract[0].content
287
+ paragraphs = content.scan(%r{<p>(.*?)</p>}m).flatten
288
+ paragraphs = [content] if paragraphs.empty?
289
+ ts = paragraphs.map { |p| Rfcxml::V3::Text.new(content: CGI.unescapeHTML(p)) }
290
+ Rfcxml::V3::Abstract.new(t: ts)
291
+ end
292
+
293
+ FORMAT_TYPES = %w[TXT HTML PDF XML DOC].freeze
294
+
295
+ def create_format # rubocop:disable Metrics/AbcSize
296
+ @item.source.each_with_object([]) do |l, a|
297
+ next unless FORMAT_TYPES.any? { |ft| l.type.casecmp(ft).zero? }
298
+
299
+ a << Rfcxml::V3::Format.new(
300
+ type: l.type, target: l.content,
301
+ )
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,52 @@
1
+ module Relaton
2
+ module Bib
3
+ module Converter
4
+ module BibXml
5
+ class ToRfcxmlReferencegroup
6
+ def initialize(item, include_keywords: true)
7
+ @item = item
8
+ @include_keywords = include_keywords
9
+ end
10
+
11
+ def transform
12
+ Rfcxml::V3::Referencegroup.new(
13
+ anchor: create_anchor,
14
+ target: create_target,
15
+ reference: build_references,
16
+ )
17
+ end
18
+
19
+ private
20
+
21
+ def build_references
22
+ @item.relation&.each_with_object([]) do |rel, refs|
23
+ next unless rel.type == "includes"
24
+
25
+ converter = ToRfcxml.new(
26
+ rel.bibitem, include_keywords: @include_keywords
27
+ )
28
+ refs << converter.transform
29
+ end
30
+ end
31
+
32
+ def create_anchor
33
+ docid = @item.docidentifier.detect(&:primary) ||
34
+ @item.docidentifier[0]
35
+ return unless docid
36
+
37
+ id = docid.content.to_s
38
+ id.sub(/^(RFC|BCP|FYI|STD) /, '\1').sub(/^\w+\./, "")
39
+ end
40
+
41
+ def create_target
42
+ target = @item.source.detect { |l| l.type.casecmp("src").zero? } ||
43
+ @item.source.detect { |l| l.type.casecmp("doi").zero? }
44
+ return unless target
45
+
46
+ target.content.to_s
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,51 @@
1
+ require_relative "bibxml/to_rfcxml"
2
+ require_relative "bibxml/to_rfcxml_referencegroup"
3
+ require_relative "bibxml/from_rfcxml"
4
+ require_relative "bibxml/from_rfcxml_referencegroup"
5
+
6
+ module Relaton
7
+ module Bib
8
+ module Converter
9
+ module BibXml
10
+ ORGNAMES = {
11
+ "IEEE" => "Institute of Electrical and Electronics Engineers",
12
+ "W3C" => "World Wide Web Consortium",
13
+ "3GPP" => "3rd Generation Partnership Project",
14
+ }.freeze
15
+
16
+ RFCPREFIXES = %w[RFC BCP FYI STD].freeze
17
+
18
+ # Forward: ItemData -> Rfcxml model
19
+ def self.from_item(item, include_keywords: true) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
20
+ if bcp?(item)
21
+ ToRfcxmlReferencegroup.new(
22
+ item, include_keywords: include_keywords
23
+ ).transform
24
+ else
25
+ ToRfcxml.new(
26
+ item, include_keywords: include_keywords
27
+ ).transform
28
+ end
29
+ end
30
+
31
+ def self.bcp?(item) # rubocop:disable Metrics/CyclomaticComplexity
32
+ item.docnumber&.match(/^BCP/) ||
33
+ (item.docidentifier.detect(&:primary) ||
34
+ item.docidentifier[0])&.content&.to_s&.include?("BCP")
35
+ end
36
+ private_class_method :bcp?
37
+
38
+ # Reverse: XML string -> ItemData
39
+ def self.to_item(xml)
40
+ if xml.include?("<referencegroup") || xml.include?("<Referencegroup")
41
+ referencegroup = Rfcxml::V3::Referencegroup.from_xml(xml)
42
+ FromRfcxmlReferencegroup.new(referencegroup).transform
43
+ else
44
+ reference = Rfcxml::V3::Reference.from_xml(xml)
45
+ FromRfcxml.new(reference).transform
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end