class Relaton::Db

Public Class Methods

new(global_cache, local_cache) click to toggle source

@param global_cache [String] directory of global DB @param local_cache [String] directory of local DB

# File lib/relaton/db.rb, line 11
def initialize(global_cache, local_cache)
  @registry = Relaton::Registry.instance
  gpath = global_cache && File.expand_path(global_cache)
  @db = open_cache_biblio(gpath, type: :global)
  lpath = local_cache && File.expand_path(local_cache)
  @local_db = open_cache_biblio(lpath, type: :local)
  @static_db = open_cache_biblio File.expand_path("../relaton/static_cache",
                                                  __dir__)
  @queues = {}
end

Private Class Methods

flush_caches(gcache, lcache) click to toggle source
# File lib/relaton/db.rb, line 487
def flush_caches(gcache, lcache)
  FileUtils.rm_rf gcache unless gcache.nil?
  FileUtils.rm_rf lcache unless lcache.nil?
end
global_bibliocache_name() click to toggle source
# File lib/relaton/db.rb, line 492
def global_bibliocache_name
  "#{Dir.home}/.relaton/cache"
end
init_bib_caches(**opts) click to toggle source

Initialse and return relaton instance, with local and global cache names local_cache: local cache name; none created if nil; “relaton” created if empty global_cache: boolean to create global_cache flush_caches: flush caches

# File lib/relaton/db.rb, line 478
def init_bib_caches(**opts) # rubocop:disable Metrics/CyclomaticComplexity
  globalname = global_bibliocache_name if opts[:global_cache]
  localname = local_bibliocache_name(opts[:local_cache])
  flush_caches globalname, localname if opts[:flush_caches]
  Relaton::Db.new(globalname, localname)
end
local_bibliocache_name(cachename) click to toggle source
# File lib/relaton/db.rb, line 496
def local_bibliocache_name(cachename)
  cachename = "relaton" if cachename.nil? || cachename.empty?
  "#{cachename}/cache"
end

Public Instance Methods

clear() click to toggle source

Clear global and local databases

# File lib/relaton/db.rb, line 36
def clear
  @db&.clear
  @local_db&.clear
end
docid_type(code) click to toggle source

The document identifier class corresponding to the given code @param code [String] @return [Array]

# File lib/relaton/db.rb, line 147
def docid_type(code)
  stdclass = standard_class(code) or return [nil, code]
  _prefix, code = strip_id_wrapper(code, stdclass)
  [@registry.processors[stdclass].idtype, code]
end
fetch(code, year = nil, opts = {}) click to toggle source

The class of reference requested is determined by the prefix of the code: GB Standard for gbbib, IETF for ietfbib, ISO for isobib, IEC or IEV for

iecbib,

@param code [String] the ISO standard Code to look up (e.g. “ISO 9000”) @param year [String] the year the standard was published (optional)

@param opts [Hash] options @option opts [Boolean] :all_parts If all-parts reference is required @option opts [Boolean] :keep_year If undated reference should return

actual reference with year

@option opts [Integer] :retries (1) Number of network retries

@return [nil, RelatonBib::BibliographicItem,

RelatonIsoBib::IsoBibliographicItem, RelatonItu::ItuBibliographicItem,
RelatonIetf::IetfBibliographicItem, RelatonIec::IecBibliographicItem,
RelatonIeee::IeeeBibliographicItem, RelatonNist::NistBibliongraphicItem,
RelatonGb::GbbibliographicItem, RelatonOgc::OgcBibliographicItem,
RelatonCalconnect::CcBibliographicItem, RelatinUn::UnBibliographicItem,
RelatonBipm::BipmBibliographicItem, RelatonIho::IhoBibliographicItem,
RelatonOmg::OmgBibliographicItem, RelatonW3c::W3cBibliographicItem]
# File lib/relaton/db.rb, line 64
def fetch(code, year = nil, opts = {})
  stdclass = standard_class(code) || return
  processor = @registry.processors[stdclass]
  ref = if processor.respond_to?(:urn_to_code)
          processor.urn_to_code(code)&.first
        else code
        end
  ref ||= code
  result = combine_doc ref, year, opts, stdclass
  result ||= check_bibliocache(ref, year, opts, stdclass)
  result
end
fetch_all(text = nil, edition: nil, year: nil) click to toggle source

fetch all standards from DB @param test [String, nil] @param edition [String], nil @param year [Integer, nil] @return [Array]

# File lib/relaton/db.rb, line 88
def fetch_all(text = nil, edition: nil, year: nil)
  result = @static_db.all do |file, yml|
    search_yml file, yml, text, edition, year
  end.compact
  db = @db || @local_db
  if db
    result += db.all do |file, xml|
      search_xml file, xml, text, edition, year
    end.compact
  end
  result
end
fetch_async(code, year = nil, opts = {}) { |fetch(*args)| ... } click to toggle source

Fetch asynchronously

# File lib/relaton/db.rb, line 102
def fetch_async(code, year = nil, opts = {}, &_block) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
  stdclass = standard_class code
  if stdclass
    unless @queues[stdclass]
      processor = @registry.processors[stdclass]
      wp = WorkersPool.new(processor.threads) { |args| yield fetch(*args) }
      @queues[stdclass] = { queue: Queue.new, workers_pool: wp }
      Thread.new { process_queue @queues[stdclass] }
    end
    @queues[stdclass][:queue] << [code, year, opts]
  else yield nil
  end
end
fetch_db(code, year = nil, opts = {}) click to toggle source

@see Relaton::Db#fetch

# File lib/relaton/db.rb, line 78
def fetch_db(code, year = nil, opts = {})
  opts[:fetch_db] = true
  fetch code, year, opts
end
fetch_std(code, year = nil, stdclass = nil, opts = {}) click to toggle source

@param code [String] @param year [String, NilClass] @param stdclass [Symbol, NilClass]

@param opts [Hash] @option opts [Boolean] :all_parts If all-parts reference is required @option opts [Boolean] :keep_year If undated reference should return

actual reference with year

@option opts [Integer] :retries (1) Number of network retries

@return [nil, RelatonBib::BibliographicItem,

RelatonIsoBib::IsoBibliographicItem, RelatonItu::ItuBibliographicItem,
RelatonIetf::IetfBibliographicItem, RelatonIec::IecBibliographicItem,
RelatonIeee::IeeeBibliographicItem, RelatonNist::NistBibliongraphicItem,
RelatonGb::GbbibliographicItem, RelatonOgc::OgcBibliographicItem,
RelatonCalconnect::CcBibliographicItem, RelatinUn::UnBibliographicItem,
RelatonBipm::BipmBibliographicItem, RelatonIho::IhoBibliographicItem,
RelatonOmg::OmgBibliographicItem, RelatonW3c::W3cBibliographicItem]
# File lib/relaton/db.rb, line 134
def fetch_std(code, year = nil, stdclass = nil, opts = {})
  std = nil
  @registry.processors.each do |name, processor|
    std = name if processor.prefix == stdclass
  end
  std = standard_class(code) or return nil unless std

  check_bibliocache(code, year, opts, std)
end
load_entry(key) click to toggle source

@param key [String] @return [Hash]

# File lib/relaton/db.rb, line 155
def load_entry(key)
  unless @local_db.nil?
    entry = @local_db[key]
    return entry if entry
  end
  @db[key]
end
mv(new_dir, type: :global) click to toggle source

Move global or local caches to anothe dirs @param new_dir [String, nil] @param type: [Symbol] @return [String, nil]

# File lib/relaton/db.rb, line 26
def mv(new_dir, type: :global)
  case type
  when :global
    @db&.mv new_dir
  when :local
    @local_db&.mv new_dir
  end
end
save_entry(key, value) click to toggle source

@param key [String] @param value [String] Bibitem xml serialisation. @option value [String] Bibitem xml serialisation.

# File lib/relaton/db.rb, line 166
def save_entry(key, value)
  @db.nil? || (@db[key] = value)
  @local_db.nil? || (@local_db[key] = value)
end
to_xml() click to toggle source

list all entries as a serialization @return [String]

# File lib/relaton/db.rb, line 173
def to_xml
  db = @local_db || @db || return
  Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
    xml.documents do
      xml.parent.add_child db.all.join(" ")
    end
  end.to_xml
end

Private Instance Methods

bib_entry(bib) click to toggle source

@param bib [RelatonBib::BibliographicItem,

RelatonIsoBib::IsoBibliographicItem, RelatonItu::ItuBibliographicItem,
RelatonIetf::IetfBibliographicItem, RelatonIec::IecBibliographicItem,
RelatonIeee::IeeeBibliographicItem, RelatonNist::NistBibliongraphicItem,
RelatonGb::GbbibliographicItem, RelatonOgc::OgcBibliographicItem,
RelatonCalconnect::CcBibliographicItem, RelatinUn::UnBibliographicItem,
RelatonBipm::BipmBibliographicItem, RelatonIho::IhoBibliographicItem,
RelatonOmg::OmgBibliographicItem, RelatonW3c::W3cBibliographicItem]

@return [String] XML or “not_found mm-dd-yyyy”

# File lib/relaton/db.rb, line 436
def bib_entry(bib)
  if bib.respond_to? :to_xml
    bib.to_xml(bibdata: true)
  else
    "not_found #{Date.today}"
  end
end
bib_retval(entry, stdclass) click to toggle source

@param entry [String] XML string @param stdclass [Symbol] @return [nil, RelatonBib::BibliographicItem,

RelatonIsoBib::IsoBibliographicItem, RelatonItu::ItuBibliographicItem,
RelatonIetf::IetfBibliographicItem, RelatonIec::IecBibliographicItem,
RelatonIeee::IeeeBibliographicItem, RelatonNist::NistBibliongraphicItem,
RelatonGb::GbbibliographicItem, RelatonOgc::OgcBibliographicItem,
RelatonCalconnect::CcBibliographicItem, RelatinUn::UnBibliographicItem,
RelatonBipm::BipmBibliographicItem, RelatonIho::IhoBibliographicItem,
RelatonOmg::OmgBibliographicItem, RelatonW3c::W3cBibliographicItem]
# File lib/relaton/db.rb, line 333
def bib_retval(entry, stdclass)
  if entry.nil? || entry.match?(/^not_found/) then nil
  else @registry.processors[stdclass].from_xml(entry)
  end
end
check_bibliocache(code, year, opts, stdclass) click to toggle source

@param code [String] @param year [String]

@param opts [Hash] @option opts [Boolean] :all_parts If all-parts reference is required @option opts [Boolean] :keep_year If undated reference should return

actual reference with year

@option opts [Integer] :retries (1) Number of network retries

@param stdclass [Symbol] @return [nil, RelatonBib::BibliographicItem,

RelatonIsoBib::IsoBibliographicItem, RelatonItu::ItuBibliographicItem,
RelatonIetf::IetfBibliographicItem, RelatonIec::IecBibliographicItem,
RelatonIeee::IeeeBibliographicItem, RelatonNist::NistBibliongraphicItem,
RelatonGb::GbbibliographicItem, RelatonOgc::OgcBibliographicItem,
RelatonCalconnect::CcBibliographicItem, RelatinUn::UnBibliographicItem,
RelatonBipm::BipmBibliographicItem, RelatonIho::IhoBibliographicItem,
RelatonOmg::OmgBibliographicItem, RelatonW3c::W3cBibliographicItem]
# File lib/relaton/db.rb, line 357
def check_bibliocache(code, year, opts, stdclass) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  id, searchcode = std_id(code, year, opts, stdclass)
  yaml = @static_db[id]
  if yaml
    return @registry.processors[stdclass].hash_to_bib YAML.safe_load(yaml)
  end

  db = @local_db || @db
  altdb = @local_db && @db ? @db : nil
  if db.nil?
    return if opts[:fetch_db]

    bibentry = new_bib_entry(searchcode, year, opts, stdclass, db: db,
                                                               id: id)
    return bib_retval(bibentry, stdclass)
  end

  db.delete(id) unless db.valid_entry?(id, year)
  if altdb
    return bib_retval(altdb[id], stdclass) if opts[:fetch_db]

    db.clone_entry id, altdb if altdb.valid_entry? id, year
    db[id] ||= new_bib_entry(searchcode, year, opts, stdclass, db: db,
                                                               id: id)
    altdb.clone_entry(id, db) if !altdb.valid_entry?(id, year)
  else
    return bib_retval(db[id], stdclass) if opts[:fetch_db]

    db[id] ||= new_bib_entry(searchcode, year, opts, stdclass, db: db,
                                                               id: id)
  end
  bib_retval(db[id], stdclass)
end
combine_doc(code, year, opts, stdclass) click to toggle source

@param code [String] @param year [String, nil] @param stdslass [String]

@param opts [Hash] options @option opts [Boolean] :all_parts If all-parts reference is required @option opts [Boolean] :keep_year If undated reference should return

actual reference with year

@option opts [Integer] :retries (1) Number of network retries

@return [nil, RelatonBib::BibliographicItem,

RelatonIsoBib::IsoBibliographicItem, RelatonItu::ItuBibliographicItem,
RelatonIetf::IetfBibliographicItem, RelatonIec::IecBibliographicItem,
RelatonIeee::IeeeBibliographicItem, RelatonNist::NistBibliongraphicItem,
RelatonGb::GbbibliographicItem, RelatonOgc::OgcBibliographicItem,
RelatonCalconnect::CcBibliographicItem, RelatinUn::UnBibliographicItem,
RelatonBipm::BipmBibliographicItem, RelatonIho::IhoBibliographicItem,
RelatonOmg::OmgBibliographicItem, RelatonW3c::W3cBibliographicItem]
# File lib/relaton/db.rb, line 248
def combine_doc(code, year, opts, stdclass) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
  if (refs = code.split " + ").size > 1
    reltype = "derivedFrom"
    reldesc = nil
  elsif (refs = code.split ", ").size > 1
    reltype = "complements"
    reldesc = RelatonBib::FormattedString.new content: "amendment"
  else return
  end

  doc = @registry.processors[stdclass].hash_to_bib docid: { id: code }
  ref = refs[0]
  updates = check_bibliocache(ref, year, opts, stdclass)
  if updates
    doc.relation << RelatonBib::DocumentRelation.new(bibitem: updates,
                                                     type: "updates")
  end
  divider = stdclass == :relaton_itu ? " " : "/"
  refs[1..-1].each_with_object(doc) do |c, d|
    bib = check_bibliocache(ref + divider + c, year, opts, stdclass)
    if bib
      d.relation << RelatonBib::DocumentRelation.new(
        type: reltype, description: reldesc, bibitem: bib
      )
    end
  end
end
match_xml_text(xml, text) click to toggle source

@param xml [String] content in XML format @param text [String, nil] text to serach @return [Boolean]

# File lib/relaton/db.rb, line 226
def match_xml_text(xml, text)
  %r{((?<attr>=((?<apstr>')|"))|>).*?#{text}.*?(?(<attr>)(?(<apstr>)'|")|<)}mi.match?(xml)
end
net_retry(code, year, opts, stdclass, retries) click to toggle source

@raise [RelatonBib::RequestError]

# File lib/relaton/db.rb, line 419
def net_retry(code, year, opts, stdclass, retries)
  @registry.processors[stdclass].get(code, year, opts)
rescue RelatonBib::RequestError => e
  raise e unless retries > 1

  net_retry(code, year, opts, stdclass, retries - 1)
end
new_bib_entry(code, year, opts, stdclass, **args) click to toggle source

@param code [String] @param year [String]

@param opts [Hash] @option opts [Boolean] :all_parts If all-parts reference is required @option opts [Boolean] :keep_year If undated reference should return

actual reference with year

@option opts [Integer] :retries (1) Number of network retries

@param stdclass [Symbol] @param db [Relaton::DbCache,`NilClass] @param id [String] docid @return [String]

# File lib/relaton/db.rb, line 404
def new_bib_entry(code, year, opts, stdclass, **args) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
  bib = net_retry(code, year, opts, stdclass, opts.fetch(:retries, 1))
  bib_id = bib&.docidentifier&.first&.id

  # when docid doesn't match bib's id then return a reference to bib's id
  if args[:db] && args[:id] &&
      bib_id && args[:id] !~ %r{#{Regexp.quote("(#{bib_id})")}}
    bid = std_id(bib.docidentifier.first.id, nil, {}, stdclass).first
    args[:db][bid] ||= bib_entry bib
    "redirection #{bid}"
  else bib_entry bib
  end
end
open_cache_biblio(dir, type: :static) click to toggle source

@param dir [String, nil] DB directory @param type [Symbol] @return [Relaton::DbCache, NilClass]

# File lib/relaton/db.rb, line 447
def open_cache_biblio(dir, type: :static) # rubocop:disable Metrics/MethodLength
  return nil if dir.nil?

  db = DbCache.new dir, type == :static ? "yml" : "xml"
  return db if type == :static

  Dir["#{dir}/*/"].each do |fdir|
    next if db.check_version?(fdir)

    FileUtils.rm_rf(fdir, secure: true)
    Util.log(
      "[relaton] WARNING: cache #{fdir}: version is obsolete and cache is "\
        "cleared.",
      :warning
    )
  end
  db
end
process_queue(qwp) click to toggle source

@param qwp [Hash] @option qwp [Queue] :queue The queue of references to fetch @option qwp [Relaton::WorkersPool] :workers_pool The pool of workers

# File lib/relaton/db.rb, line 469
def process_queue(qwp)
  while args = qwp[:queue].pop; qwp[:workers_pool] << args end
end
search_edition_year(file, content, edition, year) click to toggle source

@param file [String] file path @param content [String] content in XML or YAmL format @param edition [String, nil] edition to filter @param year [Integer, nil] year to filter @return [BibliographicItem, nil]

# File lib/relaton/db.rb, line 214
def search_edition_year(file, content, edition, year) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
  processor = @registry.processors[standard_class(file.split("/")[-2])]
  item = if file.match?(/xml$/) then processor.from_xml(content)
         else processor.hash_to_bib(YAML.safe_load(content))
         end
  item if (edition.nil? || item.edition == edition) && (year.nil? ||
    item.date.detect { |d| d.type == "published" && d.on(:year).to_s == year.to_s })
end
search_xml(file, xml, text, edition, year) click to toggle source

@param file [String] file path @param xml [String] content in XML format @param text [String, nil] text to serach @param edition [String, nil] edition to filter @param year [Integer, nil] year to filter @return [BibliographicItem, nil]

# File lib/relaton/db.rb, line 203
def search_xml(file, xml, text, edition, year)
  return unless text.nil? || match_xml_text(xml, text)

  search_edition_year(file, xml, edition, year)
end
search_yml(file, yml, text, edition, year) click to toggle source

@param file [String] file path @param yml [String] content in YAML format @param text [String, nil] text to serach @param edition [String, nil] edition to filter @param year [Integer, nil] year to filter @return [BibliographicItem, nil]

# File lib/relaton/db.rb, line 190
def search_yml(file, yml, text, edition, year)
  item = search_edition_year(file, yml, edition, year)
  return unless item

  item if match_xml_text(item.to_xml(bibdata: true), text)
end
standard_class(code) click to toggle source

@param code [String] code of standard @return [Symbol] standard class name

# File lib/relaton/db.rb, line 278
    def standard_class(code)
      @registry.processors.each do |name, processor|
        return name if /^(urn:)?#{processor.prefix}/i.match?(code) ||
          processor.defaultprefix.match(code)
      end
      allowed = @registry.processors.reduce([]) do |m, (_k, v)|
        m << v.prefix
      end
      Util.log <<~WARN, :info
        [relaton] #{code} does not have a recognised prefix: #{allowed.join(', ')}.
        See https://github.com/relaton/relaton/ for instructions on prefixing and wrapping document identifiers to disambiguate them.
      WARN
    end
std_id(code, year, opts, stdclass) click to toggle source

TODO: i18n Fofmat ID @param code [String] @param year [String]

@param opts [Hash] @option opts [Boolean] :all_parts If all-parts reference is required @option opts [Boolean] :keep_year If undated reference should return

actual reference with year

@option opts [Integer] :retries (1) Number of network retries

@param stdClass [Symbol] @return [Array<String>] docid and code

# File lib/relaton/db.rb, line 305
def std_id(code, year, opts, stdclass)
  prefix, code = strip_id_wrapper(code, stdclass)
  ret = code
  ret += (stdclass == :relaton_gb ? "-" : ":") + year if year
  ret += " (all parts)" if opts[:all_parts]
  ["#{prefix}(#{ret.strip})", code]
end
strip_id_wrapper(code, stdclass) click to toggle source

Find prefix and clean code @param code [String] @param stdClass [Symbol] @return [Array]

# File lib/relaton/db.rb, line 317
def strip_id_wrapper(code, stdclass)
  prefix = @registry.processors[stdclass].prefix
  code = code.sub(/\u2013/, "-").sub(/^#{prefix}\((.+)\)$/, "\\1")
  [prefix, code]
end