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.
- checksums.yaml +4 -4
- data/README.adoc +57 -1
- data/bin/console +0 -1
- data/lib/relaton/3gpp/bibliography.rb +82 -7
- data/lib/relaton/3gpp/data_fetcher.rb +51 -3
- data/lib/relaton/3gpp/docidentifier.rb +114 -0
- data/lib/relaton/3gpp/item.rb +6 -0
- data/lib/relaton/3gpp/parser.rb +1 -1
- data/lib/relaton/3gpp/processor.rb +4 -1
- data/lib/relaton/3gpp.rb +5 -1
- data/lib/relaton/adobe/bibdata.rb +8 -0
- data/lib/relaton/adobe/bibitem.rb +8 -0
- data/lib/relaton/adobe/bibliography.rb +92 -0
- data/lib/relaton/adobe/docidentifier.rb +49 -0
- data/lib/relaton/adobe/doctype.rb +14 -0
- data/lib/relaton/adobe/ext.rb +32 -0
- data/lib/relaton/adobe/item.rb +15 -0
- data/lib/relaton/adobe/item_base.rb +18 -0
- data/lib/relaton/adobe/item_data.rb +6 -0
- data/lib/relaton/adobe/processor.rb +45 -0
- data/lib/relaton/adobe/util.rb +8 -0
- data/lib/relaton/adobe.rb +37 -0
- data/lib/relaton/bib/model/address.rb +2 -2
- data/lib/relaton/bib/model/docidentifier.rb +24 -9
- data/lib/relaton/bib/model/localized_string.rb +1 -1
- data/lib/relaton/bib/model/structured_identifier.rb +10 -9
- data/lib/relaton/bib/sanitizer.rb +202 -6
- data/lib/relaton/bib.rb +0 -2
- data/lib/relaton/bipm/bibliography.rb +159 -10
- data/lib/relaton/bipm/data_fetcher.rb +26 -2
- data/lib/relaton/bipm/data_outcomes_parser.rb +3 -4
- data/lib/relaton/bipm/id_parser.rb +5 -4
- data/lib/relaton/bipm/model/structured_identifier.rb +21 -0
- data/lib/relaton/bipm/processor.rb +2 -2
- data/lib/relaton/bipm/rawdata_bipm_metrologia/fetcher.rb +2 -4
- data/lib/relaton/bipm/si_brochure_parser.rb +5 -3
- data/lib/relaton/bipm.rb +7 -1
- data/lib/relaton/bsi/bibliography.rb +115 -43
- data/lib/relaton/bsi/hit.rb +14 -0
- data/lib/relaton/bsi/hit_collection.rb +15 -16
- data/lib/relaton/bsi/model/docidentifier.rb +99 -1
- data/lib/relaton/bsi/processor.rb +1 -0
- data/lib/relaton/calconnect/bibliography.rb +12 -14
- data/lib/relaton/calconnect/data_fetcher.rb +77 -9
- data/lib/relaton/calconnect/docidentifier.rb +80 -0
- data/lib/relaton/calconnect/hit_collection.rb +65 -57
- data/lib/relaton/calconnect/model/item.rb +7 -0
- data/lib/relaton/calconnect/processor.rb +7 -1
- data/lib/relaton/calconnect.rb +11 -1
- data/lib/relaton/ccsds/data/fetcher.rb +17 -12
- data/lib/relaton/ccsds/data/parser.rb +1 -1
- data/lib/relaton/ccsds/hit_collection.rb +6 -1
- data/lib/relaton/ccsds/model/docidentifier.rb +121 -0
- data/lib/relaton/ccsds/model/item.rb +2 -0
- data/lib/relaton/cen/bibliography.rb +75 -47
- data/lib/relaton/cen/hit.rb +16 -1
- data/lib/relaton/cen/hit_collection.rb +59 -11
- data/lib/relaton/cen/model/docidentifier.rb +92 -1
- data/lib/relaton/cen/processor.rb +13 -8
- data/lib/relaton/cen/scraper.rb +13 -5
- data/lib/relaton/cen.rb +1 -0
- data/lib/relaton/cie/data_fetcher.rb +215 -30
- data/lib/relaton/cie/processor.rb +3 -1
- data/lib/relaton/cie/scrapper.rb +15 -2
- data/lib/relaton/cie.rb +2 -1
- data/lib/relaton/core/data_fetcher.rb +150 -3
- data/lib/relaton/core/governor.rb +320 -0
- data/lib/relaton/core/pacer.rb +134 -0
- data/lib/relaton/core/processor.rb +19 -0
- data/lib/relaton/core/request_error.rb +14 -0
- data/lib/relaton/core.rb +3 -0
- data/lib/relaton/db/registry.rb +41 -1
- data/lib/relaton/doi/crossref.rb +19 -2
- data/lib/relaton/doi/parser.rb +109 -15
- data/lib/relaton/easc/bibdata.rb +8 -0
- data/lib/relaton/easc/bibitem.rb +8 -0
- data/lib/relaton/easc/bibliography.rb +95 -0
- data/lib/relaton/easc/docidentifier.rb +100 -0
- data/lib/relaton/easc/doctype.rb +14 -0
- data/lib/relaton/easc/ext.rb +44 -0
- data/lib/relaton/easc/item.rb +13 -0
- data/lib/relaton/easc/item_base.rb +18 -0
- data/lib/relaton/easc/item_data.rb +6 -0
- data/lib/relaton/easc/processor.rb +46 -0
- data/lib/relaton/easc/util.rb +8 -0
- data/lib/relaton/easc.rb +35 -0
- data/lib/relaton/ecma/bibliography.rb +93 -25
- data/lib/relaton/ecma/data_fetcher.rb +71 -12
- data/lib/relaton/ecma/docidentifier.rb +124 -0
- data/lib/relaton/ecma/item.rb +2 -0
- data/lib/relaton/ecma/memento_parser.rb +1 -1
- data/lib/relaton/ecma/page_fetcher.rb +15 -3
- data/lib/relaton/ecma/parser_common.rb +2 -2
- data/lib/relaton/ecma/processor.rb +4 -1
- data/lib/relaton/ecma/standard_parser.rb +2 -2
- data/lib/relaton/ecma.rb +10 -1
- data/lib/relaton/etsi/bibliography.rb +67 -2
- data/lib/relaton/etsi/data_fetcher.rb +43 -4
- data/lib/relaton/etsi/processor.rb +3 -1
- data/lib/relaton/etsi.rb +2 -1
- data/lib/relaton/gb/bibliography.rb +55 -29
- data/lib/relaton/gb/docidentifier.rb +58 -9
- data/lib/relaton/gb/processor.rb +3 -0
- data/lib/relaton/gb/scraper.rb +27 -10
- data/lib/relaton/gost/bibdata.rb +8 -0
- data/lib/relaton/gost/bibitem.rb +8 -0
- data/lib/relaton/gost/bibliography.rb +107 -0
- data/lib/relaton/gost/docidentifier.rb +80 -0
- data/lib/relaton/gost/doctype.rb +16 -0
- data/lib/relaton/gost/ext.rb +46 -0
- data/lib/relaton/gost/item.rb +15 -0
- data/lib/relaton/gost/item_base.rb +18 -0
- data/lib/relaton/gost/item_data.rb +6 -0
- data/lib/relaton/gost/processor.rb +49 -0
- data/lib/relaton/gost/util.rb +8 -0
- data/lib/relaton/gost.rb +36 -0
- data/lib/relaton/iala/bibdata.rb +8 -0
- data/lib/relaton/iala/bibitem.rb +8 -0
- data/lib/relaton/iala/bibliography.rb +146 -0
- data/lib/relaton/iala/docidentifier.rb +89 -0
- data/lib/relaton/iala/doctype.rb +18 -0
- data/lib/relaton/iala/ext.rb +32 -0
- data/lib/relaton/iala/item.rb +21 -0
- data/lib/relaton/iala/item_base.rb +18 -0
- data/lib/relaton/iala/item_data.rb +6 -0
- data/lib/relaton/iala/processor.rb +43 -0
- data/lib/relaton/iala/relation.rb +7 -0
- data/lib/relaton/iala/util.rb +8 -0
- data/lib/relaton/iala.rb +35 -0
- data/lib/relaton/iana/bibliography.rb +67 -14
- data/lib/relaton/iana/data_fetcher.rb +35 -5
- data/lib/relaton/iana/processor.rb +3 -1
- data/lib/relaton/iana.rb +12 -1
- data/lib/relaton/iec/data_fetcher.rb +7 -1
- data/lib/relaton/iec/hit_collection.rb +1 -1
- data/lib/relaton/iec/model/docidentifier.rb +9 -5
- data/lib/relaton/iec/model/ext.rb +2 -2
- data/lib/relaton/iec/processor.rb +1 -0
- data/lib/relaton/ieee/bibliography.rb +25 -3
- data/lib/relaton/ieee/data_fetcher.rb +158 -17
- data/lib/relaton/ieee/idams_parser.rb +18 -11
- data/lib/relaton/ieee/processor.rb +4 -1
- data/lib/relaton/ieee/rawbib_id_parser.rb +291 -86
- data/lib/relaton/ieee.rb +2 -1
- data/lib/relaton/ietf/data_fetcher.rb +295 -12
- data/lib/relaton/ietf/processor.rb +7 -3
- data/lib/relaton/ietf/rfc/entry.rb +39 -3
- data/lib/relaton/ietf/scraper.rb +69 -36
- data/lib/relaton/ietf.rb +4 -1
- data/lib/relaton/iho/bibliography.rb +1 -1
- data/lib/relaton/iho/docidentifier.rb +1 -1
- data/lib/relaton/index/file_io.rb +11 -11
- data/lib/relaton/index/file_storage.rb +6 -1
- data/lib/relaton/index/pool.rb +6 -1
- data/lib/relaton/index/shard_source.rb +201 -0
- data/lib/relaton/index/type.rb +63 -12
- data/lib/relaton/index.rb +2 -1
- data/lib/relaton/iso/bibliography.rb +20 -15
- data/lib/relaton/iso/data_fetcher.rb +3 -3
- data/lib/relaton/iso/data_parser.rb +17 -3
- data/lib/relaton/iso/hit_collection.rb +27 -15
- data/lib/relaton/iso/item_data.rb +22 -0
- data/lib/relaton/iso/model/docidentifier.rb +24 -12
- data/lib/relaton/iso/processor.rb +1 -0
- data/lib/relaton/iso/scraper.rb +19 -3
- data/lib/relaton/itu/bibliography.rb +9 -4
- data/lib/relaton/itu/data_crawler_r.rb +664 -0
- data/lib/relaton/itu/data_fetcher.rb +496 -50
- data/lib/relaton/itu/data_merge_r.rb +149 -0
- data/lib/relaton/itu/data_parser_r.rb +163 -89
- data/lib/relaton/itu/data_parser_t.rb +228 -0
- data/lib/relaton/itu/family_cache.rb +177 -0
- data/lib/relaton/itu/governor.rb +56 -0
- data/lib/relaton/itu/hit.rb +9 -3
- data/lib/relaton/itu/hit_collection.rb +258 -86
- data/lib/relaton/itu/model/docidentifier.rb +67 -1
- data/lib/relaton/itu/model/structured_identifier.rb +19 -0
- data/lib/relaton/itu/processor.rb +10 -4
- data/lib/relaton/itu/pubid.rb +27 -5
- data/lib/relaton/itu/recommendation_fields.rb +334 -0
- data/lib/relaton/itu/recommendation_parser.rb +18 -149
- data/lib/relaton/itu/scraper.rb +13 -3
- data/lib/relaton/itu.rb +2 -1
- data/lib/relaton/jcgm/bibdata.rb +8 -0
- data/lib/relaton/jcgm/bibitem.rb +8 -0
- data/lib/relaton/jcgm/bibliography.rb +97 -0
- data/lib/relaton/jcgm/data_fetcher.rb +81 -0
- data/lib/relaton/jcgm/docidentifier.rb +102 -0
- data/lib/relaton/jcgm/doctype.rb +12 -0
- data/lib/relaton/jcgm/ext.rb +23 -0
- data/lib/relaton/jcgm/item.rb +20 -0
- data/lib/relaton/jcgm/item_base.rb +18 -0
- data/lib/relaton/jcgm/item_data.rb +6 -0
- data/lib/relaton/jcgm/meetings_parser.rb +175 -0
- data/lib/relaton/jcgm/processor.rb +71 -0
- data/lib/relaton/jcgm/relation.rb +9 -0
- data/lib/relaton/jcgm/structured_identifier.rb +40 -0
- data/lib/relaton/jcgm/util.rb +8 -0
- data/lib/relaton/jcgm.rb +24 -0
- data/lib/relaton/jis/bibliography.rb +8 -10
- data/lib/relaton/jis/data_fetcher.rb +21 -19
- data/lib/relaton/jis/docidentifier.rb +104 -5
- data/lib/relaton/jis/hit.rb +18 -23
- data/lib/relaton/jis/hit_collection.rb +19 -18
- data/lib/relaton/jis/processor.rb +1 -1
- data/lib/relaton/jis.rb +2 -3
- data/lib/relaton/logger/channels/gh_issue.rb +78 -13
- data/lib/relaton/nist/data_fetcher.rb +63 -13
- data/lib/relaton/nist/docidentifier.rb +165 -0
- data/lib/relaton/nist/item.rb +2 -0
- data/lib/relaton/nist/item_base.rb +16 -0
- data/lib/relaton/nist/mods_parser.rb +38 -12
- data/lib/relaton/nist/processor.rb +2 -1
- data/lib/relaton/nist/relation.rb +3 -0
- data/lib/relaton/nist/scraper.rb +6 -3
- data/lib/relaton/oasis/bibliography.rb +147 -6
- data/lib/relaton/oasis/data_fetcher.rb +41 -5
- data/lib/relaton/oasis/data_parser_utils.rb +37 -3
- data/lib/relaton/oasis/docidentifier.rb +54 -0
- data/lib/relaton/oasis/item.rb +3 -0
- data/lib/relaton/oasis/processor.rb +7 -1
- data/lib/relaton/oasis.rb +14 -1
- data/lib/relaton/ogc/data_fetcher.rb +23 -2
- data/lib/relaton/ogc/docidentifier.rb +105 -0
- data/lib/relaton/ogc/hit_collection.rb +78 -3
- data/lib/relaton/ogc/processor.rb +2 -1
- data/lib/relaton/ogc.rb +5 -1
- data/lib/relaton/oiml/bibliography.rb +90 -15
- data/lib/relaton/oiml/docidentifier.rb +18 -3
- data/lib/relaton/omg/docidentifier.rb +67 -0
- data/lib/relaton/omg/item.rb +1 -0
- data/lib/relaton/omg/processor.rb +1 -0
- data/lib/relaton/omg/scraper.rb +61 -16
- data/lib/relaton/omg.rb +1 -0
- data/lib/relaton/plateau/bibliography.rb +10 -3
- data/lib/relaton/plateau/data_fetcher.rb +25 -2
- data/lib/relaton/plateau/handbook_parser.rb +8 -1
- data/lib/relaton/plateau/hit.rb +10 -2
- data/lib/relaton/plateau/hit_collection.rb +31 -11
- data/lib/relaton/plateau/processor.rb +3 -1
- data/lib/relaton/plateau/technical_report_parser.rb +8 -1
- data/lib/relaton/plateau.rb +2 -1
- data/lib/relaton/sdo/config.rb +34 -0
- data/lib/relaton/sdo/fetcher.rb +52 -0
- data/lib/relaton/sdo/logo.rb +95 -0
- data/lib/relaton/sdo/name.rb +26 -0
- data/lib/relaton/sdo/organization.rb +71 -0
- data/lib/relaton/sdo/store.rb +49 -0
- data/lib/relaton/sdo.rb +29 -0
- data/lib/relaton/version.rb +1 -1
- data/lib/relaton/w3c/bibliography.rb +132 -12
- data/lib/relaton/w3c/data_fetcher.rb +194 -16
- data/lib/relaton/w3c/data_parser.rb +3 -3
- data/lib/relaton/w3c/docidentifier.rb +48 -0
- data/lib/relaton/w3c/governor.rb +32 -0
- data/lib/relaton/w3c/item.rb +3 -0
- data/lib/relaton/w3c/pubid.rb +12 -0
- data/lib/relaton/w3c/safe_realize.rb +110 -21
- data/lib/relaton/w3c.rb +12 -1
- data/lib/relaton/xsf/bibliography.rb +61 -1
- data/lib/relaton/xsf/data_fetcher.rb +55 -5
- data/lib/relaton/xsf/docidentifier.rb +46 -0
- data/lib/relaton/xsf/hit_collection.rb +31 -3
- data/lib/relaton/xsf/item.rb +6 -0
- data/lib/relaton/xsf/processor.rb +1 -0
- data/lib/relaton/xsf.rb +5 -1
- data/lib/relaton.rb +42 -0
- metadata +135 -24
- data/lib/relaton/ieee/pub_id.rb +0 -161
- data/lib/relaton/index/id_number.rb +0 -30
data/lib/relaton/doi/parser.rb
CHANGED
|
@@ -73,8 +73,14 @@ module Relaton
|
|
|
73
73
|
ATTRS = %i[type fetched title docidentifier date source abstract contributor place
|
|
74
74
|
ext relation extent series medium].freeze
|
|
75
75
|
|
|
76
|
+
# Matches content that looks like markup. Same shape as the tag detector
|
|
77
|
+
# of `Relaton::Bib::Sanitizer`, so both agree on what to parse.
|
|
78
|
+
TAG_RE = %r{<[a-zA-Z/!?]}
|
|
79
|
+
|
|
76
80
|
CROSSREF_API_URL = "https://api.crossref.org/works?query=%{query}&filter=%{filter}".freeze
|
|
77
81
|
MAX_RETRIES = 3
|
|
82
|
+
RATE_LIMITED = "429".freeze
|
|
83
|
+
MAX_RETRY_DELAY = 60
|
|
78
84
|
|
|
79
85
|
#
|
|
80
86
|
# Initialize instance.
|
|
@@ -209,10 +215,48 @@ module Relaton
|
|
|
209
215
|
# @return [Bib::Title] The title.
|
|
210
216
|
#
|
|
211
217
|
def create_title(title, type = "main")
|
|
212
|
-
cnt = str_cleanup
|
|
218
|
+
cnt = str_cleanup normalize_markup(title)
|
|
213
219
|
Bib::Title.new type: type, content: cnt, script: "Latn"
|
|
214
220
|
end
|
|
215
221
|
|
|
222
|
+
#
|
|
223
|
+
# Prepare raw Crossref markup for the relaton-bib sanitizer.
|
|
224
|
+
#
|
|
225
|
+
# Crossref returns JATS markup, and it sometimes encodes that markup as
|
|
226
|
+
# HTML entities, so decode them before the content reaches a model
|
|
227
|
+
# attribute. The markup also carries the `jats:` namespace prefix, and
|
|
228
|
+
# sometimes an `xlink:` prefix on an attribute, which Relaton never
|
|
229
|
+
# declares. Removing those prefixes is `Relaton::Bib::Sanitizer`'s job:
|
|
230
|
+
# every `Bib::Title` and `Bib::Abstract` runs the sanitizer on
|
|
231
|
+
# assignment, and it declares each undeclared prefix on a wrapper
|
|
232
|
+
# element so the markup parses and maps to the basicdoc set. An
|
|
233
|
+
# undeclared prefix that survives into the output makes
|
|
234
|
+
# relaton-render return nil, and isodoc then raises. See
|
|
235
|
+
# metanorma-pdfa#99.
|
|
236
|
+
#
|
|
237
|
+
# Upstream relaton-doi carries its own prefix removal, because the
|
|
238
|
+
# sanitizer fix is not in a released relaton-bib. Here both ship in one
|
|
239
|
+
# gem, so this method is the entity decode only.
|
|
240
|
+
#
|
|
241
|
+
# Decode only content that carries no markup of its own. Crossref
|
|
242
|
+
# escapes an ampersand and a literal angle bracket *inside* real
|
|
243
|
+
# markup, so an unconditional decode turns `<jats:p>Smith &
|
|
244
|
+
# Jones</jats:p>` into markup that no longer parses. The sanitizer
|
|
245
|
+
# would then give up on it and an unescapable string would reach the
|
|
246
|
+
# output -- the metanorma-pdfa#99 failure again, on the far more
|
|
247
|
+
# common input.
|
|
248
|
+
#
|
|
249
|
+
# @param [String, nil] str The raw Crossref content.
|
|
250
|
+
#
|
|
251
|
+
# @return [String, nil] The content, with the HTML entities decoded
|
|
252
|
+
# when it holds no markup.
|
|
253
|
+
#
|
|
254
|
+
def normalize_markup(str)
|
|
255
|
+
return str if str.nil? || str.match?(TAG_RE)
|
|
256
|
+
|
|
257
|
+
CGI.unescapeHTML(str)
|
|
258
|
+
end
|
|
259
|
+
|
|
216
260
|
#
|
|
217
261
|
# Parse a docidentifier from the source hash.
|
|
218
262
|
#
|
|
@@ -303,9 +347,10 @@ module Relaton
|
|
|
303
347
|
def parse_abstract
|
|
304
348
|
return [] unless @src["abstract"]
|
|
305
349
|
|
|
306
|
-
content = @src["abstract"]
|
|
307
350
|
abstract = Bib::Abstract.new(
|
|
308
|
-
content:
|
|
351
|
+
content: normalize_markup(@src["abstract"]),
|
|
352
|
+
language: "en",
|
|
353
|
+
script: "Latn",
|
|
309
354
|
)
|
|
310
355
|
[abstract]
|
|
311
356
|
end
|
|
@@ -448,8 +493,10 @@ module Relaton
|
|
|
448
493
|
# @return [Bib::Organization] The organization.
|
|
449
494
|
#
|
|
450
495
|
def create_org(name, abbreviation = nil)
|
|
451
|
-
n = [Bib::TypedLocalizedString.new(content: name)]
|
|
452
|
-
a =
|
|
496
|
+
n = [Bib::TypedLocalizedString.new(content: normalize_markup(name))]
|
|
497
|
+
a = if abbreviation
|
|
498
|
+
Bib::LocalizedString.new(content: normalize_markup(abbreviation))
|
|
499
|
+
end
|
|
453
500
|
Bib::Organization.new name: n, abbreviation: a
|
|
454
501
|
end
|
|
455
502
|
|
|
@@ -462,11 +509,7 @@ module Relaton
|
|
|
462
509
|
return [] unless @src["standards-body"]
|
|
463
510
|
|
|
464
511
|
name, acronym = @src["standards-body"].values_at("name", "acronym")
|
|
465
|
-
|
|
466
|
-
CGI.unescapeHTML(name),
|
|
467
|
-
acronym && CGI.unescapeHTML(acronym),
|
|
468
|
-
)
|
|
469
|
-
[contributor(org, "authorizer")]
|
|
512
|
+
[contributor(create_org(name, acronym), "authorizer")]
|
|
470
513
|
end
|
|
471
514
|
|
|
472
515
|
#
|
|
@@ -728,7 +771,8 @@ module Relaton
|
|
|
728
771
|
|
|
729
772
|
@src["container-title"].map do |ct|
|
|
730
773
|
contrib = create_authors_editors false, "editor"
|
|
731
|
-
|
|
774
|
+
title = Bib::Title.new content: normalize_markup(ct)
|
|
775
|
+
bib = Bib::ItemBase.new(title: [title], contributor: contrib)
|
|
732
776
|
Bib::Relation.new(type: "includedIn", bibitem: bib)
|
|
733
777
|
end
|
|
734
778
|
end
|
|
@@ -777,7 +821,7 @@ module Relaton
|
|
|
777
821
|
else []
|
|
778
822
|
end
|
|
779
823
|
con_ttl.map do |ct|
|
|
780
|
-
title = Bib::Title.new content: ct
|
|
824
|
+
title = Bib::Title.new content: normalize_markup(ct)
|
|
781
825
|
Bib::Series.new title: [title], abbreviation: abbrev
|
|
782
826
|
end
|
|
783
827
|
end
|
|
@@ -800,17 +844,36 @@ module Relaton
|
|
|
800
844
|
# @param [String] query The query string.
|
|
801
845
|
# @param [String] filter The filter string.
|
|
802
846
|
#
|
|
803
|
-
# @return [Array<Hash>, nil] Items array from response or nil for 4xx responses
|
|
847
|
+
# @return [Array<Hash>, nil] Items array from response or nil for 4xx responses
|
|
848
|
+
# other than 429.
|
|
804
849
|
#
|
|
805
|
-
# @raise [Relaton::RequestError] If request fails after retries
|
|
850
|
+
# @raise [Relaton::RequestError] If request fails after retries, or if the
|
|
851
|
+
# rate limit does not clear.
|
|
806
852
|
#
|
|
807
|
-
def fetch_crossref(query:, filter:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
853
|
+
def fetch_crossref(query:, filter:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
808
854
|
url = format(CROSSREF_API_URL, query: query, filter: filter)
|
|
809
855
|
retries = 0
|
|
856
|
+
throttled = 0
|
|
810
857
|
begin
|
|
811
858
|
resp = Crossref.agent.get url
|
|
812
859
|
JSON.parse(resp.body).dig("message", "items")
|
|
813
860
|
rescue Mechanize::ResponseCodeError => e
|
|
861
|
+
# 429 must be handled before the 4xx nil return: a throttle is not
|
|
862
|
+
# "no such record". Returning nil here made a rate-limited lookup look
|
|
863
|
+
# like a missing document, so callers such as #parent_item silently
|
|
864
|
+
# produced an incomplete item instead of failing.
|
|
865
|
+
if e.response_code == RATE_LIMITED
|
|
866
|
+
throttled += 1
|
|
867
|
+
if throttled <= MAX_RETRIES
|
|
868
|
+
# Cap the backoff itself, not just the header value: escalating by
|
|
869
|
+
# attempt would otherwise let a single `Retry-After: 60` stall one
|
|
870
|
+
# lookup for 60+120+180s.
|
|
871
|
+
sleep [retry_delay(e) * throttled, MAX_RETRY_DELAY].min
|
|
872
|
+
retry
|
|
873
|
+
end
|
|
874
|
+
raise Relaton::RequestError,
|
|
875
|
+
"Crossref rate limit not cleared after #{MAX_RETRIES} retries: #{url}"
|
|
876
|
+
end
|
|
814
877
|
return nil if e.response_code.start_with?("4")
|
|
815
878
|
|
|
816
879
|
raise Relaton::RequestError, "Crossref request failed: #{e.response_code} #{e.page.body}"
|
|
@@ -822,6 +885,37 @@ module Relaton
|
|
|
822
885
|
raise Relaton::RequestError, "Crossref JSON parsing error: #{e.message}"
|
|
823
886
|
end
|
|
824
887
|
end
|
|
888
|
+
|
|
889
|
+
#
|
|
890
|
+
# Base seconds to wait before retrying a throttled Crossref request.
|
|
891
|
+
# Prefers Retry-After, falls back to Crossref's X-Rate-Limit-Interval
|
|
892
|
+
# (formatted "1s", hence the digit scan). An empty-bodied 429 carries
|
|
893
|
+
# neither header, so this never assumes one is present.
|
|
894
|
+
#
|
|
895
|
+
# @param [Mechanize::ResponseCodeError] err The rate-limit error.
|
|
896
|
+
#
|
|
897
|
+
# @return [Integer] Delay in seconds, at least 1 and at most MAX_RETRY_DELAY.
|
|
898
|
+
#
|
|
899
|
+
def retry_delay(err)
|
|
900
|
+
headers = err.page&.response || {}
|
|
901
|
+
secs = retry_after_seconds(headers["retry-after"]) ||
|
|
902
|
+
headers["x-rate-limit-interval"].to_s[/\d+/].to_i
|
|
903
|
+
secs.positive? ? [secs, MAX_RETRY_DELAY].min : 1
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
#
|
|
907
|
+
# Parse a Retry-After header value, which RFC 9110 allows to be either
|
|
908
|
+
# delta-seconds or an HTTP-date. Only the numeric form is usable as a
|
|
909
|
+
# delay: scanning digits out of a date would pick up the day-of-month,
|
|
910
|
+
# silently turning a long server-mandated backoff into a few seconds.
|
|
911
|
+
#
|
|
912
|
+
# @param [String, nil] value The raw header value.
|
|
913
|
+
#
|
|
914
|
+
# @return [Integer, nil] Seconds, or nil if absent or in the date form.
|
|
915
|
+
#
|
|
916
|
+
def retry_after_seconds(value)
|
|
917
|
+
value.to_s.strip.match?(/\A\d+\z/) ? value.to_i : nil
|
|
918
|
+
end
|
|
825
919
|
end
|
|
826
920
|
end
|
|
827
921
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Easc
|
|
5
|
+
# Index-backed retrieval for the EASC flavor (no scraping). Mirrors
|
|
6
|
+
# Relaton::Oiml::Bibliography: it parses the reference with
|
|
7
|
+
# Pubid::Easc, narrows the pre-built `relaton-data-easc` index by
|
|
8
|
+
# number, fetches the matching per-document YAML over Net::HTTP, and
|
|
9
|
+
# deserializes it into a Relaton::Easc::Item.
|
|
10
|
+
module Bibliography
|
|
11
|
+
ENDPOINT = "https://raw.githubusercontent.com/relaton/relaton-data-easc/main/".freeze
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
#
|
|
15
|
+
# Search for an EASC publication by its identifier.
|
|
16
|
+
#
|
|
17
|
+
# @param text [String, Pubid::Easc::Identifier] the EASC reference to
|
|
18
|
+
# look up (e.g. "ПМГ 03-2025", "РМГ 151-2025", or the Latin
|
|
19
|
+
# transliteration "PMG 03-2025")
|
|
20
|
+
# @param year [String, nil] the edition year (optional; usually embedded
|
|
21
|
+
# in the reference)
|
|
22
|
+
# @param _opts [Hash] options (unused)
|
|
23
|
+
#
|
|
24
|
+
# @return [Relaton::Easc::Item, nil] the publication or nil if not found
|
|
25
|
+
#
|
|
26
|
+
def search(text, year = nil, _opts = {})
|
|
27
|
+
pubid = text.is_a?(String) ? ::Pubid::Easc.parse(text) : text
|
|
28
|
+
Util.info "Fetching from Relaton repository ...", key: pubid.to_s
|
|
29
|
+
# Pass the pubid so Relaton::Index narrows candidates by number via
|
|
30
|
+
# binary search before applying the block. Every row's `:id` is a
|
|
31
|
+
# Pubid::Easc::Identifier (deserialized via the `pubid_class` passed to
|
|
32
|
+
# #index), so the block compares pubids and picks the latest edition.
|
|
33
|
+
row = index.search(pubid) { |r| pubid_match?(r[:id], pubid, year) }
|
|
34
|
+
.max_by { |r| r[:id].year.to_i }
|
|
35
|
+
unless row
|
|
36
|
+
Util.info "Not found.", key: pubid.to_s
|
|
37
|
+
return
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
uri = URI("#{ENDPOINT}#{row[:file]}")
|
|
41
|
+
resp = Net::HTTP.get_response uri
|
|
42
|
+
unless resp.code == "200"
|
|
43
|
+
raise Relaton::RequestError, "Could not access #{uri}: HTTP #{resp.code}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
item = Relaton::Easc::Item.from_yaml resp.body
|
|
47
|
+
Util.info "Found: `#{item.docidentifier.first&.content}`", key: pubid.to_s
|
|
48
|
+
item.tap { |i| i.fetched = Date.today.to_s }
|
|
49
|
+
rescue SocketError, Errno::EINVAL, Errno::ECONNRESET, EOFError,
|
|
50
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
|
|
51
|
+
Net::ProtocolError, Net::ReadTimeout, OpenSSL::SSL::SSLError,
|
|
52
|
+
Errno::ETIMEDOUT => e
|
|
53
|
+
raise Relaton::RequestError, "Could not access #{uri}: #{e.message}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# Fetch an EASC publication. EASC citations are always dated
|
|
58
|
+
# (e.g. "ПМГ 03-2025"), so there is no undated/most-recent handling to
|
|
59
|
+
# mirror from the ISO/OIML fetchers — `get` just returns what `search`
|
|
60
|
+
# resolves.
|
|
61
|
+
#
|
|
62
|
+
# @see #search
|
|
63
|
+
#
|
|
64
|
+
def get(ref, year = nil, opts = {})
|
|
65
|
+
search(ref, year, opts)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def index
|
|
71
|
+
Relaton::Index.find_or_create(
|
|
72
|
+
:easc,
|
|
73
|
+
url: "#{ENDPOINT}#{INDEXFILE}.zip",
|
|
74
|
+
file: "#{INDEXFILE}.yaml",
|
|
75
|
+
pubid_class: ::Pubid::Easc::Identifier,
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Both `row_id` and `query` are Pubid::Easc::Identifier instances. A row
|
|
80
|
+
# matches when its series, defense variant, and number are equal (so a
|
|
81
|
+
# ПМГ never matches a РМГ, and the «В» defense variant never matches the
|
|
82
|
+
# plain series). Year is nil-tolerant: an unqualified query finds the
|
|
83
|
+
# latest edition (selected by `max_by` in #search); the `year` argument
|
|
84
|
+
# lets a caller pin an edition the reference string omitted.
|
|
85
|
+
def pubid_match?(row_id, query, year)
|
|
86
|
+
wanted_year = (query.year || year)&.to_s
|
|
87
|
+
row_id.series == query.series &&
|
|
88
|
+
row_id.variant.to_s == query.variant.to_s &&
|
|
89
|
+
row_id.number.to_s == query.number.to_s &&
|
|
90
|
+
(wanted_year.nil? || row_id.year.to_s == wanted_year)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Easc
|
|
3
|
+
# Structured EASC document identifier. The docid string is the canonical
|
|
4
|
+
# citation form (e.g. "ПМГ 03-2025", "РМГ 151-2025"), parsed into a
|
|
5
|
+
# `Pubid::Easc::Identifier` kept in `@pubid` while the lutaml `content`
|
|
6
|
+
# attribute stays a plain string for serialization.
|
|
7
|
+
#
|
|
8
|
+
# Parsing is **soft**: `content=` lazily requires pubid and rescues
|
|
9
|
+
# `LoadError`/`StandardError`, so a missing gem or non-EASC content leaves
|
|
10
|
+
# `@pubid` nil rather than raising.
|
|
11
|
+
#
|
|
12
|
+
# This was an empty subclass, which meant it inherited
|
|
13
|
+
# `Bib::Docidentifier`'s abstract `remove_part!` / `remove_date!` /
|
|
14
|
+
# `to_all_parts!`. `Bib::ItemData` broadcasts all three to every
|
|
15
|
+
# docidentifier, so `#to_all_parts` and `#to_most_recent_reference` raised
|
|
16
|
+
# `NotImplementedError` on **every** EASC item — and it descends from
|
|
17
|
+
# `ScriptError`, so a caller's `rescue => e` did not catch it.
|
|
18
|
+
class Docidentifier < Bib::Docidentifier
|
|
19
|
+
attr_reader :pubid
|
|
20
|
+
|
|
21
|
+
# Capture the inherited content setter before overriding #content=, so
|
|
22
|
+
# #refresh_content! can write the re-rendered string back WITHOUT
|
|
23
|
+
# re-parsing (a re-parse would rebuild @pubid and discard in-place
|
|
24
|
+
# mutations such as `all_parts = true`).
|
|
25
|
+
alias_method :store_content, :content=
|
|
26
|
+
|
|
27
|
+
def initialize(attrs = {}, options = {})
|
|
28
|
+
pubid = attrs.is_a?(Hash) ? attrs.delete(:pubid) : nil
|
|
29
|
+
attrs[:content] ||= pubid.to_s if pubid
|
|
30
|
+
super
|
|
31
|
+
@pubid = pubid if pubid
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def content=(value)
|
|
35
|
+
super
|
|
36
|
+
return unless value
|
|
37
|
+
|
|
38
|
+
@pubid = begin
|
|
39
|
+
require "pubid"
|
|
40
|
+
::Pubid::Easc.parse(value)
|
|
41
|
+
rescue LoadError, StandardError
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# `Pubid::Easc::Identifier` models publisher/series/variant/number/year,
|
|
47
|
+
# so the mapping is EASC-specific:
|
|
48
|
+
#
|
|
49
|
+
# - **`remove_date!` → clears `year`.** EASC carries no separate date
|
|
50
|
+
# component; the year is the edition discriminator, so clearing it
|
|
51
|
+
# yields the undated ("most recent") reference: `ПМГ 03-2025` →
|
|
52
|
+
# `ПМГ 03`.
|
|
53
|
+
# - **`remove_part!` → clears the (unused) `part`/`subpart`.** EASC models
|
|
54
|
+
# no part, so this is a no-op for the rendered string; implemented so it
|
|
55
|
+
# never raises, and it starts working if pubid-easc ever adds one.
|
|
56
|
+
# - **`to_all_parts!` → both, then wraps `@pubid` in pubid's `AllParts`.**
|
|
57
|
+
# `content` stays the plain stripped id — EASC's own renderer has no
|
|
58
|
+
# "(all parts)" marker, and `content` is cached from the pre-wrap
|
|
59
|
+
# pubid on purpose. `#pubid` itself, read directly, now answers
|
|
60
|
+
# `all_parts? == true` and renders WITH pubid's generic marker
|
|
61
|
+
# (`#to_s`), since it's the wrapper.
|
|
62
|
+
#
|
|
63
|
+
# All three no-op safely when `@pubid` is nil.
|
|
64
|
+
|
|
65
|
+
def remove_part!
|
|
66
|
+
clear_attr!(:part)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def remove_date!
|
|
70
|
+
clear_attr!(:year)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_all_parts!
|
|
74
|
+
return if !@pubid || @pubid.all_parts?
|
|
75
|
+
|
|
76
|
+
# `#exclude` (no args) rebuilds a full independent copy — `remove_part!`
|
|
77
|
+
# / `remove_date!` mutate `@pubid` in place, so a bare reference here
|
|
78
|
+
# would lose the original year to that mutation too.
|
|
79
|
+
original = @pubid.exclude
|
|
80
|
+
remove_part!
|
|
81
|
+
remove_date!
|
|
82
|
+
@pubid = original.to_all_parts
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def clear_attr!(attr)
|
|
88
|
+
return unless @pubid && @pubid.respond_to?("#{attr}=")
|
|
89
|
+
|
|
90
|
+
@pubid.public_send("#{attr}=", nil)
|
|
91
|
+
@pubid.subpart = nil if attr == :part && @pubid.respond_to?(:subpart=)
|
|
92
|
+
refresh_content!
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def refresh_content!
|
|
96
|
+
store_content(@pubid.to_s) if @pubid
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Easc
|
|
3
|
+
# EASC document types. Two series, both issued by the Eurasian
|
|
4
|
+
# Economic Standards Council:
|
|
5
|
+
# * PMG — ПМГ (Правила по межгосударственной стандартизации)
|
|
6
|
+
# * RMG — РМГ (Рекомендации по межгосударственной стандартизации)
|
|
7
|
+
class Doctype < Bib::Doctype
|
|
8
|
+
TYPES = %w[
|
|
9
|
+
pmg
|
|
10
|
+
rmg
|
|
11
|
+
].freeze
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Easc
|
|
3
|
+
# EASC bibliographic item extension. Adds EASC-specific structured
|
|
4
|
+
# metadata on top of the shared Relaton::Bib::Ext so the fields
|
|
5
|
+
# round-trip through both XML and YAML without per-repo merge hacks.
|
|
6
|
+
#
|
|
7
|
+
# Fields:
|
|
8
|
+
# * urn — urn:easc:<series>:...
|
|
9
|
+
# * webpage — mgscatalog.by detail URL
|
|
10
|
+
# * session — МГС council session that adopted the doc
|
|
11
|
+
# (e.g. "67МГС", "19МГС")
|
|
12
|
+
# * developer — Разработчик (e.g. "Российская Федерация")
|
|
13
|
+
# * joining_states — country codes of states that joined
|
|
14
|
+
# (collection: e.g. ["АРМ", "БЕИ", "КАЗ"])
|
|
15
|
+
# * assigned_to — Закреплен за (e.g. "МТК 536 Методология...")
|
|
16
|
+
class Ext < Bib::Ext
|
|
17
|
+
attribute :doctype, Doctype
|
|
18
|
+
attribute :urn, :string
|
|
19
|
+
attribute :webpage, :string
|
|
20
|
+
attribute :session, :string
|
|
21
|
+
attribute :developer, :string
|
|
22
|
+
attribute :joining_states, :string, collection: true, initialize_empty: true
|
|
23
|
+
attribute :assigned_to, :string
|
|
24
|
+
|
|
25
|
+
xml do
|
|
26
|
+
map_element "urn", to: :urn
|
|
27
|
+
map_element "webpage", to: :webpage
|
|
28
|
+
map_element "session", to: :session
|
|
29
|
+
map_element "developer", to: :developer
|
|
30
|
+
map_element "joining_states", to: :joining_states
|
|
31
|
+
map_element "assigned_to", to: :assigned_to
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
key_value do
|
|
35
|
+
map_element "urn", to: :urn
|
|
36
|
+
map_element "webpage", to: :webpage
|
|
37
|
+
map_element "session", to: :session
|
|
38
|
+
map_element "developer", to: :developer
|
|
39
|
+
map_element "joining_states", to: :joining_states
|
|
40
|
+
map_element "assigned_to", to: :assigned_to
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Easc
|
|
3
|
+
# EASC bibliographic item. Re-declares +ext+ to use the typed Ext
|
|
4
|
+
# subclass so EASC-specific fields round-trip natively.
|
|
5
|
+
class Item < Bib::Item
|
|
6
|
+
model ItemData
|
|
7
|
+
|
|
8
|
+
attribute :ext, Ext
|
|
9
|
+
attribute :docidentifier, Docidentifier, collection: true,
|
|
10
|
+
initialize_empty: true
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Easc
|
|
3
|
+
class ItemBase < Lutaml::Model::Serializable
|
|
4
|
+
include Bib::NamespaceHelper
|
|
5
|
+
|
|
6
|
+
attr_accessor :type
|
|
7
|
+
|
|
8
|
+
model ItemData
|
|
9
|
+
|
|
10
|
+
instance_exec(&Bib::ItemShared::ATTRIBUTES)
|
|
11
|
+
|
|
12
|
+
xml do
|
|
13
|
+
map_attribute "type", to: :type
|
|
14
|
+
instance_exec(&Bib::ItemShared::XML_BODY)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require "relaton/core/processor"
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Easc
|
|
5
|
+
# Relaton processor for the EASC flavor. Registered by
|
|
6
|
+
# Relaton::Db::Registry when the unified gem loads (see
|
|
7
|
+
# lib/relaton/db/registry.rb). Supports `relaton fetch easc ...`
|
|
8
|
+
# once the relaton-data-easc dataset is installed.
|
|
9
|
+
class Processor < Core::Processor
|
|
10
|
+
attr_reader :idtype
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@short = :relaton_easc
|
|
14
|
+
@prefix = "EASC"
|
|
15
|
+
# Both Cyrillic and Latin series prefixes route here.
|
|
16
|
+
@defaultprefix = %r{^(?:ПМГ|РМГ|PMG|RMG)\b}
|
|
17
|
+
@idtype = "EASC"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get(code, date, opts)
|
|
21
|
+
require_relative "../easc"
|
|
22
|
+
Bibliography.get(code, date, opts)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def from_xml(xml)
|
|
26
|
+
require_relative "../easc"
|
|
27
|
+
Item.from_xml xml
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def from_yaml(yaml)
|
|
31
|
+
require_relative "../easc"
|
|
32
|
+
Item.from_yaml(yaml)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def grammar_hash
|
|
36
|
+
require_relative "../easc"
|
|
37
|
+
@grammar_hash ||= ::Relaton::Easc.grammar_hash
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def remove_index_file
|
|
41
|
+
require_relative "../easc"
|
|
42
|
+
Relaton::Index.find_or_create(:easc, url: true, file: "#{INDEXFILE}.yaml").remove_file
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/relaton/easc.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "relaton/bib"
|
|
4
|
+
require "relaton/index"
|
|
5
|
+
require "relaton/version"
|
|
6
|
+
require "pubid"
|
|
7
|
+
|
|
8
|
+
# Relaton::Easc is the EASC (Евразийский экономический совет /
|
|
9
|
+
# Eurasian Economic Standards Council) flavor of the unified relaton
|
|
10
|
+
# gem. Adds typed Ext fields (urn, webpage, session, developer,
|
|
11
|
+
# joining_states) so they round-trip natively through YAML and XML
|
|
12
|
+
# without per-repo merge hacks.
|
|
13
|
+
module Relaton
|
|
14
|
+
module Easc
|
|
15
|
+
INDEXFILE = "index-v2".freeze
|
|
16
|
+
|
|
17
|
+
class Error < StandardError; end
|
|
18
|
+
|
|
19
|
+
autoload :Doctype, "relaton/easc/doctype"
|
|
20
|
+
autoload :Ext, "relaton/easc/ext"
|
|
21
|
+
autoload :ItemData, "relaton/easc/item_data"
|
|
22
|
+
autoload :ItemBase, "relaton/easc/item_base"
|
|
23
|
+
autoload :Item, "relaton/easc/item"
|
|
24
|
+
autoload :Bibitem, "relaton/easc/bibitem"
|
|
25
|
+
autoload :Bibdata, "relaton/easc/bibdata"
|
|
26
|
+
autoload :Docidentifier, "relaton/easc/docidentifier"
|
|
27
|
+
autoload :Util, "relaton/easc/util"
|
|
28
|
+
autoload :Bibliography, "relaton/easc/bibliography"
|
|
29
|
+
autoload :Processor, "relaton/easc/processor"
|
|
30
|
+
|
|
31
|
+
def self.grammar_hash
|
|
32
|
+
Digest::MD5.hexdigest Relaton::VERSION
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|