qa 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +284 -2
  3. data/app/controllers/qa/linked_data_terms_controller.rb +127 -0
  4. data/config/authorities/linked_data/agrovoc.json +61 -0
  5. data/config/authorities/linked_data/loc.json +46 -0
  6. data/config/authorities/linked_data/oclc_fast.json +78 -0
  7. data/config/initializers/linked_data_authorities.rb +17 -0
  8. data/config/routes.rb +3 -0
  9. data/lib/qa.rb +15 -0
  10. data/lib/qa/authorities.rb +2 -0
  11. data/lib/qa/authorities/base.rb +1 -1
  12. data/lib/qa/authorities/crossref.rb +16 -0
  13. data/lib/qa/authorities/crossref/generic_authority.rb +63 -0
  14. data/lib/qa/authorities/geonames.rb +2 -1
  15. data/lib/qa/authorities/linked_data.rb +10 -0
  16. data/lib/qa/authorities/linked_data/config.rb +80 -0
  17. data/lib/qa/authorities/linked_data/config/search_config.rb +170 -0
  18. data/lib/qa/authorities/linked_data/config/term_config.rb +186 -0
  19. data/lib/qa/authorities/linked_data/find_term.rb +148 -0
  20. data/lib/qa/authorities/linked_data/generic_authority.rb +49 -0
  21. data/lib/qa/authorities/linked_data/rdf_helper.rb +102 -0
  22. data/lib/qa/authorities/linked_data/search_query.rb +143 -0
  23. data/lib/qa/version.rb +1 -1
  24. data/spec/controllers/linked_data_terms_controller_spec.rb +202 -0
  25. data/spec/fixtures/authorities/linked_data/lod_full_config.json +111 -0
  26. data/spec/fixtures/authorities/linked_data/lod_lang_defaults.json +54 -0
  27. data/spec/fixtures/authorities/linked_data/lod_lang_multi_defaults.json +54 -0
  28. data/spec/fixtures/authorities/linked_data/lod_lang_no_defaults.json +52 -0
  29. data/spec/fixtures/authorities/linked_data/lod_lang_param.json +66 -0
  30. data/spec/fixtures/authorities/linked_data/lod_min_config.json +49 -0
  31. data/spec/fixtures/authorities/linked_data/lod_search_only_config.json +55 -0
  32. data/spec/fixtures/authorities/linked_data/lod_sort.json +27 -0
  33. data/spec/fixtures/authorities/linked_data/lod_term_only_config.json +59 -0
  34. data/spec/fixtures/funders-find-response.json +1 -0
  35. data/spec/fixtures/funders-noquery.json +1 -0
  36. data/spec/fixtures/funders-noresults.json +1 -0
  37. data/spec/fixtures/funders-result.json +1 -0
  38. data/spec/fixtures/journals-find-response-two-issn.json +1 -0
  39. data/spec/fixtures/journals-find-response.json +1 -0
  40. data/spec/fixtures/journals-noquery.json +1 -0
  41. data/spec/fixtures/journals-noresults.json +1 -0
  42. data/spec/fixtures/journals-result.json +705 -0
  43. data/spec/fixtures/lod_agrovoc_query_many_results.json +1 -0
  44. data/spec/fixtures/lod_agrovoc_query_no_results.json +1 -0
  45. data/spec/fixtures/lod_agrovoc_term_found.rdf.xml +217 -0
  46. data/spec/fixtures/lod_lang_search_en.rdf.xml +42 -0
  47. data/spec/fixtures/lod_lang_search_enfr.rdf.xml +48 -0
  48. data/spec/fixtures/lod_lang_search_enfrde.rdf.xml +54 -0
  49. data/spec/fixtures/lod_lang_search_fr.rdf.xml +42 -0
  50. data/spec/fixtures/lod_lang_term_en.rdf.xml +65 -0
  51. data/spec/fixtures/lod_lang_term_enfr.rdf.xml +71 -0
  52. data/spec/fixtures/lod_lang_term_enfr_noalt.rdf.xml +69 -0
  53. data/spec/fixtures/lod_lang_term_enfrde.rdf.xml +79 -0
  54. data/spec/fixtures/lod_lang_term_fr.rdf.xml +65 -0
  55. data/spec/fixtures/lod_loc_term_found.rdf.xml +262 -0
  56. data/spec/fixtures/lod_oclc_all_query_3_results.rdf.xml +142 -0
  57. data/spec/fixtures/lod_oclc_personalName_query_3_results.rdf.xml +128 -0
  58. data/spec/fixtures/lod_oclc_query_no_results.rdf.xml +13 -0
  59. data/spec/fixtures/lod_oclc_term_found.rdf.xml +51 -0
  60. data/spec/lib/authorities/crossref_spec.rb +180 -0
  61. data/spec/lib/authorities/geonames_spec.rb +2 -2
  62. data/spec/lib/authorities/linked_data/config_spec.rb +143 -0
  63. data/spec/lib/authorities/linked_data/find_term_spec.rb +5 -0
  64. data/spec/lib/authorities/linked_data/generic_authority_spec.rb +580 -0
  65. data/spec/lib/authorities/linked_data/search_config_spec.rb +385 -0
  66. data/spec/lib/authorities/linked_data/search_query_spec.rb +79 -0
  67. data/spec/lib/authorities/linked_data/term_config_spec.rb +419 -0
  68. data/spec/routing/linked_data_route_spec.rb +35 -0
  69. data/spec/spec_helper.rb +2 -0
  70. metadata +184 -39
@@ -0,0 +1,46 @@
1
+ {
2
+ "term": {
3
+ "url": {
4
+ "@context": "http://www.w3.org/ns/hydra/context.jsonld",
5
+ "@type": "IriTemplate",
6
+ "template": "http://id.loc.gov/authorities/{?subauth}/{?term_id}",
7
+ "variableRepresentation": "BasicRepresentation",
8
+ "mapping": [
9
+ {
10
+ "@type": "IriTemplateMapping",
11
+ "variable": "term_id",
12
+ "property": "hydra:freetextQuery",
13
+ "required": true
14
+ },
15
+ {
16
+ "@type": "IriTemplateMapping",
17
+ "variable": "subauth",
18
+ "property": "hydra:freetextQuery",
19
+ "required": false,
20
+ "default": "names"
21
+ }
22
+ ]
23
+ },
24
+ "qa_replacement_patterns": {
25
+ "term_id": "term_id",
26
+ "subauth": "subauth"
27
+ },
28
+ "term_id": "ID",
29
+ "language": ["en"],
30
+ "results": {
31
+ "id_predicate": "http://id.loc.gov/vocabulary/identifiers/lccn",
32
+ "label_predicate": "http://www.w3.org/2004/02/skos/core#prefLabel",
33
+ "altlabel_predicate": "http://www.w3.org/2004/02/skos/core#altLabel",
34
+ "sameas_predicate": "http://www.w3.org/2004/02/skos/core#exactMatch"
35
+ },
36
+ "subauthorities": {
37
+ "subjects": "subjects",
38
+ "names": "names",
39
+ "classification": "classification",
40
+ "child_subject": "childrensSubjects",
41
+ "genre": "genreForms",
42
+ "demographic": "demographicTerms"
43
+ }
44
+ },
45
+ "search": {}
46
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "term": {
3
+ "url": {
4
+ "@context": "http://www.w3.org/ns/hydra/context.jsonld",
5
+ "@type": "IriTemplate",
6
+ "template": "http://id.worldcat.org/fast/{?term_id}",
7
+ "variableRepresentation": "BasicRepresentation",
8
+ "mapping": [
9
+ {
10
+ "@type": "IriTemplateMapping",
11
+ "variable": "term_id",
12
+ "property": "hydra:freetextQuery",
13
+ "required": true
14
+ }
15
+ ]
16
+ },
17
+ "qa_replacement_patterns": {
18
+ "term_id": "term_id"
19
+ },
20
+ "term_id": "ID",
21
+ "results": {
22
+ "id_predicate": "http://purl.org/dc/terms/identifier",
23
+ "label_predicate": "http://www.w3.org/2004/02/skos/core#prefLabel",
24
+ "altlabel_predicate": "http://www.w3.org/2004/02/skos/core#altLabel",
25
+ "sameas_predicate": "http://schema.org/sameAs"
26
+ }
27
+ },
28
+ "search": {
29
+ "url": {
30
+ "@context": "http://www.w3.org/ns/hydra/context.jsonld",
31
+ "@type": "IriTemplate",
32
+ "template": "http://experimental.worldcat.org/fast/search?query={?subauth}+all+%22{?query}%22&sortKeys=usage&maximumRecords={?maximumRecords}",
33
+ "variableRepresentation": "BasicRepresentation",
34
+ "mapping": [
35
+ {
36
+ "@type": "IriTemplateMapping",
37
+ "variable": "query",
38
+ "property": "hydra:freetextQuery",
39
+ "required": true
40
+ },
41
+ {
42
+ "@type": "IriTemplateMapping",
43
+ "variable": "subauth",
44
+ "property": "hydra:freetextQuery",
45
+ "required": false,
46
+ "default": "cql.any"
47
+ },
48
+ {
49
+ "@type": "IriTemplateMapping",
50
+ "variable": "maximumRecords",
51
+ "property": "hydra:freetextQuery",
52
+ "required": false,
53
+ "default": "20"
54
+ }
55
+ ]
56
+ },
57
+ "qa_replacement_patterns": {
58
+ "query": "query",
59
+ "subauth": "subauth"
60
+ },
61
+ "results": {
62
+ "id_predicate": "http://purl.org/dc/terms/identifier",
63
+ "label_predicate": "http://www.w3.org/2004/02/skos/core#prefLabel",
64
+ "sort_predicate": "http://www.w3.org/2004/02/skos/core#prefLabel"
65
+ },
66
+ "subauthorities": {
67
+ "topic": "oclc.topic",
68
+ "geographic": "oclc.geographic",
69
+ "event_name": "oclc.eventName",
70
+ "personal_name": "oclc.personalName",
71
+ "corporate_name": "oclc.corporateName",
72
+ "uniform_title": "oclc.uniformTitle",
73
+ "period": "oclc.period",
74
+ "form": "oclc.form",
75
+ "alt_lc": "oclc.altlc"
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,17 @@
1
+ auth_cfg = {}
2
+ # load QA configured linked data authorities
3
+ Dir[File.join(Qa::Engine.root, 'config', 'authorities', 'linked_data', '*.json')].each do |fn|
4
+ auth = File.basename(fn, '.json').upcase.to_sym
5
+ json = File.read(File.expand_path(fn, __FILE__))
6
+ cfg = JSON.parse(json).deep_symbolize_keys
7
+ auth_cfg[auth] = cfg
8
+ end
9
+
10
+ # load app configured linked data authorities and overrides
11
+ Dir[File.join(Rails.root, 'config', 'authorities', 'linked_data', '*.json')].each do |fn|
12
+ auth = File.basename(fn, '.json').upcase.to_sym
13
+ json = File.read(File.expand_path(fn, __FILE__))
14
+ cfg = JSON.parse(json).deep_symbolize_keys
15
+ auth_cfg[auth] = cfg
16
+ end
17
+ LINKED_DATA_AUTHORITIES_CONFIG = auth_cfg.freeze
@@ -1,4 +1,7 @@
1
1
  Qa::Engine.routes.draw do
2
+ get "/search/linked_data/:vocab(/:subauthority)", controller: :linked_data_terms, action: :search
3
+ get "/show/linked_data/:vocab/:id", controller: :linked_data_terms, action: :show
4
+ get "/show/linked_data/:vocab/:subauthority/:id", controller: :linked_data_terms, action: :show
2
5
  get "/terms/:vocab(/:subauthority)", controller: :terms, action: :index
3
6
  get "/search/:vocab(/:subauthority)", controller: :terms, action: :search
4
7
  get "/show/:vocab/:id", controller: :terms, action: :show
data/lib/qa.rb CHANGED
@@ -16,4 +16,19 @@ module Qa
16
16
 
17
17
  # Raised when a subauthority is not valid
18
18
  class InvalidSubAuthority < ArgumentError; end
19
+
20
+ # Raised when a request is made to a non-configured linked data authority
21
+ class InvalidLinkedDataAuthority < ArgumentError; end
22
+
23
+ # Raised when a response is in an unsupported format
24
+ class UnsupportedFormat < ArgumentError; end
25
+
26
+ # Raised when a configuration parameter is incorrect or is required and missing
27
+ class InvalidConfiguration < ArgumentError; end
28
+
29
+ # Raised when a linked data request to a server returns a 500 error
30
+ class ServiceUnavailable < ArgumentError; end
31
+
32
+ # Raised when the server returns 404 for a find term request
33
+ class TermNotFound < ArgumentError; end
19
34
  end
@@ -19,4 +19,6 @@ module Qa::Authorities
19
19
  autoload :WebServiceBase
20
20
  autoload :AssignFast
21
21
  autoload :AssignFastSubauthority
22
+ autoload :Crossref
23
+ autoload :LinkedData
22
24
  end
@@ -31,7 +31,7 @@ module Qa::Authorities
31
31
  #
32
32
  # @todo better specify return type
33
33
  def find(_id)
34
- raise NotImplementedError, "#{self.class}#all is unimplemented."
34
+ raise NotImplementedError, "#{self.class}#find is unimplemented."
35
35
  end
36
36
 
37
37
  ##
@@ -0,0 +1,16 @@
1
+ require 'uri'
2
+ module Qa::Authorities
3
+ module Crossref
4
+ require 'qa/authorities/crossref/generic_authority'
5
+ extend AuthorityWithSubAuthority
6
+
7
+ def self.subauthority_for(subauthority)
8
+ validate_subauthority!(subauthority)
9
+ GenericAuthority.new(subauthority)
10
+ end
11
+
12
+ def self.subauthorities
13
+ ['funders', 'journals']
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,63 @@
1
+ module Qa::Authorities
2
+ class Crossref::GenericAuthority < Base
3
+ include WebServiceBase
4
+ class_attribute :label, :identifier
5
+ attr_reader :subauthority
6
+
7
+ def initialize(subauthority)
8
+ @subauthority = subauthority
9
+ end
10
+
11
+ # Create a label from the crossref result hash
12
+ #
13
+ # @param item [Hash] the crossref result
14
+ # @return [String] a label combining the name, alt-names and location
15
+ self.label = lambda do |item|
16
+ [item['name'],
17
+ item['alt-names'].blank? ? nil : "(#{item['alt-names'].join(', ')})",
18
+ item['location']].compact.join(', ')
19
+ end
20
+
21
+ def search(q)
22
+ parse_authority_response(json(build_query_url(q)))
23
+ end
24
+
25
+ def build_query_url(q)
26
+ query = URI.escape(untaint(q))
27
+ "http://api.crossref.org/#{subauthority}?query=#{query}"
28
+ end
29
+
30
+ def untaint(q)
31
+ q.gsub(/[^\w\s-]/, '')
32
+ end
33
+
34
+ def find(id)
35
+ json(find_url(id))
36
+ end
37
+
38
+ def find_url(id)
39
+ "http://api.crossref.org/#{subauthority}/#{id}"
40
+ end
41
+
42
+ private
43
+
44
+ # Reformats the data received from the service
45
+ def parse_authority_response(response)
46
+ response['message']['items'].map do |result|
47
+ case subauthority
48
+ when 'funders'
49
+ { id: result['id'],
50
+ uri: result['uri'],
51
+ label: label.call(result),
52
+ value: result['name'] }
53
+ when 'journals'
54
+ { id: result['ISSN'].first,
55
+ label: result['title'],
56
+ publisher: result['publisher'],
57
+ issn: result['ISSN'] }
58
+
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -38,7 +38,8 @@ module Qa::Authorities
38
38
  # Reformats the data received from the service
39
39
  def parse_authority_response(response)
40
40
  response['geonames'].map do |result|
41
- { 'id' => "http://sws.geonames.org/#{result['geonameId']}",
41
+ # Note: the trailing slash is meaningful.
42
+ { 'id' => "http://sws.geonames.org/#{result['geonameId']}/",
42
43
  'label' => label.call(result) }
43
44
  end
44
45
  end
@@ -0,0 +1,10 @@
1
+ module Qa::Authorities
2
+ module LinkedData
3
+ extend ActiveSupport::Autoload
4
+ autoload :GenericAuthority
5
+ autoload :RdfHelper
6
+ autoload :SearchQuery
7
+ autoload :FindTerm
8
+ autoload :Config
9
+ end
10
+ end
@@ -0,0 +1,80 @@
1
+ require 'qa/authorities/linked_data/config/term_config'.freeze
2
+ require 'qa/authorities/linked_data/config/search_config'.freeze
3
+ require 'json'
4
+
5
+ # Provide attr_reader methods for linked data authority configurations. Some default configurations are provided for several
6
+ # linked data authorities and can be found at /config/authorities/linked_data. You can add configurations for new authorities by
7
+ # adding the configuration at YOUR_APP/config/authorities/linked_data. You can modify a QA provided configuration by copying
8
+ # it to YOUR_APP/config/authorities/linked_data and making the modifications there. See README for more information on the
9
+ # structure of the configuration.
10
+ #
11
+ # This configuration processed by this class is used by Qa::Authorities::LinkedData::GenericAuthority to drive url construction
12
+ # and results processing for a specific linked data authority.
13
+ #
14
+ # @see Qa::Authorities::LinkedData::GenericAuthority#initialize
15
+ # @see Qa::Authorities::LinkedData::TermConfig
16
+ # @see Qa::Authorities::LinkedData::SearchConfig
17
+ module Qa::Authorities
18
+ module LinkedData
19
+ class Config
20
+ attr_reader :authority_name
21
+ attr_reader :authority_config
22
+
23
+ # Initialize to hold the configuration for the specifed authority. Configurations are defined in config/authorities/linked_data. See README for more information.
24
+ # @param [String] the name of the configuration file for the authority
25
+ # @return [Qa::Authorities::LinkedData::Config] instance of this class
26
+ def initialize(auth_name)
27
+ @authority_name = auth_name
28
+ auth_config
29
+ end
30
+
31
+ def search
32
+ @search ||= Qa::Authorities::LinkedData::SearchConfig.new(auth_config.fetch(:search))
33
+ end
34
+
35
+ def term
36
+ @term ||= Qa::Authorities::LinkedData::TermConfig.new(auth_config.fetch(:term))
37
+ end
38
+
39
+ # Return the full configuration for an authority
40
+ # @return [String] the authority configuration
41
+ def auth_config
42
+ @authority_config ||= LINKED_DATA_AUTHORITIES_CONFIG[@authority_name]
43
+ raise Qa::InvalidLinkedDataAuthority, "Unable to initialize linked data authority '#{@authority_name}'" if @authority_config.nil?
44
+ @authority_config
45
+ end
46
+
47
+ def self.config_value(config, key)
48
+ return nil if config.nil? || !(config.key? key)
49
+ config[key]
50
+ end
51
+
52
+ def self.predicate_uri(config, key)
53
+ pred = config_value(config, key)
54
+ pred_uri = nil
55
+ pred_uri = RDF::URI(pred) unless pred.nil? || pred.length <= 0
56
+ pred_uri
57
+ end
58
+
59
+ def self.replace_pattern(url, pattern, value)
60
+ url.gsub("{?#{pattern}}", value)
61
+ end
62
+
63
+ def self.process_subauthority(url, subauth_pattern, subauthorities, subauth_key)
64
+ pattern = subauth_pattern[:pattern]
65
+ value = subauthorities[subauth_key] || subauth_pattern[:default]
66
+ replace_pattern(url, pattern, value)
67
+ end
68
+
69
+ def self.apply_replacements(url, config, replacements = {})
70
+ return url unless config.size.positive?
71
+ config.each do |param_key, rep_pattern|
72
+ s_param_key = param_key.to_s
73
+ value = replacements[param_key] || replacements[s_param_key] || rep_pattern[:default]
74
+ url = replace_pattern(url, param_key, value)
75
+ end
76
+ url
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,170 @@
1
+ # Provide attr_reader methods specific to search configuration for linked data authority configurations. This is separated
2
+ # out for readability and file length.
3
+ # @see Qa::Authorities::LinkedData::Config
4
+ # @see Qa::Authorities::LinkedData::TermConfig
5
+ module Qa::Authorities
6
+ module LinkedData
7
+ class SearchConfig
8
+ # @param [Hash] config the search portion of the config
9
+ def initialize(config)
10
+ @search_config = config
11
+ end
12
+
13
+ attr_reader :search_config
14
+ private :search_config
15
+
16
+ # Does this authority configuration have search defined?
17
+ # @return [Boolean] true if search is configured; otherwise, false
18
+ def supports_search?
19
+ search_config.present?
20
+ end
21
+
22
+ # Return search url encoding defined in the configuration for this authority
23
+ # if it was provided
24
+ # @return [Hash,NilClass] the configured search url
25
+ def url
26
+ search_config[:url]
27
+ end
28
+
29
+ # Return search url template defined in the configuration for this authority.
30
+ # @return [String] the configured search url template
31
+ def url_template
32
+ url.fetch(:template)
33
+ end
34
+
35
+ # Return search url parameter mapping defined in the configuration for this authority.
36
+ # @return [Hash] the configured search url parameter mappings with variable name as key
37
+ def url_mappings
38
+ return @url_mappings unless @url_mappings.nil?
39
+ mappings = Config.config_value(url, :mapping)
40
+ return {} if mappings.nil?
41
+ Hash[*mappings.collect { |m| [m[:variable].to_sym, m] }.flatten]
42
+ end
43
+
44
+ # Return the preferred language for literal value selection for search query.
45
+ # Only applies if the authority provides language encoded literals.
46
+ # @return [String] the configured language for search query
47
+ def language
48
+ return @language unless @language.nil?
49
+ lang = search_config[:language]
50
+ return nil if lang.nil?
51
+ lang = [lang] if lang.is_a? String
52
+ @language = lang.collect(&:to_sym)
53
+ end
54
+
55
+ # Return results predicates if specified
56
+ # @return [Hash,NilClass] all the configured predicates to pull out of the results
57
+ def results
58
+ search_config[:results]
59
+ end
60
+
61
+ # Return results id_predicate
62
+ # @return [String] the configured predicate to use to extract the id from the results
63
+ def results_id_predicate
64
+ Config.predicate_uri(results, :id_predicate)
65
+ end
66
+
67
+ # Return results label_predicate
68
+ # @return [String] the configured predicate to use to extract label values from the results
69
+ def results_label_predicate
70
+ Config.predicate_uri(results, :label_predicate)
71
+ end
72
+
73
+ # Return results altlabel_predicate
74
+ # @return [String] the configured predicate to use to extract altlabel values from the results
75
+ def results_altlabel_predicate
76
+ Config.predicate_uri(results, :altlabel_predicate)
77
+ end
78
+
79
+ # Does this authority configuration support sorting of search results?
80
+ # @return [True|False] true if sorting of search results is supported; otherwise, false
81
+ def supports_sort?
82
+ return true unless results_sort_predicate.nil? || !results_sort_predicate.size.positive?
83
+ false
84
+ end
85
+
86
+ # Return results sort_predicate
87
+ # @return [String] the configured predicate to use for sorting results from the query search
88
+ def results_sort_predicate
89
+ Config.predicate_uri(results, :sort_predicate)
90
+ end
91
+
92
+ # Return parameters that are required for QA api
93
+ # @return [Hash] the configured search url parameter mappings
94
+ def qa_replacement_patterns
95
+ search_config.fetch(:qa_replacement_patterns)
96
+ end
97
+
98
+ # Are there replacement parameters configured for search query?
99
+ # @return [True|False] true if there are replacement parameters configured for search query; otherwise, false
100
+ def replacements?
101
+ replacement_count.positive?
102
+ end
103
+
104
+ # Return the number of possible replacement values to make in the search URL
105
+ # @return [Integer] the configured number of possible replacements in the search url
106
+ def replacement_count
107
+ replacements.size
108
+ end
109
+
110
+ # Return the replacement configurations
111
+ # @return [Hash] the configurations for search url replacements
112
+ def replacements
113
+ return @replacements unless @replacements.nil?
114
+ @replacements = {}
115
+ @replacements = url_mappings.select { |k, _v| !qa_replacement_patterns.include?(k) } unless search_config.nil? || url_mappings.nil?
116
+ @replacements
117
+ end
118
+
119
+ # Are there subauthorities configured for search query?
120
+ # @return [True|False] true if there are subauthorities configured for search query; otherwise, false
121
+ def subauthorities?
122
+ subauthority_count.positive?
123
+ end
124
+
125
+ # Is a specific subauthority configured for search query?
126
+ # @return [True|False] true if the specified subauthority is configured for search query; otherwise, false
127
+ def subauthority?(subauth_name)
128
+ subauth_name = subauth_name.to_sym if subauth_name.is_a? String
129
+ subauthorities.key? subauth_name
130
+ end
131
+
132
+ # Return the number of subauthorities defined for search query
133
+ # @return [Integer] the number of subauthorities defined for search query
134
+ def subauthority_count
135
+ subauthorities.size
136
+ end
137
+
138
+ # Return the list of subauthorities for search query
139
+ # @return [Hash] the configurations for search url replacements
140
+ def subauthorities
141
+ @subauthorities ||= {} if search_config.nil? || !(search_config.key? :subauthorities)
142
+ @subauthorities ||= search_config.fetch(:subauthorities)
143
+ end
144
+
145
+ # Return the replacement configurations
146
+ # @return [Hash] the configurations for search url replacements
147
+ def subauthority_replacement_pattern
148
+ return {} unless subauthorities?
149
+ @subauthority_replacement_pattern ||= {} if search_config.nil? || !subauthorities?
150
+ pattern = qa_replacement_patterns[:subauth]
151
+ default = url_mappings[pattern.to_sym][:default]
152
+ @subauthority_replacement_pattern ||= { pattern: pattern, default: default }
153
+ end
154
+
155
+ # Build a linked data authority search url
156
+ # @param [String] the query
157
+ # @param [String] (optional) subauthority key
158
+ # @param [Hash] (optional) replacement values with { pattern_name (defined in YAML config) => value }
159
+ # @return [String] the search encoded url
160
+ def url_with_replacements(query, sub_auth = nil, search_replacements = {})
161
+ return nil unless supports_search?
162
+ sub_auth = sub_auth.to_sym if sub_auth.is_a? String
163
+ url = Config.replace_pattern(url_template, qa_replacement_patterns[:query], query)
164
+ url = Config.process_subauthority(url, subauthority_replacement_pattern, subauthorities, sub_auth) if subauthorities?
165
+ url = Config.apply_replacements(url, replacements, search_replacements) if replacements?
166
+ url
167
+ end
168
+ end
169
+ end
170
+ end