tediparse 2.0.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/CHANGELOG.md +319 -0
- data/LICENSE +26 -0
- data/README.md +308 -0
- data/Rakefile +33 -0
- data/bin/tediparse +102 -0
- data/doc/Defining.md +97 -0
- data/doc/Generating-Grammars.md +241 -0
- data/doc/Generating.md +332 -0
- data/doc/Navigating.md +670 -0
- data/doc/Parsing.md +133 -0
- data/doc/README.md +51 -0
- data/doc/Serializing.md +100 -0
- data/doc/Tokenizing.md +136 -0
- data/doc/Validating.md +140 -0
- data/lib/ruby/array.rb +226 -0
- data/lib/ruby/blank.rb +52 -0
- data/lib/ruby/exception.rb +14 -0
- data/lib/ruby/hash.rb +14 -0
- data/lib/ruby/module.rb +60 -0
- data/lib/ruby/object.rb +56 -0
- data/lib/ruby/string.rb +89 -0
- data/lib/ruby/to_d.rb +82 -0
- data/lib/ruby/to_date.rb +26 -0
- data/lib/ruby/to_time.rb +21 -0
- data/lib/ruby/try.rb +46 -0
- data/lib/stupidedi/builder.rb +6 -0
- data/lib/stupidedi/color.rb +94 -0
- data/lib/stupidedi/config/code_list_config.rb +45 -0
- data/lib/stupidedi/config/functional_group_config.rb +72 -0
- data/lib/stupidedi/config/interchange_config.rb +86 -0
- data/lib/stupidedi/config/transaction_set_config.rb +73 -0
- data/lib/stupidedi/config.rb +100 -0
- data/lib/stupidedi/either.rb +286 -0
- data/lib/stupidedi/exceptions/invalid_element_error.rb +7 -0
- data/lib/stupidedi/exceptions/invalid_schema_error.rb +7 -0
- data/lib/stupidedi/exceptions/missing_grammar_error.rb +50 -0
- data/lib/stupidedi/exceptions/output_error.rb +7 -0
- data/lib/stupidedi/exceptions/parse_error.rb +7 -0
- data/lib/stupidedi/exceptions/stupidedi_error.rb +7 -0
- data/lib/stupidedi/exceptions/tokenize_error.rb +7 -0
- data/lib/stupidedi/exceptions/zipper_error.rb +7 -0
- data/lib/stupidedi/exceptions.rb +13 -0
- data/lib/stupidedi/inspect.rb +26 -0
- data/lib/stupidedi/interchanges/element_types/separator_val.rb +79 -0
- data/lib/stupidedi/interchanges/element_types/special_val.rb +48 -0
- data/lib/stupidedi/interchanges/element_types.rb +11 -0
- data/lib/stupidedi/interchanges.rb +21 -0
- data/lib/stupidedi/parser/builder_dsl.rb +292 -0
- data/lib/stupidedi/parser/constraint_table.rb +566 -0
- data/lib/stupidedi/parser/generation.rb +147 -0
- data/lib/stupidedi/parser/identifier_stack.rb +248 -0
- data/lib/stupidedi/parser/instruction.rb +112 -0
- data/lib/stupidedi/parser/instruction_table.rb +222 -0
- data/lib/stupidedi/parser/navigation.rb +786 -0
- data/lib/stupidedi/parser/state_machine.rb +62 -0
- data/lib/stupidedi/parser/states/abstract_state.rb +385 -0
- data/lib/stupidedi/parser/states/failure_state.rb +69 -0
- data/lib/stupidedi/parser/states/functional_group_state.rb +101 -0
- data/lib/stupidedi/parser/states/initial_state.rb +62 -0
- data/lib/stupidedi/parser/states/interchange_state.rb +96 -0
- data/lib/stupidedi/parser/states/loop_state.rb +74 -0
- data/lib/stupidedi/parser/states/table_state.rb +90 -0
- data/lib/stupidedi/parser/states/transaction_set_state.rb +120 -0
- data/lib/stupidedi/parser/states/transmission_state.rb +59 -0
- data/lib/stupidedi/parser/tokenization.rb +195 -0
- data/lib/stupidedi/parser.rb +32 -0
- data/lib/stupidedi/reader/input/abstract_input.rb +137 -0
- data/lib/stupidedi/reader/input/delegated_input.rb +112 -0
- data/lib/stupidedi/reader/input/file_input.rb +157 -0
- data/lib/stupidedi/reader/input.rb +31 -0
- data/lib/stupidedi/reader/position.rb +78 -0
- data/lib/stupidedi/reader/result.rb +172 -0
- data/lib/stupidedi/reader/segment_dict.rb +176 -0
- data/lib/stupidedi/reader/separators.rb +90 -0
- data/lib/stupidedi/reader/stream_reader.rb +173 -0
- data/lib/stupidedi/reader/token_reader.rb +465 -0
- data/lib/stupidedi/reader/tokens/component_element_tok.rb +71 -0
- data/lib/stupidedi/reader/tokens/composite_element_tok.rb +85 -0
- data/lib/stupidedi/reader/tokens/repeated_element_tok.rb +74 -0
- data/lib/stupidedi/reader/tokens/segment_tok.rb +74 -0
- data/lib/stupidedi/reader/tokens/simple_element_tok.rb +76 -0
- data/lib/stupidedi/reader.rb +121 -0
- data/lib/stupidedi/schema/abstract_def.rb +76 -0
- data/lib/stupidedi/schema/abstract_element_def.rb +35 -0
- data/lib/stupidedi/schema/abstract_element_use.rb +47 -0
- data/lib/stupidedi/schema/abstract_use.rb +79 -0
- data/lib/stupidedi/schema/code_list.rb +99 -0
- data/lib/stupidedi/schema/component_element_use.rb +76 -0
- data/lib/stupidedi/schema/composite_element_def.rb +103 -0
- data/lib/stupidedi/schema/composite_element_use.rb +78 -0
- data/lib/stupidedi/schema/element_req.rb +57 -0
- data/lib/stupidedi/schema/functional_group_def.rb +124 -0
- data/lib/stupidedi/schema/generation/definition_generator.rb +139 -0
- data/lib/stupidedi/schema/generation/element_generator.rb +221 -0
- data/lib/stupidedi/schema/generation/flat_file_reader.rb +551 -0
- data/lib/stupidedi/schema/generation/functional_group_generator.rb +64 -0
- data/lib/stupidedi/schema/generation/interchange_generator.rb +145 -0
- data/lib/stupidedi/schema/generation/master_loader_generator.rb +121 -0
- data/lib/stupidedi/schema/generation/models.rb +85 -0
- data/lib/stupidedi/schema/generation/module_loader_generator.rb +64 -0
- data/lib/stupidedi/schema/generation/registration_generator.rb +230 -0
- data/lib/stupidedi/schema/generation/runner.rb +161 -0
- data/lib/stupidedi/schema/generation/segment_generator.rb +130 -0
- data/lib/stupidedi/schema/generation/support.rb +78 -0
- data/lib/stupidedi/schema/generation/support_modules_generator.rb +126 -0
- data/lib/stupidedi/schema/generation/version_modules.rb +35 -0
- data/lib/stupidedi/schema/generation.rb +94 -0
- data/lib/stupidedi/schema/interchange_def.rb +103 -0
- data/lib/stupidedi/schema/loop_def.rb +156 -0
- data/lib/stupidedi/schema/repeat_count.rb +86 -0
- data/lib/stupidedi/schema/segment_def.rb +122 -0
- data/lib/stupidedi/schema/segment_req.rb +46 -0
- data/lib/stupidedi/schema/segment_use.rb +99 -0
- data/lib/stupidedi/schema/simple_element_def.rb +51 -0
- data/lib/stupidedi/schema/simple_element_use.rb +83 -0
- data/lib/stupidedi/schema/syntax_note.rb +52 -0
- data/lib/stupidedi/schema/table_def.rb +178 -0
- data/lib/stupidedi/schema/transaction_set_def.rb +125 -0
- data/lib/stupidedi/schema.rb +30 -0
- data/lib/stupidedi/sets.rb +42 -0
- data/lib/stupidedi/transaction_sets/builder/dsl.rb +192 -0
- data/lib/stupidedi/transaction_sets/builder.rb +188 -0
- data/lib/stupidedi/transaction_sets/common/implementations/element_reqs.rb +37 -0
- data/lib/stupidedi/transaction_sets/common/implementations/segment_reqs.rb +31 -0
- data/lib/stupidedi/transaction_sets/common/implementations.rb +11 -0
- data/lib/stupidedi/transaction_sets/common.rb +8 -0
- data/lib/stupidedi/transaction_sets/validation/ambiguity.rb +395 -0
- data/lib/stupidedi/transaction_sets/validation/implementation.rb +12 -0
- data/lib/stupidedi/transaction_sets/validation.rb +9 -0
- data/lib/stupidedi/transaction_sets.rb +25 -0
- data/lib/stupidedi/values/abstract_element_val.rb +19 -0
- data/lib/stupidedi/values/abstract_val.rb +133 -0
- data/lib/stupidedi/values/composite_element_val.rb +102 -0
- data/lib/stupidedi/values/functional_group_val.rb +105 -0
- data/lib/stupidedi/values/interchange_val.rb +102 -0
- data/lib/stupidedi/values/invalid_envelope_val.rb +61 -0
- data/lib/stupidedi/values/invalid_segment_val.rb +89 -0
- data/lib/stupidedi/values/loop_val.rb +73 -0
- data/lib/stupidedi/values/repeated_element_val.rb +113 -0
- data/lib/stupidedi/values/segment_val.rb +105 -0
- data/lib/stupidedi/values/segment_val_group.rb +20 -0
- data/lib/stupidedi/values/simple_element_val.rb +80 -0
- data/lib/stupidedi/values/table_val.rb +69 -0
- data/lib/stupidedi/values/transaction_set_val.rb +69 -0
- data/lib/stupidedi/values/transmission_val.rb +56 -0
- data/lib/stupidedi/values.rb +22 -0
- data/lib/stupidedi/version.rb +4 -0
- data/lib/stupidedi/versions/common/element_reqs.rb +13 -0
- data/lib/stupidedi/versions/common/element_types/an.rb +386 -0
- data/lib/stupidedi/versions/common/element_types/dt.rb +572 -0
- data/lib/stupidedi/versions/common/element_types/id.rb +304 -0
- data/lib/stupidedi/versions/common/element_types/nn.rb +312 -0
- data/lib/stupidedi/versions/common/element_types/operators.rb +128 -0
- data/lib/stupidedi/versions/common/element_types/r.rb +342 -0
- data/lib/stupidedi/versions/common/element_types/simple_element.rb +73 -0
- data/lib/stupidedi/versions/common/element_types/tm.rb +347 -0
- data/lib/stupidedi/versions/common/element_types.rb +29 -0
- data/lib/stupidedi/versions/common/segment_reqs.rb +15 -0
- data/lib/stupidedi/versions/common/syntax_notes.rb +172 -0
- data/lib/stupidedi/versions/common.rb +11 -0
- data/lib/stupidedi/versions.rb +25 -0
- data/lib/stupidedi/writer/claredi.rb +178 -0
- data/lib/stupidedi/writer/default.rb +119 -0
- data/lib/stupidedi/writer.rb +7 -0
- data/lib/stupidedi/zipper/abstract_cursor.rb +351 -0
- data/lib/stupidedi/zipper/dangling_cursor.rb +103 -0
- data/lib/stupidedi/zipper/edited_cursor.rb +157 -0
- data/lib/stupidedi/zipper/memoized_cursor.rb +133 -0
- data/lib/stupidedi/zipper/path.rb +132 -0
- data/lib/stupidedi/zipper/root_cursor.rb +120 -0
- data/lib/stupidedi/zipper/stack_cursor.rb +107 -0
- data/lib/stupidedi/zipper.rb +45 -0
- data/lib/stupidedi.rb +69 -0
- data/lib/tediparse.rb +1 -0
- metadata +249 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Stupidedi
|
|
3
|
+
using Refinements
|
|
4
|
+
|
|
5
|
+
module TransactionSets
|
|
6
|
+
#
|
|
7
|
+
# {Builder} is a simple DSL for construction a transaction set
|
|
8
|
+
#
|
|
9
|
+
module Builder
|
|
10
|
+
|
|
11
|
+
autoload :Dsl, "stupidedi/transaction_sets/builder/dsl"
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class << Builder
|
|
17
|
+
# @return [Schema::TransactionSetDef]
|
|
18
|
+
def build(functional_group, id, name, *table_defs)
|
|
19
|
+
Schema::TransactionSetDef.build(functional_group, id, name, *table_defs)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @group Element Constraints
|
|
23
|
+
#######################################################################
|
|
24
|
+
|
|
25
|
+
# @param [String, ...] values
|
|
26
|
+
def Values(*values)
|
|
27
|
+
[:Values, values]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param [Integer] n
|
|
31
|
+
def MaxLength(n)
|
|
32
|
+
[:MaxLength, n]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param [Integer] n
|
|
36
|
+
def MaxPrecision(n)
|
|
37
|
+
[:MaxPrecision, n]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @endgroup
|
|
41
|
+
#######################################################################
|
|
42
|
+
|
|
43
|
+
# @group Definition Constructors
|
|
44
|
+
#######################################################################
|
|
45
|
+
|
|
46
|
+
# @param [Schema::ElementReq] requirement
|
|
47
|
+
# @param [String] name
|
|
48
|
+
def Element(requirement, name, *constraints)
|
|
49
|
+
[:Element, requirement, name, constraints]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [SegmentUse]
|
|
53
|
+
def Segment(position, segment_def, name, requirement, repeat_count, *elements)
|
|
54
|
+
unless elements.length == segment_def.element_uses.length
|
|
55
|
+
raise Exceptions::InvalidSchemaError,
|
|
56
|
+
"segment #{segment_def.id} has #{segment_def.element_uses.length}" +
|
|
57
|
+
" elements but #{elements.length} arguments were given"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
element_idx = "00"
|
|
61
|
+
element_uses = elements.zip(segment_def.element_uses).map do |e, u|
|
|
62
|
+
e_tag, e_requirement, e_name, e_arguments = e
|
|
63
|
+
element_idx = element_idx.succ
|
|
64
|
+
descriptor = "#{segment_def.id}#{element_idx}"
|
|
65
|
+
|
|
66
|
+
unless e_tag == :Element
|
|
67
|
+
raise Exceptions::InvalidSchemaError,
|
|
68
|
+
"given argument for #{descriptor} must be Element(...)"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if u.composite?
|
|
72
|
+
e_repeat_count, e_arguments = e_arguments.partition{|x| x.is_a?(Schema::RepeatCount) }
|
|
73
|
+
|
|
74
|
+
changes = Hash.new
|
|
75
|
+
changes[:requirement] = e_requirement
|
|
76
|
+
|
|
77
|
+
if e_repeat_count.length == 1
|
|
78
|
+
changes[:repeat_count] = e_repeat_count.head
|
|
79
|
+
elsif e_repeat_count.length > 1
|
|
80
|
+
raise Exceptions::InvalidSchemaError,
|
|
81
|
+
"more than one RepeatCount was specified for #{descriptor}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
unless e_requirement.forbidden?
|
|
85
|
+
unless e_arguments.length == u.definition.component_uses.length
|
|
86
|
+
raise Exceptions::InvalidSchemaError,
|
|
87
|
+
"composite element #{u.definition.id} at #{descriptor} has
|
|
88
|
+
#{u.definition.component_uses.length} component elements but
|
|
89
|
+
#{e_arguments.length} arguments were given".join
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# ComponentElementUses
|
|
93
|
+
component_idx = "00"
|
|
94
|
+
component_uses = e_arguments.zip(u.definition.component_uses).map do |e1, c|
|
|
95
|
+
c_tag, c_requirement, c_name, c_arguments = e1
|
|
96
|
+
component_idx = component_idx.succ
|
|
97
|
+
descriptor = "#{descriptor}-#{component_idx}"
|
|
98
|
+
|
|
99
|
+
unless c_tag == :Element
|
|
100
|
+
raise Exceptions::InvalidSchemaError,
|
|
101
|
+
"given argument for #{descriptor} must be Element(...)"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
mod_element(c, descriptor, c_requirement, c_name, c_arguments)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
changes[:definition] = u.definition.copy(:name => e_name,
|
|
108
|
+
:component_uses => component_uses)
|
|
109
|
+
else
|
|
110
|
+
changes[:definition] = u.definition.copy(:name => e_name)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
u.copy(changes)
|
|
114
|
+
else
|
|
115
|
+
mod_element(u, descriptor, e_requirement, e_name, e_arguments)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
segment_def.
|
|
120
|
+
copy(:name => name, :element_uses => element_uses).
|
|
121
|
+
use(position, requirement, repeat_count)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @endgroup
|
|
125
|
+
#######################################################################
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# @return [Schema::SimpleElementUse]
|
|
130
|
+
def mod_element(element_use, descriptor, requirement, name, arguments)
|
|
131
|
+
unless requirement.is_a?(Schema::ElementReq)
|
|
132
|
+
raise Exceptions::InvalidSchemaError,
|
|
133
|
+
"first argument to Element must be a Schema::ElementReq but got #{requirement.inspect} for #{descriptor}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
unless name.is_a?(String)
|
|
137
|
+
raise Exceptions::InvalidSchemaError,
|
|
138
|
+
"second argument to Element must be a String for #{descriptor}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
changes = Hash.new # changes to SegmentUse
|
|
142
|
+
dhanges = Hash.new # changes to SegmentDef
|
|
143
|
+
|
|
144
|
+
changes[:requirement] = requirement
|
|
145
|
+
changes[:definition] = element_use.definition.copy(:name => name)
|
|
146
|
+
|
|
147
|
+
repeat_count = arguments.select{|x| x.is_a?(Schema::RepeatCount) }
|
|
148
|
+
|
|
149
|
+
if repeat_count.length == 1
|
|
150
|
+
changes[:repeat_count] = repeat_count.head
|
|
151
|
+
elsif repeat_count.length > 1
|
|
152
|
+
raise Exceptions::InvalidSchemaError,
|
|
153
|
+
"more than one RepeatCount specified for #{descriptor}"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
allowed_values = arguments.select{|x| x.is_a?(Array) and x.head == :Values }
|
|
157
|
+
|
|
158
|
+
if allowed_values.length == 1
|
|
159
|
+
begin
|
|
160
|
+
changes[:allowed_values] = element_use.allowed_values.replace(allowed_values.head.last)
|
|
161
|
+
rescue ArgumentError => e
|
|
162
|
+
raise Exceptions::InvalidSchemaError,
|
|
163
|
+
"#{e.message} for #{descriptor}"
|
|
164
|
+
end
|
|
165
|
+
elsif allowed_values.length > 1
|
|
166
|
+
raise Exceptions::InvalidSchemaError,
|
|
167
|
+
"more than one Values specified for #{descriptor}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
max_length = arguments.select{|x| x.is_a?(Array) and x.head == :MaxLength }
|
|
171
|
+
|
|
172
|
+
if max_length.length == 1
|
|
173
|
+
dhanges[:max_length] = max_length.head.last
|
|
174
|
+
elsif max_length.length > 1
|
|
175
|
+
raise Exceptions::InvalidSchemaError,
|
|
176
|
+
"more than one MaxLength specified for #{descriptor}"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
if dhanges.empty?
|
|
180
|
+
element_use.copy(changes)
|
|
181
|
+
else
|
|
182
|
+
element_use.copy(changes.merge(:definition =>
|
|
183
|
+
element_use.definition.copy(dhanges)))
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Stupidedi
|
|
3
|
+
module TransactionSets
|
|
4
|
+
module Common
|
|
5
|
+
module Implementations
|
|
6
|
+
#
|
|
7
|
+
# @see X222.pdf 2.2 Implementation Usage
|
|
8
|
+
#
|
|
9
|
+
module ElementReqs
|
|
10
|
+
# Declares that this element must always be sent. Required elements
|
|
11
|
+
# within Situational segments when the segment occurs.
|
|
12
|
+
#
|
|
13
|
+
# Similarly, required component elements within situational composite
|
|
14
|
+
# elements only occur when the composite element occurs.
|
|
15
|
+
Required = Schema::ElementReq.new(true, false, "R")
|
|
16
|
+
|
|
17
|
+
# Use of this element varies, depending on data content and business
|
|
18
|
+
# context as described in the defining rule. The defining rule is
|
|
19
|
+
# documented in a Situational Rule attached to the element.
|
|
20
|
+
#
|
|
21
|
+
# There are two forms of Situational Rules. The first is "Required when
|
|
22
|
+
# <condition>. If not required by <condition>, the element may be
|
|
23
|
+
# provided at the discretion of the sender, but it cannot be required or
|
|
24
|
+
# requested by the receiver.'
|
|
25
|
+
#
|
|
26
|
+
# The other form is "Required when <condition>. If not required by
|
|
27
|
+
# <condition>, do not send". The element described by the rule cannot
|
|
28
|
+
# occur except when the condition holds.
|
|
29
|
+
Situational = Schema::ElementReq.new(false, false, "S")
|
|
30
|
+
|
|
31
|
+
# This element must never be sent.
|
|
32
|
+
NotUsed = Schema::ElementReq.new(false, true, "N")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Stupidedi
|
|
3
|
+
module TransactionSets
|
|
4
|
+
module Common
|
|
5
|
+
module Implementations
|
|
6
|
+
#
|
|
7
|
+
# @see X222.pdf 2.2 Implementation Usage
|
|
8
|
+
#
|
|
9
|
+
module SegmentReqs
|
|
10
|
+
# Declares that this segment must always be sent. Required segments
|
|
11
|
+
# within Situational loops only occur when the loop occurs.
|
|
12
|
+
Required = Schema::SegmentReq.new(true, false, "R")
|
|
13
|
+
|
|
14
|
+
# Use of this segment varies, depending on data content and business
|
|
15
|
+
# context as described in the defining rule. The defining rule is
|
|
16
|
+
# documented in a Situational Rule attached to the segment.
|
|
17
|
+
#
|
|
18
|
+
# There are two forms of Situational Rules. The first is "Required when
|
|
19
|
+
# <condition>. If not required by <condition>, the segment may be
|
|
20
|
+
# provided at the discretion of the sender, but it cannot be required or
|
|
21
|
+
# requested by the receiver.'
|
|
22
|
+
#
|
|
23
|
+
# The other form is "Required when <condition>. If not required by
|
|
24
|
+
# <condition>, do not send". The segment described by the rule cannot
|
|
25
|
+
# occur except when the condition holds.
|
|
26
|
+
Situational = Schema::SegmentReq.new(false, false, "S")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Stupidedi
|
|
3
|
+
module TransactionSets
|
|
4
|
+
module Common
|
|
5
|
+
module Implementations
|
|
6
|
+
autoload :ElementReqs, "stupidedi/transaction_sets/common/implementations/element_reqs"
|
|
7
|
+
autoload :SegmentReqs, "stupidedi/transaction_sets/common/implementations/segment_reqs"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Stupidedi
|
|
3
|
+
using Refinements
|
|
4
|
+
|
|
5
|
+
module TransactionSets
|
|
6
|
+
module Validation
|
|
7
|
+
#
|
|
8
|
+
# @todo
|
|
9
|
+
#
|
|
10
|
+
class Ambiguity
|
|
11
|
+
include Color
|
|
12
|
+
include Parser::Tokenization
|
|
13
|
+
|
|
14
|
+
attr_reader :config
|
|
15
|
+
attr_reader :machine
|
|
16
|
+
attr_reader :reader
|
|
17
|
+
attr_reader :queue
|
|
18
|
+
|
|
19
|
+
def initialize(machine, reader, isa_elements, gs_elements, st_elements)
|
|
20
|
+
@config = machine.config
|
|
21
|
+
@reader = reader
|
|
22
|
+
@queue = []
|
|
23
|
+
@queued = Set.new
|
|
24
|
+
|
|
25
|
+
@elements = Hash.new{|h,k| h[k] = [] }
|
|
26
|
+
@elements[:ISA] = isa_elements
|
|
27
|
+
@elements[:GS] = gs_elements
|
|
28
|
+
@elements[:ST] = st_elements
|
|
29
|
+
|
|
30
|
+
enqueue(machine.active.head)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def audit
|
|
34
|
+
while cursor = @queue.shift
|
|
35
|
+
step(Stupidedi::Parser::StateMachine.new(@config, [cursor]))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def enqueue(zipper)
|
|
42
|
+
# Avoid unecessary traversal over instruction tables that we've already
|
|
43
|
+
# queued. NOTE: This means some segments in the definition could be
|
|
44
|
+
# skipped entirely, due to have the same #successors as another segment.
|
|
45
|
+
#
|
|
46
|
+
# For instance, consider HP835's Table 1:
|
|
47
|
+
#
|
|
48
|
+
# d::TableDef.header("Table 1 - Header",
|
|
49
|
+
# b::Segment(100, s::ST, "Transaction Set Header", ...)
|
|
50
|
+
# b::Segment(200, s::BPR, "Financial Information", ...)
|
|
51
|
+
# b::Segment(400, s::TRN, "Reassociation Trace Number", ...)
|
|
52
|
+
# b::Segment(500, s::CUR, "Foreign Currency Information", ...)
|
|
53
|
+
# b::Segment(600, s::REF, "Receiver Identification", ...)
|
|
54
|
+
# b::Segment(600, s::REF, "Version Identification", ...)
|
|
55
|
+
# b::Segment(700, s::DTM, "Production Date", ...)
|
|
56
|
+
#
|
|
57
|
+
# It's important to know that two segments at the same position don't
|
|
58
|
+
# come "before" or "after" each other, even though they might be listed
|
|
59
|
+
# in a particular order.
|
|
60
|
+
#
|
|
61
|
+
# Starting at the CUR segment, its successors are the two REF segments
|
|
62
|
+
# and some other stuff. If we generate the first REF segment, due to
|
|
63
|
+
# having the same position (600) as the other REF segment, it also has
|
|
64
|
+
# both REF segments as successors. The second REF segment also has the
|
|
65
|
+
# same set of successors.
|
|
66
|
+
#
|
|
67
|
+
# Therefore, all three segments (CUR, REF, REF) have the same set of
|
|
68
|
+
# successors and it doesn't matter which one we choose; we only need
|
|
69
|
+
# to choose one.
|
|
70
|
+
#
|
|
71
|
+
id = zipper.node.instructions.hash
|
|
72
|
+
unless @queued.member?(id)
|
|
73
|
+
@queued.add(id)
|
|
74
|
+
@queue.push(zipper)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def step(machine)
|
|
79
|
+
machine.successors.head.constraints.each do |segment_id, table|
|
|
80
|
+
case table
|
|
81
|
+
when Stupidedi::Parser::ConstraintTable::Shallowest,
|
|
82
|
+
Stupidedi::Parser::ConstraintTable::Deepest
|
|
83
|
+
segment_tok =
|
|
84
|
+
mksegment_tok(@reader.segment_dict, segment_id, @elements[segment_id], nil)
|
|
85
|
+
|
|
86
|
+
instructions = table.matches(segment_tok, false, :insert)
|
|
87
|
+
if instructions.length > 1
|
|
88
|
+
raise Exceptions::InvalidSchemaError,
|
|
89
|
+
"proceeding from #{pp_machine(machine)}, there are no
|
|
90
|
+
constraints on segment #{segment_id} to choose between ".join +
|
|
91
|
+
table.pretty_inspect
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
op = instructions.head
|
|
95
|
+
as = machine.execute(op, machine.active.head, @reader, segment_tok)
|
|
96
|
+
enqueue(as)
|
|
97
|
+
|
|
98
|
+
when Stupidedi::Parser::ConstraintTable::Stub
|
|
99
|
+
if table.instructions.length == 1
|
|
100
|
+
segment_tok =
|
|
101
|
+
mksegment_tok(@reader.segment_dict, segment_id, @elements[segment_id], nil)
|
|
102
|
+
|
|
103
|
+
op = table.matches(nil, false, :insert).head
|
|
104
|
+
as = machine.execute(op, machine.active.head, @reader, segment_tok)
|
|
105
|
+
enqueue(as)
|
|
106
|
+
else
|
|
107
|
+
raise Exceptions::InvalidSchemaError,
|
|
108
|
+
"stub proceeding from #{pp_machine(machine)}, there are no
|
|
109
|
+
constraints on segment #{segment_id} to choose between ".join +
|
|
110
|
+
table.pretty_inspect
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
when Stupidedi::Parser::ConstraintTable::ValueBased
|
|
114
|
+
disjoint, distinct = table.basis(table.instructions, :insert)
|
|
115
|
+
|
|
116
|
+
if disjoint.empty?
|
|
117
|
+
# We're definitely going to throw an error now, but we need to
|
|
118
|
+
# get more information to make a useful error message. First
|
|
119
|
+
# check that interesting elements are required
|
|
120
|
+
designators = optional_elements(distinct, table.instructions)
|
|
121
|
+
|
|
122
|
+
case designators.length
|
|
123
|
+
when 0
|
|
124
|
+
when 1
|
|
125
|
+
raise Exceptions::InvalidSchemaError,
|
|
126
|
+
"proceeding from #{pp_machine(machine)}, the element
|
|
127
|
+
#{designators.join(", ")} is designated optional, but
|
|
128
|
+
is necessary to choose between ".join + table.pretty_inspect
|
|
129
|
+
else
|
|
130
|
+
raise Exceptions::InvalidSchemaError,
|
|
131
|
+
"proceeding from #{pp_machine(machine)}, the elements
|
|
132
|
+
#{designators.join(", ")} are designated optional, but
|
|
133
|
+
at least one is necessary to choose between ".join +
|
|
134
|
+
table.pretty_inspect
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Next report cases of element values that cause ambiguity
|
|
138
|
+
designators, ex_designator, ex_value, ex_instructions =
|
|
139
|
+
distinct_elements(distinct, table.instructions)
|
|
140
|
+
|
|
141
|
+
case designators.length
|
|
142
|
+
when 0
|
|
143
|
+
raise Exceptions::InvalidSchemaError,
|
|
144
|
+
"proceeding from #{pp_machine(machine)}, there are no
|
|
145
|
+
constraints on segment #{segment_id} to choose
|
|
146
|
+
between ".join + table.pretty_inspect
|
|
147
|
+
when 1
|
|
148
|
+
raise Exceptions::InvalidSchemaError,
|
|
149
|
+
"proceeding from #{pp_machine(machine)}, the element
|
|
150
|
+
#{ex_designator} has overlapping values among possible
|
|
151
|
+
choices; for example when it has value the #{ex_value},
|
|
152
|
+
it is not possible to choose between ".join +
|
|
153
|
+
table.copy(:instructions => ex_instructions).pretty_inspect
|
|
154
|
+
else
|
|
155
|
+
raise Exceptions::InvalidSchemaError,
|
|
156
|
+
"proceeding from #{pp_machine(machine)}, the elements
|
|
157
|
+
#{designators.join(", ")} each have overlapping values
|
|
158
|
+
among choices; for example when #{ex_designator} has
|
|
159
|
+
the value #{ex_value}, it is not possible to choose
|
|
160
|
+
between ".join + table.copy(:instructions => ex_instructions).pretty_inspect
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
# It's possible there is no ambiguity here, but we need to check
|
|
164
|
+
designators = optional_elements(disjoint, table.instructions)
|
|
165
|
+
|
|
166
|
+
case designators.length
|
|
167
|
+
when 0
|
|
168
|
+
when 1
|
|
169
|
+
raise Exceptions::InvalidSchemaError,
|
|
170
|
+
"proceeding from #{pp_machine(machine)}, the element
|
|
171
|
+
#{designators.join(", ")} is designated optional in at
|
|
172
|
+
least one case, but is necessary to choose between ".join +
|
|
173
|
+
table.pretty_inspect
|
|
174
|
+
else
|
|
175
|
+
raise Exceptions::InvalidSchemaError,
|
|
176
|
+
"proceeding from #{pp_machine(machine)}, the elements
|
|
177
|
+
#{designators.join(", ")} are designated optional in at
|
|
178
|
+
least one case each, but at least one is necessary to
|
|
179
|
+
choose between ".join + table.pretty_inspect
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
mksegments(table).each do |op_, segment_tok_|
|
|
183
|
+
enqueue(machine.execute(op_, machine.active.head, @reader, segment_tok_))
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
else
|
|
187
|
+
raise "unexpected kind of constraint table: #{constraint_table.class}"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# For each instruction in the given ConstraintTable::ValueBased, return
|
|
193
|
+
# the segment token which will be matched to that instruction.
|
|
194
|
+
#
|
|
195
|
+
# @return [Array<(Instruction, SegmentTok)>]
|
|
196
|
+
def mksegments(table)
|
|
197
|
+
disjoint, = table.basis(table.instructions, :insert)
|
|
198
|
+
remaining = Set.new(table.instructions)
|
|
199
|
+
segment_id = table.instructions.head.segment_use.id
|
|
200
|
+
segments = []
|
|
201
|
+
|
|
202
|
+
# Scan each interesting element location
|
|
203
|
+
disjoint.each do |(m, n), map|
|
|
204
|
+
if remaining.empty?
|
|
205
|
+
# We've already generated results for each possible instruction
|
|
206
|
+
break
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Likely many values are mapped to only a few unique keys; in this
|
|
210
|
+
# case, `instructions` is a singleton array (that's what makes the
|
|
211
|
+
# value non-ambiguous, there's only one possible instruction)
|
|
212
|
+
map.each do |value, instructions|
|
|
213
|
+
op, = instructions
|
|
214
|
+
repeatable = op.segment_use.definition.element_uses.at(m).repeatable?
|
|
215
|
+
elements = Array.new(m)
|
|
216
|
+
elements[m] =
|
|
217
|
+
if n.nil?
|
|
218
|
+
if repeatable
|
|
219
|
+
repeated(value)
|
|
220
|
+
else
|
|
221
|
+
value
|
|
222
|
+
end
|
|
223
|
+
else
|
|
224
|
+
components = Array.new(n)
|
|
225
|
+
components[n] = value
|
|
226
|
+
|
|
227
|
+
if repeatable
|
|
228
|
+
repeated(composite(components))
|
|
229
|
+
else
|
|
230
|
+
composite(components)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
if remaining.member?(op)
|
|
235
|
+
remaining.delete(op)
|
|
236
|
+
segments.push([op, mksegment_tok(@reader.segment_dict, segment_id, elements, nil)])
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
segments
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Return a list of element designators that are needed to choose a single
|
|
245
|
+
# instruction, but are not #required? in the transaction set definition.
|
|
246
|
+
#
|
|
247
|
+
# @return [Array<String>]
|
|
248
|
+
def optional_elements(disjoint, instructions)
|
|
249
|
+
designators = []
|
|
250
|
+
|
|
251
|
+
disjoint.each do |(m, n), map|
|
|
252
|
+
required = true
|
|
253
|
+
|
|
254
|
+
# Element must be required for all possible segment_uses
|
|
255
|
+
instructions.each do |op|
|
|
256
|
+
element_use = op.segment_use.definition.element_uses.at(m)
|
|
257
|
+
|
|
258
|
+
if n.nil?
|
|
259
|
+
required &&= element_use.required?
|
|
260
|
+
else
|
|
261
|
+
component_use = element_use.definition.component_uses.at(n)
|
|
262
|
+
required &&= element_use.required? and component_use.required?
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
unless required
|
|
267
|
+
segment_id = instructions.head.segment_use.id
|
|
268
|
+
designators << "#{segment_id}-#{"%02d" % (m+1)}#{n.try{"-%02d" % (n+1)}}"
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
if designators.length < disjoint.length
|
|
273
|
+
# This means at least one designator *was* required, therefore we can
|
|
274
|
+
# be assured that valid input will not be ambiguous
|
|
275
|
+
[]
|
|
276
|
+
else
|
|
277
|
+
# None of the useful elements were required, so even valid input (that
|
|
278
|
+
# has none of these elements present), will be ambiguous
|
|
279
|
+
designators
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Build a list of offending elements (eg, HL-03) and provide an example
|
|
284
|
+
# value that resolves to conflicting instructions.
|
|
285
|
+
#
|
|
286
|
+
# @return [(Array<String>, String, String, Array<Instruction>)]
|
|
287
|
+
def distinct_elements(distinct, instructions)
|
|
288
|
+
designators = []
|
|
289
|
+
ex_designator = nil
|
|
290
|
+
ex_value = nil
|
|
291
|
+
ex_instructions = nil
|
|
292
|
+
|
|
293
|
+
distinct.each do |(m, n), map|
|
|
294
|
+
designators <<
|
|
295
|
+
"#{instructions.head.segment_use.id}-#{"%02d" % (m+1)}#{n.try{"-%02d" % (n+1)}}"
|
|
296
|
+
|
|
297
|
+
if ex_designator.nil?
|
|
298
|
+
ex_designator = designators.last
|
|
299
|
+
ex_value, ex_instructions = map.max_by{|_, v| v.length }
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
return designators, ex_designator, ex_value, ex_instructions
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# @return [String]
|
|
307
|
+
def pp_machine(machine)
|
|
308
|
+
zipper = machine.active.head
|
|
309
|
+
segment = zipper.node.zipper.node
|
|
310
|
+
|
|
311
|
+
if segment.valid?
|
|
312
|
+
id = "% 3s" % segment.definition.id.to_s
|
|
313
|
+
name = segment.definition.name
|
|
314
|
+
width = 18
|
|
315
|
+
|
|
316
|
+
if name.length > width - 2
|
|
317
|
+
id = id + ": #{name.slice(0, width - 2)}.."
|
|
318
|
+
else
|
|
319
|
+
id = id + ": #{name}"
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
args = "position:%d, repeat:%s" %
|
|
323
|
+
[segment.usage.position,
|
|
324
|
+
segment.usage.repeat_count.inspect]
|
|
325
|
+
|
|
326
|
+
ansi.segment("SegmentDef[#{ansi.bold(id)}](#{args})")
|
|
327
|
+
else
|
|
328
|
+
segment.pretty_inspect
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
class << Ambiguity
|
|
334
|
+
# @param interchange_def
|
|
335
|
+
# Required. tediparse does not bundle envelope grammars, so callers
|
|
336
|
+
# must pass an explicit InterchangeDef — typically the synthetic one
|
|
337
|
+
# used to drive the rest of their test harness.
|
|
338
|
+
def build(transaction_set_def, functional_group_def, interchange_def)
|
|
339
|
+
# Use dummy identifiers to link transaction_set_def to the parser
|
|
340
|
+
config = mkconfig(transaction_set_def, functional_group_def,
|
|
341
|
+
"ISA11", "GS01", "GS08", "ST01", interchange_def)
|
|
342
|
+
|
|
343
|
+
builder = Parser::BuilderDsl.new(Parser.build(config, Zipper::Stack), false)
|
|
344
|
+
|
|
345
|
+
# These lists of elements are re-used when Ambiguity needs to construct
|
|
346
|
+
# an ISA, GS, or ST segment as it walks the transaction_set_def
|
|
347
|
+
isa_elements =
|
|
348
|
+
[ "00", "AUTHORIZATION",
|
|
349
|
+
"00", "PASSWORD",
|
|
350
|
+
"ZZ", "SUBMITTER ID",
|
|
351
|
+
"ZZ", "RECEIVER ID",
|
|
352
|
+
"191225", "1230",
|
|
353
|
+
"^",
|
|
354
|
+
"ISA11",
|
|
355
|
+
"123456789", "0", "T", ":" ]
|
|
356
|
+
|
|
357
|
+
gs_elements =
|
|
358
|
+
[ "GS01",
|
|
359
|
+
"SENDER ID",
|
|
360
|
+
"RECEIVER ID",
|
|
361
|
+
"20191225", "1230", "1", "X",
|
|
362
|
+
"GS08" ]
|
|
363
|
+
|
|
364
|
+
st_elements =
|
|
365
|
+
[ "ST01",
|
|
366
|
+
"1234" ]
|
|
367
|
+
|
|
368
|
+
builder.ISA(*isa_elements)
|
|
369
|
+
builder.GS(*gs_elements)
|
|
370
|
+
builder.ST(*st_elements)
|
|
371
|
+
|
|
372
|
+
new(builder.machine, builder.reader, isa_elements, gs_elements, st_elements)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def mkconfig(definition, functional_group_def, isa11, gs01, gs08, st01, interchange_def)
|
|
376
|
+
Config.new.customize do |c|
|
|
377
|
+
c.interchange.customize do |x|
|
|
378
|
+
# We can use whatever interchange version we like, it does not
|
|
379
|
+
# have any bearing or relationship to the given `definition`
|
|
380
|
+
x.register(isa11, interchange_def)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
c.functional_group.customize do |x|
|
|
384
|
+
x.register(gs08, functional_group_def)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
c.transaction_set.customize do |x|
|
|
388
|
+
x.register(gs08, gs01, st01, definition)
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|