team-umlaut/umlaut

View on GitHub
app/service_adaptors/opac.rb

Summary

Maintainability
F
4 days
Test Coverage
# FIXME This service is not working
# For starters it needs the method service_types_generated()

class Opac < Service
  attr_reader :record_attributes, :display_name

  # The Opac class has a few assumptions
  # * You have some sort of bib client to get MARC records from your OPAC
  # * This operates independently of holdings data
  # * If your client can supply holdings information, you have the methods:
  # - init_holdings_client
  # - get_holdings
  #
  # The Opac class should be extended with your local connection client and
  # not called directly.
  
  def handle(request)
    # It's unlikely that consortial catalogs allow borrowing
    # of journals, but this may need to pushed to a client class
    
    if request.referent.format == 'journal' and @consortial
      return request.dispatched(self, true)
    end  
    @record_attributes = {}
    self.search_bib_data(request)
    if self.respond_to?(:init_holdings_client)
      opac_client = self.init_holdings_client
      opac_client.get_holdings(@record_attributes.keys)  
      self.check_holdings(opac_client.results, request)    
    else
      self.add_link_to_opac(client,request)
    end
    return request.dispatched(self, true)    
  end
  
  # Searches the bibliographic database
  def search_bib_data(request)    
    client = self.init_bib_client
    client.search_by_referent(request.referent)
    self.collect_record_attributes(client, request)    
  end
  
  # The client is expected to respond to <b>results</b> and return
  # an array of MARC records.  Override this method (and parse_for_fulltext,
  # collect_subjects, and enhance_referent) if your client does not return 
  # MARC.
  def collect_record_attributes(client, request) 
    require 'marc'    
    client.results.each do | record |
      MARC::XMLReader.new(StringIO.new(record.to_s)).each do | rec |
        id = nil
        
        # Assumes your local control number to be defined in 001
        rec.find_all { | f| '001' === f.tag}.each do | bibnum |
          id = bibnum.value
          @record_attributes[id] = {}
        end 
        
        # Conferences have their own crazy logic
        if self.is_conference?(rec)
          @record_attributes[id][:conference] = true
        else
          @record_attributes[id][:conference] = false
        end
        self.parse_for_fulltext_links(rec, request)              
        self.enhance_referent(rec, request, client.accuracy)
      end    
    end
  end  
  
  # Searches the MARC record for 856 tags, checks to see
  # if they seem to be digital representations and, if so,
  # adds them as fulltext service types
  def parse_for_fulltext_links(marc, request)    
    eight_fifty_sixes = []    
    marc.find_all { | f| '856' === f.tag}.each do | link |
      eight_fifty_sixes << link
    end    
    eight_fifty_sixes.each do | link |
      next if link.indicator2.match(/[28]/)
      next if link['u'].match(/(sfx\.galib\.uga\.edu)|(findit\.library\.gatech\.edu)/)
      label = (link['z']||'Electronic Access')
      request.add_service_response(
        :service=>self,
        :key=>label,
        :value_string=>link['u'],
        :service_type_value => 'fulltext'
        )
    end 
  end   
  
  # The client should be able to return an array of holdings objects
  def check_holdings(holdings, request)      
    return if holdings.empty?
    # These need to moved to services.yml
    electronic_locations = ['INTERNET', 'NETLIBRARY', 'GALILEO']
    holdings.each do | holding | 
      @record_attributes[holding.identifier.to_s][:holdings] = holding
      holding.locations.each do | location |
        next if electronic_locations.index(location.code)
        location.items.each do | item |         
          if request.referent.format == 'journal' and request.referent.metadata["volume"] and @record_attributes[holding.identifier.to_s][:conference] == false
            copy_match = false
            if item.enumeration
              if vol_match = item.enumeration.match(/VOL [A-z0-9\-]*/) 
                vol = vol_match[0]                
                vol.sub!(/^VOL\s*/, '')
                 (svol, evol) = vol.split('-')
                if request.referent.metadata["volume"] == svol
                  copy_match = true
                elsif evol
                  if request.referent.metadata["volume"] == evol
                    copy_match = true
                  elsif request.referent.metadata["volume"].to_i > svol.to_i and request.referent.metadata["volume"].to_i < evol.to_i                    
                    copy_match = true
                  end
                end
              end
            end
            if copy_match == true
              request.add_service_response(:service=>self,:key=>holding.identifier.to_s,:value_string=>location.name,:value_alt_string=>item.call_number,:value_text=>item.status.to_s, :service_type_value => 'holding')         
              break
            end                                           
          else  
            request.add_service_response(:service=>self,:key=>holding.identifier.to_s,:value_string=>location.name,:value_alt_string=>item.call_number,:value_text=>item.status.to_s,:service_type_value=>'holding')
          end                           
        end
      end
    end         
  end   
  
  # Check the leader to determine if this is defined as a conference or proceeding
  # in the MARC
  def is_conference?(marc)
    # Check the leader/008 for books and serials
    return true if marc['008'].value[29,1] == '1' && marc.leader[6,1].match(/[at]/) && marc.leader[7,1].match(/[abcdms]/)      
    # Check the leader/008 for scores and recordings
    return true if marc['008'].value[30,2] == 'c' && marc.leader[6,1].match(/[cdij]/) && marc.leader[7,1].match(/[abcdms]/)
    # Loop through the 006s
    marc.find_all {|f| ('006') === f.tag}.each { | fxd_fld |
      return true if fxd_fld.value[12,1] == '1' && fxd_fld.value[0,1].match(/[ats]{1}/)
      return true if fxd_fld.value[13,2]== 'c' && fxd_fld.value[0,1].match(/[cdij]{1}/)
    }      
    return false  
  end 
  

  # Determines the kind of record referred to in the MARC leader/fixed fields
  # (Book, Serial, Map, etc.)
  def record_type(marc)
    type = marc.leader[6,1]
    blvl = marc.leader[7,1]
    valid_types = ['a','t','g','k','r','o','p','e','f','c','d','i','j','m']
    rec_types = {
      'BKS' => { :type => /[at]{1}/,    :blvl => /[acdm]{1}/ },
      'SER' => { :type => /[a]{1}/,    :blvl => /[bs]{1}/ },
      'VIS' => { :type => /[gkro]{1}/,    :blvl => /[abcdms]{1}/ },
      'MIX' => { :type => /[p]{1}/,    :blvl => /[cd]{1}/ },
      'MAP' => { :type => /[ef]{1}/,    :blvl => /[abcdms]{1}/ },
      'SCO' => { :type => /[cd]{1}/,    :blvl => /[abcdms]{1}/ },
      'REC' => { :type => /[ij]{1}/,    :blvl => /[abcdms]{1}/ },
      'COM' => { :type => /[m]{1}/,    :blvl => /[abcdms]{1}/ }
    } 
    
    rec_types.each_key do | rec_type |
      return rec_type if type.match(rec_types[rec_type][:type]) and blvl.match(rec_types[rec_type][:blvl])
    end 
  end  
  
  # When given a ServiceResponse object as its argument, returns the anchor text and URL
  def to_fulltext(response)
    return {:display_text=>response.response_key, :url=>response.value_string}
  end
  
  # When given a ServiceResponse object as its argument, returns the object as a hash
  # with the keys:
  # - :display_text
  # - :call_number
  # - :status
  # - :source_name (which equates to self.display_name)
  def to_holding(response)
    return {:display_text=>response.value_string,:call_number=>response.value_alt_string,:status=>response.value_text,:source_name=>self.display_name}
  end 
  
  
  
  # When given a MARC record, this method fills in any missing
  # pieces of the Request.referent.  'accuracy' is a 3 point scale
  # determined by the client, 1 = 'possibly the correct resource', 2 = 
  # same resource, but not necessarily the same edition, 3 = exactly the 
  # same resource
  def enhance_referent(marc, request, accuracy)
    return unless accuracy > 2
    
    title_key = case request.referent.format
    when "book" then "btitle"
    when "journal" then "jtitle"
    when "dissertation" then "title"
    end
    metadata = request.referent.metadata
    unless metadata[title_key]
      if request.referent.metadata["title"] && title_key != "title"
        request.referent.enhance_referent(title_key, metadata["title"])
      else 
        request.referent.enhance_referent(title_key, marc['245'].value)
      end
    end
    unless metadata["au"]
      if marc['100'] && marc['100']['a']
        request.referent.enhance_referent('au', marc['100']['a'])
      end
    end
    unless metadata["aucorp"]
      if marc['110'] && marc['110']['a']
        request.referent.enhance_referent('aucorp', marc['110']['a'])
      end      
    end
    return unless accuracy > 3    
    unless metadata["place"]
      if marc['260'] && marc['260']['a']
        request.referent.enhance_referent('place', marc['260']['a'])
      end       
    end     
    unless metadata["pub"]
      if marc['260'] && marc['260']['b']
        request.referent.enhance_referent('pub', marc['260']['b'])
      end       
    end  
    unless metadata["edition"]  
      if marc['250'] && marc['250']['a']
        request.referent.enhance_referent('edition', marc['250']['a'])
      end       
    end 
    unless metadata["series"]
      if marc['490'] && marc['490']['a']
        request.referent.enhance_referent('series', marc['490']['a'])
      elsif marc['730'] && marc['730']['a']
        request.referent.enhance_referent('series', marc['730']['a'])        
      end        
    end 
    unless metadata["date"] or request.referent.format == 'journal'
      if marc['260'] && marc['260']['c']
        request.referent.enhance_referent('date', marc['260']['c'])   
      end   
    end 
    unless request.referent.format
      type = self.record_type(marc)
      request.referent.enhance_referent('format', 'book', false) if type == "BKS"
      request.referent.enhance_referent('format', 'journal', false) if type == "SER"
    end    
    unless metadata["genre"]
      if self.is_conference?(marc)
        if metadata["atitle"]
          request.referent.enhance_referent('genre', 'proceeding')
        else
          request.referent.enhance_referent('genre', 'conference')
        end
      elsif type = self.nature_of_contents(marc)
        case type
        when "dissertation" then request.referent.enhance_referent('format', 'dissertation', false)            
        when "patent" then request.referent.enhance_referent('format', 'patent', false)
        when "report" then request.referent.enhance_referent('genre', 'report')
        end
      else
        type = self.record_type(marc)
        if type == "BKS"
          request.referent.enhance_referent('format', 'book', false) unless request.referent.format == 'book'
          if metadata["atitle"]
            request.referent.enhance_referent('genre', 'bookpart')
          else
            request.referent.enhance_referent('genre', 'book')
          end
        elsif type == "SER"
          request.referent.enhance_referent('format', 'journal', false) unless request.referent.format == 'journal'        
          if metadata["atitle"]
            request.referent.enhance_referent('genre', 'article')
          elsif metadata["issue"]
            request.referent.enhance_referent('genre', 'issue')
          else
            request.referent.enhance_referent('genre', 'journal')
          end
        end
      end
    end
    
    unless metadata["isbn"]
      if marc['020'] && marc['020']['a']
        request.referent.enhance_referent('isbn', marc['020']['a'])
      end
    end   
    unless metadata["issn"]
      if marc['022'] && marc['022']['a']
        request.referent.enhance_referent('issn', marc['022']['a'])
      end    
    end 
    unless metadata["sici"]
      if marc['024'] && marc['024'].indicator1 == "4"
        request.referent.enhance_referent('sici', marc['024']['a'])      
      end       
    end
    
    unless metadata["coden"]
      if marc['030'] && marc['030']['a']
        request.referent.enhance_referent('coden', marc['030']['a'])      
      end       
    end    
  end 
  
  # When given a MARC record, determines if the referent should be
  # defined as a dissertation, report or patent.
  def nature_of_contents(marc)
    types = {'m'=>'dissertation','t'=>'report','j'=>'patent'}
    idx = nil
    if self.record_type(marc) == 'BKS'
      idx = 24
      len = 4
    elsif self.record_type(marc) == 'SER'
      idx = 25
      len = 3
    end
    if idx
      marc['008'].value[idx,len].split(//).each do | char | 
        return types[char] if types.keys.index(char)
      end
    end
    marc.find_all {|f| ('006') === f.tag}.each do | fxd_fld |
      idx = nil
      if fxd_fld.value[0,1].match(/[at]{1}/)
        idx = 7
        len = 4
      elsif fxd_fld.value[0,1].match('s')
        idx = 8
        len = 3
      end     
      if idx 
        fxd_fld.value[idx,len].split(//).each do | char | 
          return types[char] if types.keys.index(char)
        end
      end  
    end        
    return false      
  end    
  
  # Builds a URL either based on a URL in the value_string or builds a link to the 
  # OPAC using the direct_link_arg and the bib ID.
  def response_url(service_type, http_params)
    response service_type.service_response
    return CGI.unescapeHTML(response.value_string) if response.value_string.match(/^http(s)?:\/\//)
    return @url+'&'+@direct_link_arg+response.response_key          
  end
end