ruby-marc-spec 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/build.yml +18 -0
- data/.gitignore +388 -0
- data/.gitmodules +3 -0
- data/.idea/codeStyles/codeStyleConfig.xml +5 -0
- data/.idea/go.imports.xml +6 -0
- data/.idea/inspectionProfiles/Project_Default.xml +23 -0
- data/.idea/marc_spec.iml +102 -0
- data/.idea/misc.xml +6 -0
- data/.idea/modules.xml +8 -0
- data/.idea/templateLanguages.xml +6 -0
- data/.idea/vcs.xml +7 -0
- data/.rubocop.yml +269 -0
- data/.ruby-version +1 -0
- data/.simplecov +8 -0
- data/CHANGES.md +3 -0
- data/Gemfile +6 -0
- data/LICENSE.md +21 -0
- data/README.md +172 -0
- data/Rakefile +20 -0
- data/lib/.rubocop.yml +5 -0
- data/lib/marc/spec/module_info.rb +14 -0
- data/lib/marc/spec/parsing/closed_int_range.rb +28 -0
- data/lib/marc/spec/parsing/closed_lc_alpha_range.rb +28 -0
- data/lib/marc/spec/parsing/parser.rb +213 -0
- data/lib/marc/spec/parsing.rb +1 -0
- data/lib/marc/spec/queries/al_num_range.rb +105 -0
- data/lib/marc/spec/queries/applicable.rb +18 -0
- data/lib/marc/spec/queries/character_spec.rb +81 -0
- data/lib/marc/spec/queries/comparison_string.rb +45 -0
- data/lib/marc/spec/queries/condition.rb +133 -0
- data/lib/marc/spec/queries/condition_context.rb +49 -0
- data/lib/marc/spec/queries/dsl.rb +80 -0
- data/lib/marc/spec/queries/indicator_value.rb +77 -0
- data/lib/marc/spec/queries/operator.rb +129 -0
- data/lib/marc/spec/queries/part.rb +63 -0
- data/lib/marc/spec/queries/position.rb +59 -0
- data/lib/marc/spec/queries/position_or_range.rb +27 -0
- data/lib/marc/spec/queries/query.rb +94 -0
- data/lib/marc/spec/queries/query_executor.rb +52 -0
- data/lib/marc/spec/queries/selector.rb +12 -0
- data/lib/marc/spec/queries/subfield.rb +88 -0
- data/lib/marc/spec/queries/subfield_value.rb +63 -0
- data/lib/marc/spec/queries/tag.rb +107 -0
- data/lib/marc/spec/queries/transform.rb +154 -0
- data/lib/marc/spec/queries.rb +1 -0
- data/lib/marc/spec.rb +32 -0
- data/rakelib/.rubocop.yml +19 -0
- data/rakelib/bundle.rake +8 -0
- data/rakelib/coverage.rake +11 -0
- data/rakelib/gem.rake +54 -0
- data/rakelib/parser_specs/formatter.rb +31 -0
- data/rakelib/parser_specs/parser_specs.rb.txt.erb +35 -0
- data/rakelib/parser_specs/rule.rb +95 -0
- data/rakelib/parser_specs/suite.rb +91 -0
- data/rakelib/parser_specs/test.rb +97 -0
- data/rakelib/parser_specs.rb +1 -0
- data/rakelib/rubocop.rake +18 -0
- data/rakelib/spec.rake +27 -0
- data/ruby-marc-spec.gemspec +42 -0
- data/spec/.rubocop.yml +46 -0
- data/spec/README.md +16 -0
- data/spec/data/b23161018-sru.xml +182 -0
- data/spec/data/sandburg.xml +82 -0
- data/spec/generated/char_indicator_spec.rb +174 -0
- data/spec/generated/char_spec.rb +113 -0
- data/spec/generated/comparison_string_spec.rb +74 -0
- data/spec/generated/field_tag_spec.rb +156 -0
- data/spec/generated/index_char_spec.rb +669 -0
- data/spec/generated/index_indicator_spec.rb +174 -0
- data/spec/generated/index_spec.rb +113 -0
- data/spec/generated/index_sub_spec_spec.rb +1087 -0
- data/spec/generated/indicators_spec.rb +75 -0
- data/spec/generated/position_or_range_spec.rb +110 -0
- data/spec/generated/sub_spec_spec.rb +208 -0
- data/spec/generated/sub_spec_sub_spec_spec.rb +1829 -0
- data/spec/generated/subfield_char_spec.rb +405 -0
- data/spec/generated/subfield_range_range_spec.rb +48 -0
- data/spec/generated/subfield_range_spec.rb +87 -0
- data/spec/generated/subfield_range_sub_spec_spec.rb +214 -0
- data/spec/generated/subfield_tag_range_spec.rb +477 -0
- data/spec/generated/subfield_tag_sub_spec_spec.rb +3216 -0
- data/spec/generated/subfield_tag_tag_spec.rb +5592 -0
- data/spec/marc/spec/parsing/closed_int_range_spec.rb +49 -0
- data/spec/marc/spec/parsing/closed_lc_alpha_range_spec.rb +49 -0
- data/spec/marc/spec/parsing/parser_spec.rb +545 -0
- data/spec/marc/spec/queries/al_num_range_spec.rb +114 -0
- data/spec/marc/spec/queries/character_spec_spec.rb +28 -0
- data/spec/marc/spec/queries/comparison_string_spec.rb +28 -0
- data/spec/marc/spec/queries/indicator_value_spec.rb +28 -0
- data/spec/marc/spec/queries/query_spec.rb +200 -0
- data/spec/marc/spec/queries/subfield_spec.rb +92 -0
- data/spec/marc/spec/queries/subfield_value_spec.rb +31 -0
- data/spec/marc/spec/queries/tag_spec.rb +144 -0
- data/spec/marc/spec/queries/transform_spec.rb +459 -0
- data/spec/marc_spec_spec.rb +247 -0
- data/spec/scratch_spec.rb +112 -0
- data/spec/spec_helper.rb +23 -0
- metadata +341 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'marc/spec/queries/al_num_range'
|
2
|
+
require 'marc/spec/queries/position'
|
3
|
+
require 'marc/spec/queries/comparison_string'
|
4
|
+
require 'marc/spec/queries/condition'
|
5
|
+
|
6
|
+
require 'marc/spec/queries/character_spec'
|
7
|
+
require 'marc/spec/queries/indicator_value'
|
8
|
+
require 'marc/spec/queries/operator'
|
9
|
+
require 'marc/spec/queries/query'
|
10
|
+
require 'marc/spec/queries/subfield'
|
11
|
+
require 'marc/spec/queries/subfield_value'
|
12
|
+
require 'marc/spec/queries/tag'
|
13
|
+
|
14
|
+
module MARC
|
15
|
+
module Spec
|
16
|
+
module Queries
|
17
|
+
module DSL
|
18
|
+
|
19
|
+
def all_c(*c)
|
20
|
+
Condition.all_of(*c)
|
21
|
+
end
|
22
|
+
|
23
|
+
def any_c(*c)
|
24
|
+
Condition.any_of(*c)
|
25
|
+
end
|
26
|
+
|
27
|
+
def vfv(sf)
|
28
|
+
sf
|
29
|
+
end
|
30
|
+
|
31
|
+
def rng(from, to = nil)
|
32
|
+
AlNumRange.new(from, to)
|
33
|
+
end
|
34
|
+
|
35
|
+
def q(t = nil, s: nil, c: nil, sq: [])
|
36
|
+
Query.new(tag: t, selector: s, condition: c, subqueries: sq)
|
37
|
+
end
|
38
|
+
|
39
|
+
def pos(p)
|
40
|
+
Position.new(p)
|
41
|
+
end
|
42
|
+
|
43
|
+
def cstr(s)
|
44
|
+
ComparisonString.new(s)
|
45
|
+
end
|
46
|
+
|
47
|
+
def c(*args)
|
48
|
+
case args.size
|
49
|
+
when 2
|
50
|
+
Condition.new(args[0], right: args[1])
|
51
|
+
when 3
|
52
|
+
Condition.new(args[1], left: args[0], right: args[2])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def cspec(chars)
|
57
|
+
# noinspection RubyArgCount
|
58
|
+
CharacterSpec.new(chars)
|
59
|
+
end
|
60
|
+
|
61
|
+
def indv(ind)
|
62
|
+
IndicatorValue.new(ind)
|
63
|
+
end
|
64
|
+
|
65
|
+
def sf(code, index = nil)
|
66
|
+
Subfield.new(code, index: index)
|
67
|
+
end
|
68
|
+
|
69
|
+
def sfv(sf, cspc = nil)
|
70
|
+
cspc = cspec(cspc) if cspc.is_a?(PositionOrRange)
|
71
|
+
SubfieldValue.new(sf, cspc)
|
72
|
+
end
|
73
|
+
|
74
|
+
def tag(t, index = nil)
|
75
|
+
Tag.new(t, index)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'marc/spec/queries/selector'
|
2
|
+
|
3
|
+
module MARC
|
4
|
+
module Spec
|
5
|
+
module Queries
|
6
|
+
class IndicatorValue
|
7
|
+
include Selector
|
8
|
+
|
9
|
+
# ------------------------------------------------------------
|
10
|
+
# Constants
|
11
|
+
|
12
|
+
VALID_INDICATORS = [1, 2].freeze
|
13
|
+
|
14
|
+
# ------------------------------------------------------------
|
15
|
+
# Attributes
|
16
|
+
|
17
|
+
attr_reader :tag, :ind
|
18
|
+
|
19
|
+
# ------------------------------------------------------------
|
20
|
+
# Initializer
|
21
|
+
|
22
|
+
def initialize(ind)
|
23
|
+
@ind = valid_indicator(ind)
|
24
|
+
end
|
25
|
+
|
26
|
+
# ------------------------------------------------------------
|
27
|
+
# Object overrides
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"^#{ind}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# ------------------------------
|
34
|
+
# Applicable
|
35
|
+
|
36
|
+
def can_apply?(marc_obj)
|
37
|
+
%i[indicator1 indicator2].all? { |m| marc_obj.respond_to?(m) }
|
38
|
+
end
|
39
|
+
|
40
|
+
# ------------------------------------------------------------
|
41
|
+
# Protected methods
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
# ------------------------------
|
46
|
+
# Applicable
|
47
|
+
|
48
|
+
def do_apply(marc_obj)
|
49
|
+
ind_val = ind_val_for(marc_obj)
|
50
|
+
ind_val ? [ind_val] : []
|
51
|
+
end
|
52
|
+
|
53
|
+
def equality_attrs
|
54
|
+
%i[ind]
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def ind_val_for(data_field)
|
60
|
+
case ind
|
61
|
+
when 1
|
62
|
+
data_field.indicator1
|
63
|
+
when 2
|
64
|
+
data_field.indicator2
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def valid_indicator(ind_val)
|
69
|
+
ind = int_or_nil(ind_val)
|
70
|
+
return ind if VALID_INDICATORS.include?(ind)
|
71
|
+
|
72
|
+
raise ArgumentError, "Not a valid indicator: #{ind_val.inspect}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'typesafe_enum'
|
2
|
+
|
3
|
+
module MARC
|
4
|
+
module Spec
|
5
|
+
module Queries
|
6
|
+
# NOTE: && and || are not defined in the MARCspec standard but are
|
7
|
+
# implementation details of repeated (AND) and chained (OR) subspecs
|
8
|
+
# -- see https://marcspec.github.io/MARCspec/marc-spec.html#general
|
9
|
+
class Operator < TypesafeEnum::Base
|
10
|
+
|
11
|
+
# ------------------------------------------------------------
|
12
|
+
# Enum instances
|
13
|
+
|
14
|
+
new(:EQ, '=') do
|
15
|
+
def apply(lr, rr)
|
16
|
+
return false if rr.nil?
|
17
|
+
return rarr_eq?(lr, rr) if rr.is_a?(Array)
|
18
|
+
return larr_eq?(lr, rr) if lr.is_a?(Array)
|
19
|
+
|
20
|
+
lr == rr
|
21
|
+
end
|
22
|
+
|
23
|
+
def larr_eq?(larr, rr)
|
24
|
+
larr.any? { |l| apply(l, rr) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def rarr_eq?(lr, rarr)
|
28
|
+
rarr.any? { |r| apply(lr, r) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
new(:NEQ, '!=') do
|
33
|
+
def apply(lr, rr)
|
34
|
+
!Operator::EQ.apply(lr, rr)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
new(:INCL, '~') do
|
39
|
+
def apply(lr, rr)
|
40
|
+
return false if [lr, rr].any?(&:nil?) # TODO: is this right?
|
41
|
+
return rarr_incl?(lr, rr) if rr.is_a?(Array)
|
42
|
+
return larr_incl?(lr, rr) if lr.is_a?(Array)
|
43
|
+
|
44
|
+
lr.include?(rr)
|
45
|
+
end
|
46
|
+
|
47
|
+
def larr_incl?(larr, rr)
|
48
|
+
larr.any? { |l| apply(l, rr) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def rarr_incl?(lr, rarr)
|
52
|
+
rarr.any? { |r| apply(lr, r) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
new(:NINCL, '!~') do
|
57
|
+
def apply(lr, rr)
|
58
|
+
!Operator::INCL.apply(lr, rr)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
new(:NEXIST, '!') do
|
63
|
+
def apply(right)
|
64
|
+
right.is_a?(Array) ? right.empty? : right.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
new(:EXIST, '?') do
|
69
|
+
def apply(right)
|
70
|
+
!Operator::NEXIST.apply(right)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
new(:AND, '&&') do
|
75
|
+
def apply(left, right)
|
76
|
+
left && right
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
new(:OR, '||') do
|
81
|
+
def apply(left, right)
|
82
|
+
left || right
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# ------------------------------------------------------------
|
87
|
+
# Class methods
|
88
|
+
|
89
|
+
class << self
|
90
|
+
def from_str(op_str)
|
91
|
+
Operator.find_by_value(op_str.to_s).tap do |op|
|
92
|
+
raise ArgumentError, "No such operator: #{op_str}" unless op
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# ------------------------------------------------------------
|
98
|
+
# Operator
|
99
|
+
|
100
|
+
def binary?
|
101
|
+
![Operator::EXIST, Operator::NEXIST].include?(self)
|
102
|
+
end
|
103
|
+
|
104
|
+
def logical?
|
105
|
+
[Operator::AND, Operator::OR].include?(self)
|
106
|
+
end
|
107
|
+
|
108
|
+
# ------------------------------------------------------------
|
109
|
+
# Object overrides
|
110
|
+
|
111
|
+
def to_s
|
112
|
+
op_str
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [String]
|
116
|
+
def op_str
|
117
|
+
# noinspection RubyMismatchedReturnType
|
118
|
+
value
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_expression(left, right)
|
122
|
+
return ["(#{left})", self, "(#{right})"].join if logical?
|
123
|
+
|
124
|
+
[left, self, right].join
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module MARC
|
2
|
+
module Spec
|
3
|
+
module Queries
|
4
|
+
# Supermodule of partial query
|
5
|
+
module Part
|
6
|
+
|
7
|
+
# ------------------------------------------------------------
|
8
|
+
# Object overrides
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
"#{class_name(self)}<#{to_s_inspect}>"
|
12
|
+
end
|
13
|
+
|
14
|
+
def eql?(other)
|
15
|
+
return false unless other.class == self.class
|
16
|
+
|
17
|
+
equality_attrs.all? do |attr|
|
18
|
+
send(attr) == other.send(attr)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
alias :== eql?
|
23
|
+
|
24
|
+
def hash
|
25
|
+
@hash_val ||= begin
|
26
|
+
equality_vals = equality_attrs.map { |attr| send(attr) }
|
27
|
+
equality_vals.inject(31 + self.class.hash) { |r, v| 31 * r + v.hash }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# ------------------------------------------------------------
|
32
|
+
# Protected methods
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def to_s_inspect
|
37
|
+
to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def ensure_type(v, type, allow_nil: false)
|
41
|
+
return if allow_nil && v.nil?
|
42
|
+
return v if v.is_a?(type)
|
43
|
+
|
44
|
+
raise ArgumentError, "Not a #{class_name(type)}: #{v.inspect}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def int_or_nil(v)
|
48
|
+
return nil if v.nil? || v == '#'
|
49
|
+
|
50
|
+
Integer(v)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def class_name(t)
|
56
|
+
return class_name(t.class) unless t.is_a?(Class) || t.is_a?(Module)
|
57
|
+
|
58
|
+
t.name.sub(/^.*::/, '')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'marc/spec/queries/position_or_range'
|
2
|
+
|
3
|
+
module MARC
|
4
|
+
module Spec
|
5
|
+
module Queries
|
6
|
+
class Position
|
7
|
+
include PositionOrRange
|
8
|
+
|
9
|
+
# ------------------------------------------------------------
|
10
|
+
# Attributes
|
11
|
+
|
12
|
+
attr_reader :position
|
13
|
+
|
14
|
+
def initialize(position)
|
15
|
+
@position = int_or_nil(position)
|
16
|
+
end
|
17
|
+
|
18
|
+
# ------------------------------------------------------------
|
19
|
+
# Instance methods
|
20
|
+
|
21
|
+
# @overload select_from(seq)
|
22
|
+
# Selects the value at the specified position and returns it as a
|
23
|
+
# single-element array, for compatibility with AlNumRange
|
24
|
+
# @param seq [Array] the input array
|
25
|
+
# @return [Array] a single-element array containing the result, or
|
26
|
+
# an empty array if the index is out of bounds
|
27
|
+
# @overload select_from(seq)
|
28
|
+
# Selects the character at the specified position
|
29
|
+
# @param seq [String] the input array
|
30
|
+
# @return [String, nil] a single-element array, or nil if the index
|
31
|
+
# is out of bounds
|
32
|
+
def select_from(seq)
|
33
|
+
# we use raw_result[-1] instead of raw_result.last b/c seq might be a string
|
34
|
+
raw_result = seq[position.nil? ? -1 : position]
|
35
|
+
seq.is_a?(String) ? wrap_string_result(raw_result) : wrap_array_result(raw_result)
|
36
|
+
end
|
37
|
+
|
38
|
+
# ------------------------------------------------------------
|
39
|
+
# Object overrides
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
(position || '#').to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
# ------------------------------------------------------------
|
46
|
+
# Protected methods
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
# ------------------------------
|
51
|
+
# Part
|
52
|
+
|
53
|
+
def equality_attrs
|
54
|
+
[:position]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'marc/spec/queries/applicable'
|
2
|
+
|
3
|
+
module MARC
|
4
|
+
module Spec
|
5
|
+
module Queries
|
6
|
+
# Marker interface for positions and ranges
|
7
|
+
# TODO: unify Position and AlNumRange?
|
8
|
+
module PositionOrRange
|
9
|
+
include Part
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def wrap_string_result(result)
|
14
|
+
result unless result.nil? || result.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
# NOTE: We can't use `Array()` because we don't want to indiscriminately call `to_ary` / `to_a`
|
18
|
+
def wrap_array_result(result)
|
19
|
+
return [] unless result
|
20
|
+
return result if result.is_a?(Array)
|
21
|
+
|
22
|
+
[result]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'marc/spec/queries/part'
|
2
|
+
|
3
|
+
module MARC
|
4
|
+
module Spec
|
5
|
+
module Queries
|
6
|
+
class Query
|
7
|
+
include Part
|
8
|
+
|
9
|
+
attr_reader :tag, :selector, :condition, :subqueries
|
10
|
+
|
11
|
+
# TODO: separate factory methods for possible cases? (see transform.rb)
|
12
|
+
# TODO: separate query (w/o subqueries) from wrapper w/subqueries?
|
13
|
+
def initialize(tag: nil, selector: nil, condition: nil, subqueries: [])
|
14
|
+
@tag = ensure_type(tag, Tag, allow_nil: true)
|
15
|
+
# TODO: do we need the Selector interface at all?
|
16
|
+
@selector = ensure_type(selector, Applicable, allow_nil: true)
|
17
|
+
@condition = ensure_type(condition, Condition, allow_nil: true)
|
18
|
+
@subqueries = subqueries.map { |sq| ensure_type(sq, Query) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
StringIO.new.tap do |out|
|
23
|
+
out << tag if tag
|
24
|
+
out << selector if selector
|
25
|
+
out << "{#{condition}}" if condition
|
26
|
+
out << subqueries.join
|
27
|
+
end.string
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: don't support nested subqueries
|
31
|
+
def execute(executor, context_fields, context_result = nil)
|
32
|
+
fields = tag ? executor.apply_tag(tag) : context_fields
|
33
|
+
return [] if fields.empty?
|
34
|
+
|
35
|
+
field_results = root_results(fields, executor, context_result)
|
36
|
+
return field_results if subqueries.empty?
|
37
|
+
|
38
|
+
fields.each_with_object([]) do |field, results|
|
39
|
+
subqueries.each do |subquery|
|
40
|
+
subquery_results = subquery.execute(executor, [field])
|
41
|
+
results.concat(subquery_results)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def equality_attrs
|
49
|
+
%i[tag selector condition subqueries]
|
50
|
+
end
|
51
|
+
|
52
|
+
# rubocop:disable Metrics/AbcSize
|
53
|
+
def to_s_inspect
|
54
|
+
StringIO.new.tap do |out|
|
55
|
+
out << tag.inspect if tag
|
56
|
+
out << selector.inspect if selector
|
57
|
+
out << "{#{condition.inspect}}" if condition
|
58
|
+
out << subqueries.map(&:inspect).join
|
59
|
+
end.string
|
60
|
+
end
|
61
|
+
# rubocop:enable Metrics/AbcSize
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def root_results(fields, executor, context_result)
|
66
|
+
field_results = results_for_fields(executor, fields)
|
67
|
+
# TODO: something less ridiculous
|
68
|
+
return field_results unless field_results.empty? && select_from_context?(context_result)
|
69
|
+
|
70
|
+
selector.apply(context_result)
|
71
|
+
end
|
72
|
+
|
73
|
+
def select_from_context?(context_result)
|
74
|
+
tag.nil? && context_result && selector.can_apply?(context_result)
|
75
|
+
end
|
76
|
+
|
77
|
+
def results_for_fields(executor, fields)
|
78
|
+
fields.each_with_object([]) do |field, results|
|
79
|
+
field_results = results_for_field(executor, field)
|
80
|
+
results.concat(field_results)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def results_for_field(executor, field)
|
85
|
+
results = executor.apply_selector(selector, field)
|
86
|
+
return results unless condition
|
87
|
+
|
88
|
+
results.select { |result| executor.condition_met?(condition, field, result) }
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'marc'
|
2
|
+
require 'marc/spec/queries/query'
|
3
|
+
|
4
|
+
module MARC
|
5
|
+
module Spec
|
6
|
+
module Queries
|
7
|
+
class QueryExecutor
|
8
|
+
include Part
|
9
|
+
|
10
|
+
attr_reader :marc_record, :root_query, :root_tag, :root_fields
|
11
|
+
|
12
|
+
def initialize(marc_record, root, cache = {})
|
13
|
+
@marc_record = ensure_type(marc_record, MARC::Record)
|
14
|
+
@root_query = as_query(root)
|
15
|
+
@cache = cache
|
16
|
+
|
17
|
+
@root_tag = root_query.tag || Tag.new('...')
|
18
|
+
@root_fields = apply_tag(root_tag)
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute
|
22
|
+
root_query.execute(self, root_fields)
|
23
|
+
end
|
24
|
+
|
25
|
+
def apply_tag(tag)
|
26
|
+
cache[tag] ||= tag.apply(marc_record)
|
27
|
+
end
|
28
|
+
|
29
|
+
def apply_selector(selector, field)
|
30
|
+
return [field] unless selector
|
31
|
+
|
32
|
+
cache_key = [selector, field]
|
33
|
+
cache[cache_key] ||= selector.apply(field)
|
34
|
+
end
|
35
|
+
|
36
|
+
def condition_met?(condition, context_field, context_result)
|
37
|
+
cond_ctx = ConditionContext.new(context_field, context_result, self)
|
38
|
+
condition.met?(cond_ctx)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def as_query(root)
|
44
|
+
return root if root.is_a?(Query)
|
45
|
+
return Query.new(tag: root) if root.is_a?(Tag)
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :cache
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'marc/spec/queries/selector'
|
3
|
+
|
4
|
+
module MARC
|
5
|
+
module Spec
|
6
|
+
module Queries
|
7
|
+
class Subfield
|
8
|
+
include Selector
|
9
|
+
|
10
|
+
# ------------------------------------------------------------
|
11
|
+
# Attributes
|
12
|
+
|
13
|
+
attr_reader :code, :index
|
14
|
+
|
15
|
+
# ------------------------------------------------------------
|
16
|
+
# Initializer
|
17
|
+
|
18
|
+
def initialize(code, index: nil)
|
19
|
+
@code = parse_code(code)
|
20
|
+
@index = ensure_type(index, PositionOrRange, allow_nil: true)
|
21
|
+
end
|
22
|
+
|
23
|
+
# ------------------------------------------------------------
|
24
|
+
# Object overrides
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
StringIO.new.tap do |out|
|
28
|
+
out << '$'
|
29
|
+
out << code
|
30
|
+
out << "[#{index}]" if index
|
31
|
+
end.string
|
32
|
+
end
|
33
|
+
|
34
|
+
# ------------------------------
|
35
|
+
# Applicable
|
36
|
+
|
37
|
+
def can_apply?(marc_obj)
|
38
|
+
marc_obj.respond_to?(:subfields)
|
39
|
+
end
|
40
|
+
|
41
|
+
# ------------------------------------------------------------
|
42
|
+
# Protected methods
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
# ------------------------------
|
47
|
+
# Applicable
|
48
|
+
|
49
|
+
def do_apply(data_field)
|
50
|
+
subfields = all_subfields(data_field)
|
51
|
+
raw_result = index ? index.select_from(subfields) : subfields
|
52
|
+
Array(raw_result)
|
53
|
+
end
|
54
|
+
|
55
|
+
# ------------------------------
|
56
|
+
# Predicate
|
57
|
+
|
58
|
+
def to_s_inspect
|
59
|
+
StringIO.new.tap do |out|
|
60
|
+
out << '$'
|
61
|
+
out << code.inspect
|
62
|
+
out << "[#{index.inspect}]" if index
|
63
|
+
end.string
|
64
|
+
end
|
65
|
+
|
66
|
+
def equality_attrs
|
67
|
+
%i[code index]
|
68
|
+
end
|
69
|
+
|
70
|
+
# ------------------------------------------------------------
|
71
|
+
# Private methods
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def all_subfields(data_field)
|
76
|
+
data_field.subfields.select { |sf| code.include?(sf.code) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_code(code)
|
80
|
+
raise ArgumentError, 'Code cannot be nil' if code.nil?
|
81
|
+
|
82
|
+
code.is_a?(AlNumRange) ? code : code.to_s
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|