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,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Stupidedi
|
|
3
|
+
using Refinements
|
|
4
|
+
|
|
5
|
+
module Writer
|
|
6
|
+
class Claredi
|
|
7
|
+
def initialize(node)
|
|
8
|
+
@node = node
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# @return [String]
|
|
12
|
+
def write(out = StringIO.new)
|
|
13
|
+
out << %Q(<html lang="en"><head>)
|
|
14
|
+
out << %Q(<meta charset="iso-8859-1">)
|
|
15
|
+
out << style
|
|
16
|
+
out << js
|
|
17
|
+
out << "</head><body>"
|
|
18
|
+
build(@node, out)
|
|
19
|
+
out << "</body></html>"
|
|
20
|
+
out
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def js
|
|
26
|
+
<<-JS
|
|
27
|
+
<script type="text/javascript">
|
|
28
|
+
function toggle() {
|
|
29
|
+
var trim = document.getElementsByClassName("trim");
|
|
30
|
+
for (var k = 0; k < trim.length; k ++)
|
|
31
|
+
trim[k].classList.toggle("hide");
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
JS
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [String]
|
|
38
|
+
def style
|
|
39
|
+
<<-CSS
|
|
40
|
+
<style>
|
|
41
|
+
body { font-size: 0.75em; }
|
|
42
|
+
|
|
43
|
+
.interchange, .functionalgr, .table, .loop > .label {
|
|
44
|
+
font-weight: bold;
|
|
45
|
+
font-family: Optima, Trebuchet, sans-serif;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.interchange, .functionalgr, .transaction {
|
|
49
|
+
margin-left: 1em;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.transaction {
|
|
53
|
+
margin-top: 1.5em;
|
|
54
|
+
margin-bottom: 1em;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.transaction > .label {
|
|
58
|
+
font-weight: bold;
|
|
59
|
+
font-size: 1.5em;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.table, .loop {
|
|
63
|
+
margin-top: 0.5em;
|
|
64
|
+
margin-bottom: 0.5em;
|
|
65
|
+
margin-left: 0.5em;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.table { margin-left: 1em; margin-bottom: 2em; }
|
|
69
|
+
|
|
70
|
+
.table > .label/*
|
|
71
|
+
.interchange > .label,
|
|
72
|
+
.functionalgr > .label*/ {
|
|
73
|
+
font-size: 1.25em;
|
|
74
|
+
border-bottom: 3px solid black;
|
|
75
|
+
margin-top: 1em;
|
|
76
|
+
margin-bottom: 0.5em;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.loop { border: 1px solid grey; border-right: 0; }
|
|
80
|
+
.loop > .label { padding: 3px; background-color: #ddd; }
|
|
81
|
+
.loop > .segment { margin: 3px 0 3px 6px; }
|
|
82
|
+
|
|
83
|
+
.segment {
|
|
84
|
+
font-weight: normal;
|
|
85
|
+
font-family: 'Andale Mono', Monospace, monospace;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.segment .label:first-child { font-weight: bold; }
|
|
89
|
+
|
|
90
|
+
.trim { color: #bbb; display: inline; }
|
|
91
|
+
.hide { display: none; }
|
|
92
|
+
|
|
93
|
+
#form {
|
|
94
|
+
display: block;
|
|
95
|
+
padding: 3px;
|
|
96
|
+
border: 1px solid #ccc;
|
|
97
|
+
margin: 3px 3px 30px 3px;
|
|
98
|
+
font-family: Optima, Trebuchet, sans-serif;
|
|
99
|
+
background-color: #eee;
|
|
100
|
+
}
|
|
101
|
+
</style>
|
|
102
|
+
CSS
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [void]
|
|
106
|
+
def build(node, out)
|
|
107
|
+
if node.element?
|
|
108
|
+
if node.composite?
|
|
109
|
+
tmp = StringIO.new
|
|
110
|
+
node.children.each{|e| build(e, tmp) }
|
|
111
|
+
tmp.string.gsub!(%r{^(<span [^>]+>):}, "\\1*")
|
|
112
|
+
tmp.string.gsub!(%r{((?:<span [^<]+>:?</span>)+)$}, %Q(<em class="trim hide">\\1</em>))
|
|
113
|
+
out << tmp.string
|
|
114
|
+
elsif node.component?
|
|
115
|
+
out << %Q(<span class="label" title="#{node.definition.name}">:#{node.to_x12}</span>)
|
|
116
|
+
elsif node.repeated?
|
|
117
|
+
tmp = StringIO.new
|
|
118
|
+
node.children.each{|e| build(e, tmp) }
|
|
119
|
+
tmp.string.gsub!("*", "^")
|
|
120
|
+
out << tmp.string
|
|
121
|
+
else
|
|
122
|
+
out << %Q(<span class="label" title="#{node.definition.name}">*#{node.to_x12}</span>)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
elsif node.segment?
|
|
126
|
+
out << %Q(<div class="segment"><span class="label" title="#{node.definition.name}">)
|
|
127
|
+
out << node.definition.id
|
|
128
|
+
out << "</span>"
|
|
129
|
+
tmp = StringIO.new
|
|
130
|
+
node.children.each{|e| build(e, tmp) }
|
|
131
|
+
tmp.string.gsub!(%r{((?:<span [^>]+>\*</span>)+)$}, %Q(<div class="trim hide">\\1</div>))
|
|
132
|
+
out << "#{tmp.string}~</div>\n"
|
|
133
|
+
|
|
134
|
+
elsif node.loop?
|
|
135
|
+
if m = /^(\d\w+)(?: [:-])? (.+)$/.match(node.definition.id)
|
|
136
|
+
id = m.captures[0]
|
|
137
|
+
name = m.captures[1]
|
|
138
|
+
name = name.split(/\s+/).map(&:capitalize).join(" ")
|
|
139
|
+
out << %Q(<div class="loop"><div class="label">#{name} (#{id})</div>\n)
|
|
140
|
+
else
|
|
141
|
+
out << %Q(<div class="loop"><div class="label">#{node.definition.id}</div>\n)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
node.children.each{|c| build(c, out) }
|
|
145
|
+
out << "</div>\n"
|
|
146
|
+
|
|
147
|
+
elsif node.table?
|
|
148
|
+
out << %Q(<div class="table"><div class="label">#{node.definition.id}</div>\n)
|
|
149
|
+
node.children.each{|c| build(c, out) }
|
|
150
|
+
out << "</div>\n"
|
|
151
|
+
|
|
152
|
+
elsif node.transaction_set?
|
|
153
|
+
out << %Q(<div class="transaction"><div class="label">Transaction Set #{node.definition.id}</div>\n)
|
|
154
|
+
node.children.each{|c| build(c, out) }
|
|
155
|
+
out << "</div>\n"
|
|
156
|
+
|
|
157
|
+
elsif node.functional_group?
|
|
158
|
+
out << %Q(<div class="functionalgr">)
|
|
159
|
+
# out << %Q(<div class="label">Functional Group (#{node.definition.id})</div>\n)
|
|
160
|
+
node.children.each{|c| build(c, out) }
|
|
161
|
+
out << "</div>\n"
|
|
162
|
+
|
|
163
|
+
elsif node.interchange?
|
|
164
|
+
out << %Q(<div class="interchange">)
|
|
165
|
+
# out << %Q(<div class="label">Interchange (#{node.definition.id})</div>\n)
|
|
166
|
+
node.children.each{|c| build(c, out) }
|
|
167
|
+
out << "</div>\n"
|
|
168
|
+
|
|
169
|
+
elsif node.transmission?
|
|
170
|
+
out << %Q(<label id="form"><input type="checkbox" id="hide" onchange="toggle()" checked/> Hide empty suffixes</label>)
|
|
171
|
+
out << %Q(<div class="transmission">\n)
|
|
172
|
+
node.children.each{|c| build(c, out) }
|
|
173
|
+
out << "</div>\n"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module Stupidedi
|
|
2
|
+
using Refinements
|
|
3
|
+
|
|
4
|
+
module Writer
|
|
5
|
+
class Default
|
|
6
|
+
# @return [Reader::Separators]
|
|
7
|
+
attr_reader :separators
|
|
8
|
+
|
|
9
|
+
def initialize(zipper, separators = Reader::Separators.empty)
|
|
10
|
+
@zipper, @separators =
|
|
11
|
+
zipper, separators
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return out
|
|
15
|
+
def write(out = "")
|
|
16
|
+
unless @zipper.node.transmission? or @zipper.node.interchange?
|
|
17
|
+
common = @separators.characters & @zipper.node.characters
|
|
18
|
+
message = common.to_a.map(&:inspect).join(", ")
|
|
19
|
+
|
|
20
|
+
if common.present?
|
|
21
|
+
raise Exceptions::OutputError,
|
|
22
|
+
"separator characters #{message} occur as data"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
out.tap { recurse(@zipper.node, @separators, out) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def recurse(value, separators, out)
|
|
32
|
+
return if value.invalid?
|
|
33
|
+
|
|
34
|
+
if value.segment?
|
|
35
|
+
segment(value, separators, out)
|
|
36
|
+
else
|
|
37
|
+
if value.interchange?
|
|
38
|
+
unless separators.merge(value.separators) == separators
|
|
39
|
+
common = separators.characters & value.characters
|
|
40
|
+
message = common.to_a.map(&:inspect).join(", ")
|
|
41
|
+
|
|
42
|
+
if common.present?
|
|
43
|
+
raise Exceptions::OutputError,
|
|
44
|
+
"separator characters #{message} occur as data"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Change ISA11 and ISA16
|
|
48
|
+
value = value.replace_separators(separators)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
separators = value.separators
|
|
52
|
+
|
|
53
|
+
raise Exceptions::OutputError,
|
|
54
|
+
"separators.segment cannot be blank" if separators.segment.blank?
|
|
55
|
+
|
|
56
|
+
raise Exceptions::OutputError,
|
|
57
|
+
"separators.element cannot be blank" if separators.element.blank?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
value.children.each{|c| recurse(c, separators, out) }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def segment(s, separators, out)
|
|
65
|
+
# It's likely annoying for the user to need to check for at least
|
|
66
|
+
# one non-blank element before generating a segment. Instead, we
|
|
67
|
+
# can check that after the segment was generated and supress any
|
|
68
|
+
# empty segments here.
|
|
69
|
+
return if s.empty?
|
|
70
|
+
|
|
71
|
+
out << s.id.to_s
|
|
72
|
+
|
|
73
|
+
# Trailing empty elements (including component elements) can be omitted,
|
|
74
|
+
# so "NM1*XX*1:2::::*****~" should be abbreviated to "NM1*XX*1:2~".
|
|
75
|
+
elements = s.children.
|
|
76
|
+
reverse.drop_while(&:empty?).reverse # Remove the trailing empties
|
|
77
|
+
|
|
78
|
+
elements.each do |e|
|
|
79
|
+
out << separators.element
|
|
80
|
+
element(e, separators, out)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
out << separators.segment
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def element(e, separators, out)
|
|
87
|
+
if e.simple?
|
|
88
|
+
out << e.to_x12
|
|
89
|
+
|
|
90
|
+
elsif e.composite?
|
|
91
|
+
components = e.children.
|
|
92
|
+
reverse.drop_while(&:empty?).reverse
|
|
93
|
+
|
|
94
|
+
unless components.empty?
|
|
95
|
+
out << components.head.to_x12
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
components.tail.each do |c|
|
|
99
|
+
out << separators.component
|
|
100
|
+
out << c.to_x12
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
elsif e.repeated?
|
|
104
|
+
occurrences = e.children.
|
|
105
|
+
reverse.drop_while(&:empty?).reverse
|
|
106
|
+
|
|
107
|
+
unless occurrences.empty?
|
|
108
|
+
element(occurrences.head, separators, out)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
occurrences.tail.each do |o|
|
|
112
|
+
out << separators.repetition
|
|
113
|
+
element(o, separators, out)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Stupidedi
|
|
3
|
+
using Refinements
|
|
4
|
+
|
|
5
|
+
module Zipper
|
|
6
|
+
class AbstractCursor
|
|
7
|
+
# @return [#leaf?, #children, #copy]
|
|
8
|
+
abstract :node
|
|
9
|
+
|
|
10
|
+
# @return [AbstractPath]
|
|
11
|
+
abstract :path
|
|
12
|
+
|
|
13
|
+
# @group Querying the Tree Location
|
|
14
|
+
#########################################################################
|
|
15
|
+
|
|
16
|
+
# (see AbstractPath#depth)
|
|
17
|
+
def depth
|
|
18
|
+
path.depth
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# (see AbstractPath#first?)
|
|
22
|
+
def first?
|
|
23
|
+
path.first?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# (see AbstractPath#last?)
|
|
27
|
+
def last?
|
|
28
|
+
path.last?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# True if the node has no children
|
|
32
|
+
abstract :leaf?
|
|
33
|
+
|
|
34
|
+
# True if the node has no parent
|
|
35
|
+
abstract :root?
|
|
36
|
+
|
|
37
|
+
# Returns nodes between this zipper and the other, including `self.node`
|
|
38
|
+
# and `other.node` as end points. When this and the other zipper point to
|
|
39
|
+
# the same node within the tree, a single node is returned. Otherwise, the
|
|
40
|
+
# nodes are returned in order according to a left-to-right depth-first
|
|
41
|
+
# pre-order traversal.
|
|
42
|
+
#
|
|
43
|
+
# @note This method assumes `other` is a zipper for the same tree as the
|
|
44
|
+
# tree wrapped by `this`. In general, there is no way to know if that is
|
|
45
|
+
# or isn't the case, without comparing the entire tree. If this method
|
|
46
|
+
# is called on two different trees, the results are undefined.
|
|
47
|
+
#
|
|
48
|
+
# @return [Array]
|
|
49
|
+
def between(other)
|
|
50
|
+
# Collect ancestors of self, sorted oldest first (deepest last). This
|
|
51
|
+
# forms a boundary of nodes, which is called a "spine" below
|
|
52
|
+
zipper = self
|
|
53
|
+
lspine = [self]
|
|
54
|
+
|
|
55
|
+
until zipper.root?
|
|
56
|
+
zipper = zipper.up
|
|
57
|
+
lspine.unshift(zipper)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Collect ancestors of self, sorted oldest first (deepest last). This
|
|
61
|
+
# forms a list of boundary nodes, which is called a "spine" below
|
|
62
|
+
zipper = other
|
|
63
|
+
rspine = [other]
|
|
64
|
+
|
|
65
|
+
until zipper.root?
|
|
66
|
+
zipper = zipper.up
|
|
67
|
+
rspine.unshift(zipper)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Now we have two spines, both beginning with the root node. We remove
|
|
71
|
+
# the prefix common to both spines.
|
|
72
|
+
while a = lspine.first and b = rspine.first
|
|
73
|
+
if a.path == b.path
|
|
74
|
+
lspine.shift
|
|
75
|
+
rspine.shift
|
|
76
|
+
else
|
|
77
|
+
break
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if lspine.empty?
|
|
82
|
+
# The other node is a child of self's node, and rspine contains all
|
|
83
|
+
# the nodes along the path between the two nodes, not including the
|
|
84
|
+
# self node.
|
|
85
|
+
return node.cons(rspine.map(&:node))
|
|
86
|
+
|
|
87
|
+
elsif rspine.empty?
|
|
88
|
+
# Self's node is a child of other's node, and lspine contains all
|
|
89
|
+
# the nodes along the path between the two nodes, not including the
|
|
90
|
+
# other node
|
|
91
|
+
return other.node.cons(lspine.map(&:node))
|
|
92
|
+
|
|
93
|
+
elsif lspine.head.path.position > rspine.head.path.position
|
|
94
|
+
# The first elements of lspine and rspine are siblings that share a
|
|
95
|
+
# common parent. Arrange them such that lspine is on the left, and
|
|
96
|
+
# so rspine is on the right
|
|
97
|
+
lspine, rspine = rspine, lspine
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
between = []
|
|
101
|
+
|
|
102
|
+
# Starting at the bottom of the left spine working upward, accumulate
|
|
103
|
+
# all the nodes to the right of the spine. Remember this is contained
|
|
104
|
+
# within the subtree under lspine.head
|
|
105
|
+
lspine.each{|z| between << z.node }
|
|
106
|
+
lspine.tail.reverse.each do |zipper1|
|
|
107
|
+
until zipper1.last?
|
|
108
|
+
zipper1 = zipper1.next
|
|
109
|
+
between.concat(zipper1.flatten)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# For the sibling nodes directly between (not including) lspine.head
|
|
114
|
+
# and rspine.head, we can accumulate the entire subtrees.
|
|
115
|
+
count = rspine.head.path.position - lspine.head.path.position - 1
|
|
116
|
+
zipper = lspine.head
|
|
117
|
+
|
|
118
|
+
count.times do
|
|
119
|
+
zipper = zipper.next
|
|
120
|
+
between.concat(zipper.flatten)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
between << rspine.head.node
|
|
124
|
+
|
|
125
|
+
rspine.tail.each do |zipper_|
|
|
126
|
+
count = zipper_.path.position
|
|
127
|
+
zipper_ = zipper_.first
|
|
128
|
+
|
|
129
|
+
# We have to do a bit more work to traverse the siblings in left-to-
|
|
130
|
+
# right order, because `zipper_` is now the left spine. We start on
|
|
131
|
+
# the first sibling and move left a fixed number of times
|
|
132
|
+
count.times do
|
|
133
|
+
between.concat(zipper_.flatten)
|
|
134
|
+
zipper_ = zipper_.next
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Now zipper_ is along the left spine. We don't expand it here, but the
|
|
138
|
+
# next item in rspine is the next child along the left spine
|
|
139
|
+
between << zipper_.node
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
between
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Flattens the subtree into an Array of nodes. The nodes are arranged
|
|
146
|
+
# according to a depth-first left-to-right preorder traversal.
|
|
147
|
+
#
|
|
148
|
+
# @return [Array]
|
|
149
|
+
def flatten
|
|
150
|
+
nodes = []
|
|
151
|
+
queue = [node]
|
|
152
|
+
|
|
153
|
+
while node = queue.shift
|
|
154
|
+
nodes << node
|
|
155
|
+
queue.unshift(*node.children) unless node.leaf?
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
nodes
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @group Traversing the Tree
|
|
162
|
+
#########################################################################
|
|
163
|
+
|
|
164
|
+
# Navigate to the first child node
|
|
165
|
+
#
|
|
166
|
+
# @return [MemoizedCursor]
|
|
167
|
+
def down
|
|
168
|
+
@__down ||= begin
|
|
169
|
+
if leaf?
|
|
170
|
+
raise Exceptions::ZipperError,
|
|
171
|
+
"cannot descend into leaf node"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
head, *tail = @node.children
|
|
175
|
+
|
|
176
|
+
MemoizedCursor.new(head,
|
|
177
|
+
Hole.new([], @path, tail), self)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Navigate to the first child node, or if there are no children,
|
|
182
|
+
# create a placeholder where the first child node will be placed
|
|
183
|
+
#
|
|
184
|
+
# @return [AbstractCursor]
|
|
185
|
+
def dangle
|
|
186
|
+
if node.leaf?
|
|
187
|
+
raise Exceptions::ZipperError,
|
|
188
|
+
"cannot descend into leaf node"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
if leaf?
|
|
192
|
+
DanglingCursor.new(self)
|
|
193
|
+
else
|
|
194
|
+
down
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Navigate to the `nth` child node
|
|
199
|
+
#
|
|
200
|
+
# @return [AbstractCursor]
|
|
201
|
+
def child(n)
|
|
202
|
+
if n < 0
|
|
203
|
+
raise Exceptions::ZipperError,
|
|
204
|
+
"child index cannot be negative"
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
cursor = down
|
|
208
|
+
until n.zero?
|
|
209
|
+
cursor = cursor.next
|
|
210
|
+
n -= 1
|
|
211
|
+
end
|
|
212
|
+
cursor
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Returns a list of cursors, one pointing to each child node.
|
|
216
|
+
#
|
|
217
|
+
# @return [Array<MemoizedCursor>]
|
|
218
|
+
def children
|
|
219
|
+
children = []
|
|
220
|
+
|
|
221
|
+
unless leaf?
|
|
222
|
+
zipper = down
|
|
223
|
+
children << zipper
|
|
224
|
+
|
|
225
|
+
until zipper.last?
|
|
226
|
+
zipper = zipper.next
|
|
227
|
+
children << zipper
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
children
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Recursively descend to each node's `nth` child
|
|
235
|
+
#
|
|
236
|
+
# @return [AbstractCursor]
|
|
237
|
+
def descendant(n, *ns)
|
|
238
|
+
cursor = self
|
|
239
|
+
|
|
240
|
+
n.cons(ns).each do |n_|
|
|
241
|
+
cursor = cursor.child(n_)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
cursor
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Navigate to the parent node
|
|
248
|
+
#
|
|
249
|
+
# @return [AbstractCursor]
|
|
250
|
+
abstract :up
|
|
251
|
+
|
|
252
|
+
# Navigate to the next (rightward) sibling node
|
|
253
|
+
#
|
|
254
|
+
# @return [AbstractCursor]
|
|
255
|
+
abstract :next
|
|
256
|
+
|
|
257
|
+
# Navigate to the previous (leftward) sibling node
|
|
258
|
+
#
|
|
259
|
+
# @return [AbstractCursor]
|
|
260
|
+
abstract :prev
|
|
261
|
+
|
|
262
|
+
# Navigate to the first (leftmost) sibling node
|
|
263
|
+
#
|
|
264
|
+
# @return [AbstractCursor]
|
|
265
|
+
abstract :first
|
|
266
|
+
|
|
267
|
+
# Navigate to the last (rightmost) sibling node
|
|
268
|
+
#
|
|
269
|
+
# @return [AbstractCursor]
|
|
270
|
+
abstract :last
|
|
271
|
+
|
|
272
|
+
# Navigate to the root node
|
|
273
|
+
#
|
|
274
|
+
# @return [RootCursor]
|
|
275
|
+
def root
|
|
276
|
+
cursor = self
|
|
277
|
+
cursor = cursor.up until cursor.root?
|
|
278
|
+
cursor
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# @group Editing the Tree
|
|
282
|
+
#########################################################################
|
|
283
|
+
|
|
284
|
+
# Insert a new sibling node after (to the right of) the current node,
|
|
285
|
+
# and navigate to the new sibling node
|
|
286
|
+
#
|
|
287
|
+
# @return [EditedCursor]
|
|
288
|
+
abstract :append, :args => %w(sibling)
|
|
289
|
+
|
|
290
|
+
# Insert a new sibling node before (to the left of) the current node,
|
|
291
|
+
# and navigate to the new sibling node
|
|
292
|
+
#
|
|
293
|
+
# @return [EditedCursor]
|
|
294
|
+
abstract :prepend, :args => %w(sibling)
|
|
295
|
+
|
|
296
|
+
# (see #append)
|
|
297
|
+
def insert_right(sibling)
|
|
298
|
+
append(sibling)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# (see #prepend)
|
|
302
|
+
def insert_left(sibling)
|
|
303
|
+
prepend(sibling)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Insert a new child node before (to the left of) any existing children
|
|
307
|
+
# nodes and navigate to the new child node
|
|
308
|
+
#
|
|
309
|
+
# @return [EditedCursor]
|
|
310
|
+
def prepend_child(child)
|
|
311
|
+
if node.leaf?
|
|
312
|
+
raise Exceptions::ZipperError,
|
|
313
|
+
"cannot add child to leaf node"
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
EditedCursor.new(child,
|
|
317
|
+
Hole.new([], path, node.children), self)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Insert a new child node after (to the right of) any existing children
|
|
321
|
+
# nodes and navigate to the new child node
|
|
322
|
+
#
|
|
323
|
+
# @return [EditedCursor]
|
|
324
|
+
def append_child(child)
|
|
325
|
+
if node.leaf?
|
|
326
|
+
raise Exceptions::ZipperError,
|
|
327
|
+
"cannot add child to leaf node"
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
EditedCursor.new(child,
|
|
331
|
+
Hole.new(node.children.reverse, path, []), self)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Replace the current node with the given node
|
|
335
|
+
#
|
|
336
|
+
# @return [AbstractCursor]
|
|
337
|
+
abstract :replace, :args => %w(node)
|
|
338
|
+
|
|
339
|
+
# Remove the current node, and navigate to the next (rightward) node if
|
|
340
|
+
# one exists. Otherwise, navigate to the previous (leftward) node if one
|
|
341
|
+
# exists. Otherwise, create a placeholder where the next sibling node will
|
|
342
|
+
# be created.
|
|
343
|
+
#
|
|
344
|
+
# @return [EditedCursor]
|
|
345
|
+
abstract :delete
|
|
346
|
+
|
|
347
|
+
# @endgroup
|
|
348
|
+
#########################################################################
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|