blacklight_advanced_search 6.0.2 → 6.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +15 -0
  4. data/.rubocop_todo.yml +351 -0
  5. data/.solr_wrapper.yml +5 -0
  6. data/.travis.yml +4 -7
  7. data/Gemfile +18 -11
  8. data/Rakefile +24 -34
  9. data/VERSION +1 -1
  10. data/app/controllers/advanced_controller.rb +5 -7
  11. data/app/controllers/blacklight_advanced_search/advanced_controller.rb +5 -8
  12. data/app/helpers/advanced_helper.rb +4 -6
  13. data/blacklight_advanced_search.gemspec +11 -8
  14. data/lib/blacklight_advanced_search.rb +29 -34
  15. data/lib/blacklight_advanced_search/advanced_query_parser.rb +12 -13
  16. data/lib/blacklight_advanced_search/advanced_search_builder.rb +28 -32
  17. data/lib/blacklight_advanced_search/catalog_helper_override.rb +11 -34
  18. data/lib/blacklight_advanced_search/controller.rb +1 -1
  19. data/lib/blacklight_advanced_search/filter_parser.rb +7 -9
  20. data/lib/blacklight_advanced_search/parsing_nesting_parser.rb +5 -8
  21. data/lib/blacklight_advanced_search/redirect_legacy_params_filter.rb +23 -25
  22. data/lib/blacklight_advanced_search/render_constraints_override.rb +46 -33
  23. data/lib/blacklight_advanced_search/version.rb +0 -1
  24. data/lib/generators/blacklight_advanced_search/assets_generator.rb +4 -8
  25. data/lib/generators/blacklight_advanced_search/blacklight_advanced_search_generator.rb +0 -2
  26. data/lib/generators/blacklight_advanced_search/install_generator.rb +9 -5
  27. data/lib/generators/blacklight_advanced_search/templates/advanced_controller.rb +0 -2
  28. data/lib/parsing_nesting/grammar.rb +22 -25
  29. data/lib/parsing_nesting/tree.rb +156 -168
  30. data/solr/conf/_rest_managed.json +3 -0
  31. data/solr/conf/admin-extra.html +31 -0
  32. data/solr/conf/elevate.xml +36 -0
  33. data/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
  34. data/solr/conf/protwords.txt +21 -0
  35. data/solr/conf/schema.xml +635 -0
  36. data/solr/conf/scripts.conf +24 -0
  37. data/solr/conf/solrconfig.xml +411 -0
  38. data/solr/conf/spellings.txt +2 -0
  39. data/solr/conf/stopwords.txt +58 -0
  40. data/solr/conf/stopwords_en.txt +58 -0
  41. data/solr/conf/synonyms.txt +31 -0
  42. data/solr/conf/xslt/example.xsl +132 -0
  43. data/solr/conf/xslt/example_atom.xsl +67 -0
  44. data/solr/conf/xslt/example_rss.xsl +66 -0
  45. data/solr/conf/xslt/luke.xsl +337 -0
  46. data/solr/sample_solr_documents.yml +2692 -0
  47. data/spec/features/blacklight_advanced_search_form_spec.rb +0 -2
  48. data/spec/helpers/advanced_helper_spec.rb +0 -2
  49. data/spec/integration/blacklight_stub_spec.rb +0 -2
  50. data/spec/lib/advanced_search_builder_spec.rb +7 -14
  51. data/spec/lib/blacklight_advanced_search/render_constraints_override_spec.rb +39 -0
  52. data/spec/lib/deep_merge_spec.rb +109 -34
  53. data/spec/lib/filter_parser_spec.rb +8 -14
  54. data/spec/parsing_nesting/build_tree_spec.rb +73 -81
  55. data/spec/parsing_nesting/consuming_spec.rb +2 -12
  56. data/spec/parsing_nesting/to_solr_spec.rb +93 -130
  57. data/spec/spec_helper.rb +0 -3
  58. data/spec/test_app_templates/app/controllers/catalog_controller.rb +3 -3
  59. data/spec/test_app_templates/lib/generators/test_app_generator.rb +3 -3
  60. metadata +63 -13
  61. data/spec/spec.opts +0 -4
@@ -1,18 +1,17 @@
1
1
  require 'parsing_nesting/grammar'
2
2
  module ParsingNesting::Tree
3
-
4
3
  # Get parslet output for string (parslet output is json-y objects), and
5
4
  # transform to an actual abstract syntax tree made up of more semantic
6
- # ruby objects, Node's. The top one will always be a List.
7
- #
5
+ # ruby objects, Node's. The top one will always be a List.
6
+ #
8
7
  # Call #to_query on resulting Node in order to transform to Solr query,
9
8
  # optionally passing in Solr params to be used as LocalParams in nested
10
- # dismax queries.
9
+ # dismax queries.
11
10
  #
12
11
  # Our approach here works, but as we have to put in special cases
13
12
  # it starts getting messy. Ideally we might want to actually transform
14
13
  # the Object graph (abstract syntax tree) instead of trying to handle
15
- # special cases in #to_query.
14
+ # special cases in #to_query.
16
15
  # For instance, transform object graph for a problematic pure-negative
17
16
  # clause to the corresponding object graph without that (-a AND -b) ==>
18
17
  # (NOT (a OR b). Transform (NOT NOT a) to (a). That would probably be
@@ -21,117 +20,115 @@ module ParsingNesting::Tree
21
20
  # multiple levels. But it's working for now.
22
21
  #
23
22
  # the #negate method was an experiment in transforming parse tree in
24
- # place, but isn't being used. But it's left as a sign post.
25
- def self.parse(string, query_parser='dismax')
23
+ # place, but isn't being used. But it's left as a sign post.
24
+ def self.parse(string, query_parser = 'dismax')
26
25
  to_node_tree(ParsingNesting::Grammar.new.parse(string), query_parser)
27
26
  end
28
-
29
27
 
30
28
  # theoretically Parslet's Transform could be used for this, but I think the
31
29
  # manner in which I'm parsing to Parslet labelled hash isn't exactly what
32
30
  # Parslet Transform is set up to work with, I couldn't figure it out. But
33
31
  # easy enough to do 'manually'.
34
32
  def self.to_node_tree(tree, query_parser)
35
- if tree.kind_of? Array
33
+ if tree.is_a? Array
36
34
  # at one point I was normalizing top-level lists of one item to just
37
35
  # be that item, no list wrapper. But having the list wrapper
38
- # at the top level is actually useful for Solr output.
39
- List.new( tree.collect {|i| to_node_tree(i, query_parser)}, query_parser)
40
- elsif tree.kind_of? Hash
36
+ # at the top level is actually useful for Solr output.
37
+ List.new(tree.collect { |i| to_node_tree(i, query_parser) }, query_parser)
38
+ elsif tree.is_a? Hash
41
39
  if list = tree[:list]
42
- List.new( list.collect {|i| to_node_tree(i, query_parser)}, query_parser)
40
+ List.new(list.collect { |i| to_node_tree(i, query_parser) }, query_parser)
43
41
  elsif tree.has_key?(:and_list)
44
- AndList.new( tree[:and_list].collect{|i| to_node_tree(i, query_parser) }, query_parser)
42
+ AndList.new(tree[:and_list].collect { |i| to_node_tree(i, query_parser) }, query_parser)
45
43
  elsif tree.has_key?(:or_list)
46
- OrList.new( tree[:or_list].collect{|i| to_node_tree(i, query_parser) }, query_parser )
44
+ OrList.new(tree[:or_list].collect { |i| to_node_tree(i, query_parser) }, query_parser)
47
45
  elsif not_payload = tree[:not_expression]
48
- NotExpression.new( to_node_tree(not_payload, query_parser) )
46
+ NotExpression.new(to_node_tree(not_payload, query_parser))
49
47
  elsif tree.has_key?(:mandatory)
50
- MandatoryClause.new( to_node_tree(tree[:mandatory], query_parser))
48
+ MandatoryClause.new(to_node_tree(tree[:mandatory], query_parser))
51
49
  elsif tree.has_key?(:excluded)
52
- ExcludedClause.new( to_node_tree(tree[:excluded], query_parser))
50
+ ExcludedClause.new(to_node_tree(tree[:excluded], query_parser))
53
51
  elsif phrase = tree[:phrase]
54
- Phrase.new( phrase )
52
+ Phrase.new(phrase)
55
53
  elsif tree.has_key?(:token)
56
- Term.new( tree[:token].to_s )
54
+ Term.new(tree[:token].to_s)
57
55
  end
58
56
  end
59
57
  end
60
-
58
+
61
59
  class Node
62
60
  # this default to_query works well for anything that is embeddable in
63
- # a standard way.
64
- # non-embeddable nodes will have to override and do it different.
61
+ # a standard way.
62
+ # non-embeddable nodes will have to override and do it different.
65
63
  def to_query(solr_params)
66
64
  build_nested_query([self], solr_params)
67
65
  end
68
-
66
+
69
67
  protected # some utility methods
70
-
68
+
71
69
  # Builds a query from a list of Node's that have #to_embed, and some
72
70
  # solr params to embed as LocalParams.
73
71
  #
74
72
  # By default will create a nested _query_, handling escaping appropriately.
75
- # but pass in :always_nested=>false, and it will sometimes be an ordinary
76
- # query where possible. (possibly still with LocalParams).
73
+ # but pass in :always_nested=>false, and it will sometimes be an ordinary
74
+ # query where possible. (possibly still with LocalParams).
77
75
  #
78
76
  # LocalParams will be default have "!dismax" added to them, but set
79
77
  # :force_deftype to something else (or nil) if you want.
80
78
  #
81
79
  # Also takes care of simple "pure negative" queries like "-one -two",
82
80
  # converting them to a nested NOT query that will be handled appropriately.
83
- # those simple negatives can't be handled right by dismax otherwise.
84
- def build_nested_query(embeddables, solr_params={}, options = {})
85
- options = {:always_nested => true,
86
- :force_deftype => "dismax"}.merge(options)
87
-
81
+ # those simple negatives can't be handled right by dismax otherwise.
82
+ def build_nested_query(embeddables, solr_params = {}, options = {})
83
+ options = { :always_nested => true,
84
+ :force_deftype => "dismax" }.merge(options)
85
+
88
86
  # if it's pure negative, we need to transform
89
- if embeddables.find_all{|n| n.kind_of?(ExcludedClause)}.length == embeddables.length
90
- negated = NotExpression.new( List.new(embeddables.collect {|n| n.operand}, options[:force_deftype] ))
91
- solr_params = solr_params.merge(:mm => "1")
92
- return negated.to_query(solr_params)
87
+ if embeddables.find_all { |n| n.is_a?(ExcludedClause) }.length == embeddables.length
88
+ negated = NotExpression.new(List.new(embeddables.collect { |n| n.operand }, options[:force_deftype]))
89
+ solr_params = solr_params.merge(:mm => "1")
90
+ return negated.to_query(solr_params)
93
91
  else
94
-
95
- inner_query = build_local_params(solr_params, options[:force_deftype]) +
96
- embeddables.collect {|n| n.to_embed}.join(" ")
97
-
98
- if options[:always_nested]
92
+
93
+ inner_query = build_local_params(solr_params, options[:force_deftype]) +
94
+ embeddables.collect { |n| n.to_embed }.join(" ")
95
+
96
+ if options[:always_nested]
99
97
  return '_query_:"' + bs_escape(inner_query) + '"'
100
98
  else
101
99
  return inner_query
102
100
  end
103
101
 
104
- end
102
+ end
105
103
  end
106
104
 
107
105
  # Pass in nil 2nd argument if you DON'T want to embed
108
106
  # "!dismax" in your local params. Used by #to_single_query_params
109
107
  def build_local_params(hash = {}, force_deftype = "dismax")
110
108
  # we insist on dismax for our embedded queries, or whatever
111
- # other defType supplied in 2nd argument.
109
+ # other defType supplied in 2nd argument.
112
110
  hash = hash.dup
113
111
  if force_deftype
114
112
  hash[:defType] = force_deftype
115
113
  hash.delete("defType") # avoid weird colision with hard to debug results
116
- end
117
-
118
- if (hash.size > 0)
114
+ end
115
+
116
+ if !hash.empty?
119
117
  defType = hash.delete(:defType) || hash.delete("defType")
120
- "{!" + (defType ? "#{defType} " : "") + hash.collect {|k,v| "#{k}=#{ v.to_s.include?(" ") ? "'"+v+"'" : v }"}.join(" ") + "}"
118
+ "{!" + (defType ? "#{defType} " : "") + hash.collect { |k, v| "#{k}=#{v.to_s.include?(" ") ? "'" + v + "'" : v}" }.join(" ") + "}"
121
119
  else
122
- #no local params!
120
+ # no local params!
123
121
  ""
124
122
  end
125
123
  end
126
-
127
- def bs_escape(val, char='"')
124
+
125
+ def bs_escape(val, char = '"')
128
126
  # crazy double escaping to actually get a single backslash
129
127
  # in there without triggering regexp capture reference
130
128
  val.gsub(char, '\\\\' + char)
131
129
  end
132
130
  end
133
-
134
-
131
+
135
132
  class List < Node
136
133
  attr_accessor :list
137
134
  attr_reader :query_parser
@@ -139,143 +136,139 @@ module ParsingNesting::Tree
139
136
  @query_parser = query_parser
140
137
  self.list = aList
141
138
  end
139
+
142
140
  def can_embed?
143
141
  false
144
142
  end
145
-
146
- def simple_pure_negative?
147
- (list.find_all {|i| i.kind_of? ExcludedClause }.length) == list.length
148
- end
149
-
150
- def to_query(solr_params={})
143
+
144
+ def simple_pure_negative?
145
+ list.find_all { |i| i.is_a? ExcludedClause }.length == list.length
146
+ end
147
+
148
+ def to_query(solr_params = {})
151
149
  queries = []
152
-
153
- (embeddable, gen_full_query) = list.partition {|i| i.respond_to?(:can_embed?) && i.can_embed?}
154
-
150
+
151
+ (embeddable, gen_full_query) = list.partition { |i| i.respond_to?(:can_embed?) && i.can_embed? }
152
+
155
153
  unless embeddable.empty?
156
154
  queries << build_nested_query(embeddable, solr_params, force_deftype: query_parser)
157
155
  end
158
-
156
+
159
157
  gen_full_query.each do |node|
160
158
  queries << node.to_query(solr_params)
161
159
  end
162
-
160
+
163
161
  queries.join(" AND ")
164
162
  end
165
163
 
166
164
  # Returns a Hash, assumes this will be the ONLY :q, used for
167
165
  # parsing 'simple search' to Solr. Pass in params that need to
168
- # be LOCAL solr params (using "{foo=bar}" embedded in query).
166
+ # be LOCAL solr params (using "{foo=bar}" embedded in query).
169
167
  # Params that should be sent to Solr seperately are caller's responsibility,
170
- # merge em into the returned hash.
168
+ # merge em into the returned hash.
171
169
  #
172
170
  # For very simple queries, this will produce an ordinary Solr q
173
171
  # much like would be produced ordinarily. But for AND/OR/NOT, will
174
- # sometimes include multiple nested queries instead.
172
+ # sometimes include multiple nested queries instead.
175
173
  #
176
174
  # This method will still sometimes return a single nested _query_, that
177
175
  # could theoretically really be ordinary query possibly with localparams.
178
176
  # It still works, but isn't optimizing for a simpler query, because
179
177
  # it's using much of the same code used for combining multiple fields
180
178
  # that need nested queries. Maybe we'll optimize later, but the code
181
- # gets tricky.
179
+ # gets tricky.
182
180
  def to_single_query_params(solr_local_params)
183
181
  # Can it be expressed in a single dismax?
184
-
185
- if list.find_all {|i| i.respond_to?(:can_embed?) && i.can_embed? }.length == list.length
186
- {
187
- #build_local_params(solr_local_params, nil) + list.collect {|n| n.to_embed}.join(" "),
188
- :q => build_nested_query(list, solr_local_params, :always_nested => false, :force_deftype => nil),
182
+
183
+ if list.find_all { |i| i.respond_to?(:can_embed?) && i.can_embed? }.length == list.length
184
+ {
185
+ # build_local_params(solr_local_params, nil) + list.collect {|n| n.to_embed}.join(" "),
186
+ :q => build_nested_query(list, solr_local_params, :always_nested => false, :force_deftype => nil),
189
187
  :defType => query_parser
190
- }
188
+ }
191
189
  else
192
190
  # Can't be expressed in a single dismax, do it the normal way
193
- {
191
+ {
194
192
  :q => self.to_query(solr_local_params),
195
- :defType => "lucene"
193
+ :defType => "lucene"
196
194
  }
197
195
  end
198
196
  end
199
-
197
+
200
198
  def negate
201
- List.new(list.collect {|i| i.negate})
199
+ List.new(list.collect { |i| i.negate })
202
200
  end
203
201
  end
204
-
202
+
205
203
  class AndList < List
206
-
207
204
  # We make an and-list embeddable only if all it's elements
208
205
  # are embeddable, then no problem we just embed them all
209
206
  # as Solr '+' mandatory, and achieve the AND.
210
207
  # For now, pure negative is considered not embeddable, although
211
208
  # theoretically it could sometimes be embedded if transformed
212
- # properly.
209
+ # properly.
213
210
  def can_embed?
214
- (! simple_pure_negative?) && ! list.collect {|i| i.can_embed?}.include?(false)
211
+ !simple_pure_negative? && !list.collect { |i| i.can_embed? }.include?(false)
215
212
  end
216
-
213
+
217
214
  # Only if all operands are embeddable.
218
215
  # Trick is if they were bare terms/phrases, we add a '+' on
219
216
  # front, but if they already were +/-, then we don't need to,
220
- # and leaving them along will have desired semantics.
217
+ # and leaving them along will have desired semantics.
221
218
  # This works even on "-", because dismax mm seems to not consider "-"
222
- # clauses, they are always required regardless of mm.
219
+ # clauses, they are always required regardless of mm.
223
220
  def to_embed
224
221
  list.collect do |operand|
225
222
  s = operand.to_embed
226
223
  if s =~ /^\+/ || s =~ /^\-/
227
224
  s
228
225
  else
229
- '+'+s
226
+ '+' + s
230
227
  end
231
228
  end.join(" ")
232
229
  end
233
-
230
+
234
231
  # for those that aren't embeddable, or pure negative
235
232
  def to_query(local_params)
236
233
  if simple_pure_negative?
237
234
  # Can do it in one single nested dismax, if we're simple arguments
238
- # that are pure negative.
235
+ # that are pure negative.
239
236
  # build_nested_query will handle negating the pure negative for
240
- # us.
237
+ # us.
241
238
  build_nested_query(list, local_params)
242
- else
243
- "( " +
244
- list.collect do |i|
245
- i.to_query(local_params)
246
- end.join(" AND ") +
247
- " )"
239
+ else
240
+ "( " +
241
+ list.collect do |i|
242
+ i.to_query(local_params)
243
+ end.join(" AND ") +
244
+ " )"
248
245
  end
249
246
  end
250
-
251
- # convent logical property here, not(a AND b) === not(a) OR not(b)
247
+
248
+ # convent logical property here, not(a AND b) === not(a) OR not(b)
252
249
  def negate
253
- OrList.new( list.collect {|n| n.negate} )
250
+ OrList.new(list.collect { |n| n.negate })
254
251
  end
255
-
256
252
  end
257
-
258
-
259
- class OrList < List
260
-
253
+
254
+ class OrList < List
261
255
  # never embeddable
262
256
  def can_embed?
263
257
  false
264
258
  end
265
-
266
-
259
+
267
260
  def to_query(local_params)
268
261
  # Okay, we're never embeddable as such, but sometimes we can
269
262
  # turn our operands into one single nested dismax query with mm=1, when
270
263
  # all our operands are 'simple', other times we need to actually do
271
- # two seperate nested queries seperated by lucene OR.
264
+ # two seperate nested queries seperated by lucene OR.
272
265
  # If all our children are embeddable but _not_ an "AndList", we can
273
266
  # do the one query part. The AndList is theoretically embeddable, but
274
- # not in a way compatible with flattening an OR to one query.
267
+ # not in a way compatible with flattening an OR to one query.
275
268
  # Sorry, this part is one of the least clean part of this code!
276
269
 
277
- not_flattenable = list.find {|i| ! (i.can_embed? && ! i.kind_of?(AndList) )}
278
-
270
+ not_flattenable = list.find { |i| !(i.can_embed? && !i.is_a?(AndList)) }
271
+
279
272
  if not_flattenable
280
273
  to_multi_queries(local_params)
281
274
  elsif simple_pure_negative?
@@ -284,156 +277,153 @@ module ParsingNesting::Tree
284
277
  to_one_dismax_query(local_params)
285
278
  end
286
279
  end
287
-
280
+
288
281
  # build_nested_query isn't smart enough to handle refactoring
289
- # a simple pure negative "OR", that needs an mm of 100%.
282
+ # a simple pure negative "OR", that needs an mm of 100%.
290
283
  # Let's just do it ourselves. What we're doing makes more sense
291
284
  # if you remember that:
292
285
  # -a OR -b === NOT (a AND b)
293
286
  def to_simple_pure_negative_query(local_params)
294
287
  # take em out of their ExcludedClauses
295
- embeddables = list.collect {|n| n.operand}
288
+ embeddables = list.collect { |n| n.operand }
296
289
  # and insist on mm 100%
297
290
  solr_params = local_params.merge(:mm => "100%")
298
-
299
- # and put the NOT in front to preserve semantics.
300
- return 'NOT _query_:"' +
301
- bs_escape(build_local_params(solr_params) +
302
- embeddables.collect {|n| n.to_embed}.join(" ")) +
291
+
292
+ # and put the NOT in front to preserve semantics.
293
+ 'NOT _query_:"' +
294
+ bs_escape(build_local_params(solr_params) +
295
+ embeddables.collect { |n| n.to_embed }.join(" ")) +
303
296
  '"'
304
297
  end
305
-
306
- # all our arguments are 'simple' (terms and phrases with +/-),
307
- # put am all in one single dismax with mm forced to 1.
308
- def to_one_dismax_query(local_params)
298
+
299
+ # all our arguments are 'simple' (terms and phrases with +/-),
300
+ # put am all in one single dismax with mm forced to 1.
301
+ def to_one_dismax_query(local_params)
309
302
  build_nested_query(list, local_params.merge(:mm => "1"))
310
303
  end
311
-
312
- def to_multi_queries(local_params)
313
- "( " +
304
+
305
+ def to_multi_queries(local_params)
306
+ "( " +
314
307
  list.collect do |i|
315
- if i.kind_of?(NotExpression) || (i.respond_to?(:simple_pure_negative?) && i.simple_pure_negative?)
308
+ if i.is_a?(NotExpression) || (i.respond_to?(:simple_pure_negative?) && i.simple_pure_negative?)
316
309
  # need special handling to work around Solr 1.4.1's lack of handling
317
310
  # of pure negative in an OR
318
311
  "(*:* AND #{i.to_query(local_params)})"
319
312
  else
320
313
  i.to_query(local_params)
321
- end
314
+ end
322
315
  end.join(" OR ") +
323
- " )"
316
+ " )"
324
317
  end
325
-
318
+
326
319
  # convenient logical property here, not(a OR b) === not(a) AND not(b)
327
320
  def negate
328
- AndList.new( list.collect {|n| n.negate})
321
+ AndList.new(list.collect { |n| n.negate })
329
322
  end
330
-
331
323
  end
332
-
333
-
334
- class NotExpression
324
+
325
+ class NotExpression
335
326
  def initialize(exp)
336
327
  self.operand = exp
337
328
  end
338
329
  attr_accessor :operand
339
-
330
+
340
331
  # We have to do the weird thing with *:* AND NOT (real thing), because
341
332
  # Solr 1.4.1 seems not to be able to handle "x OR NOT y" otherwise, at least
342
333
  # in some cases, but does fine with
343
334
  # "x OR (*:* AND NOT y)", which should mean the same thing.
344
335
  def to_query(solr_params)
345
336
  # rescue double-nots to not treat them crazy-like and make the query
346
- # more work for Solr than it needs to be with a double-negative.
347
- if operand.kind_of?(NotExpression)
337
+ # more work for Solr than it needs to be with a double-negative.
338
+ if operand.is_a?(NotExpression)
348
339
  operand.operand.to_query(solr_params)
349
340
  else
350
341
  "NOT " + operand.to_query(solr_params)
351
342
  end
352
343
  end
353
-
344
+
354
345
  def can_embed?
355
346
  false
356
347
  end
357
-
348
+
358
349
  def negate
359
350
  operand
360
351
  end
361
352
  end
362
-
353
+
363
354
  class MandatoryClause < Node
364
355
  attr_accessor :operand
365
356
  def initialize(v)
366
357
  self.operand = v
367
358
  end
368
-
359
+
369
360
  def can_embed?
370
- #right now '+' clauses only apply to terms/phrases
371
- #which we can embed with a + in front.
361
+ # right now '+' clauses only apply to terms/phrases
362
+ # which we can embed with a + in front.
372
363
  true
373
364
  end
365
+
374
366
  def to_embed
375
367
  '+' + operand.to_embed
376
368
  end
377
369
 
378
370
  # negating mandatory to excluded is decent semantics, although
379
- # it's not strictly 'true', it's a choice.
371
+ # it's not strictly 'true', it's a choice.
380
372
  def negate
381
- ExcludedClause.new( operand )
373
+ ExcludedClause.new(operand)
382
374
  end
383
375
  end
384
-
376
+
385
377
  class ExcludedClause < Node
386
378
  attr_accessor :operand
387
-
379
+
388
380
  def initialize(v)
389
381
  self.operand = v
390
- end
391
-
382
+ end
383
+
392
384
  def can_embed?
393
- #right now '-' clauses only apply to terms/phrases, which
394
- #we can embed with a '-' in front.
385
+ # right now '-' clauses only apply to terms/phrases, which
386
+ # we can embed with a '-' in front.
395
387
  true
396
388
  end
397
-
389
+
398
390
  def to_embed
399
391
  '-' + operand.to_embed
400
392
  end
401
-
393
+
402
394
  # negating excluded to mandatory is a pretty decent choice
403
395
  def negate
404
- MandatoryClause.new( operand )
396
+ MandatoryClause.new(operand)
405
397
  end
406
-
398
+
407
399
  def simple_pure_negative?
408
400
  true
409
401
  end
410
-
411
402
  end
412
-
413
-
403
+
414
404
  class Phrase < Node
415
405
  attr_accessor :value
416
-
406
+
417
407
  def initialize(string)
418
408
  self.value = string
419
409
  end
420
-
410
+
421
411
  def can_embed?
422
412
  true
423
413
  end
424
-
414
+
425
415
  def to_embed
426
416
  '"' + value + '"'
427
417
  end
428
-
418
+
429
419
  def negate
430
420
  ExcludedClause.new(self)
431
421
  end
432
422
  end
433
-
423
+
434
424
  class Term < Node
435
- attr_accessor :value
436
-
425
+ attr_accessor :value
426
+
437
427
  def initialize(string)
438
428
  self.value = string
439
429
  end
@@ -441,15 +431,13 @@ module ParsingNesting::Tree
441
431
  def can_embed?
442
432
  true
443
433
  end
444
-
434
+
445
435
  def to_embed
446
436
  value
447
- end
448
-
437
+ end
438
+
449
439
  def negate
450
440
  ExcludedClause.new(self)
451
441
  end
452
442
  end
453
443
  end
454
-
455
-