# encoding: UTF-8 # Thomson Reuters Links Article Match Retrieval Service # # Can generate links to "cited by" and "similar articles" pages for a specific # article in Web of Knowledge. # # Can also generate links to the Journal Citation Reports (JCR) page for a given # journal title. # # REQUIREMENTS: You must be an ISI customer if you want these links to actually # work for your users. Off-campus users should be sent through EZProxy, see # the EZProxy plug-in. # # You need to register for the the Thomson 'Links Article Match Retrieval' # (LAMR) service api, which is used here. To register, see: # http://wokinfo.com/products_tools/products/related/amr/ # # You register by IP address, so no API key is needed once your registration # goes through. # # If you later need to change the IP addresses entitled to use this API, use # http://scientific.thomson.com/scientific/techsupport/cpe/form.html. # to request a change. # # File support/outage tickets at http://ip-science.thomsonreuters.com/support/ # # Note, as of 13 april 09, there's a bug in the API where journal titles including # ampersands cause an error. We will catch those errors and output a 'warning' # instead of an 'error', since it's a known problem. class TrLinks < Service require 'open-uri' require 'nokogiri' require 'net/http' require 'builder' include MetadataHelper def service_types_generated types = [] types << ServiceTypeValue[:cited_by] if (@include_cited_by || @include_similar) types << ServiceTypeValue[:highlighted_link] if @include_jcr return types end def initialize(config) #defaults @wos_app_name = "Umlaut" @wok_display_name = "Web of Knowledge\xc2\xae" # trademark symbol @display_name = @wok_display_name @api_url = "https://ws.isiknowledge.com/cps/xrpc" @include_cited_by = true @include_similar = true @include_jcr = true @credits = { @wok_display_name => "http://apps.webofknowledge.com" } super(config) end def handle(request) unless ( sufficient_metadata?(request.referent)) return request.dispatched(self, true) end xml = gen_lamr_request(request) isi_response = do_lamr_request(xml) begin #raise if it's an error HTTP response isi_response.value response_xml = Nokogiri::XML(isi_response.body) # Check for errors. if (error = (response_xml.at('val[@name = "error"]') || response_xml.at("error") || response_xml.at('null[@name = "error"]'))) raise IsiResponseException.new("ISI service reported error: #{error.inner_text}") end add_responses( request, response_xml ) rescue IsiResponseException => e # Is this the known problem with ampersands? # if so, output a warning, but report success not exception, # because this is a known condition. metadata = request.referent.metadata if ( (metadata["title"] && metadata["title"].include?('&')) || (metadata["jtitle"] && metadata['jtitle'].include?('&'))) Rails.logger.warn("ISI LAMR still exhibiting ampersand problems: #{e.message} ; OpenURL: ?#{request.to_context_object.kev}") return request.dispatched(self, true) else # Log the error, return exception condition. error_parts = [] error_parts << "ISI URI: #{@api_url}" error_parts << "Request XML: #{xml}" error_parts << "ISI Response body: #{response_xml}" Rails.logger.error("#{e.message} ; " + error_parts.join("\n ") ) return request.dispatched(self, false, e) end end return request.dispatched(self, true) end # A DOI is always sufficient. Otherwise, it gets complicated because the ISI # service is kind of picky in weird ways. ISSN alone is not enough, we need # jtitle. Once you have jtitle, Vol/issue/start page are often enough, but # article title really helps, and jtitle+atitle+year is often enough too. def sufficient_metadata?(referent) metadata = referent.metadata return get_doi(referent) || get_pmid(referent) || ( ( metadata['jtitle'] || metadata['title'] ) && (! (metadata['atitle'].blank? || metadata['date'].blank? ) || ! ( metadata['volume'].blank? || metadata['issue'].blank? || metadata['spage'].blank?)) ) end # produces XML to be posted to Thomson 'Links Article Match Retrieval' service api. def gen_lamr_request(request) output = "" builder = Builder::XmlMarkup.new(:target => output, :indent => 2) builder.instruct!(:xml, :encoding => "UTF-8") builder.request(:xmlns => "http://www.isinet.com/xrpc41", :src => "app.id=Umlaut") do builder.fn(:name => "LinksAMR.retrieve") do builder.list do # first map is authentication info. empty 'map' element since we are IP authenticated. builder.map # specify what we're requesting builder.map do builder.list(:name=>"WOS") do builder.val("timesCited") builder.val("ut") builder.val("doi") builder.val("sourceURL") builder.val("citingArticlesURL") builder.val("relatedRecordsURL") end end # specify our query builder.map do builder.map(:name => "cite_id") do # Here's the tricky part, depends on what we've got. metadata = request.referent.metadata # DOI if ( doi = get_doi(request.referent)) builder.val(doi, :name => "doi") end if ( pmid = get_pmid(request.referent)) builder.val(pmid, :name => "pmid") end # Journal title is crucial for ISI -- ISSN alone is # not enough, weirdly! if ( ! metadata['jtitle'].blank? ) builder.val(metadata['jtitle'], :name => "stitle" ) else builder.val(metadata['title'], :name => 'stitle' ) end # ISSN, not actually used much by ISI, but can't hurt. if ( issn = request.referent.issn ) # ISSN _needs_ a hyphen for WoS, bah! unless issn.match( /\-/ ) issn = issn[0,4] + '-' + issn[4,7] end builder.val(issn, :name => "issn") end # article title often helpful. unless ( metadata['atitle'].blank?) builder.val( metadata['atitle'], :name => "atitle") end # year unless ( metadata['date'].blank?) #first four digits are year builder.val( metadata["date"][0,4], :name => "year" ) end # Vol/issue/page. Oddly, issue isn't used very strongly # by ISI, but can't hurt. unless ( metadata['volume'].blank? ) builder.val(metadata['volume'], :name => 'vol') end unless ( metadata['issue'].blank? ) builder.val( metadata['issue'] , :name => 'issue') end unless ( metadata['spage'].blank? ) builder.val(metadata['spage'], :name => 'spage ') end end end end end end return output end def do_lamr_request(xml) uri = URI.parse(@api_url) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if (uri.scheme == 'https') headers = {'Content-Type' => 'application/xml'} return http.post(uri.request_uri, xml, headers) end def add_responses(request, response_xml) results = response_xml.at('map[@name ="cite_id"] map[@name="WOS"]') unless (results) error_message = "#{self.id}: " error_message << 'Unexpected ISI response. The ISI response was not reported as an error, but did not contain a inside a as we expected it to:' error_message << "\n ISI XML request:\n#{gen_lamr_request(request)}\n" error_message << "\n ISI http response status: #{isi_response.code}\n" error_message << "\n ISI http response body:\n#{isi_response.body}\n" Rails.logger.error(error_message) end # cited by count = results.at('val[@name="timesCited"]') count = count ? count.inner_text.to_i : 0 cited_by_url = results.at('val[@name="citingArticlesURL"]') cited_by_url = cited_by_url.inner_text if cited_by_url if (@include_cited_by && count > 0 && cited_by_url ) label = ServiceTypeValue[:cited_by].display_name_pluralize.downcase.capitalize if count && count == 1 label = ServiceTypeValue[:cited_by].display_name.downcase.capitalize end request.add_service_response(:service=>self, :display_text => "#{count} #{label}", :count=> count, :url => cited_by_url, :debug_info => "url: " + cited_by_url, :service_type_value => :cited_by) end # similar similar_url = results.at('val[@name ="relatedRecordsURL"]') similar_url = similar_url.inner_text if similar_url if (@include_similar && similar_url ) request.add_service_response( :service=>self, :display_text => " #{ServiceTypeValue[:similar].display_name_pluralize.downcase.capitalize}", :url => similar_url, :debug_info => "url: " + similar_url, :service_type_value => :similar) end end end class IsiResponseException < StandardError end