relaton/relaton-bib

View on GitHub
lib/relaton_bib/hash_converter.rb

Summary

Maintainability
C
1 day
Test Coverage
module RelatonBib
  module HashConverter
    extend self
    # rubocop:disable Metrics/MethodLength, Metrics/AbcSize

    # @param args [Hash]
    # @return [Hash]
    def hash_to_bib(args)
      return nil unless args.is_a?(Hash)

      ret = Marshal.load(Marshal.dump(symbolize(args))) # deep copy
      title_hash_to_bib(ret)
      link_hash_to_bib(ret)
      language_hash_to_bib(ret)
      script_hash_to_bib(ret)
      dates_hash_to_bib(ret)
      docid_hash_to_bib(ret)
      version_hash_to_bib(ret)
      biblionote_hash_to_bib(ret)
      abstract_hash_to_bib(ret)
      formattedref_hash_to_bib(ret)
      docstatus_hash_to_bib(ret)
      contributors_hash_to_bib(ret)
      copyright_hash_to_bib(ret)
      relations_hash_to_bib(ret)
      series_hash_to_bib(ret)
      medium_hash_to_bib(ret)
      place_hash_to_bib(ret)
      extent_hash_to_bib(ret)
      size_hash_to_bib(ret)
      accesslocation_hash_to_bib(ret)
      classification_hash_to_bib(ret)
      validity_hash_to_bib(ret)
      keyword_hash_to_bib(ret)
      # ret[:keyword] = RelatonBib.array(ret[:keyword])
      ret[:license] = RelatonBib.array(ret[:license])
      editorialgroup_hash_to_bib ret
      ics_hash_to_bib ret
      structuredidentifier_hash_to_bib ret
      doctype_hash_to_bib ret
      ret
    end
    # rubocop:enable Metrics/MethodLength, Metrics/AbcSize

    def keyword_hash_to_bib(ret)
      ret[:keyword] = RelatonBib.array(ret[:keyword]).map do |keyword|
        localizedstring keyword
      end
    end

    def extent_hash_to_bib(ret)
      return unless ret[:extent]

      ret[:extent] = RelatonBib.array(ret[:extent]).map do |e|
        locality e
        # ret[:extent][i] = Locality.new(e[:type], e[:reference_from],
        #                                       e[:reference_to])
      end
    end

    def locality(loc)
      if loc[:locality_stack]
        LocalityStack.new(loc[:locality_stack].map { |l| locality(l) })
      else
        l = loc[:locality]
        Locality.new(l[:type], l[:reference_from], l[:reference_to])
      end
    end

    def size_hash_to_bib(ret)
      return unless ret[:size]

      ret[:size] = RelatonBib.array(ret[:size])
      size = ret[:size]&.map do |val|
        BibliographicSize::Value.new(**val)
      end
      ret[:size] = BibliographicSize.new(size)
    end

    def title_hash_to_bib(ret)
      return unless ret[:title]

      ret[:title] = RelatonBib.array(ret[:title])
        .reduce(TypedTitleStringCollection.new) do |m, t|
        if t.is_a?(Hash) then m << TypedTitleString.new(**t)
        else
          m + TypedTitleString.from_string(t)
        end
      end
    end

    def language_hash_to_bib(ret)
      return unless ret[:language]

      ret[:language] = RelatonBib.array(ret[:language])
    end

    def script_hash_to_bib(ret)
      return unless ret[:script]

      ret[:script] = RelatonBib.array(ret[:script])
    end

    def abstract_hash_to_bib(ret)
      return unless ret[:abstract]

      ret[:abstract] = RelatonBib.array(ret[:abstract]).map do |a|
        a.is_a?(String) ? FormattedString.new(content: a) : a
      end
    end

    def link_hash_to_bib(ret)
      return unless ret[:link]

      ret[:link] = RelatonBib.array(ret[:link])
    end

    def place_hash_to_bib(ret)
      return unless ret[:place]

      ret[:place] = RelatonBib.array(ret[:place]).map do |pl|
        pl.is_a?(String) ? Place.new(name: pl) : Place.new(**pl)
      end
    end

    def accesslocation_hash_to_bib(ret)
      return unless ret[:accesslocation]

      ret[:accesslocation] = RelatonBib.array(ret[:accesslocation])
    end

    def dates_hash_to_bib(ret) # rubocop:disable Metrics/AbcSize
      return unless ret[:date]

      ret[:date] = RelatonBib.array(ret[:date])
      ret[:date].each_with_index do |d, i|
        # value is synonym of on: it is reserved word in YAML
        if d[:value]
          ret[:date][i][:on] ||= d[:value]
          ret[:date][i].delete(:value)
        end
      end
    end

    def docid_hash_to_bib(ret) # rubocop:disable Metrics/AbcSize
      return unless ret[:docid]

      ret[:docid] = RelatonBib.array(ret[:docid]).map do |id|
        id[:type] ||= id[:id].match(/^\w+(?=\s)/)&.to_s
        create_docid(**id)
      end
    end

    def create_docid(**args)
      DocumentIdentifier.new(**args)
    end

    def version_hash_to_bib(ret)
      return unless ret[:version]

      ret[:version] = RelatonBib.array(ret[:version]).map do |v|
        BibliographicItem::Version.new(v[:revision_date], v[:draft])
      end
    end

    def biblionote_hash_to_bib(ret) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
      return unless ret[:biblionote]

      ret[:biblionote] = RelatonBib.array(ret[:biblionote])
        .reduce(BiblioNoteCollection.new([])) do |mem, n|
        mem << if n.is_a?(String) then BiblioNote.new content: n
                else BiblioNote.new(**n)
                end
      end
    end

    def formattedref_hash_to_bib(ret)
      ret[:formattedref] &&
        ret[:formattedref] = formattedref(ret[:formattedref])
    end

    def docstatus_hash_to_bib(ret)
      ret[:docstatus] && ret[:docstatus] = DocumentStatus.new(
        stage: stage(ret[:docstatus][:stage]),
        substage: stage(ret[:docstatus][:substage]),
        iteration: ret[:docstatus][:iteration],
      )
    end

    # @param stg [Hash]
    # @return [RelatonBib::DocumentStatus::Stage]
    def stage(stg)
      return unless stg

      args = stg.is_a?(String) ? { value: stg } : stg
      DocumentStatus::Stage.new(**args)
    end

    def contributors_hash_to_bib(ret) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength,Metrics/PerceivedComplexity
      return unless ret[:contributor]

      ret[:contributor] = RelatonBib.array(ret[:contributor])
      ret[:contributor]&.each_with_index do |c, i|
        roles = RelatonBib.array(ret[:contributor][i][:role]).map do |r|
          if r.is_a? Hash
            desc = RelatonBib.array(r[:description]).map { |d| d.is_a?(String) ? d : d[:content] }
            { type: r[:type], description: desc }
          # elsif r.is_a? Array
          #   { type: r[0], description: r.fetch(1) }
          else
            { type: r }
          end
        end
        ret[:contributor][i][:role] = roles
        ret[:contributor][i][:entity] = if c[:person]
                                          person_hash_to_bib(c[:person])
                                        else
                                          org_hash_to_bib(c[:organization])
                                        end
        ret[:contributor][i].delete(:person)
        ret[:contributor][i].delete(:organization)
      end
    end

    def org_hash_to_bib(org) # rubocop:disable Metrics/AbcSize
      return nil if org.nil?

      org[:identifier] = RelatonBib.array(org[:identifier])&.map do |a|
        OrgIdentifier.new(a[:type], a[:id])
      end
      org[:subdivision] = RelatonBib.array(org[:subdivision]).map do |sd|
        LocalizedString.new sd.is_a?(Hash) ? sd[:content] : sd
      end
      org[:contact] = contacts_hash_to_bib(org)
      org[:logo] = Image.new(**org[:logo][:image]) if org[:logo]
      org
    end

    def person_hash_to_bib(person)
      Person.new(
        name: fullname_hash_to_bib(person),
        credential: RelatonBib.array(person[:credential]),
        affiliation: affiliation_hash_to_bib(person),
        contact: contacts_hash_to_bib(person),
        identifier: person_identifiers_hash_to_bib(person),
      )
    end

    def fullname_hash_to_bib(person) # rubocop:disable Metrics/AbcSize
      n = person[:name]
      fname, inits = given_hash_to_bib n[:given] || n # `n` is for backward compatibility
      FullName.new(
        abbreviation: localizedstring(n[:abbreviation]),
        forename: fname, initials: inits,
        addition: RelatonBib.array(n[:addition])&.map { |f| localizedstring(f) },
        prefix: RelatonBib.array(n[:prefix])&.map { |f| localizedstring(f) },
        surname: localizedstring(n[:surname]),
        completename: localizedstring(n[:completename])
      )
    end

    def given_hash_to_bib(given)
      return [[], nil] unless given

      fname = RelatonBib.array(given[:forename])&.map { |f| forename_hash_to_bib(f) }
      inits = localizedstring(given[:formatted_initials])
      [fname, inits]
    end

    def forename_hash_to_bib(fname)
      case fname
      when Hash then Forename.new(**fname)
      when String then Forename.new(content: fname)
      end
    end

    def person_identifiers_hash_to_bib(person)
      RelatonBib.array(person[:identifier])&.map do |a|
        PersonIdentifier.new(a[:type], a[:id])
      end
    end

    def affiliation_hash_to_bib(person) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
      return [] unless person[:affiliation]

      RelatonBib.array(person[:affiliation]).map do |a|
        a[:description] = RelatonBib.array(a[:description]).map do |d|
          cnt = if d.is_a?(Hash)
                  { content: d[:content], language: d[:language],
                    script: d[:script], format: d[:format] }
                else { content: d }
                end
          FormattedString.new(**cnt)
        end
        Affiliation.new(
          organization: Organization.new(**org_hash_to_bib(a[:organization])),
          description: a[:description], name: localizedstring(a[:name])
        )
      end
    end

    def contacts_hash_to_bib(entity) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity
      return [] unless entity[:contact]

      RelatonBib.array(entity[:contact]).map do |a|
        type, value = a.reject { |k, _| k == :type }.flatten
        case type
        when :street, :city, :state, :country, :postcode # it's for old version compatibility, should be removed in the future
          a[:street] = RelatonBib.array(a[:street])
          Address.new(**a)
        when :address then create_address(a[:address])
        when :phone, :email, :uri
          Contact.new(type: type.to_s, value: value, subtype: a[:type])
        else # it's for old version compatibility, should be removed in the future
          Contact.new(**a)
        end
      end
    end

    def create_address(adr)
      if adr.is_a?(Hash)
        adr[:street] = RelatonBib.array(adr[:street])
        Address.new(**adr)
      else
        Address.new(formatted_address: adr)
      end
    end

    # @param ret [Hash]
    def copyright_hash_to_bib(ret)
      return unless ret[:copyright]

      ret[:copyright] = RelatonBib.array(ret[:copyright]).map do |c|
        c[:owner] = RelatonBib.array(c[:owner]).map do |o|
          org_hash_to_bib(o)
        end
        c
      end
    end

    # @param ret [Hash]
    def relations_hash_to_bib(ret)
      return unless ret[:relation]

      ret[:relation] = RelatonBib.array(ret[:relation])
      ret[:relation]&.each do |r|
        if r[:description]
          r[:description] = FormattedString.new(**r[:description])
        end
        relation_bibitem_hash_to_bib(r)
        relation_locality_hash_to_bib(r)
        relation_source_locality_hash_to_bib(r)
      end
    end

    # @param rel [Hash] relation
    def relation_bibitem_hash_to_bib(rel)
      if rel[:bibitem]
        rel[:bibitem] = bib_item hash_to_bib(rel[:bibitem])
      else
        Util.warn "Bibitem missing: `#{rel}`"
        rel[:bibitem] = nil
      end
    end

    # @param item_hash [Hash]
    # @return [RelatonBib::BibliographicItem]
    def bib_item(item_hash)
      BibliographicItem.new(**item_hash)
    end

    # @param rel [Hash] relation
    # @return [RelatonBib::LocalityStack]
    def relation_locality_hash_to_bib(rel)
      rel[:locality] = RelatonBib.array(rel[:locality])&.map do |bl|
        LocalityStack.new locality_locality_stack(bl)
      end
    end

    def locality_locality_stack(lls)
      if lls[:locality_stack]
        RelatonBib.array(lls[:locality_stack]).map do |lc|
          l = lc[:locality] || lc
          Locality.new(l[:type], l[:reference_from], l[:reference_to])
        end
      else
        [Locality.new(lls[:type], lls[:reference_from], lls[:reference_to])]
      end
    end

    # @param rel [Hash] relation
    def relation_source_locality_hash_to_bib(rel) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
      rel[:source_locality] = RelatonBib.array(rel[:source_locality])&.map do |sl|
        sls = if sl[:source_locality_stack]
                RelatonBib.array(sl[:source_locality_stack]).map do |l|
                  SourceLocality.new(l[:type], l[:reference_from],
                                      l[:reference_to])
                end
              else
                l = SourceLocality.new(sl[:type], sl[:reference_from],
                                        sl[:reference_to])
                [l]
              end
        SourceLocalityStack.new sls
      end
    end

    # @param ret [Hash]
    def series_hash_to_bib(ret) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
      ret[:series] = RelatonBib.array(ret[:series])&.map do |s|
        s[:formattedref] && s[:formattedref] = formattedref(s[:formattedref])
        if s[:title]
          s[:title] = { content: s[:title] } unless s[:title].is_a?(Hash)
          s[:title] = typed_title_strig(s[:title])
        end
        s[:abbreviation] &&
          s[:abbreviation] = localizedstring(s[:abbreviation])
        Series.new(**s)
      end
    end

    # @param title [Hash]
    # @return [RelatonBib::TypedTitleString]
    def typed_title_strig(title)
      TypedTitleString.new(**title)
    end

    # @param ret [Hash]
    def medium_hash_to_bib(ret)
      ret[:medium] = Medium.new(**ret[:medium]) if ret[:medium]
    end

    # @param ret [Hash]
    def classification_hash_to_bib(ret)
      if ret[:classification]
        ret[:classification] = RelatonBib.array(ret[:classification]).map do |cls|
          Classification.new(**cls)
        end
      end
    end

    # @param ret [Hash]
    def validity_hash_to_bib(ret)
      return unless ret[:validity]

      b = parse_validity_time(ret[:validity], :begins)
      e = parse_validity_time(ret[:validity], :ends)
      r = parse_validity_time(ret[:validity], :revision)
      ret[:validity] = Validity.new(begins: b, ends: e, revision: r)
    end

    def parse_validity_time(val, period)
      t = val[period]&.to_s
      return unless t

      p = period == :ends ? -1 : 1
      case t
      when /^\d{4}$/
        Date.new(t.to_i, p, p).to_time
      when /^(?<year>\d{4})-(?<month>\d{1,2})$/
        Date.new($~[:year].to_i, $~[:month].to_i, p).to_time
      else Time.parse t
      end
    end

    # @param ret [Hash]
    def editorialgroup_hash_to_bib(ret)
      return unless ret[:editorialgroup]

      technical_committee = RelatonBib.array(ret[:editorialgroup]).map do |wg|
        TechnicalCommittee.new WorkGroup.new(**wg)
      end
      ret[:editorialgroup] = EditorialGroup.new technical_committee
    end

    # @param ret [Hash]
    def ics_hash_to_bib(ret)
      return unless ret[:ics]

      ret[:ics] = RelatonBib.array(ret[:ics]).map { |ics| ICS.new(**ics) }
    end

    # @param ret [Hash]
    def structuredidentifier_hash_to_bib(ret)
      return unless ret[:structuredidentifier]

      sids = RelatonBib.array(ret[:structuredidentifier]).map do |si|
        si[:agency] = RelatonBib.array si[:agency]
        StructuredIdentifier.new(**si)
      end
      ret[:structuredidentifier] = StructuredIdentifierCollection.new sids
    end

    # @param ogj [Hash, Array, String]
    # @return [Hash, Array, String]
    def symbolize(obj)
      case obj
      when Hash
        obj.reduce({}) do |memo, (k, v)|
          memo[k.to_sym] = symbolize(v)
          memo
        end
      when Array then obj.reduce([]) { |memo, v| memo << symbolize(v) }
      else obj
      end
    end

    # @param lst [Hash, Array<RelatonBib::LocalizedString>, String]
    # @return [RelatonBib::LocalizedString]
    def localizedstring(lst)
      return unless lst

      if lst.is_a?(Hash)
        LocalizedString.new(lst[:content], lst[:language], lst[:script])
      else LocalizedString.new(lst)
      end
    end

    # @param frf [Hash, String]
    # @return [RelatonBib::FormattedRef]
    def formattedref(frf)
      if frf.is_a?(Hash)
        RelatonBib::FormattedRef.new(**frf)
      else
        RelatonBib::FormattedRef.new(content: frf)
      end
    end

    def doctype_hash_to_bib(ret)
      return unless ret[:doctype]

      ret[:doctype] = if ret[:doctype].is_a?(String)
                        create_doctype(type: ret[:doctype])
                      else create_doctype(**ret[:doctype])
                      end
    end

    def create_doctype(**args)
      DocumentType.new(**args)
    end
  end
end