ruby-marc-spec 0.1.0

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