qa 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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