ruby-marc-spec 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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +18 -0
  3. data/.gitignore +388 -0
  4. data/.gitmodules +3 -0
  5. data/.idea/codeStyles/codeStyleConfig.xml +5 -0
  6. data/.idea/go.imports.xml +6 -0
  7. data/.idea/inspectionProfiles/Project_Default.xml +23 -0
  8. data/.idea/marc_spec.iml +102 -0
  9. data/.idea/misc.xml +6 -0
  10. data/.idea/modules.xml +8 -0
  11. data/.idea/templateLanguages.xml +6 -0
  12. data/.idea/vcs.xml +7 -0
  13. data/.rubocop.yml +269 -0
  14. data/.ruby-version +1 -0
  15. data/.simplecov +8 -0
  16. data/CHANGES.md +3 -0
  17. data/Gemfile +6 -0
  18. data/LICENSE.md +21 -0
  19. data/README.md +172 -0
  20. data/Rakefile +20 -0
  21. data/lib/.rubocop.yml +5 -0
  22. data/lib/marc/spec/module_info.rb +14 -0
  23. data/lib/marc/spec/parsing/closed_int_range.rb +28 -0
  24. data/lib/marc/spec/parsing/closed_lc_alpha_range.rb +28 -0
  25. data/lib/marc/spec/parsing/parser.rb +213 -0
  26. data/lib/marc/spec/parsing.rb +1 -0
  27. data/lib/marc/spec/queries/al_num_range.rb +105 -0
  28. data/lib/marc/spec/queries/applicable.rb +18 -0
  29. data/lib/marc/spec/queries/character_spec.rb +81 -0
  30. data/lib/marc/spec/queries/comparison_string.rb +45 -0
  31. data/lib/marc/spec/queries/condition.rb +133 -0
  32. data/lib/marc/spec/queries/condition_context.rb +49 -0
  33. data/lib/marc/spec/queries/dsl.rb +80 -0
  34. data/lib/marc/spec/queries/indicator_value.rb +77 -0
  35. data/lib/marc/spec/queries/operator.rb +129 -0
  36. data/lib/marc/spec/queries/part.rb +63 -0
  37. data/lib/marc/spec/queries/position.rb +59 -0
  38. data/lib/marc/spec/queries/position_or_range.rb +27 -0
  39. data/lib/marc/spec/queries/query.rb +94 -0
  40. data/lib/marc/spec/queries/query_executor.rb +52 -0
  41. data/lib/marc/spec/queries/selector.rb +12 -0
  42. data/lib/marc/spec/queries/subfield.rb +88 -0
  43. data/lib/marc/spec/queries/subfield_value.rb +63 -0
  44. data/lib/marc/spec/queries/tag.rb +107 -0
  45. data/lib/marc/spec/queries/transform.rb +154 -0
  46. data/lib/marc/spec/queries.rb +1 -0
  47. data/lib/marc/spec.rb +32 -0
  48. data/rakelib/.rubocop.yml +19 -0
  49. data/rakelib/bundle.rake +8 -0
  50. data/rakelib/coverage.rake +11 -0
  51. data/rakelib/gem.rake +54 -0
  52. data/rakelib/parser_specs/formatter.rb +31 -0
  53. data/rakelib/parser_specs/parser_specs.rb.txt.erb +35 -0
  54. data/rakelib/parser_specs/rule.rb +95 -0
  55. data/rakelib/parser_specs/suite.rb +91 -0
  56. data/rakelib/parser_specs/test.rb +97 -0
  57. data/rakelib/parser_specs.rb +1 -0
  58. data/rakelib/rubocop.rake +18 -0
  59. data/rakelib/spec.rake +27 -0
  60. data/ruby-marc-spec.gemspec +42 -0
  61. data/spec/.rubocop.yml +46 -0
  62. data/spec/README.md +16 -0
  63. data/spec/data/b23161018-sru.xml +182 -0
  64. data/spec/data/sandburg.xml +82 -0
  65. data/spec/generated/char_indicator_spec.rb +174 -0
  66. data/spec/generated/char_spec.rb +113 -0
  67. data/spec/generated/comparison_string_spec.rb +74 -0
  68. data/spec/generated/field_tag_spec.rb +156 -0
  69. data/spec/generated/index_char_spec.rb +669 -0
  70. data/spec/generated/index_indicator_spec.rb +174 -0
  71. data/spec/generated/index_spec.rb +113 -0
  72. data/spec/generated/index_sub_spec_spec.rb +1087 -0
  73. data/spec/generated/indicators_spec.rb +75 -0
  74. data/spec/generated/position_or_range_spec.rb +110 -0
  75. data/spec/generated/sub_spec_spec.rb +208 -0
  76. data/spec/generated/sub_spec_sub_spec_spec.rb +1829 -0
  77. data/spec/generated/subfield_char_spec.rb +405 -0
  78. data/spec/generated/subfield_range_range_spec.rb +48 -0
  79. data/spec/generated/subfield_range_spec.rb +87 -0
  80. data/spec/generated/subfield_range_sub_spec_spec.rb +214 -0
  81. data/spec/generated/subfield_tag_range_spec.rb +477 -0
  82. data/spec/generated/subfield_tag_sub_spec_spec.rb +3216 -0
  83. data/spec/generated/subfield_tag_tag_spec.rb +5592 -0
  84. data/spec/marc/spec/parsing/closed_int_range_spec.rb +49 -0
  85. data/spec/marc/spec/parsing/closed_lc_alpha_range_spec.rb +49 -0
  86. data/spec/marc/spec/parsing/parser_spec.rb +545 -0
  87. data/spec/marc/spec/queries/al_num_range_spec.rb +114 -0
  88. data/spec/marc/spec/queries/character_spec_spec.rb +28 -0
  89. data/spec/marc/spec/queries/comparison_string_spec.rb +28 -0
  90. data/spec/marc/spec/queries/indicator_value_spec.rb +28 -0
  91. data/spec/marc/spec/queries/query_spec.rb +200 -0
  92. data/spec/marc/spec/queries/subfield_spec.rb +92 -0
  93. data/spec/marc/spec/queries/subfield_value_spec.rb +31 -0
  94. data/spec/marc/spec/queries/tag_spec.rb +144 -0
  95. data/spec/marc/spec/queries/transform_spec.rb +459 -0
  96. data/spec/marc_spec_spec.rb +247 -0
  97. data/spec/scratch_spec.rb +112 -0
  98. data/spec/spec_helper.rb +23 -0
  99. metadata +341 -0
@@ -0,0 +1,459 @@
1
+ require 'spec_helper'
2
+
3
+ module MARC::Spec
4
+ module Queries
5
+ describe Transform do
6
+ include DSL
7
+
8
+ attr_reader :parser
9
+ attr_reader :xform
10
+
11
+ before(:each) do
12
+ @parser = Parsing::Parser.new
13
+ @xform = Transform.new
14
+ end
15
+
16
+ # ------------------------------------------------------------
17
+ # Helper methods
18
+
19
+ def check_all(expecteds)
20
+ aggregate_failures do
21
+ expecteds.each do |input_str, expected|
22
+ parse_tree = parser.parse(input_str)
23
+ actual = xform.apply(parse_tree)
24
+ expect(actual).to eq(expected), -> { failure_msg_for(input_str, actual, expected, parse_tree) }
25
+ end
26
+ end
27
+ end
28
+
29
+ def failure_msg_for(input_str, actual, expected, parse_tree)
30
+ [
31
+ input_str.inspect,
32
+ "expected: \t#{expected}",
33
+ " \t#{expected.inspect}",
34
+ "actual: \t#{actual}",
35
+ " \t#{actual.inspect}",
36
+ "parse_tree:\t#{parse_tree}"
37
+ ].join("\n\t")
38
+ end
39
+
40
+ # ------------------------------------------------------------
41
+ # Atoms
42
+
43
+ describe 'position atom' do
44
+ it 'returns a Position' do
45
+ parse_tree = { pos: '17' }
46
+ result = xform.apply(parse_tree)
47
+ expect(result).to be_a(Position)
48
+ expect(result.position).to eq(17)
49
+ end
50
+ end
51
+
52
+ describe 'range atom' do
53
+ it 'returns an AlphanumericRange' do
54
+ parse_tree = { from: '11', to: '17' }
55
+ result = xform.apply(parse_tree)
56
+ expect(result).to be_a(AlNumRange)
57
+ expect(result.from).to eq(11)
58
+ expect(result.to).to eq(17)
59
+ end
60
+ end
61
+
62
+ describe 'condition atom' do
63
+ it 'returns a Condition' do
64
+ parse_tree = { left: nil, operator: '?', right: { tag: '956' } }
65
+ result = xform.apply(parse_tree)
66
+ expect(result).to be_a(Condition)
67
+ expect(result.left).to be_nil
68
+ expect(result.operator).to eq(Operator::EXIST)
69
+ expect(result.right).to eq(q(tag('956')))
70
+ end
71
+ end
72
+
73
+ # ------------------------------------------------------------
74
+ # Applicables
75
+
76
+ describe 'applicables' do
77
+
78
+ describe 'fieldSpec' do
79
+ describe 'fieldTag' do
80
+ it 'returns a Tag' do
81
+ expecteds = {
82
+ '856' => tag('856'),
83
+ '.56' => tag('.56'),
84
+ '856[3]' => tag('856', pos(3))
85
+ }
86
+ check_all(expecteds)
87
+ end
88
+ end
89
+
90
+ describe 'fieldTag w/characterSpec' do
91
+ it 'returns a FixedField' do
92
+ expecteds = {
93
+ '856/3-12' => q(tag('856'), s: cspec(rng(3, 12))),
94
+ '856[3]/3-12' => q(tag('856', pos(3)), s: cspec(rng(3, 12)))
95
+ }
96
+
97
+ check_all(expecteds)
98
+ end
99
+ end
100
+ end
101
+
102
+ describe 'subfieldSpec' do
103
+
104
+ describe 'fieldTag w/subfieldSpec' do
105
+ context 'single subfield' do
106
+ it 'returns a VarField' do
107
+ expecteds = {
108
+ '856$u' => q(tag('856'), s: vfv(sf('u'))),
109
+ '856[3]$u' => q(tag('856', pos(3)), s: vfv(sf('u'))),
110
+ '856$u[3]' => q(tag('856'), s: vfv(sf('u', pos(3)))),
111
+ '856$u[3]/1-2' => q(tag('856'), s: vfv(sfv(sf('u', pos(3)), rng(1, 2)))),
112
+ '856$u/1-2' => q(tag('856'), s: vfv(sfv(sf('u'), rng(1, 2))))
113
+ }
114
+
115
+ check_all(expecteds)
116
+ end
117
+ end
118
+
119
+ context 'numeric subfield range' do
120
+ it 'returns a VarField' do
121
+ range_str = '4-5'
122
+
123
+ expecteds = {
124
+ "856$#{range_str}" => q(tag('856'), s: vfv(sf(rng(4, 5)))),
125
+ "856[3]$#{range_str}" => q(tag('856', pos(3)), s: vfv(sf(rng(4, 5)))),
126
+ "856$#{range_str}[3]" => q(tag('856'), s: vfv(sf(rng(4, 5), pos(3)))),
127
+ "856$#{range_str}[3]/1-2" => q(tag('856'), s: vfv(sfv(sf(rng(4, 5), pos(3)), rng(1, 2)))),
128
+ "856$#{range_str}/1-2" => q(tag('856'), s: vfv(sfv(sf(rng(4, 5)), rng(1, 2))))
129
+ }
130
+
131
+ check_all(expecteds)
132
+ end
133
+ end
134
+
135
+ context 'alphabetical subfield range' do
136
+ it 'returns a VarField' do
137
+ range_str = 'd-g'
138
+
139
+ expecteds = {
140
+ "856$#{range_str}" => q(tag('856'), s: vfv(sf(rng('d', 'g')))),
141
+ "856[3]$#{range_str}" => q(tag('856', pos(3)), s: vfv(sf(rng('d', 'g')))),
142
+ "856$#{range_str}[3]" => q(tag('856'), s: vfv(sf(rng('d', 'g'), pos(3)))),
143
+ "856$#{range_str}[3]/1-2" => q(tag('856'), s: vfv(sfv(sf(rng('d', 'g'), pos(3)), rng(1, 2)))),
144
+ "856$#{range_str}/1-2" => q(tag('856'), s: vfv(sfv(sf(rng('d', 'g')), rng(1, 2))))
145
+ }
146
+
147
+ check_all(expecteds)
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ describe 'indicatorSpec' do
154
+ describe 'fieldTag w/indicator' do
155
+ it 'returns an Indicator' do
156
+ expecteds = {
157
+ '856^1' => q(tag('856'), s: indv(1)),
158
+ '856[3-#]^2' => q(tag('856', rng(3)), s: indv(2))
159
+ }
160
+ check_all(expecteds)
161
+ end
162
+ end
163
+ end
164
+
165
+ end
166
+
167
+ # ------------------------------------------------------------
168
+ # Conditions
169
+
170
+ describe 'conditions' do
171
+ describe 'subSpec' do
172
+ describe 'unary condition' do
173
+ it 'returns a Condition' do
174
+ *args = '?', tag('956')
175
+ expecteds = {
176
+ '...{?956}' => q(tag('...'), c: c(*args))
177
+ }
178
+ check_all(expecteds)
179
+ end
180
+ end
181
+ end
182
+
183
+ end
184
+
185
+ # ------------------------------------------------------------
186
+ # Complete queries
187
+
188
+ describe 'queries' do
189
+ describe 'subSpec' do
190
+ let(:tag956) { tag('956') }
191
+ let(:vf956u) { q(tag956, s: vfv(sf('u'))) }
192
+ let(:vf956u_exist) do
193
+ *args = '?', vf956u
194
+ c(*args)
195
+ end
196
+
197
+ let(:tag856) { tag('856') }
198
+ let(:vf856u) { q(tag856, s: vfv(sf('u'))) }
199
+ let(:vf856u_exist) do
200
+ *args = '?', vf856u
201
+ c(*args)
202
+ end
203
+
204
+ let(:vf956u_eq_vf856u) do
205
+ *args = vf956u, '=', vf856u
206
+ c(*args)
207
+ end
208
+
209
+ # TODO: handle implicit left during transform?
210
+ let(:implicit_eq_vf856u) do
211
+ *args = '=', vf856u
212
+ c(*args)
213
+ end
214
+
215
+ # TODO: collapse to one set of expectations
216
+ context 'single subSpec' do
217
+ it 'returns a Query for an implicit unary ?' do
218
+ expecteds = {
219
+ '956{956$u}' => q(tag956, c: vf956u_exist),
220
+ '956^1{956$u}' => q(tag956, s: indv(1), c: vf956u_exist)
221
+ }
222
+ check_all(expecteds)
223
+ end
224
+
225
+ it 'returns a Query for an explicit unary ?' do
226
+ expecteds = {
227
+ '956{?956$u}' => q(tag956, c: vf956u_exist),
228
+ '956^1{?956$u}' => q(tag956, s: indv(1), c: vf956u_exist)
229
+ }
230
+ check_all(expecteds)
231
+ end
232
+
233
+ it 'returns a Query for a binary =' do
234
+ expecteds = {
235
+ '956{956$u=856$u}' => q(tag956, c: vf956u_eq_vf856u)
236
+ }
237
+ check_all(expecteds)
238
+ end
239
+
240
+ it 'returns a Query for a unary =' do
241
+ expecteds = {
242
+ '956$u{=856$u}' => q(tag956, s: vfv(sf('u')), c: implicit_eq_vf856u)
243
+ }
244
+ check_all(expecteds)
245
+ end
246
+ end
247
+
248
+ context 'repeated subSpecs' do
249
+ it 'returns a Query' do
250
+ expecteds = {
251
+ '956$u{?956$u}{956$u=856$u}' => q(tag956, s: vfv(sf('u')), c: vf956u_exist.and(vf956u_eq_vf856u)),
252
+ '956$u{?956$u}{=856$u}' => q(tag956, s: vfv(sf('u')), c: vf956u_exist.and(implicit_eq_vf856u))
253
+ }
254
+ check_all(expecteds)
255
+ end
256
+ end
257
+
258
+ context 'chained subTerms' do
259
+ it 'returns a Query' do
260
+ expecteds = {
261
+ '956$u{?856$u|956$u=856$u}' => q(tag956, s: vfv(sf('u')), c: vf856u_exist.or(vf956u_eq_vf856u)),
262
+ '956$u{?856$u|=856$u}' => q(tag956, s: vfv(sf('u')), c: vf856u_exist.or(implicit_eq_vf856u))
263
+ }
264
+ check_all(expecteds)
265
+ end
266
+ end
267
+ end
268
+
269
+ describe 'comparisonString' do
270
+ it 'returns a Query' do
271
+ expecteds = {
272
+ '008/18{LDR/6=\t}' => q(tag('008'), s: cspec(pos(18)), c: c(
273
+ q(tag('LDR'), s: cspec(pos(6))),
274
+ '=',
275
+ cstr('t')
276
+ ))
277
+ }
278
+ check_all(expecteds)
279
+ end
280
+ end
281
+
282
+ describe 'subfieldSpec' do
283
+ it 'handles complex combinations of subfields and subspecs' do
284
+ expecteds = {
285
+ # cf. https://github.com/MARCspec/MARCspec/issues/30
286
+ '880$a{?$f}$1$2$b{=\\x}$c{$d=\\12}$e{880$f=\\q}' => q(
287
+ tag('880'),
288
+ sq: [
289
+ q(
290
+ s: sf('a'),
291
+ c: c('?', q(s: sf('f')))
292
+ ),
293
+ q(s: sf('1')),
294
+ q(s: sf('2')),
295
+ q(
296
+ s: sf('b'),
297
+ c: c('=', cstr('x'))
298
+ ),
299
+ q(
300
+ s: sf('c'),
301
+ c: c(q(s: sf('d')), '=', cstr('12'))
302
+ ),
303
+ q(
304
+ s: sf('e'),
305
+ c: c(q(tag('880'), s: sf('f')), '=', cstr('q'))
306
+ )
307
+ ]
308
+ )
309
+ }
310
+ check_all(expecteds)
311
+ end
312
+ end
313
+ end
314
+
315
+ # ------------------------------------------------------------
316
+ # Examples
317
+
318
+ # Examples from http://marcspec.github.io/MARCspec/marc-spec.html#general-form
319
+ describe 'examples' do
320
+ it '9.2 Reference to field data' do
321
+ examples = {
322
+ 'LDR' => tag('LDR'),
323
+ '00.' => tag('00.'),
324
+ '7..' => tag('7..'),
325
+ '100' => tag('100')
326
+ }
327
+ check_all(examples)
328
+ end
329
+
330
+ it '9.3 Reference to substring"' do
331
+ examples = {
332
+ 'LDR/0-4' => q(tag('LDR'), s: cspec(rng(0, 4))),
333
+ 'LDR/6' => q(tag('LDR'), s: cspec(pos(6))),
334
+ '007/0' => q(tag('007'), s: cspec(pos(0))),
335
+ '007/1-#' => q(tag('007'), s: cspec(rng(1))),
336
+ '007/#' => q(tag('007'), s: cspec(pos(nil))),
337
+ '245$a/#-1' =>
338
+ q(tag('245'), s: vfv(sfv(
339
+ sf('a'), rng(nil, 1)
340
+ )))
341
+
342
+ }
343
+ check_all(examples)
344
+ end
345
+
346
+ it '9.4 Reference to data content' do
347
+ sqs_abc = (%w[a b c].map { |code| q(s: vfv(sf(code))) })
348
+ sqs_dollar_underscore = %w[_ $].map { |code| q(s: vfv(sf(code))) }
349
+ examples = {
350
+ '245$a' => q(tag('245'), s: vfv(sf('a'))),
351
+ '245$a$b$c' => q(tag('245'), sq: sqs_abc),
352
+ '245$a-c' => q(tag('245'), s: vfv(sf(rng('a', 'c')))),
353
+ '...$_$$' => q(tag('...'), sq: sqs_dollar_underscore)
354
+ }
355
+ check_all(examples)
356
+ end
357
+
358
+ it '9.5 Reference to occurrence' do
359
+ examples = {
360
+ '300[0]' => tag('300', pos(0)),
361
+ '300[1]' => tag('300', pos(1)),
362
+ '300[0-2]' => tag('300', rng(0, 2)),
363
+ '300[1-#]' => tag('300', rng(1)),
364
+ '300[#]' => tag('300', pos(nil)),
365
+ '300[#-1]' => tag('300', rng(nil, 1))
366
+ }
367
+ check_all(examples)
368
+ end
369
+
370
+ it '9.6 Reference to indicator values' do
371
+ examples = {
372
+ '880^1' => q(tag('880'), s: indv(1)),
373
+ '880[1]^2' => q(tag('880', pos(1)), s: indv(2))
374
+ }
375
+ check_all(examples)
376
+ end
377
+
378
+ context '9.7 SubSpecs' do
379
+ it '9.7.1 General' do
380
+ vf020c_if_vf020a = q(
381
+ tag('020'),
382
+ s: vfv(sf('c')),
383
+ c: c('?', q(tag('020'), s: vfv(sf('a'))))
384
+ )
385
+
386
+ vf020z_unless_vf020a = q(
387
+ tag('020'),
388
+ s: vfv(sf('z')),
389
+ c: c('!', q(tag('020'), s: vfv(sf('a'))))
390
+ )
391
+
392
+ ldr7 = q(tag('LDR'), s: cspec(pos(7)))
393
+ ldr6 = q(tag('LDR'), s: cspec(pos(6)))
394
+
395
+ ff007_0 = q(tag('007'), s: cspec(pos(0)))
396
+
397
+ examples = {
398
+ '020$c{?020$a}' => vf020c_if_vf020a,
399
+ '020$c{020$c?020$a}' => vf020c_if_vf020a,
400
+ '020$z{!020$a}' => vf020z_unless_vf020a,
401
+ '020$z{020$z!020$a}' => vf020z_unless_vf020a,
402
+ '008/18{LDR/6=\\t}' => q(tag('008'), s: cspec(pos(18)), c: c(ldr6, '=', cstr('t'))),
403
+ '245$b{007/0=\\a|007/0=\\t}' => q(
404
+ tag('245'),
405
+ s: vfv(sf('b')),
406
+ c: any_c(
407
+ c(ff007_0, '=', cstr('a')),
408
+ c(ff007_0, '=', cstr('t'))
409
+ )
410
+ ),
411
+ '008/18{LDR/6=\\a}{LDR/7=\\a|LDR/7=\\c|LDR/7=\\d|LDR/7=\\m}' => q(
412
+ tag('008'),
413
+ s: cspec(pos(18)),
414
+ c: all_c(
415
+ c(ldr6, '=', cstr('a')),
416
+ any_c(
417
+ c(ldr7, '=', cstr('a')),
418
+ c(ldr7, '=', cstr('c')),
419
+ c(ldr7, '=', cstr('d')),
420
+ c(ldr7, '=', cstr('m'))
421
+ )
422
+ )
423
+ ),
424
+ '880$a{100$6~$6/3-5}{100$6~\880}' => q(
425
+ tag('880'),
426
+ s: vfv(sf('a')),
427
+ c: all_c(
428
+ c(
429
+ q(
430
+ tag('100'),
431
+ s: vfv(sf('6'))
432
+ ),
433
+ '~',
434
+ q(s: sfv(sf('6'), rng(3, 5)))
435
+ ),
436
+ c(
437
+ q(tag('100'), s: vfv(sf('6'))),
438
+ '~',
439
+ cstr('880')
440
+ )
441
+ )
442
+ )
443
+ }
444
+ check_all(examples)
445
+ end
446
+
447
+ it '9.7.2 Abbreviations' do
448
+ examples = {
449
+ '007[1]/3{/0=\\v}' => q(tag('007', pos(1)), s: cspec(pos(3)), c: c(q(s: cspec(pos('0'))), '=', cstr('v'))),
450
+ '007[1]/3{007[1]/0=\\v}' => q(tag('007', pos(1)), s: cspec(pos(3)), c: c(q(tag('007', pos(1)), s: cspec(pos(0))), '=', cstr('v'))),
451
+ '245$a{/#=\/}' => q(tag('245'), s: sf('a'), c: c(q(s: cspec(pos('#'))), '=', cstr('/')))
452
+ }
453
+ check_all(examples)
454
+ end
455
+ end
456
+ end
457
+ end
458
+ end
459
+ end
@@ -0,0 +1,247 @@
1
+ require 'spec_helper'
2
+
3
+ describe MARC::Spec do
4
+ attr_reader :marc_record
5
+
6
+ def failure_msg_for(query_str, actual, expected)
7
+ query = MARC::Spec.parse_query(query_str)
8
+ expected_str = expected.respond_to?(:map) ? expected.map(&:to_s) : expected.to_s
9
+ actual_str = actual.respond_to?(:map) ? actual.map(&:to_s) : actual.to_s
10
+ expected_inspect = expected.respond_to?(:map) ? expected.map(&:inspect) : expected.inspect
11
+ actual_inspect = actual.respond_to?(:map) ? actual.map(&:inspect) : actual.inspect
12
+ [
13
+ query_str.inspect,
14
+ "query: \t#{query.inspect}",
15
+ "expected: \t#{expected_str}",
16
+ " \t#{expected_inspect}",
17
+ "actual: \t#{actual_str}",
18
+ " \t#{actual_inspect}"
19
+ ].join("\n\t")
20
+ end
21
+
22
+ def subfield_codes(df)
23
+ df.subfields.map(&:code)
24
+ end
25
+
26
+ def subfields(df, code)
27
+ codes = Array(code)
28
+ df.subfields.select { |sf| codes.include?(sf.code) }
29
+ end
30
+
31
+ def check_all(examples)
32
+ aggregate_failures do
33
+ examples.each do |query_str, expected|
34
+ expected = [expected] unless expected.is_a?(Array)
35
+ actual = MARC::Spec.find(query_str, marc_record)
36
+ expect(actual).to eq(expected), -> { failure_msg_for(query_str, actual, expected) }
37
+ end
38
+ end
39
+ end
40
+
41
+ describe 'non-repeated subfields' do
42
+ before(:each) do
43
+ @marc_record = MARC::XMLReader.new('spec/data/sandburg.xml').first
44
+ end
45
+
46
+ # Examples from http://marcspec.github.io/MARCspec/marc-spec.html#general-form
47
+ describe 'examples' do
48
+ it '9.2 Reference to field data' do
49
+ examples = {
50
+ 'LDR' => marc_record.leader,
51
+ '00.' => marc_record.fields.select { |f| f.tag =~ /^00.$/ },
52
+ '7..' => marc_record.fields.select { |f| f.tag.start_with?('7') },
53
+ '100' => marc_record.fields('100')
54
+ }
55
+ check_all(examples)
56
+ end
57
+
58
+ it '9.3 Reference to substring' do
59
+ leader = marc_record.leader
60
+ _005 = marc_record['005']
61
+ examples = {
62
+ 'LDR/0-4' => leader[0..4],
63
+ 'LDR/6' => leader[6],
64
+ '005/0' => _005.value[0],
65
+ '005/1-#' => _005.value[1..],
66
+ '005/#' => _005.value[-1],
67
+ '005/#-1' => _005.value[-2..]
68
+ }
69
+ check_all(examples)
70
+ end
71
+
72
+ it '9.4 Reference to data content' do
73
+ _260 = marc_record['260']
74
+ examples = {
75
+ '260$a' => subfields(_260, 'a'),
76
+ '260$a$b$c' => subfields(_260, %w[a b c]),
77
+ '260$a-c' => subfields(_260, %w[a b c]),
78
+ '...$a$c' => marc_record.fields.each_with_object([]) do |f, sff|
79
+ next unless f.respond_to?(:subfields)
80
+
81
+ sff.concat(subfields(f, %w[a c]))
82
+ end
83
+ }
84
+ check_all(examples)
85
+ end
86
+
87
+ it '9.5 Reference to occurrence' do
88
+ _650s = marc_record.fields('650')
89
+ examples = {
90
+ '650' => _650s,
91
+ '650[0]' => _650s[0],
92
+ '650[1]' => _650s[1],
93
+ '650[0-2]' => _650s[0..2],
94
+ '650[1-#]' => _650s[1..],
95
+ '650[#-1]' => _650s[-2..],
96
+ '650[0]$a' => subfields(_650s[0], 'a')
97
+ }
98
+ check_all(examples)
99
+ end
100
+
101
+ it '9.6 Reference to indicator values' do
102
+ _650s = marc_record.fields('650')
103
+ examples = {
104
+ '650^1' => _650s.map(&:indicator1),
105
+ '650[1]^2' => _650s[1].indicator2
106
+ }
107
+ check_all(examples)
108
+ end
109
+
110
+ context '9.7 SubSpecs' do
111
+ context '9.7.1 General' do
112
+ it 'checking existence/nonexistence of fields' do
113
+ all_020c = subfields(marc_record['020'], 'c')
114
+ _650s = marc_record.fields('650')
115
+ _650s_with_x = _650s.select { |df| subfield_codes(df).include?('x') }
116
+ examples = {
117
+ '020$c{?020$a}' => all_020c,
118
+ '020$c{020$c?020$a}' => all_020c,
119
+ # 650 subfield a if that 650 also has a subfield x
120
+ '650$a{?$x}' => _650s_with_x.flat_map { |df| subfields(df, 'a') },
121
+ # all 650 subfields a if any 650 has a subfield x
122
+ '650$a{?650$x}' => _650s.flat_map { |df| subfields(df, 'a') },
123
+ '020$c{!020$z}' => all_020c,
124
+ '020$c{020$c!020$z}' => all_020c
125
+ }
126
+ check_all(examples)
127
+ end
128
+
129
+ it 'checking values against comparison strings' do
130
+ _008 = marc_record['008']
131
+ _245 = marc_record['245']
132
+ examples = {
133
+ '008/22{LDR/6=\a}' => _008.value[22],
134
+ '008/22{LDR/6=\b}' => [],
135
+ '245$c{008/22=\j}' => subfields(_245, 'c'),
136
+ '245$c{008/22=\q}' => [],
137
+ '245$c{008/22=\q|008/22=\j}' => subfields(_245, 'c'),
138
+ '245$c{LDR/6=\a}{008/22=\q|008/22=\j}' => subfields(_245, 'c'),
139
+ '245$c{LDR/6=\q}{008/22=\q|008/22=\j}' => [],
140
+ '245$c{008/22=\q|008/22=\j}{LDR/6=\q}' => []
141
+ }
142
+ check_all(examples)
143
+ end
144
+ end
145
+
146
+ context '9.7.2 Abbreviations' do
147
+ it 'matches against a fixed field value substring' do
148
+ _008 = marc_record['008']
149
+ examples = {
150
+ '008/3{/0=\9}' => _008.value[3],
151
+ '008/3{008/0=\9}' => _008.value[3],
152
+ '008/3{/0=\a}' => [],
153
+ '008[0]/3{/0=\9}' => _008.value[3],
154
+ '008[0]/3{008[0]/0=\9}' => _008.value[3],
155
+ '008[0]/3{/0=\a}' => []
156
+ }
157
+ check_all(examples)
158
+ end
159
+
160
+ it 'matches against a subfield value' do
161
+ _020 = marc_record['020']
162
+ all_020_c = subfields(_020, 'c')
163
+ _245 = marc_record['245']
164
+ all_245_a = subfields(_245, 'a')
165
+ examples = {
166
+ '020$c{$a}' => all_020_c,
167
+ '020$c{020$a}' => all_020_c,
168
+ '020$c{$z}' => [],
169
+ '020$c{020$z}' => [],
170
+ '245$a{/#=\\/}' => all_245_a,
171
+ '245$a{/#!=\\;}' => all_245_a,
172
+ '245$a{/#=\\;}' => [],
173
+ '245$a{245$a/#=\\/}' => all_245_a,
174
+ '245$a{245$a/#=\\;}' => []
175
+ }
176
+ check_all(examples)
177
+ end
178
+
179
+ it 'matches against an indicator value' do
180
+ _650s = marc_record.fields('650')
181
+ examples = {
182
+ '650[0]{^2=\\0}' => _650s.first,
183
+ '650[0]{^2!=\\1}' => _650s.first,
184
+ '650[0]{^1=\\0}' => [],
185
+ '650[0]{$x~\\poetry}{^2=\\0}' => _650s.first,
186
+ '650[0]{$x~\\prose}{^2=\\0}' => [],
187
+ '650[0]{$x!~\\prose}{^2=\\0}' => _650s.first,
188
+ '650[0]{$x~\\poetry}{^2=\\1}' => [],
189
+ '650[0]{^2=\\0}{$x~\\poetry}' => _650s.first,
190
+ '650[0]{^2=\\0}{$x~\\prose}' => [],
191
+ '650[0]{^2=\\0}{$x!~\\prose}' => _650s.first,
192
+ '650[0]{^2=\\1}{$x~\\poetry}' => []
193
+ }
194
+ check_all(examples)
195
+ end
196
+
197
+ context '9.7.2.1 implicit abbreviations' do
198
+ it 'matches against an implicit subfield' do
199
+ _245 = marc_record['245']
200
+ all_245_a = subfields(_245, 'a')
201
+ examples = {
202
+ '245$a{/0-9=\\Arithmetic}' => all_245_a,
203
+ '245$a{$a/0-9=\\Arithmetic}' => all_245_a,
204
+ '245$a{245$a/0-9=\\Arithmetic}' => all_245_a,
205
+ '245$a{/0-9=\\Mathematic}' => [],
206
+ '245$a{$a/0-9=\\Mathematic}' => [],
207
+ '245$a{245$a/0-9=\\Mathematic}' => []
208
+ }
209
+ check_all(examples)
210
+ end
211
+ end
212
+
213
+ context '10.4 SubSpec validation' do
214
+ describe '~' do
215
+ it 'matches any left element against any right element' do
216
+ _020 = marc_record['020']
217
+ examples = {
218
+ '020{001/0-3~...^1}' => _020
219
+ }
220
+ check_all(examples)
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
228
+
229
+ describe 'repeated subfields' do
230
+ before(:each) do
231
+ @marc_record = MARC::XMLReader.new('spec/data/b23161018-sru.xml').first
232
+ end
233
+
234
+ describe 'examples' do
235
+ it '9.5 Reference to occurrence' do
236
+ _998 = marc_record['998']
237
+ all_998a = subfields(_998, 'a')
238
+ examples = {
239
+ '998$a[0]' => all_998a.first,
240
+ '998$a[#]' => all_998a.last,
241
+ '998$a[#-1]' => all_998a[-2..]
242
+ }
243
+ check_all(examples)
244
+ end
245
+ end
246
+ end
247
+ end