relaton/relaton-bib

View on GitHub
lib/relaton_bib/renderer/bibxml.rb

Summary

Maintainability
C
1 day
Test Coverage
module RelatonBib
  module Renderer
    class BibXML
      def initialize(bib)
        @bib = bib
      end

      #
      # Render BibXML (RFC)
      #
      # @param [Nokogiri::XML::Builder, nil] builder
      # @param [Boolean] include_keywords (false)
      #
      # @return [String, Nokogiri::XML::Builder::NodeBuilder] XML
      #
      def render(builder: nil, include_keywords: true)
        if builder
          render_bibxml builder, include_keywords
        else
          Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
            render_bibxml xml, include_keywords
          end.doc.root.to_xml
        end
      end

      #
      # Render BibXML (RFC, BCP)
      #
      # @param [Nokogiri::XML::Builder] builder
      # @param [Boolean] include_bibdata
      #
      def render_bibxml(builder, include_keywords) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
        target = @bib.link.detect { |l| l.type.casecmp("src").zero? } ||
          @bib.link.detect { |l| l.type.casecmp("doi").zero? }
        bxml = if @bib.docnumber&.match(/^BCP/) || (@bib.docidentifier.detect(&:primary)&.id ||
                    @bib.docidentifier[0].id).include?("BCP")
                 render_bibxml_refgroup(builder, include_keywords)
               else
                 render_bibxml_ref(builder, include_keywords)
               end
        bxml[:target] = target.content.to_s if target
      end

      #
      # Render BibXML (BCP)
      #
      # @param [Nokogiri::XML::Builder] builder
      # @param [Boolean] include_keywords
      #
      def render_bibxml_refgroup(builder, include_keywords)
        builder.referencegroup(**ref_attrs) do |b|
          @bib.relation.each do |r|
            r.bibitem.to_bibxml(b, include_keywords: include_keywords) if r.type == "includes"
          end
        end
      end

      #
      # Render BibXML (RFC)
      #
      # @param [Nokogiri::XML::Builder] builder
      # @param [Boolean] include_keywords
      #
      def render_bibxml_ref(builder, include_keywords) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
        builder.reference(**ref_attrs) do |xml|
          if @bib.title.any? || @bib.contributor.any? || @bib.date.any? || @bib.abstract.any? ||
              @bib.editorialgroup&.technical_committee&.any? ||
              (include_keywords && @bib.keyword.any?)
            xml.front do
              xml.title @bib.title[0].title.content if @bib.title.any?
              render_authors xml
              render_date xml
              render_workgroup xml
              render_keyword xml if include_keywords
              render_abstract xml
            end
          end
          render_seriesinfo xml
          render_format xml
        end
      end

      #
      # Create reference attributes
      #
      # @return [Hash<Symbol=>String>] attributes
      #
      def ref_attrs # rubocop:disable Metrics/AbcSize
        discopes = %w[anchor docName number]
        attrs = @bib.docidentifier.each_with_object({}) do |di, h|
          next unless discopes.include?(di.scope)

          h[di.scope.to_sym] = di.id
        end
        return attrs if attrs.any?

        @bib.docidentifier.first&.tap do |di|
          anchor = di.type == "IANA" ? di.id.split[1..].join(" ").upcase : di.id
          return { anchor: anchor.gsub(" ", ".") }
        end
      end

      #
      # Render authors
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      #
      def render_authors(builder) # rubocop:disable Metrics/AbcSize
        @bib.contributor.each do |c|
          builder.author do |xml|
            xml.parent[:role] = "editor" if c.role.detect { |r| r.type == "editor" }
            if c.entity.is_a?(Person) then render_person xml, c.entity
            else render_organization xml, c.entity, c.role
            end
            render_address xml, c
          end
        end
      end

      #
      # Render address
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      # @param [RelatonBib::ContributionInfo] contrib contributor
      #
      def render_address(builder, contrib) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
        address, contact = address_contact contrib.entity.contact
        if address || contact.any?
          builder.address do |xml|
            # address = contrib.entity.contact.detect { |cn| cn.is_a? Address }
            if address
              xml.postal do
                xml.city address.city if address.city
                xml.code address.postcode if address.postcode
                xml.country address.country if address.country
                xml.region address.state if address.state
                xml.street address.street[0] if address.street.any?
              end
            end
            render_contact xml, contact
          end
        end
      end

      def address_contact(contact) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
        addr = contact.detect do |c|
          c.is_a?(Address) && (c.city || c.postcode || c.country || c.state || c.street.any?)
        end
        cont = contact.select { |c| c.is_a?(Contact) }
        [addr, cont]
      end

      #
      # Render contact
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      # @param [Array<RelatonBib::Address, RelatonBib::Contact>] addr contact
      #
      def render_contact(builder, addr)
        %w[phone email uri].each do |type|
          cont = addr.detect { |cn| cn.is_a?(Contact) && cn.type == type }
          builder.send type, cont.value if cont
        end
      end

      #
      # Render person
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      # @param [RelatonBib::Person] person person
      #
      def render_person(builder, person) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
        render_organization builder, person.affiliation.first&.organization
        if person.name.completename
          builder.parent[:fullname] = person.name.completename.content
        elsif person.name.forename.any?
          builder.parent[:fullname] = person.name.forename.map do |n|
            n.content || n.initial
          end.join " "
        end
        if person.name.initials
          builder.parent[:initials] = person.name.initials.content
        elsif person.name.forename.any?
          builder.parent[:initials] = person.name.forename.map do |f|
            "#{f.initial || f.content[0]}."
          end.join
        end
        if person.name.surname
          if !person.name.completename && person.name.forename.any? && person.name.surname
            builder.parent[:fullname] += " #{person.name.surname}"
          end
          builder.parent[:surname] = person.name.surname.content
        end
      end

      #
      # Render organization
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      # @param [RelatonBib::Organization] org organization
      #
      def render_organization(builder, org, _role = []) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize
        abbrev = org&.abbreviation&.content
        orgname = org&.name&.first&.content
        orgname = if BibXMLParser::ORGNAMES.key?(abbrev) then abbrev
                  else BibXMLParser::ORGNAMES.key(orgname) || orgname || abbrev
                  end
        # if role.detect { |r| r.description.detect { |d| d.content == "BibXML author" } }
        #   builder.parent[:fullname] = orgname
        # else
        org = builder.organization orgname
        org[:abbrev] = abbrev if abbrev
        # end
      end

      #
      # Render date
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      #
      def render_date(builder) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
        dt = @bib.date.detect { |d| d.type == "published" }
        return unless dt

        elm = builder.date
        y = dt.on(:year) || dt.from(:year) || dt.to(:year)
        elm[:year] = y if y
        m = dt.on(:month) || dt.from(:month) || dt.to(:month)
        elm[:month] = Date::MONTHNAMES[m] if m
        # rfcs = %w[RFC BCP FYI STD]
        # unless rfcs.include?(doctype) && docidentifier.detect { |di| rfcs.include? di.type }
        d = dt.on(:day) || dt.from(:day) || dt.to(:day)
        elm[:day] = d if d
        # end
      end

      #
      # Render workgroup
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      #
      def render_workgroup(builder)
        @bib.editorialgroup&.technical_committee&.each do |tc|
          builder.workgroup tc.workgroup.name
        end
      end

      #
      # Render keyword
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      #
      def render_keyword(builder)
        @bib.keyword.each { |kw| builder.keyword kw.content }
      end

      #
      # Render abstract
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      #
      def render_abstract(builder)
        return unless @bib.abstract.any?

        builder.abstract { |xml| xml << @bib.abstract[0].content.gsub(/(<\/?)p(>)/, '\1t\2') }
      end

      #
      # Render seriesinfo
      #
      # @param [Nokogiri::XML::Builder] builder xml builder
      #
      def render_seriesinfo(builder) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
        @bib.docidentifier.each do |di|
          if BibXMLParser::SERIESINFONAMES.include?(di.type) && di.scope != "trademark"
            builder.seriesInfo(name: di.type, value: di.id)
          end
        end
        # di_types = docidentifier.map(&:type)
        @bib.series.select do |s|
          s.title && # !di_types.include?(s.title.title.to_s) &&
            !BibXMLParser::SERIESINFONAMES.include?(s.title.title.to_s)
        end.uniq { |s| s.title.title.to_s }.each do |s|
          si = builder.seriesInfo(name: s.title.title.to_s)
          si[:value] = s.number if s.number
        end
      end

      def render_format(builder)
        @bib.link.select { |l| l.type == "TXT" }.each do |l|
          builder.format type: l.type, target: l.content
        end
      end
    end
  end
end