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.
- 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
|