mkxms-mssql 1.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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +1 -0
- data/bin/mkxms-mssql +5 -0
- data/lib/mkxms/mssql/access_object_definition.rb +61 -0
- data/lib/mkxms/mssql/adoption_script_writer.rb +1486 -0
- data/lib/mkxms/mssql/check_constraint_handler.rb +56 -0
- data/lib/mkxms/mssql/database_handler.rb +339 -0
- data/lib/mkxms/mssql/default_constraint_handler.rb +46 -0
- data/lib/mkxms/mssql/engine.rb +88 -0
- data/lib/mkxms/mssql/exceptions.rb +10 -0
- data/lib/mkxms/mssql/filegroup_handler.rb +81 -0
- data/lib/mkxms/mssql/foreign_key_handler.rb +85 -0
- data/lib/mkxms/mssql/function_handler.rb +74 -0
- data/lib/mkxms/mssql/indented_string_builder.rb +199 -0
- data/lib/mkxms/mssql/index_column.rb +11 -0
- data/lib/mkxms/mssql/index_handler.rb +98 -0
- data/lib/mkxms/mssql/keylike_constraint_helper.rb +67 -0
- data/lib/mkxms/mssql/permission_handler.rb +115 -0
- data/lib/mkxms/mssql/primary_key_handler.rb +36 -0
- data/lib/mkxms/mssql/property_handler.rb +87 -0
- data/lib/mkxms/mssql/query_cursor.rb +111 -0
- data/lib/mkxms/mssql/role_handler.rb +55 -0
- data/lib/mkxms/mssql/schema_handler.rb +42 -0
- data/lib/mkxms/mssql/sql_string_manipulators.rb +46 -0
- data/lib/mkxms/mssql/statistics_handler.rb +59 -0
- data/lib/mkxms/mssql/stored_procedure_handler.rb +65 -0
- data/lib/mkxms/mssql/table_handler.rb +180 -0
- data/lib/mkxms/mssql/unique_constraint_handler.rb +32 -0
- data/lib/mkxms/mssql/utils.rb +83 -0
- data/lib/mkxms/mssql/version.rb +5 -0
- data/lib/mkxms/mssql/view_handler.rb +58 -0
- data/lib/mkxms/mssql.rb +62 -0
- data/mkxms-mssql.gemspec +26 -0
- data/spec/utils/indented_string_builder_spec.rb +218 -0
- data/spec/utils/query_cursor_spec.rb +57 -0
- metadata +142 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'mkxms/mssql/property_handler'
|
2
|
+
|
3
|
+
module Mkxms; end
|
4
|
+
|
5
|
+
module Mkxms::Mssql
|
6
|
+
class CheckConstraint
|
7
|
+
include ExtendedProperties, Property::Hosting
|
8
|
+
|
9
|
+
def initialize(schema, table, name, enabled: true, when_replicated: true)
|
10
|
+
@schema = schema
|
11
|
+
@table = table
|
12
|
+
@name = name
|
13
|
+
@enabled = enabled
|
14
|
+
@when_replicated = when_replicated
|
15
|
+
@expression = ''
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :schema, :table, :name, :enabled, :when_replicated
|
19
|
+
attr_reader :expression
|
20
|
+
|
21
|
+
def to_sql
|
22
|
+
"ALTER TABLE #@schema.#@table ADD%s CHECK%s #@expression;%s" % [
|
23
|
+
@name ? " CONSTRAINT #@name" : '',
|
24
|
+
@when_replicated ? '' : ' NOT FOR REPLICATION',
|
25
|
+
@enabled ? '' : "\nALTER TABLE #@schema.#@table NOCHECK CONSTRAINT #@name;"
|
26
|
+
] + (name ? extended_properties_sql.joined_on_new_lines : '')
|
27
|
+
end
|
28
|
+
|
29
|
+
def qualified_table
|
30
|
+
"#@schema.#@table"
|
31
|
+
end
|
32
|
+
|
33
|
+
def qualified_name
|
34
|
+
"#@schema.#@name" if @name
|
35
|
+
end
|
36
|
+
|
37
|
+
def property_subject_identifiers
|
38
|
+
['SCHEMA', schema, 'TABLE', table, 'CONSTRAINT', name].map {|s| Utils::unquoted_name(s)}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class CheckConstraintHandler
|
43
|
+
include PropertyHandler::ElementHandler
|
44
|
+
|
45
|
+
def initialize(constraints, node)
|
46
|
+
a = node.attributes
|
47
|
+
|
48
|
+
@check = CheckConstraint.new(a['schema'], a['table'], a['name'],
|
49
|
+
enabled: !a['disabled'], when_replicated: !a['not-for-replication'])
|
50
|
+
end
|
51
|
+
|
52
|
+
def handle_text(text, parent_element)
|
53
|
+
@check.expression << text
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,339 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'set'
|
3
|
+
require 'xmigra'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
%w[
|
7
|
+
|
8
|
+
adoption_script_writer
|
9
|
+
check_constraint_handler
|
10
|
+
default_constraint_handler
|
11
|
+
filegroup_handler
|
12
|
+
foreign_key_handler
|
13
|
+
function_handler
|
14
|
+
index_handler
|
15
|
+
permission_handler
|
16
|
+
primary_key_handler
|
17
|
+
property_handler
|
18
|
+
role_handler
|
19
|
+
schema_handler
|
20
|
+
statistics_handler
|
21
|
+
stored_procedure_handler
|
22
|
+
table_handler
|
23
|
+
unique_constraint_handler
|
24
|
+
utils
|
25
|
+
view_handler
|
26
|
+
|
27
|
+
].each {|f| require "mkxms/mssql/" + f}
|
28
|
+
|
29
|
+
module Mkxms; end
|
30
|
+
|
31
|
+
module Mkxms::Mssql
|
32
|
+
class Mkxms::Mssql::DatabaseHandler
|
33
|
+
extend Utils::InitializedAttributes
|
34
|
+
include ExtendedProperties, PropertyHandler::ElementHandler
|
35
|
+
|
36
|
+
ADOPTION_SQL_FILE = "adopt.sql"
|
37
|
+
|
38
|
+
def initialize(**kwargs)
|
39
|
+
@schema_dir = kwargs[:schema_dir] || Pathname.pwd
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :schema_dir
|
43
|
+
attr_init(:filegroups, :schemas, :roles, :tables, :column_defaults, :pku_constraints, :foreign_keys, :check_constraints){[]}
|
44
|
+
attr_init(:indexes, :statistics){[]}
|
45
|
+
attr_init(:views, :udfs, :procedures){[]}
|
46
|
+
attr_init(:permissions){[]}
|
47
|
+
|
48
|
+
def handle_database_element(parse)
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle_filegroup_element(parse)
|
52
|
+
parse.delegate_to FilegroupHandler, filegroups
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_fulltext_document_type_element(parse)
|
56
|
+
# TODO: Check that these types are registered in the target instance
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_schema_element(parse)
|
60
|
+
parse.delegate_to SchemaHandler, schemas
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_role_element(parse)
|
64
|
+
parse.delegate_to RoleHandler, roles
|
65
|
+
end
|
66
|
+
|
67
|
+
def handle_table_element(parse)
|
68
|
+
parse.delegate_to TableHandler, tables
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle_default_constraint_element(parse)
|
72
|
+
parse.delegate_to DefaultConstraintHandler, column_defaults
|
73
|
+
end
|
74
|
+
|
75
|
+
def handle_primary_key_element(parse)
|
76
|
+
parse.delegate_to PrimaryKeyHandler, pku_constraints
|
77
|
+
end
|
78
|
+
|
79
|
+
def handle_foreign_key_element(parse)
|
80
|
+
parse.delegate_to ForeignKeyHandler, foreign_keys
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_unique_constraint_element(parse)
|
84
|
+
parse.delegate_to UniqueConstraintHandler, pku_constraints
|
85
|
+
end
|
86
|
+
|
87
|
+
def handle_check_constraint_element(parse)
|
88
|
+
parse.delegate_to CheckConstraintHandler, check_constraints
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle_index_element(parse)
|
92
|
+
parse.delegate_to IndexHandler, indexes
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_statistics_element(parse)
|
96
|
+
parse.delegate_to StatisticsHandler, statistics
|
97
|
+
end
|
98
|
+
|
99
|
+
def handle_view_element(parse)
|
100
|
+
parse.delegate_to ViewHandler, views
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_stored_procedure_element(parse)
|
104
|
+
parse.delegate_to StoredProcedureHandler, procedures
|
105
|
+
end
|
106
|
+
|
107
|
+
def handle_user_defined_function_element(parse)
|
108
|
+
parse.delegate_to FunctionHandler, udfs
|
109
|
+
end
|
110
|
+
|
111
|
+
def handle_granted_element(parse)
|
112
|
+
parse.delegate_to PermissionHandler, permissions
|
113
|
+
end
|
114
|
+
|
115
|
+
def handle_denied_element(parse)
|
116
|
+
parse.delegate_to PermissionHandler, permissions
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_source_files
|
120
|
+
dbinfo_path = @schema_dir.join(XMigra::SchemaManipulator::DBINFO_FILE)
|
121
|
+
|
122
|
+
if dbinfo_path.exist?
|
123
|
+
raise ProgramArgumentError.new("#{@schema_dir} already contains an XMigra schema")
|
124
|
+
end
|
125
|
+
|
126
|
+
# TODO: Sort dependencies of triggers, views, user defined functions, and
|
127
|
+
# stored procedures to determine which ones must be incorporated into a
|
128
|
+
# migration (all the ones depended on by any triggers).
|
129
|
+
|
130
|
+
# Create schema_dir if it does not exist
|
131
|
+
@schema_dir.mkpath
|
132
|
+
|
133
|
+
# Create and populate @schema_dir + XMigra::SchemaManipulator::DBINFO_FILE
|
134
|
+
dbinfo_path.open('w') do |dbi|
|
135
|
+
dbi.puts "system: #{XMigra::MSSQLSpecifics::SYSTEM_NAME}"
|
136
|
+
end
|
137
|
+
|
138
|
+
# TODO: Create migration to check required filegroups and files
|
139
|
+
|
140
|
+
# Migration: Create roles
|
141
|
+
create_migration(
|
142
|
+
"create-roles",
|
143
|
+
"Create roles for accessing the database.",
|
144
|
+
(roles.map(&:definition_sql) + roles.map(&:authorization_sql).compact + roles.map(&:membership_sql)).join("\n"),
|
145
|
+
roles.map(&:name).sort
|
146
|
+
)
|
147
|
+
|
148
|
+
# Migration: Create schemas
|
149
|
+
create_migration(
|
150
|
+
"create-schemas",
|
151
|
+
"Create schemas for containing database objects and controlling access.",
|
152
|
+
joined_modobj_sql(schemas, sep: "\nGO\n"),
|
153
|
+
schemas.map(&:name).sort
|
154
|
+
)
|
155
|
+
|
156
|
+
tables.each do |table|
|
157
|
+
# Migration: Create table
|
158
|
+
qual_name = [table.schema, table.name].join('.')
|
159
|
+
create_migration(
|
160
|
+
"create-table #{qual_name}",
|
161
|
+
"Create #{qual_name} table.",
|
162
|
+
table.to_sql,
|
163
|
+
[table.schema, qual_name]
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Migration: Add column defaults
|
168
|
+
create_migration(
|
169
|
+
"add-column-defaults",
|
170
|
+
"Add default constraints to table columns.",
|
171
|
+
joined_modobj_sql(column_defaults),
|
172
|
+
column_defaults.map {|d| [d.schema, d.qualified_table, d.qualified_column, d.qualified_name].compact}.flatten.uniq.sort
|
173
|
+
)
|
174
|
+
|
175
|
+
# Migration: Add primary key and unique constraints
|
176
|
+
create_migration(
|
177
|
+
"add-primary-key-and-unique-constraints",
|
178
|
+
"Add primary key and unique constraints.",
|
179
|
+
joined_modobj_sql(pku_constraints),
|
180
|
+
pku_constraints.map {|c| [c.schema, c.qualified_table, c.qualified_name].compact}.flatten.uniq.sort
|
181
|
+
)
|
182
|
+
|
183
|
+
# Migration: Add foreign key constraints
|
184
|
+
create_migration(
|
185
|
+
"add-foreign-key-constraints",
|
186
|
+
"Add foreign key constraints.",
|
187
|
+
joined_modobj_sql(foreign_keys),
|
188
|
+
foreign_keys.map {|c| [c.schema, c.qualified_table, c.qualified_name].compact}.flatten.uniq.sort
|
189
|
+
)
|
190
|
+
|
191
|
+
# Migration: Add check constraints
|
192
|
+
create_migration(
|
193
|
+
"add-check-constraints",
|
194
|
+
"Add check constraints.",
|
195
|
+
joined_modobj_sql(check_constraints),
|
196
|
+
check_constraints.map {|c| [c.schema, c.qualified_table, c.qualified_name].compact}.flatten.uniq.sort
|
197
|
+
)
|
198
|
+
|
199
|
+
# Check that no super-permissions reference a view, user-defined function, or stored procedure
|
200
|
+
access_object_names = (views + udfs + procedures).map {|ao| ao.qualified_name}
|
201
|
+
permissions.map {|p| p.super_permissions}.flatten.select do |p|
|
202
|
+
access_object_names.include?(p.target)
|
203
|
+
end.group_by {|p| p.target}.tap do |problems|
|
204
|
+
raise UnsupportedFeatureError.new(
|
205
|
+
"#{problems[0].target} cannot be granted the required permission(s)."
|
206
|
+
) if problems.length == 1
|
207
|
+
|
208
|
+
raise UnsupportedFeatureError.new(
|
209
|
+
(
|
210
|
+
["The required permissions cannot be granted on:"] +
|
211
|
+
problems.map {|p| ' ' + p.target}
|
212
|
+
).join("\n")
|
213
|
+
) unless problems.empty?
|
214
|
+
end
|
215
|
+
|
216
|
+
# Write a migration with all super-permissions
|
217
|
+
super_permissions = permissions.map {|p| p.super_permissions_sql}.inject([], :concat)
|
218
|
+
create_migration(
|
219
|
+
"add-super-permissions",
|
220
|
+
"Add permissions that confound the normal GRANT model.",
|
221
|
+
super_permissions.join("\n"),
|
222
|
+
permissions.map {|p| p.super_permissions.map(&:unscoped_target)}.flatten.uniq.sort
|
223
|
+
) unless super_permissions.empty?
|
224
|
+
|
225
|
+
indexes.each do |index|
|
226
|
+
write_index_def(index)
|
227
|
+
end
|
228
|
+
|
229
|
+
write_statistics
|
230
|
+
|
231
|
+
views.each do |view|
|
232
|
+
write_access_def(view, 'view')
|
233
|
+
end
|
234
|
+
|
235
|
+
udfs.each do |udf|
|
236
|
+
write_access_def(udf, 'function')
|
237
|
+
end
|
238
|
+
|
239
|
+
procedures.each do |procedure|
|
240
|
+
write_access_def(procedure, 'stored procedure')
|
241
|
+
end
|
242
|
+
|
243
|
+
@schema_dir.join(XMigra::SchemaManipulator::PERMISSIONS_FILE).open('w') do |p_file|
|
244
|
+
YAML.dump(
|
245
|
+
permissions.map do |p|
|
246
|
+
p.regular_permissions_graph.map do |k, v|
|
247
|
+
[k, {p.subject => v}]
|
248
|
+
end.to_h
|
249
|
+
end.inject({}) do |r, n|
|
250
|
+
r.update(n) {|k, lv, rv| lv.merge rv}
|
251
|
+
end,
|
252
|
+
p_file
|
253
|
+
)
|
254
|
+
end
|
255
|
+
|
256
|
+
create_adoption_script
|
257
|
+
end
|
258
|
+
|
259
|
+
def migration_chain
|
260
|
+
@migration_chain ||= XMigra::NewMigrationAdder.new(@schema_dir)
|
261
|
+
end
|
262
|
+
|
263
|
+
def create_migration(summary, description, sql, change_targets)
|
264
|
+
migration_chain.add_migration(
|
265
|
+
summary,
|
266
|
+
description: description,
|
267
|
+
sql: sql,
|
268
|
+
changes: change_targets
|
269
|
+
)
|
270
|
+
end
|
271
|
+
|
272
|
+
def joined_modobj_sql(ary, sep: "\n")
|
273
|
+
ary.map(&:to_sql).join(sep)
|
274
|
+
end
|
275
|
+
|
276
|
+
def write_access_def(access_obj, obj_type)
|
277
|
+
# Use Psych mid-level emitting API to specify literal syntax for SQL
|
278
|
+
def_tree = Psych::Nodes::Mapping.new
|
279
|
+
["define", obj_type, "sql"].each do |s|
|
280
|
+
def_tree.children << Psych::Nodes::Scalar.new(s)
|
281
|
+
end
|
282
|
+
def_tree.children << Psych::Nodes::Scalar.new(access_obj.to_sql, nil, nil, false, true,
|
283
|
+
Psych::Nodes::Scalar::LITERAL)
|
284
|
+
unless (references = access_obj.respond_to?(:references) ? access_obj.references : []).empty?
|
285
|
+
def_tree.children << Psych::Nodes::Scalar.new('referencing')
|
286
|
+
def_tree.children << (ref_seq = Psych::Nodes::Sequence.new)
|
287
|
+
references.each do |r|
|
288
|
+
ref_seq.children << Psych::Nodes::Scalar.new(r)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def_doc = Psych::Nodes::Document.new
|
293
|
+
def_doc.children << def_tree
|
294
|
+
def_stream = Psych::Nodes::Stream.new
|
295
|
+
def_stream.children << def_doc
|
296
|
+
|
297
|
+
access_dir = @schema_dir.join(XMigra::SchemaManipulator::ACCESS_SUBDIR)
|
298
|
+
access_dir.mkpath
|
299
|
+
access_dir.join(access_obj.qualified_name + '.yaml').open('w') do |ao_file|
|
300
|
+
def_str = def_stream.to_yaml(nil, line_width: -1)
|
301
|
+
ao_file.puts(def_str)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def write_index_def(index)
|
306
|
+
indexes_dir = @schema_dir.join(XMigra::SchemaManipulator::INDEXES_SUBDIR)
|
307
|
+
indexes_dir.mkpath
|
308
|
+
index_path = indexes_dir.join(index.name + '.yaml')
|
309
|
+
|
310
|
+
raise UnsupportedFeatureError.new(
|
311
|
+
"Index file #{index_path} already exists."
|
312
|
+
) if index_path.exist?
|
313
|
+
|
314
|
+
index_path.open('w') do |index_file|
|
315
|
+
YAML.dump({'sql' => index.to_sql}, index_file, line_width: -1)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def write_statistics
|
320
|
+
statistics_path = @schema_dir.join(XMigra::MSSQLSpecifics::STATISTICS_FILE)
|
321
|
+
|
322
|
+
statistics_path.open('w') do |stats_file|
|
323
|
+
YAML.dump(
|
324
|
+
Hash[statistics.map(&:name_params_pair)],
|
325
|
+
stats_file,
|
326
|
+
line_width: -1
|
327
|
+
)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def create_adoption_script
|
332
|
+
adoption_script_path = @schema_dir.join(ADOPTION_SQL_FILE)
|
333
|
+
|
334
|
+
writer = AdoptionScriptWriter.new(self)
|
335
|
+
|
336
|
+
writer.create_script adoption_script_path
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Mkxms; end
|
2
|
+
|
3
|
+
module Mkxms::Mssql
|
4
|
+
class DefaultConstraint
|
5
|
+
def initialize(schema, table, column, name)
|
6
|
+
@schema, @table, @column, @name = schema, table, column, name
|
7
|
+
@expression = ''
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :schema, :table, :column, :name, :expression
|
11
|
+
|
12
|
+
def to_sql
|
13
|
+
"ALTER TABLE #@schema.#@table ADD #{"CONSTRAINT #@name" if @name} DEFAULT #@expression FOR #@column;"
|
14
|
+
end
|
15
|
+
|
16
|
+
def qualified_table
|
17
|
+
"#@schema.#@table"
|
18
|
+
end
|
19
|
+
|
20
|
+
def qualified_column
|
21
|
+
"#@schema.#@table.#@column"
|
22
|
+
end
|
23
|
+
|
24
|
+
def qualified_name
|
25
|
+
"#@schema.#@name" if @name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class DefaultConstraintHandler
|
30
|
+
def initialize(constraints, node)
|
31
|
+
a = node.attributes
|
32
|
+
@constraint = DefaultConstraint.new(
|
33
|
+
a['schema'],
|
34
|
+
a['table'],
|
35
|
+
a['column'],
|
36
|
+
a['name'],
|
37
|
+
).tap do |c|
|
38
|
+
constraints << c
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_text(text, parent_element)
|
43
|
+
@constraint.expression << text
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require "rexml/document"
|
2
|
+
require "rexml/element"
|
3
|
+
require "mkxms/mssql/utils"
|
4
|
+
|
5
|
+
module Mkxms; end
|
6
|
+
|
7
|
+
module Mkxms::Mssql
|
8
|
+
class Mkxms::Mssql::Engine
|
9
|
+
ParseItem = Struct.new(:context, :node) do
|
10
|
+
def delegate_to(klass, *constructor_args)
|
11
|
+
args = constructor_args + [node]
|
12
|
+
self.context = klass.new(*args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
MissingHandler = Struct.new(:context_class, :handler_name) do
|
17
|
+
def to_s
|
18
|
+
"#{self.context_class.name} does not define #{self.handler_name}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class ParseErrors < Exception
|
23
|
+
def initialize(errors)
|
24
|
+
@errors = errors
|
25
|
+
super(@errors.map(&:to_s).join("\n"))
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :errors
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(document, initial_context)
|
32
|
+
@initial_context = initial_context
|
33
|
+
@parse_items = [ParseItem.new(initial_context, document.root)]
|
34
|
+
@missing_handlers = []
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :missing_handlers
|
38
|
+
|
39
|
+
def run
|
40
|
+
until @parse_items.empty?
|
41
|
+
parse_item
|
42
|
+
end
|
43
|
+
|
44
|
+
errors = @missing_handlers
|
45
|
+
raise ParseErrors.new(errors) unless errors.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def parse_item
|
50
|
+
item = @parse_items.shift
|
51
|
+
case item.node
|
52
|
+
when REXML::Element
|
53
|
+
begin
|
54
|
+
handler = item.context.method(handler_name = element_handler_method_name(item.node))
|
55
|
+
rescue NameError
|
56
|
+
record_missing_handler(item.context.class, handler_name)
|
57
|
+
return
|
58
|
+
end
|
59
|
+
result = ParseItem.new(item.context, item.node)
|
60
|
+
handler[result]
|
61
|
+
@parse_items = item.node.children.select do |node|
|
62
|
+
[REXML::Element, REXML::Text].any? {|c| node.kind_of? c}
|
63
|
+
end.map do |node|
|
64
|
+
ParseItem.new(result.context, node)
|
65
|
+
end + @parse_items
|
66
|
+
when REXML::Text
|
67
|
+
begin
|
68
|
+
handler = item.context.method(:handle_text)
|
69
|
+
rescue
|
70
|
+
record_missing_handler(item.context.class, :handle_text) unless item.node.value =~ /^\s*$/
|
71
|
+
return
|
72
|
+
end
|
73
|
+
handler[item.node.value, item.node.parent]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def element_handler_method_name(node)
|
78
|
+
case node
|
79
|
+
when REXML::Element
|
80
|
+
"handle_#{Utils.code_sym_for node.name}_element".to_sym
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def record_missing_handler(context_class, method_name)
|
85
|
+
@missing_handlers << MissingHandler.new(context_class, method_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|