simple_solr_client 0.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 (136) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +349 -0
  5. data/Rakefile +11 -0
  6. data/lib/simple_solr.rb +42 -0
  7. data/lib/simple_solr/client.rb +139 -0
  8. data/lib/simple_solr/client/core_admin.rb +0 -0
  9. data/lib/simple_solr/core.rb +50 -0
  10. data/lib/simple_solr/core/admin.rb +47 -0
  11. data/lib/simple_solr/core/core_data.rb +51 -0
  12. data/lib/simple_solr/core/index.rb +25 -0
  13. data/lib/simple_solr/core/search.rb +21 -0
  14. data/lib/simple_solr/response/document.rb +45 -0
  15. data/lib/simple_solr/response/generic_response.rb +19 -0
  16. data/lib/simple_solr/response/query_response.rb +54 -0
  17. data/lib/simple_solr/schema.rb +261 -0
  18. data/lib/simple_solr/schema/analysis.rb +58 -0
  19. data/lib/simple_solr/schema/copyfield.rb +42 -0
  20. data/lib/simple_solr/schema/dynamic_field.rb +23 -0
  21. data/lib/simple_solr/schema/field.rb +35 -0
  22. data/lib/simple_solr/schema/field_or_type.rb +112 -0
  23. data/lib/simple_solr/schema/field_type.rb +62 -0
  24. data/lib/simple_solr/schema/matcher.rb +16 -0
  25. data/lib/simple_solr/version.rb +3 -0
  26. data/simple_solr_client.gemspec +39 -0
  27. data/solr_sample_core/conf/_schema_analysis_stopwords_english.json +38 -0
  28. data/solr_sample_core/conf/_schema_analysis_synonyms_english.json +11 -0
  29. data/solr_sample_core/conf/admin-extra.html +24 -0
  30. data/solr_sample_core/conf/admin-extra.menu-bottom.html +25 -0
  31. data/solr_sample_core/conf/admin-extra.menu-top.html +25 -0
  32. data/solr_sample_core/conf/clustering/carrot2/kmeans-attributes.xml +19 -0
  33. data/solr_sample_core/conf/clustering/carrot2/lingo-attributes.xml +24 -0
  34. data/solr_sample_core/conf/clustering/carrot2/stc-attributes.xml +19 -0
  35. data/solr_sample_core/conf/currency.xml +67 -0
  36. data/solr_sample_core/conf/elevate.xml +38 -0
  37. data/solr_sample_core/conf/lang/contractions_ca.txt +8 -0
  38. data/solr_sample_core/conf/lang/contractions_fr.txt +15 -0
  39. data/solr_sample_core/conf/lang/contractions_ga.txt +5 -0
  40. data/solr_sample_core/conf/lang/contractions_it.txt +23 -0
  41. data/solr_sample_core/conf/lang/hyphenations_ga.txt +5 -0
  42. data/solr_sample_core/conf/lang/stemdict_nl.txt +6 -0
  43. data/solr_sample_core/conf/lang/stoptags_ja.txt +420 -0
  44. data/solr_sample_core/conf/lang/stopwords_ar.txt +125 -0
  45. data/solr_sample_core/conf/lang/stopwords_bg.txt +193 -0
  46. data/solr_sample_core/conf/lang/stopwords_ca.txt +220 -0
  47. data/solr_sample_core/conf/lang/stopwords_ckb.txt +136 -0
  48. data/solr_sample_core/conf/lang/stopwords_cz.txt +172 -0
  49. data/solr_sample_core/conf/lang/stopwords_da.txt +110 -0
  50. data/solr_sample_core/conf/lang/stopwords_de.txt +294 -0
  51. data/solr_sample_core/conf/lang/stopwords_el.txt +78 -0
  52. data/solr_sample_core/conf/lang/stopwords_en.txt +54 -0
  53. data/solr_sample_core/conf/lang/stopwords_es.txt +356 -0
  54. data/solr_sample_core/conf/lang/stopwords_eu.txt +99 -0
  55. data/solr_sample_core/conf/lang/stopwords_fa.txt +313 -0
  56. data/solr_sample_core/conf/lang/stopwords_fi.txt +97 -0
  57. data/solr_sample_core/conf/lang/stopwords_fr.txt +186 -0
  58. data/solr_sample_core/conf/lang/stopwords_ga.txt +110 -0
  59. data/solr_sample_core/conf/lang/stopwords_gl.txt +161 -0
  60. data/solr_sample_core/conf/lang/stopwords_hi.txt +235 -0
  61. data/solr_sample_core/conf/lang/stopwords_hu.txt +211 -0
  62. data/solr_sample_core/conf/lang/stopwords_hy.txt +46 -0
  63. data/solr_sample_core/conf/lang/stopwords_id.txt +359 -0
  64. data/solr_sample_core/conf/lang/stopwords_it.txt +303 -0
  65. data/solr_sample_core/conf/lang/stopwords_ja.txt +127 -0
  66. data/solr_sample_core/conf/lang/stopwords_lv.txt +172 -0
  67. data/solr_sample_core/conf/lang/stopwords_nl.txt +119 -0
  68. data/solr_sample_core/conf/lang/stopwords_no.txt +194 -0
  69. data/solr_sample_core/conf/lang/stopwords_pt.txt +253 -0
  70. data/solr_sample_core/conf/lang/stopwords_ro.txt +233 -0
  71. data/solr_sample_core/conf/lang/stopwords_ru.txt +243 -0
  72. data/solr_sample_core/conf/lang/stopwords_sv.txt +133 -0
  73. data/solr_sample_core/conf/lang/stopwords_th.txt +119 -0
  74. data/solr_sample_core/conf/lang/stopwords_tr.txt +212 -0
  75. data/solr_sample_core/conf/lang/userdict_ja.txt +29 -0
  76. data/solr_sample_core/conf/mapping-FoldToASCII.txt +3813 -0
  77. data/solr_sample_core/conf/mapping-ISOLatin1Accent.txt +246 -0
  78. data/solr_sample_core/conf/protwords.txt +21 -0
  79. data/solr_sample_core/conf/schema.xml +62 -0
  80. data/solr_sample_core/conf/scripts.conf +24 -0
  81. data/solr_sample_core/conf/solrconfig.xml +1702 -0
  82. data/solr_sample_core/conf/spellings.txt +2 -0
  83. data/solr_sample_core/conf/stopwords.txt +14 -0
  84. data/solr_sample_core/conf/syn.txt +0 -0
  85. data/solr_sample_core/conf/synonyms.txt +29 -0
  86. data/solr_sample_core/conf/token_fixing_charfilter.txt +110 -0
  87. data/solr_sample_core/conf/update-script.js +53 -0
  88. data/solr_sample_core/conf/velocity/README.txt +101 -0
  89. data/solr_sample_core/conf/velocity/VM_global_library.vm +175 -0
  90. data/solr_sample_core/conf/velocity/browse.vm +33 -0
  91. data/solr_sample_core/conf/velocity/cluster.vm +19 -0
  92. data/solr_sample_core/conf/velocity/cluster_results.vm +31 -0
  93. data/solr_sample_core/conf/velocity/debug.vm +28 -0
  94. data/solr_sample_core/conf/velocity/did_you_mean.vm +9 -0
  95. data/solr_sample_core/conf/velocity/error.vm +11 -0
  96. data/solr_sample_core/conf/velocity/facet_fields.vm +23 -0
  97. data/solr_sample_core/conf/velocity/facet_pivot.vm +12 -0
  98. data/solr_sample_core/conf/velocity/facet_queries.vm +12 -0
  99. data/solr_sample_core/conf/velocity/facet_ranges.vm +23 -0
  100. data/solr_sample_core/conf/velocity/facets.vm +10 -0
  101. data/solr_sample_core/conf/velocity/footer.vm +43 -0
  102. data/solr_sample_core/conf/velocity/head.vm +35 -0
  103. data/solr_sample_core/conf/velocity/header.vm +7 -0
  104. data/solr_sample_core/conf/velocity/hit.vm +25 -0
  105. data/solr_sample_core/conf/velocity/hit_grouped.vm +43 -0
  106. data/solr_sample_core/conf/velocity/hit_plain.vm +25 -0
  107. data/solr_sample_core/conf/velocity/join_doc.vm +20 -0
  108. data/solr_sample_core/conf/velocity/jquery.autocomplete.css +48 -0
  109. data/solr_sample_core/conf/velocity/jquery.autocomplete.js +763 -0
  110. data/solr_sample_core/conf/velocity/layout.vm +24 -0
  111. data/solr_sample_core/conf/velocity/main.css +230 -0
  112. data/solr_sample_core/conf/velocity/mime_type_lists.vm +68 -0
  113. data/solr_sample_core/conf/velocity/pagination_bottom.vm +22 -0
  114. data/solr_sample_core/conf/velocity/pagination_top.vm +29 -0
  115. data/solr_sample_core/conf/velocity/product_doc.vm +32 -0
  116. data/solr_sample_core/conf/velocity/query.vm +42 -0
  117. data/solr_sample_core/conf/velocity/query_form.vm +64 -0
  118. data/solr_sample_core/conf/velocity/query_group.vm +43 -0
  119. data/solr_sample_core/conf/velocity/query_spatial.vm +75 -0
  120. data/solr_sample_core/conf/velocity/results_list.vm +22 -0
  121. data/solr_sample_core/conf/velocity/richtext_doc.vm +153 -0
  122. data/solr_sample_core/conf/velocity/suggest.vm +8 -0
  123. data/solr_sample_core/conf/velocity/tabs.vm +50 -0
  124. data/solr_sample_core/conf/xslt/example.xsl +132 -0
  125. data/solr_sample_core/conf/xslt/example_atom.xsl +67 -0
  126. data/solr_sample_core/conf/xslt/example_rss.xsl +66 -0
  127. data/solr_sample_core/conf/xslt/luke.xsl +337 -0
  128. data/solr_sample_core/conf/xslt/updateXml.xsl +70 -0
  129. data/spec/client_basics_spec.rb +26 -0
  130. data/spec/connect_spec.rb +25 -0
  131. data/spec/core_basics.rb +21 -0
  132. data/spec/index_spec.rb +31 -0
  133. data/spec/load_spec.rb +7 -0
  134. data/spec/minitest_helper.rb +36 -0
  135. data/spec/schema_spec.rb +113 -0
  136. metadata +284 -0
File without changes
@@ -0,0 +1,50 @@
1
+ # Pre-define the inheritance so Ruby doesn't complain
2
+ # on import.
3
+ require 'simple_solr/client'
4
+ require 'simple_solr/schema'
5
+ module SimpleSolrClient
6
+ class Core < Client
7
+ end
8
+ end
9
+
10
+
11
+ require 'simple_solr/core/admin'
12
+ require 'simple_solr/core/core_data'
13
+ require 'simple_solr/core/index'
14
+ require 'simple_solr/core/search'
15
+
16
+ class SimpleSolrClient::Core
17
+
18
+
19
+ include SimpleSolrClient::Core::Admin
20
+ include SimpleSolrClient::Core::CoreData
21
+ include SimpleSolrClient::Core::Index
22
+ include SimpleSolrClient::Core::Search
23
+
24
+
25
+ attr_reader :core
26
+ alias_method :name, :core
27
+
28
+ def initialize(url, core)
29
+ super(url)
30
+ @core = core
31
+ end
32
+
33
+
34
+ # Override #url so we're now talking to the core
35
+ def url(*args)
36
+ [@base_url, @core, *args].join('/').chomp('/')
37
+ end
38
+
39
+ # Send JSON to this core's update/json handler
40
+ def update(object_to_post, response_type = nil)
41
+ post_json('update/json', object_to_post, response_type)
42
+ end
43
+
44
+ def schema
45
+ @schema ||= SimpleSolrClient::Schema.new(self)
46
+ end
47
+
48
+ end
49
+
50
+
@@ -0,0 +1,47 @@
1
+ module SimpleSolrClient::Core::Admin
2
+ def ping
3
+ get('admin/ping')
4
+ end
5
+
6
+ # Is the server up (and responding to a ping?)
7
+ # @return [Boolean]
8
+ def up?
9
+ begin
10
+ ping.status == 'OK'
11
+ rescue
12
+ false
13
+ end
14
+ end
15
+
16
+ # Send a commit command
17
+ # @return self
18
+ def commit
19
+ update({'commit' => {}})
20
+ self
21
+ end
22
+
23
+ # Send an optimize command
24
+ # @return self
25
+ def optimize
26
+ update({"optimize" => {}})
27
+ self
28
+ end
29
+
30
+ # Reload the core (for when you've changed the schema, solrconfig, synonyms, etc.)
31
+ # Make sure to mark the schema as dirty!
32
+ # @return self
33
+ def reload
34
+ get('admin/cores', {:force_top_level_url => true, :core => core, :action => 'RELOAD'})
35
+ @schema = nil
36
+ self
37
+ end
38
+
39
+ # Unload the current core and delete all its files
40
+ # @return The Solr response
41
+ def unload
42
+ get('admin/cores', {:force_top_level_url => true, :core => core, :action => 'UNLOAD', :deleteInstanceDir => true})
43
+ end
44
+
45
+
46
+ end
47
+
@@ -0,0 +1,51 @@
1
+ module SimpleSolrClient::Core::CoreData
2
+ attr_reader :raw_solr_hash
3
+
4
+
5
+ # Get the core data for this core
6
+ # This is weird in that while the data is about a specific
7
+ # core, we need to call it at the client_url level; hence
8
+ # all the screwing around with force_top_level_url
9
+ #
10
+ # It would make sense to cache this until, say, a commit or a
11
+ # reload, but the added complexity isn't yet worth it.
12
+ def core_data_hash
13
+ cdata = get('admin/cores', {:force_top_level_url => true})
14
+ cdata['status'][core]
15
+ end
16
+
17
+ def index
18
+ core_data_hash['index']
19
+ end
20
+
21
+ def default?
22
+ core_data_hash['isDefaultCore']
23
+ end
24
+
25
+ def last_modified
26
+ Time.parse index['lastModified']
27
+ end
28
+
29
+ def number_of_documents
30
+ index['numDocs']
31
+ end
32
+
33
+ def data_dir
34
+ core_data_hash['dataDir']
35
+ end
36
+
37
+ def instance_dir
38
+ core_data_hash['instanceDir']
39
+ end
40
+
41
+ def config_file
42
+ File.join(instance_dir, 'conf', core_data_hash['config'])
43
+ end
44
+
45
+ def schema_file
46
+ File.join(instance_dir, 'conf', core_data_hash['schema'])
47
+ end
48
+
49
+ end
50
+
51
+
@@ -0,0 +1,25 @@
1
+ module SimpleSolrClient::Core::Index
2
+ # Add the given hash or array of hashes
3
+ # @return self
4
+ def add_docs(*hash_or_hashes)
5
+ update(hash_or_hashes.flatten)
6
+ self
7
+ end
8
+
9
+ # A raw delete. Your query needs to be legal (e.g., escaped) already
10
+ # @param [String] q The query to identify items to delete
11
+ # @return self
12
+ def delete(q)
13
+ update({:delete => {:query => q}})
14
+ self
15
+ end
16
+
17
+ # Delete all document in the index and immdiately commit
18
+ # @return self
19
+ def clear
20
+ delete('*:*').commit
21
+ self
22
+ end
23
+
24
+
25
+ end
@@ -0,0 +1,21 @@
1
+ require 'simple_solr/response/query_response'
2
+
3
+ module SimpleSolrClient::Core::Search
4
+
5
+ def fv_search(field, value)
6
+ v = value
7
+ v = SimpleSolrClient.lucene_escape Array(value).join(' ') unless v == '*'
8
+ kv = "#{field}:(#{v})"
9
+ get('select', {:q => kv}, SimpleSolrClient::Response::QueryResponse)
10
+ end
11
+
12
+ def all
13
+ fv_search('*', '*')
14
+ end
15
+
16
+ def id(i)
17
+ fv_search('id', i).first
18
+ end
19
+
20
+
21
+ end
@@ -0,0 +1,45 @@
1
+ class SimpleSolrClient::Response::Document
2
+ extend Forwardable
3
+ include Comparable
4
+
5
+ attr_accessor :rank
6
+
7
+ def <=>(other)
8
+ other = other.score if other.respond_to? :score,
9
+ self.score <=> other
10
+ end
11
+
12
+ # @!method [](key)
13
+ # @param [String] key The name of the stored field
14
+ # @return [String, Array<String>] the value(s) of the requested field
15
+ def_delegators :@solr_doc_hash, :[], :keys, :values, :to_s
16
+
17
+ # !@attribute solr_doc_hash
18
+ # @return [Hash] the original, un-munged solr data for this document passed into the initializer
19
+ attr_accessor :solr_doc_hash, :rank
20
+
21
+
22
+ # Create a single document from a Hash representation of the solr return value
23
+ # @param solrdochash [Hash] The ruby-hash representation of a Solr document
24
+ # as returned by a solr query
25
+ def initialize(solrdochash)
26
+ @solr_doc_hash = solrdochash
27
+ end
28
+
29
+
30
+ # The value of the 'id' field of this document
31
+ def id
32
+ @solr_doc_hash['id']
33
+ end
34
+
35
+ # The score of this document on thsi query
36
+ def score
37
+ @solr_doc_hash['score']
38
+ end
39
+
40
+
41
+ def to_h
42
+ @solr_doc_hash.merge({'_rank' => @rank})
43
+ end
44
+
45
+ end
@@ -0,0 +1,19 @@
1
+ require 'forwardable'
2
+
3
+ module SimpleSolrClient
4
+ module Response
5
+ class GenericResponse
6
+ extend Forwardable
7
+ def_delegators :@solr_response, :[]
8
+
9
+ def initialize(solr_response_hash)
10
+ @solr_response = solr_response_hash
11
+ end
12
+
13
+ def status
14
+ @solr_response['status']
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,54 @@
1
+ require 'simple_solr/response/generic_response'
2
+ require 'simple_solr/response/document'
3
+
4
+ class SimpleSolrClient::Response::QueryResponse < SimpleSolrClient::Response::GenericResponse
5
+ extend Forwardable
6
+ include Enumerable
7
+
8
+ attr_reader :num_found, :docs, :first_index, :docs, :params, :page
9
+
10
+ def_delegators :@docs, :each, :count, :size
11
+ def_delegators :@indexed_docs, :[]
12
+
13
+ def initialize(solr_response)
14
+ super
15
+ resp = @solr_response['response']
16
+ @num_found = resp['numFound']
17
+ @first_index = resp['start'] + 1
18
+
19
+ @docs = []
20
+ @indexed_docs = {}
21
+ resp['docs'].each_with_index do |d, i|
22
+ doc_rank = i + @first_index
23
+ doc = SimpleSolrClient::Response::Document.new(d)
24
+ doc.rank = doc_rank
25
+ @docs << doc
26
+ @indexed_docs[doc.id] = doc
27
+ end
28
+ end
29
+
30
+ def last_index
31
+ @first_index + @num_found
32
+ end
33
+
34
+ def rank(id)
35
+ @indexed_docs[id.to_s].rank
36
+ end
37
+
38
+ def score(id)
39
+ @indexed_docs[id.to_s].score
40
+ end
41
+
42
+ # @return [Boolean] True if there are no documents
43
+ def empty?
44
+ @docs.empty?
45
+ end
46
+
47
+
48
+ def each_with_rank
49
+ return self.enum_for(:each_with_rank) unless block_given?
50
+ @docs.each { |x| yield x, x.rank }
51
+ end
52
+
53
+ end
54
+
@@ -0,0 +1,261 @@
1
+ require 'nokogiri'
2
+
3
+ require 'simple_solr/schema/matcher'
4
+ require 'simple_solr/schema/copyfield'
5
+ require 'simple_solr/schema/field'
6
+ require 'simple_solr/schema/dynamic_field'
7
+ require 'simple_solr/schema/field_type'
8
+
9
+ class SimpleSolrClient::Schema
10
+ # A simplistic representation of a schema
11
+
12
+
13
+ attr_reader :xmldoc
14
+
15
+ def initialize(core)
16
+ @core = core
17
+ @fields = {}
18
+ @dynamic_fields = {}
19
+ @copy_fields = Hash.new { |h, k| h[k] = [] }
20
+ @field_types = {}
21
+ self.load
22
+ end
23
+
24
+
25
+ def fields
26
+ @fields.values.map { |x| x.resolve_type(self) }
27
+ end
28
+
29
+ def field(n)
30
+ @fields[n].resolve_type(self)
31
+ end
32
+
33
+ def dynamic_fields
34
+ @dynamic_fields.values.map { |x| x.resolve_type(self) }
35
+ end
36
+
37
+ def dynamic_field(n)
38
+ @dynamic_fields[n].resolve_type(self)
39
+ end
40
+
41
+ def copy_fields_for(n)
42
+ @copy_fields[n]
43
+ end
44
+
45
+ def copy_fields
46
+ @copy_fields.values.flatten
47
+ end
48
+
49
+ def add_field(f)
50
+ @fields[f.name] = f
51
+ field(f.name)
52
+ end
53
+
54
+ def drop_field(str)
55
+ @fields.delete(str)
56
+ self
57
+ end
58
+
59
+
60
+ def field_types
61
+ @field_types.values
62
+ end
63
+
64
+ def field_type(k)
65
+ @field_types[k]
66
+ end
67
+
68
+
69
+ # When we add dynamic fields, we need to keep them sorted by
70
+ # length of the key, since that's how they match
71
+ def add_dynamic_field(f)
72
+ raise "Dynamic field should be dynamic and have a '*' in it somewhere; '#{f.name}' does not" unless f.name =~ /\*/
73
+ @dynamic_fields[f.name] = f
74
+
75
+ @dynamic_fields = @dynamic_fields.sort { |a, b| b[0].size <=> a[0].size }.to_h
76
+
77
+ end
78
+
79
+ def drop_dynamic_field(str)
80
+ @dynamic_fields.delete(str)
81
+ self
82
+ end
83
+
84
+ def add_copy_field(f)
85
+ cf = @copy_fields[f.source]
86
+ cf << f
87
+ end
88
+
89
+ def drop_copy_field(str)
90
+ @copy_fields.delete(str)
91
+ self
92
+ end
93
+
94
+ def add_field_type(ft)
95
+ ft.core = @core
96
+ @field_types[ft.name] = ft
97
+ end
98
+
99
+ def drop_field_type(str)
100
+ @field_types.delete(str)
101
+ self
102
+ end
103
+
104
+
105
+ # For loading, we get the information about the fields via the API,
106
+ # but grab an XML document for modifying/writing
107
+ def load
108
+ @xmldoc = Nokogiri.XML(@core.raw_get_content('admin/file', {:file => 'schema.xml'})) do |config|
109
+ config.noent
110
+ end
111
+ load_explicit_fields
112
+ load_dynamic_fields
113
+ load_copy_fields
114
+ load_field_types
115
+ end
116
+
117
+
118
+ def load_explicit_fields
119
+ @fields = {}
120
+ @core.get('schema/fields')['fields'].each do |field_hash|
121
+ add_field(Field.new_from_solr_hash(field_hash))
122
+ end
123
+ end
124
+
125
+ def load_dynamic_fields
126
+ @dynamic_fields = {}
127
+ @core.get('schema/dynamicfields')['dynamicFields'].each do |field_hash|
128
+ f = DynamicField.new_from_solr_hash(field_hash)
129
+ if @dynamic_fields[f.name]
130
+ raise "Dynamic field '#{f.name}' defined more than once"
131
+ end
132
+ add_dynamic_field(f)
133
+ end
134
+ end
135
+
136
+ def load_copy_fields
137
+ @copy_fields = Hash.new { |h, k| h[k] = [] }
138
+ @core.get('schema/copyfields')['copyFields'].each do |cfield_hash|
139
+ add_copy_field(CopyField.new(cfield_hash['source'], cfield_hash['dest']))
140
+ end
141
+ end
142
+
143
+ def load_field_types
144
+ @field_types = {}
145
+ @core.get('schema/fieldtypes')['fieldTypes'].each do |fthash|
146
+ ft = FieldType.new_from_solr_hash(fthash)
147
+ type_name = ft.name
148
+ attr = "[@name=\"#{type_name}\"]"
149
+ node = @xmldoc.css("fieldType#{attr}").first || @xmldoc.css("fieldtype#{attr}").first
150
+ unless node
151
+ puts "Failed for type #{type_name}"
152
+ end
153
+ ft.xml = node.to_xml
154
+ add_field_type(ft)
155
+ end
156
+ end
157
+
158
+ def clean_schema_xml
159
+ d = @xmldoc.dup
160
+ d.xpath('//comment()').remove
161
+ d.css('field').remove
162
+ d.css('fieldType').remove
163
+ d.css('fieldtype').remove
164
+ d.css('dynamicField').remove
165
+ d.css('copyField').remove
166
+ d.css('dynamicfield').remove
167
+ d.css('copyfield').remove
168
+ d.css('schema').children.find_all { |x| x.name == 'text' }.each { |x| x.remove }
169
+ d
170
+ end
171
+
172
+ def to_xml
173
+ # Get a clean schema XML document
174
+ d = clean_schema_xml
175
+ s = d.css('schema').first
176
+ [fields, dynamic_fields, copy_fields, field_types].flatten.each do |f|
177
+ s.add_child f.to_xml_node
178
+ end
179
+ d.to_xml
180
+ end
181
+
182
+
183
+ def write
184
+ File.open(@core.schema_file, 'w:utf-8') do |out|
185
+ out.puts self.to_xml
186
+ end
187
+ end
188
+
189
+ def reload
190
+ @core.reload
191
+ end
192
+
193
+
194
+ # Figuring out which fields are actually produced can be hard:
195
+ # * If a non-dynamic field name matches, no dynamic_fields will match
196
+ # * The result of a copyField may match another dynamicField, but the
197
+ # result of *that* will not match more copyFields
198
+ # * dynamicFields are matched longest to shortest
199
+ #
200
+ # Suppose I have the following:
201
+ # dynamic *_ts => string
202
+ # dynamic *_t => string
203
+ # dynamic *_s => string
204
+ # dynamic *_ddd => string
205
+ #
206
+ # copy *_ts => *_t
207
+ # copy *_ts => *_s
208
+ # copy *_s => *_ddd
209
+ #
210
+ # You might expect:
211
+ # name_ts => string
212
+ # name_ts copied to name_t => string
213
+ # name_ts copied to name_s => string
214
+ # name_s copied to name_ddd => string
215
+ #
216
+ # ...giving us name_ts, name_t, name_s, and name_ddd
217
+ #
218
+ # What you'll find is that we don't get name_ddd, since
219
+ # name_s was generated by a wildcard-enabled copyField
220
+ # and that's where things stop.
221
+ #
222
+ # However, if you explicitly add a field called
223
+ # name_s, it *will* get copied to name_ddd.
224
+ #
225
+ # Yeah. It's confusing.
226
+
227
+
228
+ def first_matching_field(str)
229
+ f = fields.find { |x| x.matches str } or first_matching_dfield(str)
230
+ end
231
+
232
+ def first_matching_dfield(str)
233
+ df = dynamic_fields.find { |x| x.matches str }
234
+ if df
235
+ f = Field.new(df.to_h)
236
+ f[:name] = df.dynamic_name str
237
+ end
238
+ f
239
+
240
+ end
241
+
242
+ def resulting_fields(str)
243
+ rv = []
244
+ f = first_matching_field(str)
245
+ rv << f
246
+ copy_fields.each do |cf|
247
+ if cf.matches(f.name)
248
+ dname = cf.dynamic_name(f.name)
249
+ fmf = Field.new(first_matching_field(dname).to_h)
250
+ fmf[:name] = dname
251
+ rv << fmf
252
+ end
253
+ end
254
+ rv.uniq
255
+ end
256
+
257
+ end
258
+
259
+
260
+
261
+