activefacts 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +83 -0
- data/README.rdoc +81 -0
- data/Rakefile +41 -0
- data/bin/afgen +46 -0
- data/bin/cql +52 -0
- data/examples/CQL/Address.cql +46 -0
- data/examples/CQL/Blog.cql +54 -0
- data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
- data/examples/CQL/Death.cql +16 -0
- data/examples/CQL/Genealogy.cql +95 -0
- data/examples/CQL/Marriage.cql +18 -0
- data/examples/CQL/Metamodel.cql +238 -0
- data/examples/CQL/MultiInheritance.cql +19 -0
- data/examples/CQL/OilSupply.cql +47 -0
- data/examples/CQL/Orienteering.cql +108 -0
- data/examples/CQL/PersonPlaysGame.cql +17 -0
- data/examples/CQL/SchoolActivities.cql +31 -0
- data/examples/CQL/SimplestUnary.cql +12 -0
- data/examples/CQL/SubtypePI.cql +32 -0
- data/examples/CQL/Warehousing.cql +99 -0
- data/examples/CQL/WindowInRoomInBldg.cql +22 -0
- data/lib/activefacts.rb +10 -0
- data/lib/activefacts/api.rb +25 -0
- data/lib/activefacts/api/concept.rb +384 -0
- data/lib/activefacts/api/constellation.rb +106 -0
- data/lib/activefacts/api/entity.rb +239 -0
- data/lib/activefacts/api/instance.rb +54 -0
- data/lib/activefacts/api/numeric.rb +158 -0
- data/lib/activefacts/api/role.rb +94 -0
- data/lib/activefacts/api/standard_types.rb +67 -0
- data/lib/activefacts/api/support.rb +59 -0
- data/lib/activefacts/api/value.rb +122 -0
- data/lib/activefacts/api/vocabulary.rb +120 -0
- data/lib/activefacts/cql.rb +31 -0
- data/lib/activefacts/cql/CQLParser.treetop +104 -0
- data/lib/activefacts/cql/Concepts.treetop +112 -0
- data/lib/activefacts/cql/DataTypes.treetop +66 -0
- data/lib/activefacts/cql/Expressions.treetop +113 -0
- data/lib/activefacts/cql/FactTypes.treetop +185 -0
- data/lib/activefacts/cql/Language/English.treetop +92 -0
- data/lib/activefacts/cql/LexicalRules.treetop +169 -0
- data/lib/activefacts/cql/Rakefile +6 -0
- data/lib/activefacts/cql/parser.rb +88 -0
- data/lib/activefacts/generate/absorption.rb +87 -0
- data/lib/activefacts/generate/cql.rb +441 -0
- data/lib/activefacts/generate/cql/html.rb +397 -0
- data/lib/activefacts/generate/null.rb +19 -0
- data/lib/activefacts/generate/ordered.rb +557 -0
- data/lib/activefacts/generate/ruby.rb +326 -0
- data/lib/activefacts/generate/sql/server.rb +164 -0
- data/lib/activefacts/generate/text.rb +21 -0
- data/lib/activefacts/input/cql.rb +1268 -0
- data/lib/activefacts/input/orm.rb +926 -0
- data/lib/activefacts/persistence.rb +1 -0
- data/lib/activefacts/persistence/composition.rb +653 -0
- data/lib/activefacts/support.rb +51 -0
- data/lib/activefacts/version.rb +3 -0
- data/lib/activefacts/vocabulary.rb +6 -0
- data/lib/activefacts/vocabulary/extensions.rb +343 -0
- data/lib/activefacts/vocabulary/metamodel.rb +303 -0
- data/script/txt2html +71 -0
- data/spec/absorption_spec.rb +95 -0
- data/spec/api/autocounter.rb +82 -0
- data/spec/api/constellation.rb +130 -0
- data/spec/api/entity_type.rb +101 -0
- data/spec/api/instance.rb +428 -0
- data/spec/api/roles.rb +122 -0
- data/spec/api/value_type.rb +112 -0
- data/spec/api_spec.rb +14 -0
- data/spec/cql_cql_spec.rb +58 -0
- data/spec/cql_parse_spec.rb +31 -0
- data/spec/cql_ruby_spec.rb +60 -0
- data/spec/cql_sql_spec.rb +54 -0
- data/spec/cql_symbol_tables_spec.rb +259 -0
- data/spec/cql_unit_spec.rb +336 -0
- data/spec/cqldump_spec.rb +169 -0
- data/spec/norma_cql_spec.rb +48 -0
- data/spec/norma_ruby_spec.rb +50 -0
- data/spec/norma_sql_spec.rb +45 -0
- data/spec/norma_tables_spec.rb +94 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +173 -0
@@ -0,0 +1,926 @@
|
|
1
|
+
#
|
2
|
+
# Read a NORMA file into an ActiveFacts vocabulary
|
3
|
+
#
|
4
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
5
|
+
#
|
6
|
+
# This code uses variables prefixed with x_ when they refer to Rexml nodes.
|
7
|
+
# Every node having an id="..." is indexed in @x_by_id[] hash before processing.
|
8
|
+
# As we build ActiveFacts objects to match, we index those in @by_id[].
|
9
|
+
# Both these hashes may be looked up by any of the ref="..." values in the file.
|
10
|
+
#
|
11
|
+
require 'rexml/document'
|
12
|
+
require 'activefacts/vocabulary'
|
13
|
+
|
14
|
+
module ActiveFacts
|
15
|
+
module Input
|
16
|
+
# The ORM Input module is activated whenever afgen is called upon to process a file
|
17
|
+
# whose name ends in .orm (a file from NORMA). This parser uses Rexml so it's very slow.
|
18
|
+
# The file is parsed to a constellation and the vocabulary object defined in that file is returned.
|
19
|
+
class ORM
|
20
|
+
def self.readfile(filename)
|
21
|
+
File.open(filename) {|file|
|
22
|
+
self.read(file, filename)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.read(file, filename = "stdin")
|
27
|
+
ORM.new(file, filename).read
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(file, filename = "stdin")
|
31
|
+
@file = file
|
32
|
+
@filename = filename
|
33
|
+
end
|
34
|
+
|
35
|
+
def read
|
36
|
+
begin
|
37
|
+
@document = REXML::Document.new(@file)
|
38
|
+
rescue => e
|
39
|
+
puts "Failed to parse XML in #{@filename}: #{e.inspect}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Find the Vocabulary and do some setup:
|
43
|
+
root = @document.elements[1]
|
44
|
+
if root.expanded_name == "ormRoot:ORM2"
|
45
|
+
x_models = root.elements.to_a("orm:ORMModel")
|
46
|
+
throw "No vocabulary found" unless x_models.size == 1
|
47
|
+
@x_model = x_models[0]
|
48
|
+
elsif root.name == "ORMModel"
|
49
|
+
@x_model = @document.elements[1]
|
50
|
+
else
|
51
|
+
pp root
|
52
|
+
throw "NORMA vocabulary not found in file"
|
53
|
+
end
|
54
|
+
|
55
|
+
read_vocabulary
|
56
|
+
@vocabulary
|
57
|
+
end
|
58
|
+
|
59
|
+
def read_vocabulary
|
60
|
+
@constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
|
61
|
+
@vocabulary = @constellation.Vocabulary(@x_model.attributes['Name'])
|
62
|
+
|
63
|
+
# Find all elements having an "id" attribute and index them
|
64
|
+
x_identified = @x_model.elements.to_a("//*[@id]")
|
65
|
+
@x_by_id = x_identified.inject({}){|h, x|
|
66
|
+
id = x.attributes['id']
|
67
|
+
h[id] = x
|
68
|
+
h
|
69
|
+
}
|
70
|
+
|
71
|
+
# Everything we build will be indexed here:
|
72
|
+
@by_id = {}
|
73
|
+
|
74
|
+
read_entity_types
|
75
|
+
read_value_types
|
76
|
+
read_fact_types
|
77
|
+
read_nested_types
|
78
|
+
read_subtypes
|
79
|
+
read_roles
|
80
|
+
read_constraints
|
81
|
+
# REVISIT: Skip instance data for now:
|
82
|
+
#read_instances
|
83
|
+
end
|
84
|
+
|
85
|
+
def read_entity_types
|
86
|
+
# get and process all the entity types:
|
87
|
+
entity_types = []
|
88
|
+
x_entity_types = @x_model.elements.to_a("orm:Objects/orm:EntityType")
|
89
|
+
x_entity_types.each{|x|
|
90
|
+
id = x.attributes['id']
|
91
|
+
name = x.attributes['Name'] || ""
|
92
|
+
name.gsub!(/\s/,'')
|
93
|
+
name = nil if name.size == 0
|
94
|
+
# puts "EntityType #{name} is #{id}"
|
95
|
+
entity_types <<
|
96
|
+
@by_id[id] =
|
97
|
+
entity_type =
|
98
|
+
@constellation.EntityType(name, @vocabulary)
|
99
|
+
independent = x.attributes['IsIndependent']
|
100
|
+
entity_type.is_independent = true if independent && independent == 'true'
|
101
|
+
personal = x.attributes['IsPersonal']
|
102
|
+
entity_type.is_personal = true if personal && personal == 'true'
|
103
|
+
# x_pref = x.elements.to_a("orm:PreferredIdentifier")[0]
|
104
|
+
# if x_pref
|
105
|
+
# pi_id = x_pref.attributes['ref']
|
106
|
+
# @pref_id_for[pi_id] = x
|
107
|
+
# end
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
def read_value_types
|
112
|
+
# Now the value types:
|
113
|
+
value_types = []
|
114
|
+
x_value_types = @x_model.elements.to_a("orm:Objects/orm:ValueType")
|
115
|
+
#pp x_value_types
|
116
|
+
x_value_types.each{|x|
|
117
|
+
id = x.attributes['id']
|
118
|
+
name = x.attributes['Name'] || ""
|
119
|
+
name.gsub!(/\s/,'')
|
120
|
+
name = nil if name.size == 0
|
121
|
+
|
122
|
+
cdt = x.elements.to_a('orm:ConceptualDataType')[0]
|
123
|
+
scale = cdt.attributes['Scale']
|
124
|
+
scale = scale != "" && scale.to_i
|
125
|
+
length = cdt.attributes['Length']
|
126
|
+
length = length != "" && length.to_i
|
127
|
+
base_type = @x_by_id[cdt.attributes['ref']]
|
128
|
+
type_name = "#{base_type.name}"
|
129
|
+
type_name.sub!(/^orm:/,'')
|
130
|
+
type_name.sub!(/DataType\Z/,'')
|
131
|
+
type_name.sub!(/Numeric\Z/,'')
|
132
|
+
type_name.sub!(/Temporal\Z/,'')
|
133
|
+
length = 32 if type_name =~ /Integer\Z/ && length.to_i == 0 # Set default integer length
|
134
|
+
|
135
|
+
# REVISIT: Need to handle standard types better here:
|
136
|
+
data_type = type_name != name ? @constellation.ValueType(type_name, @vocabulary) : nil
|
137
|
+
|
138
|
+
# puts "ValueType #{name} is #{id}"
|
139
|
+
value_types <<
|
140
|
+
@by_id[id] =
|
141
|
+
vt = @constellation.ValueType(name, @vocabulary)
|
142
|
+
vt.supertype = data_type
|
143
|
+
vt.length = length if length
|
144
|
+
vt.scale = scale if scale
|
145
|
+
independent = x.attributes['IsIndependent']
|
146
|
+
vt.is_independent = true if independent && independent == 'true'
|
147
|
+
personal = x.attributes['IsPersonal']
|
148
|
+
vt.is_personal = true if personal && personal == 'true'
|
149
|
+
|
150
|
+
x_ranges = x.elements.to_a("orm:ValueRestriction/orm:ValueConstraint/orm:ValueRanges/orm:ValueRange")
|
151
|
+
next if x_ranges.size == 0
|
152
|
+
vt.value_restriction = @constellation.ValueRestriction(:new)
|
153
|
+
x_ranges.each{|x_range|
|
154
|
+
v_range = value_range(x_range)
|
155
|
+
ar = @constellation.AllowedRange(v_range, vt.value_restriction)
|
156
|
+
}
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
def value_range(x_range)
|
161
|
+
min = x_range.attributes['MinValue']
|
162
|
+
max = x_range.attributes['MaxValue']
|
163
|
+
q = "'"
|
164
|
+
min = min =~ /[^0-9\.]/ ? q+min+q : min.to_i
|
165
|
+
max = max =~ /[^0-9\.]/ ? q+max+q : max.to_i
|
166
|
+
# ValueRange takes a minimum and/or a maximum Bound, each takes value and whether inclusive
|
167
|
+
@constellation.ValueRange(
|
168
|
+
min ? [min.to_s, true] : nil,
|
169
|
+
max ? [max.to_s, true] : nil
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
def read_fact_types
|
174
|
+
# Handle the fact types:
|
175
|
+
facts = []
|
176
|
+
@x_facts = @x_model.elements.to_a("orm:Facts/orm:Fact")
|
177
|
+
@x_facts.each{|x|
|
178
|
+
id = x.attributes['id']
|
179
|
+
name = x.attributes['Name'] || x.attributes['_Name']
|
180
|
+
name = "<unnamed>" if !name
|
181
|
+
name.gsub!(/\s/,'')
|
182
|
+
name = "" if !name || name.size == 0
|
183
|
+
# Note that the new metamodel doesn't have a name for a facttype unless it's objectified
|
184
|
+
|
185
|
+
# puts "FactType #{name || id}"
|
186
|
+
facts << @by_id[id] = fact_type = @constellation.FactType(:new)
|
187
|
+
}
|
188
|
+
end
|
189
|
+
|
190
|
+
def read_subtypes
|
191
|
+
# Handle the subtype fact types:
|
192
|
+
facts = []
|
193
|
+
@x_subtypes = @x_model.elements.to_a("orm:Facts/orm:SubtypeFact")
|
194
|
+
@x_mappings = @document.elements.to_a("ormRoot:ORM2/oialtocdb:MappingCustomization/oialtocdb:AssimilationMappings/oialtocdb:AssimilationMapping/oialtocdb:FactType")
|
195
|
+
|
196
|
+
@x_subtypes.each{|x|
|
197
|
+
id = x.attributes['id']
|
198
|
+
name = x.attributes['Name'] || x.attributes['_Name'] || ''
|
199
|
+
name.gsub!(/\s/,'')
|
200
|
+
name = nil if name.size == 0
|
201
|
+
# puts "FactType #{name || id}"
|
202
|
+
|
203
|
+
mapping = @x_mappings.detect{ |m| m.attributes['ref'] == id }
|
204
|
+
mapping_choice = mapping ? mapping.parent.attributes['AbsorptionChoice'] : 'Absorbed'
|
205
|
+
|
206
|
+
x_subtype_role = x.elements['orm:FactRoles/orm:SubtypeMetaRole']
|
207
|
+
subtype_role_id = x_subtype_role.attributes['id']
|
208
|
+
subtype_id = x_subtype_role.elements['orm:RolePlayer'].attributes['ref']
|
209
|
+
subtype = @by_id[subtype_id]
|
210
|
+
subtype.is_independent = true if mapping_choice == 'Separate'
|
211
|
+
# REVISIT: Provide a way in the metamodel of handling Partition, (and mapping choices that vary for each supertype?)
|
212
|
+
|
213
|
+
x_supertype_role = x.elements['orm:FactRoles/orm:SupertypeMetaRole']
|
214
|
+
supertype_role_id = x_supertype_role.attributes['id']
|
215
|
+
supertype_id = x_supertype_role.elements['orm:RolePlayer'].attributes['ref']
|
216
|
+
supertype = @by_id[supertype_id]
|
217
|
+
|
218
|
+
throw "For Subtype fact #{name}, the supertype #{supertype_id} was not found" if !supertype
|
219
|
+
throw "For Subtype fact #{name}, the subtype #{subtype_id} was not found" if !subtype
|
220
|
+
# $stderr.puts "#{subtype.name} is a subtype of #{supertype.name}"
|
221
|
+
|
222
|
+
inheritance_fact = @constellation.TypeInheritance(subtype, supertype)
|
223
|
+
inheritance_fact.fact_type_id = :new
|
224
|
+
if x.attributes["IsPrimary"] == "true" or # Old way
|
225
|
+
x.attributes["PreferredIdentificationPath"] == "true" # Newer
|
226
|
+
# $stderr.puts "#{supertype.name} is primary supertype of #{subtype.name}"
|
227
|
+
inheritance_fact.provides_identification = true
|
228
|
+
end
|
229
|
+
facts << @by_id[id] = inheritance_fact
|
230
|
+
|
231
|
+
# Create the new Roles so we can find constraints on them:
|
232
|
+
subtype_role = @by_id[subtype_role_id] = @constellation.Role(inheritance_fact, 0, subtype)
|
233
|
+
supertype_role = @by_id[supertype_role_id] = @constellation.Role(inheritance_fact, 1, supertype)
|
234
|
+
|
235
|
+
# Create readings, so constraints can be verbalised for example:
|
236
|
+
rs = @constellation.RoleSequence(:new)
|
237
|
+
@constellation.RoleRef(rs, 0).role = subtype_role
|
238
|
+
@constellation.RoleRef(rs, 1).role = supertype_role
|
239
|
+
|
240
|
+
# reading = @constellation.Reading(inheritance_fact, 0)
|
241
|
+
# reading.reading_text = "{1} is {0}"
|
242
|
+
# reading.role_sequence = rs
|
243
|
+
|
244
|
+
reading = @constellation.Reading(inheritance_fact, 0)
|
245
|
+
reading.reading_text = "{0} is a subtype of {1}"
|
246
|
+
reading.role_sequence = rs
|
247
|
+
|
248
|
+
# The required uniqueness constraints are already present in the NORMA file, don't duplicate them
|
249
|
+
=begin
|
250
|
+
# Create uniqueness constraints over the subtyping fact type
|
251
|
+
p1rs = @constellation.RoleSequence(:new)
|
252
|
+
@constellation.RoleRef(p1rs, 0).role = subtype_role
|
253
|
+
pc1 = @constellation.PresenceConstraint(:new)
|
254
|
+
pc1.name = "#{subtype.name}MustHaveSupertype#{supertype.name}"
|
255
|
+
pc1.vocabulary = @vocabulary
|
256
|
+
pc1.role_sequence = p1rs
|
257
|
+
pc1.is_mandatory = true # A subtype instance must have a supertype instance
|
258
|
+
pc1.min_frequency = 1
|
259
|
+
pc1.max_frequency = 1
|
260
|
+
pc1.is_preferred_identifier = false
|
261
|
+
|
262
|
+
# The supertype role often identifies the subtype:
|
263
|
+
p2rs = @constellation.RoleSequence(:new)
|
264
|
+
@constellation.RoleRef(p2rs, 0).role = supertype_role
|
265
|
+
pc2 = @constellation.PresenceConstraint(:new)
|
266
|
+
pc2.name = "#{supertype.name}MayBeA#{subtype.name}"
|
267
|
+
pc2.vocabulary = @vocabulary
|
268
|
+
pc2.role_sequence = p2rs
|
269
|
+
pc2.is_mandatory = false
|
270
|
+
pc2.min_frequency = 0
|
271
|
+
pc2.max_frequency = 1
|
272
|
+
pc2.is_preferred_identifier = inheritance_fact.provides_identification
|
273
|
+
=end
|
274
|
+
}
|
275
|
+
end
|
276
|
+
|
277
|
+
def read_nested_types
|
278
|
+
# Process NestedTypes, but ignore ones having a NestedPredicate with IsImplied="true"
|
279
|
+
# We'll ignore the fact roles (and constraints) that implied objectifications have.
|
280
|
+
# This happens for all ternaries and higher order facts
|
281
|
+
nested_types = []
|
282
|
+
x_nested_types = @x_model.elements.to_a("orm:Objects/orm:ObjectifiedType")
|
283
|
+
x_nested_types.each{|x|
|
284
|
+
id = x.attributes['id']
|
285
|
+
name = x.attributes['Name'] || ""
|
286
|
+
name.gsub!(/\s/,'')
|
287
|
+
name = nil if name.size == 0
|
288
|
+
|
289
|
+
x_fact_type = x.elements.to_a('orm:NestedPredicate')[0]
|
290
|
+
is_implied = x_fact_type.attributes['IsImplied'] == "true"
|
291
|
+
|
292
|
+
fact_id = x_fact_type.attributes['ref']
|
293
|
+
fact_type = @by_id[fact_id]
|
294
|
+
throw "Nested fact #{fact_id} not found" if !fact_type
|
295
|
+
|
296
|
+
#if is_implied
|
297
|
+
# puts "Implied type #{name} (#{id}) nests #{fact_type ? fact_type.fact_type_id : "unknown"}"
|
298
|
+
# @by_id[id] = fact_type
|
299
|
+
#else
|
300
|
+
begin
|
301
|
+
#puts "NestedType #{name} is #{id}, nests #{fact_type.fact_type_id}"
|
302
|
+
nested_types <<
|
303
|
+
@by_id[id] =
|
304
|
+
nested_type = @constellation.EntityType(name, @vocabulary)
|
305
|
+
nested_type.fact_type = fact_type
|
306
|
+
end
|
307
|
+
}
|
308
|
+
end
|
309
|
+
|
310
|
+
def read_roles
|
311
|
+
@x_facts.each{|x|
|
312
|
+
id = x.attributes['id']
|
313
|
+
fact_type = @by_id[id]
|
314
|
+
fact_name = x.attributes['Name'] || x.attributes['_Name'] || ''
|
315
|
+
fact_name.gsub!(/\s/,'')
|
316
|
+
fact_name = nil if fact_name == ''
|
317
|
+
|
318
|
+
x_fact_roles = x.elements.to_a('orm:FactRoles/*')
|
319
|
+
x_reading_orders = x.elements.to_a('orm:ReadingOrders/*')
|
320
|
+
|
321
|
+
# Deal with FactRoles (Roles):
|
322
|
+
x_fact_roles.each{|x|
|
323
|
+
name = x.attributes['Name'] || ""
|
324
|
+
name.gsub!(/\s/,'')
|
325
|
+
name = nil if name.size == 0
|
326
|
+
|
327
|
+
# _IsMandatory = x.attributes['_IsMandatory']
|
328
|
+
# _Multiplicity = x.attributes['_Multiplicity]
|
329
|
+
id = x.attributes['id']
|
330
|
+
ref = x.elements[1].attributes['ref']
|
331
|
+
|
332
|
+
# Find the concept that plays the role:
|
333
|
+
concept = @by_id[ref]
|
334
|
+
throw "RolePlayer for #{name||ref} was not found" if !concept
|
335
|
+
|
336
|
+
# Skip implicit roles added by NORMA to make unaries into binaries.
|
337
|
+
# This would make constraints over the deleted roles impossible,
|
338
|
+
# so as a SPECIAL CASE we index the unary role by the id of the
|
339
|
+
# implicit role. That means care is needed when handling unary FTs.
|
340
|
+
if (ox = @x_by_id[ref]) && ox.attributes['IsImplicitBooleanValue']
|
341
|
+
x_other_role = x.parent.elements.to_a('orm:Role').reject{|x_role|
|
342
|
+
x_role == x
|
343
|
+
}[0]
|
344
|
+
other_role_id = x_other_role.attributes["id"]
|
345
|
+
other_role = @by_id[other_role_id]
|
346
|
+
# puts "Indexing unary FT role #{other_role_id} by implicit boolean role #{id}"
|
347
|
+
@by_id[id] = other_role
|
348
|
+
|
349
|
+
# The role name of the ignored role is the one that applies:
|
350
|
+
role_name = x.attributes['Name']
|
351
|
+
other_role.role_name = role_name if role_name && role_name != ''
|
352
|
+
|
353
|
+
concept.delete # Delete our object for the implicit boolean ValueType
|
354
|
+
@by_id.delete(ref) # and de-index it from our list
|
355
|
+
next
|
356
|
+
end
|
357
|
+
|
358
|
+
#puts "#{@vocabulary}, Name=#{x.attributes['Name']}, concept=#{concept}"
|
359
|
+
throw "Role is played by #{concept.class} not Concept" if !(@constellation.vocabulary.concept(:Concept) === concept)
|
360
|
+
|
361
|
+
name = x.attributes['Name'] || ''
|
362
|
+
name.gsub!(/\s/,'')
|
363
|
+
name = nil if name.size == 0
|
364
|
+
#puts "Creating role #{name} nr#{fact_type.all_role.size} of #{fact_type.fact_type_id} played by #{concept.name}"
|
365
|
+
|
366
|
+
role = @by_id[id] = @constellation.Role(fact_type, fact_type.all_role.size, concept)
|
367
|
+
role.role_name = name if name
|
368
|
+
# puts "Fact #{fact_name} (id #{fact_type.fact_type_id.object_id}) role #{x.attributes['Name']} is played by #{concept.name}, role is #{role.object_id}"
|
369
|
+
|
370
|
+
x_vr = x.elements.to_a("orm:ValueRestriction")
|
371
|
+
x_vr.each{|vr|
|
372
|
+
x_ranges = vr.elements.to_a("orm:RoleValueConstraint/orm:ValueRanges/orm:ValueRange")
|
373
|
+
next if x_ranges.size == 0
|
374
|
+
role.role_value_restriction = @constellation.ValueRestriction(:new)
|
375
|
+
x_ranges.each{|x_range|
|
376
|
+
v_range = value_range(x_range)
|
377
|
+
ar = @constellation.AllowedRange(v_range, role.role_value_restriction)
|
378
|
+
}
|
379
|
+
}
|
380
|
+
|
381
|
+
# puts "Adding Role #{role.name} to #{fact_type.name}"
|
382
|
+
#fact_type.add_role(role)
|
383
|
+
# puts "\tRole #{role} is #{id}"
|
384
|
+
}
|
385
|
+
|
386
|
+
# Deal with Readings:
|
387
|
+
x_reading_orders.each{|x|
|
388
|
+
x_role_sequence = x.elements.to_a('orm:RoleSequence/*')
|
389
|
+
x_readings = x.elements.to_a('orm:Readings/orm:Reading/orm:Data')
|
390
|
+
|
391
|
+
# Build an array of the Roles needed:
|
392
|
+
role_array = x_role_sequence.map{|x| @by_id[x.attributes['ref']] }
|
393
|
+
|
394
|
+
# puts "Reading #{x_readings.map(&:text).inspect}"
|
395
|
+
role_sequence = get_role_sequence(role_array)
|
396
|
+
|
397
|
+
#role_sequence.all_role_ref.each_with_index{|rr, i|
|
398
|
+
# # REVISIT: rr.leading_adjective = ...; Add adjectives here
|
399
|
+
# }
|
400
|
+
|
401
|
+
x_readings.each_with_index{|x, i|
|
402
|
+
reading = @constellation.Reading(fact_type, fact_type.all_reading.size)
|
403
|
+
reading.role_sequence = role_sequence
|
404
|
+
# REVISIT: The downcase here only needs to be the initial letter of each word, but be safe:
|
405
|
+
reading.reading_text = extract_adjectives(x.text, role_sequence).downcase
|
406
|
+
}
|
407
|
+
}
|
408
|
+
}
|
409
|
+
# @vocabulary.fact_types.each{|ft| puts ft }
|
410
|
+
end
|
411
|
+
|
412
|
+
def extract_adjectives(reading_text, role_sequence)
|
413
|
+
(0...role_sequence.all_role_ref.size).each{|i|
|
414
|
+
role_ref = role_sequence.all_role_ref[i]
|
415
|
+
role = role_ref.role
|
416
|
+
|
417
|
+
word = '\b([A-Za-z][A-Za-z0-9_]*)\b'
|
418
|
+
leading_adjectives_re = "(?:#{word}- *(?:#{word} +)?)"
|
419
|
+
trailing_adjectives_re = "(?: +(?:#{word} +) *-#{word}?)"
|
420
|
+
role_with_adjectives_re =
|
421
|
+
%r| ?#{leading_adjectives_re}?\{#{i}\}#{trailing_adjectives_re}? ?|
|
422
|
+
|
423
|
+
reading_text.gsub!(role_with_adjectives_re) {
|
424
|
+
la = [[$1]*"", [$2]*""]*" ".gsub(/\s+/, ' ').sub(/\s+\Z/,'').strip
|
425
|
+
ta = [[$1]*"", [$2]*""]*" ".gsub(/\s+/, ' ').sub(/\A\s+/,'').strip
|
426
|
+
#puts "Setting leading adj #{la.inspect} from #{reading_text.inspect} for #{role_ref.role.concept.name}" if la != ""
|
427
|
+
# REVISIT: Dunno what's up here, but removing the "if" test makes this chuck exceptions:
|
428
|
+
role_ref.leading_adjective = la if la != ""
|
429
|
+
role_ref.trailing_adjective = ta if ta != ""
|
430
|
+
|
431
|
+
#puts "Reading '#{reading_text}' has role #{i} adjectives '#{la}' '#{ta}'" if la != "" || ta != ""
|
432
|
+
|
433
|
+
" {#{i}} "
|
434
|
+
}
|
435
|
+
}
|
436
|
+
reading_text.sub!(/\A /, '')
|
437
|
+
reading_text.sub!(/ \Z/, '')
|
438
|
+
reading_text
|
439
|
+
end
|
440
|
+
|
441
|
+
def get_role_sequence(role_array)
|
442
|
+
# puts "Getting RoleSequence [#{role_array.map{|r| "#{r.concept.name} (role #{r.object_id})" }*", "}]"
|
443
|
+
|
444
|
+
# Look for an existing RoleSequence
|
445
|
+
# REVISIT: This searches all role sequences. Perhaps we could narrow it down first instead?
|
446
|
+
role_sequence = @constellation.RoleSequence.values.detect{|c|
|
447
|
+
#puts "Checking RoleSequence [#{c.all_role_ref.map{|rr| rr.role.concept.name}*", "}]"
|
448
|
+
role_array == c.all_role_ref.map{|rr| rr.role }
|
449
|
+
}
|
450
|
+
# puts "Found matching RoleSequence!" if role_sequence
|
451
|
+
return role_sequence if role_sequence
|
452
|
+
|
453
|
+
# Make a new RoleSequence:
|
454
|
+
role_sequence = @constellation.RoleSequence(:new) unless role_sequence
|
455
|
+
role_array.each_with_index{|r, i|
|
456
|
+
role_ref = @constellation.RoleRef(role_sequence, i)
|
457
|
+
role_ref.role = r
|
458
|
+
}
|
459
|
+
role_sequence
|
460
|
+
end
|
461
|
+
|
462
|
+
def map_roles(x_roles, why = nil)
|
463
|
+
role_array = x_roles.map{|x|
|
464
|
+
id = x.attributes['ref']
|
465
|
+
role = @by_id[id]
|
466
|
+
if (why && !role)
|
467
|
+
# We didn't make Implied objects, so some constraints are unconnectable
|
468
|
+
x_role = @x_by_id[id]
|
469
|
+
x_player = x_role.elements.to_a('orm:RolePlayer')[0]
|
470
|
+
x_object = @x_by_id[x_player.attributes['ref']]
|
471
|
+
x_nests = nil
|
472
|
+
if (x_object.name.to_s == 'ObjectifiedType')
|
473
|
+
x_nests = x_object.elements.to_a('orm:NestedPredicate')[0]
|
474
|
+
implied = x_nests.attributes['IsImplied']
|
475
|
+
x_fact = @x_by_id[x_nests.attributes['ref']]
|
476
|
+
end
|
477
|
+
|
478
|
+
# This might have been a role of an ImpliedFact, which makes it safe to ignore.
|
479
|
+
next if 'ImpliedFact' == x_role.parent.parent.name
|
480
|
+
|
481
|
+
# Talk about why this wasn't found - this shouldn't happen.
|
482
|
+
if (!x_nests || !implied)
|
483
|
+
#puts "="*60
|
484
|
+
puts "Skipping #{why}, #{x_role.name} #{id} not found"
|
485
|
+
|
486
|
+
if (x_nests)
|
487
|
+
puts "Role is on #{implied ? "implied " : ""}objectification #{x_object}"
|
488
|
+
puts "which objectifies #{x_fact}"
|
489
|
+
end
|
490
|
+
puts x_object.to_s
|
491
|
+
end
|
492
|
+
end
|
493
|
+
role
|
494
|
+
}
|
495
|
+
role_array.include?(nil) ? nil : get_role_sequence(role_array)
|
496
|
+
end
|
497
|
+
|
498
|
+
def read_constraints
|
499
|
+
@constraints_by_rs = {}
|
500
|
+
|
501
|
+
read_mandatory_constraints
|
502
|
+
read_uniqueness_constraints
|
503
|
+
read_exclusion_constraints
|
504
|
+
read_subset_constraints
|
505
|
+
read_ring_constraints
|
506
|
+
read_equality_constraints
|
507
|
+
read_frequency_constraints
|
508
|
+
read_residual_mandatory_constraints
|
509
|
+
end
|
510
|
+
|
511
|
+
def read_mandatory_constraints
|
512
|
+
x_mandatory_constraints = @x_model.elements.to_a("orm:Constraints/orm:MandatoryConstraint")
|
513
|
+
@mandatory_constraints_by_rs = {}
|
514
|
+
@mandatory_constraint_rs_by_id = {}
|
515
|
+
x_mandatory_constraints.each{|x|
|
516
|
+
name = x.attributes["Name"] || ''
|
517
|
+
name.gsub!(/\s/,'')
|
518
|
+
name = nil if name.size == 0
|
519
|
+
|
520
|
+
# As of Feb 2008, all NORMA ValueTypes have an implied mandatory constraint.
|
521
|
+
if x.elements.to_a("orm:ImpliedByObjectType").size > 0
|
522
|
+
# $stderr.puts "Skipping ImpliedMandatoryConstraint #{name} over #{roles}"
|
523
|
+
next
|
524
|
+
end
|
525
|
+
|
526
|
+
x_roles = x.elements.to_a("orm:RoleSequence/orm:Role")
|
527
|
+
roles = map_roles(x_roles, "mandatory constraint #{name}")
|
528
|
+
next if !roles
|
529
|
+
|
530
|
+
# If X-OR mandatory, the Exclusion is accessed by:
|
531
|
+
# x_exclusion = (ex = x.elements.to_a("orm:ExclusiveOrExclusionConstraint")[0]) &&
|
532
|
+
# @x_by_id[ex.attributes['ref']]
|
533
|
+
# puts "Mandatory #{name}(#{roles}) is paired with exclusive #{x_exclusion.attributes['Name']}" if x_exclusion
|
534
|
+
|
535
|
+
@mandatory_constraints_by_rs[roles] = x
|
536
|
+
@mandatory_constraint_rs_by_id[x.attributes['id']] = roles
|
537
|
+
}
|
538
|
+
end
|
539
|
+
|
540
|
+
def read_residual_mandatory_constraints
|
541
|
+
@mandatory_constraints_by_rs.each { |roles, x|
|
542
|
+
# Create a simply-mandatory PresenceConstraint for each mandatory constraint
|
543
|
+
name = x.attributes["Name"] || ''
|
544
|
+
name.gsub!(/\s/,'')
|
545
|
+
name = nil if name.size == 0
|
546
|
+
#puts "Residual Mandatory #{name}: #{roles.to_s}"
|
547
|
+
|
548
|
+
pc = @constellation.PresenceConstraint(:new)
|
549
|
+
pc.vocabulary = @vocabulary
|
550
|
+
pc.name = name
|
551
|
+
pc.role_sequence = roles
|
552
|
+
pc.is_mandatory = true
|
553
|
+
pc.min_frequency = 1
|
554
|
+
pc.max_frequency = nil
|
555
|
+
pc.is_preferred_identifier = false
|
556
|
+
|
557
|
+
(@constraints_by_rs[roles] ||= []) << pc
|
558
|
+
}
|
559
|
+
end
|
560
|
+
|
561
|
+
def read_uniqueness_constraints
|
562
|
+
x_uniqueness_constraints = @x_model.elements.to_a("orm:Constraints/orm:UniquenessConstraint")
|
563
|
+
x_uniqueness_constraints.each{|x|
|
564
|
+
name = x.attributes["Name"] || ''
|
565
|
+
name.gsub!(/\s/,'')
|
566
|
+
name = nil if name.size == 0
|
567
|
+
id = x.attributes["id"]
|
568
|
+
x_pi = x.elements.to_a("orm:PreferredIdentifierFor")[0]
|
569
|
+
pi = x_pi ? @by_id[eref = x_pi.attributes['ref']] : nil
|
570
|
+
|
571
|
+
# Skip uniqueness constraints on implied concepts
|
572
|
+
if x_pi && !pi
|
573
|
+
puts "Skipping uniqueness constraint #{name}, entity not found"
|
574
|
+
next
|
575
|
+
end
|
576
|
+
|
577
|
+
# A uniqueness constraint on a fact having an implied objectification isn't preferred:
|
578
|
+
# if pi &&
|
579
|
+
# (x_pi_for = @x_by_id[eref]) &&
|
580
|
+
# (np = x_pi_for.elements.to_a('orm:NestedPredicate')[0]) &&
|
581
|
+
# np.attributes['IsImplied']
|
582
|
+
# pi = nil
|
583
|
+
# end
|
584
|
+
|
585
|
+
# Get the RoleSequence:
|
586
|
+
x_roles = x.elements.to_a("orm:RoleSequence/orm:Role")
|
587
|
+
next if x_roles.size == 0
|
588
|
+
roles = map_roles(x_roles, "uniqueness constraint #{name}")
|
589
|
+
next if !roles
|
590
|
+
|
591
|
+
# There is an implicit uniqueness constraint when any object plays a unary. Skip it.
|
592
|
+
if (x_roles.size == 1 &&
|
593
|
+
(id = x_roles[0].attributes['ref']) &&
|
594
|
+
(x_role = @x_by_id[id]) &&
|
595
|
+
x_role.parent.elements.size == 2 &&
|
596
|
+
(sibling = x_role.parent.elements[2]) &&
|
597
|
+
(ib_id = sibling.elements[1].attributes['ref']) &&
|
598
|
+
(ib = @x_by_id[ib_id]) &&
|
599
|
+
ib.attributes['IsImplicitBooleanValue'])
|
600
|
+
unary_identifier = true
|
601
|
+
end
|
602
|
+
|
603
|
+
if (mc = @mandatory_constraints_by_rs[roles])
|
604
|
+
# Remove absorbed mandatory constraints, leaving residual ones.
|
605
|
+
# puts "Absorbing MC #{mc.attributes['Name']}"
|
606
|
+
@mandatory_constraints_by_rs.delete(roles)
|
607
|
+
@mandatory_constraint_rs_by_id.delete(mc.attributes['id'])
|
608
|
+
end
|
609
|
+
|
610
|
+
# A UC that spans more than one Role of a fact will be a Preferred Id for the implied object
|
611
|
+
#puts "Unique" + rs.to_s +
|
612
|
+
# (pi ? " (preferred id for #{pi.name})" : "") +
|
613
|
+
# (mc ? " (mandatory)" : "") if pi && !mc
|
614
|
+
|
615
|
+
# A TypeInheritance fact type has a UC on each role.
|
616
|
+
# If it's on the identification path, mark it as preferred identifier
|
617
|
+
role = roles.all_role_ref[0].role
|
618
|
+
supertype_constraint = role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) &&
|
619
|
+
role.concept == role.fact_type.supertype &&
|
620
|
+
role.fact_type.provides_identification
|
621
|
+
|
622
|
+
pc = @constellation.PresenceConstraint(:new)
|
623
|
+
pc.vocabulary = @vocabulary
|
624
|
+
pc.name = name
|
625
|
+
pc.role_sequence = roles
|
626
|
+
pc.is_mandatory = true if mc
|
627
|
+
pc.min_frequency = mc ? 1 : 0
|
628
|
+
pc.max_frequency = 1
|
629
|
+
pc.is_preferred_identifier = true if pi || unary_identifier || supertype_constraint
|
630
|
+
#puts "#{name} covers #{roles.describe} has min=#{pc.min_frequency}, max=1, preferred=#{pc.is_preferred_identifier.inspect}" if emit_special_debug
|
631
|
+
|
632
|
+
#puts roles.verbalise
|
633
|
+
#puts pc.verbalise
|
634
|
+
|
635
|
+
(@constraints_by_rs[roles] ||= []) << pc
|
636
|
+
}
|
637
|
+
end
|
638
|
+
|
639
|
+
def read_exclusion_constraints
|
640
|
+
x_exclusion_constraints = @x_model.elements.to_a("orm:Constraints/orm:ExclusionConstraint")
|
641
|
+
x_exclusion_constraints.each{|x|
|
642
|
+
name = x.attributes["Name"] || ''
|
643
|
+
name.gsub!(/\s/,'')
|
644
|
+
name = nil if name.size == 0
|
645
|
+
x_mandatory = (m = x.elements.to_a("orm:ExclusiveOrMandatoryConstraint")[0]) &&
|
646
|
+
@x_by_id[mc_id = m.attributes['ref']]
|
647
|
+
role_sequences =
|
648
|
+
x.elements.to_a("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
|
649
|
+
x_role_refs = x_rs.elements.to_a("orm:Role")
|
650
|
+
map_roles(
|
651
|
+
x_role_refs , # .map{|xr| @x_by_id[xr.attributes['ref']] },
|
652
|
+
"exclusion constraint #{name}"
|
653
|
+
)
|
654
|
+
}
|
655
|
+
if x_mandatory
|
656
|
+
# Remove absorbed mandatory constraints, leaving residual ones.
|
657
|
+
mc_rs = @mandatory_constraint_rs_by_id[mc_id]
|
658
|
+
@mandatory_constraint_rs_by_id.delete(mc_id)
|
659
|
+
@mandatory_constraints_by_rs.delete(mc_rs)
|
660
|
+
end
|
661
|
+
|
662
|
+
ec = @constellation.SetExclusionConstraint(:new)
|
663
|
+
ec.vocabulary = @vocabulary
|
664
|
+
ec.name = name
|
665
|
+
# ec.enforcement =
|
666
|
+
role_sequences.each{|rs|
|
667
|
+
@constellation.SetComparisonRoles(ec, rs)
|
668
|
+
}
|
669
|
+
ec.is_mandatory = true if x_mandatory
|
670
|
+
}
|
671
|
+
end
|
672
|
+
|
673
|
+
def read_equality_constraints
|
674
|
+
x_equality_constraints = @x_model.elements.to_a("orm:Constraints/orm:EqualityConstraint")
|
675
|
+
x_equality_constraints.each{|x|
|
676
|
+
name = x.attributes["Name"] || ''
|
677
|
+
name.gsub!(/\s/,'')
|
678
|
+
name = nil if name.size == 0
|
679
|
+
role_sequences =
|
680
|
+
x.elements.to_a("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
|
681
|
+
x_role_refs = x_rs.elements.to_a("orm:Role")
|
682
|
+
map_roles(
|
683
|
+
x_role_refs , # .map{|xr| @x_by_id[xr.attributes['ref']] },
|
684
|
+
"equality constraint #{name}"
|
685
|
+
)
|
686
|
+
}
|
687
|
+
|
688
|
+
ec = @constellation.SetEqualityConstraint(:new)
|
689
|
+
ec.vocabulary = @vocabulary
|
690
|
+
ec.name = name
|
691
|
+
# ec.enforcement =
|
692
|
+
role_sequences.each{|rs|
|
693
|
+
@constellation.SetComparisonRoles(ec, rs)
|
694
|
+
}
|
695
|
+
}
|
696
|
+
end
|
697
|
+
|
698
|
+
def read_subset_constraints
|
699
|
+
x_subset_constraints = @x_model.elements.to_a("orm:Constraints/orm:SubsetConstraint")
|
700
|
+
x_subset_constraints.each{|x|
|
701
|
+
name = x.attributes["Name"] || ''
|
702
|
+
name.gsub!(/\s/,'')
|
703
|
+
name = nil if name.size == 0
|
704
|
+
role_sequences =
|
705
|
+
x.elements.to_a("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
|
706
|
+
x_role_refs = x_rs.elements.to_a("orm:Role")
|
707
|
+
map_roles(
|
708
|
+
x_role_refs , # .map{|xr| @x_by_id[xr.attributes['ref']] },
|
709
|
+
"equality constraint #{name}"
|
710
|
+
)
|
711
|
+
}
|
712
|
+
|
713
|
+
ec = @constellation.SubsetConstraint(:new)
|
714
|
+
ec.vocabulary = @vocabulary
|
715
|
+
ec.name = name
|
716
|
+
# ec.enforcement =
|
717
|
+
ec.subset_role_sequence = role_sequences[0]
|
718
|
+
ec.superset_role_sequence = role_sequences[1]
|
719
|
+
}
|
720
|
+
end
|
721
|
+
|
722
|
+
def read_ring_constraints
|
723
|
+
x_ring_constraints = @x_model.elements.to_a("orm:Constraints/orm:RingConstraint")
|
724
|
+
x_ring_constraints.each{|x|
|
725
|
+
name = x.attributes["Name"] || ''
|
726
|
+
name.gsub!(/\s/,'')
|
727
|
+
name = nil if name.size == 0
|
728
|
+
type = x.attributes["Type"]
|
729
|
+
# begin
|
730
|
+
# # Convert the RingConstraint name to a number:
|
731
|
+
# type_num = eval("::ActiveFacts::RingConstraint::#{type}")
|
732
|
+
# rescue => e
|
733
|
+
# throw "RingConstraint type #{type} isn't known"
|
734
|
+
# end
|
735
|
+
|
736
|
+
from, to = *x.elements.to_a("orm:RoleSequence/orm:Role").map{|xr|
|
737
|
+
@by_id[xr.attributes['ref']]
|
738
|
+
}
|
739
|
+
rc = @constellation.RingConstraint(:new)
|
740
|
+
rc.vocabulary = @vocabulary
|
741
|
+
rc.name = name
|
742
|
+
# rc.enforcement =
|
743
|
+
rc.role = from
|
744
|
+
rc.other_role = to
|
745
|
+
rc.ring_type = type
|
746
|
+
}
|
747
|
+
end
|
748
|
+
|
749
|
+
def read_frequency_constraints
|
750
|
+
x_frequency_constraints = @x_model.elements.to_a("orm:Constraints/orm:FrequencyConstraint")
|
751
|
+
# REVISIT: FrequencyConstraints not handled yet
|
752
|
+
end
|
753
|
+
|
754
|
+
def read_instances
|
755
|
+
population = Population.new(@vocabulary, "sample")
|
756
|
+
|
757
|
+
# Value instances first, then entities then facts:
|
758
|
+
|
759
|
+
x_values = @x_model.elements.to_a("orm:Objects/orm:ValueType/orm:Instances/orm:ValueTypeInstance/orm:Value")
|
760
|
+
#pp x_values.map{|v| [ v.parent.attributes['id'], v.text ] }
|
761
|
+
x_values.each{|v|
|
762
|
+
id = v.parent.attributes['id']
|
763
|
+
# Get details of the ValueType:
|
764
|
+
xvt = v.parent.parent.parent
|
765
|
+
vt_id = xvt.attributes['id']
|
766
|
+
vtname = xvt.attributes['Name'] || ''
|
767
|
+
vtname.gsub!(/\s/,'')
|
768
|
+
vtname = nil if name.size == 0
|
769
|
+
vt = @by_id[vt_id]
|
770
|
+
throw "ValueType #{vtname} not found" unless vt
|
771
|
+
|
772
|
+
i = Instance.new(vt, v.text)
|
773
|
+
@by_id[id] = i
|
774
|
+
# show_xmlobj(v)
|
775
|
+
}
|
776
|
+
|
777
|
+
# Use the "id" attribute of EntityTypeInstance
|
778
|
+
x_entities = @x_model.elements.to_a("orm:Objects/orm:EntityType/orm:Instances/orm:EntityTypeInstance")
|
779
|
+
#pp x_entities
|
780
|
+
# x_entities.each{|v| show_xmlobj(v) }
|
781
|
+
last_et_id = nil
|
782
|
+
last_et = nil
|
783
|
+
et = nil
|
784
|
+
x_entities.each{|v|
|
785
|
+
id = v.attributes['id']
|
786
|
+
|
787
|
+
# Get details of the EntityType:
|
788
|
+
xet = v.parent.parent
|
789
|
+
et_id = xet.attributes['id']
|
790
|
+
if (et_id != last_et_id)
|
791
|
+
etname = xet.attributes['Name'] || ''
|
792
|
+
etname.gsub!(/\s/,'')
|
793
|
+
etname = nil if name.size == 0
|
794
|
+
last_et = et = @by_id[et_id]
|
795
|
+
last_et_id = et_id
|
796
|
+
throw "EntityType #{etname} not found" unless et
|
797
|
+
end
|
798
|
+
|
799
|
+
instance = Instance.new(et)
|
800
|
+
@by_id[id] = instance
|
801
|
+
# puts "Made new EntityType #{etname}"
|
802
|
+
}
|
803
|
+
|
804
|
+
# The EntityType instances have implicit facts for the PI facts.
|
805
|
+
# We must create implicit PI facts after all the instances.
|
806
|
+
entity_count = 0
|
807
|
+
pi_fact_count = 0
|
808
|
+
x_entities.each{|v|
|
809
|
+
id = v.attributes['id']
|
810
|
+
instance = @by_id[id]
|
811
|
+
et = @by_id[v.parent.parent.attributes['id']]
|
812
|
+
next unless (preferred_id = et.preferred_identifier)
|
813
|
+
|
814
|
+
# puts "Create identifying facts using #{preferred_id}"
|
815
|
+
|
816
|
+
# Collate the referenced objects by role:
|
817
|
+
role_instances = v.elements[1].elements.inject({}){|h, v|
|
818
|
+
etri = @x_by_id[v.attributes['ref']]
|
819
|
+
x_role_id = etri.parent.parent.attributes['id']
|
820
|
+
role = @by_id[x_role_id]
|
821
|
+
object = @by_id[object_id = etri.attributes['ref']]
|
822
|
+
h[role] = object
|
823
|
+
h
|
824
|
+
}
|
825
|
+
|
826
|
+
# Create an instance of each required fact type, for compound identification:
|
827
|
+
preferred_id.role_sequence.map(&:fact_type).uniq.each{|ft|
|
828
|
+
# puts "\tFor FactType #{ft}"
|
829
|
+
fact_roles = ft.roles.map{|role|
|
830
|
+
if role.concept == et
|
831
|
+
object = instance
|
832
|
+
else
|
833
|
+
object = role_instances[role]
|
834
|
+
# puts "\t\tinstance for role #{role} is #{object}"
|
835
|
+
end
|
836
|
+
FactRole.new(role, object)
|
837
|
+
}
|
838
|
+
f = Fact.new(population, ft, *fact_roles)
|
839
|
+
pi_fact_count += 1
|
840
|
+
}
|
841
|
+
entity_count += 1
|
842
|
+
}
|
843
|
+
# puts "Created #{pi_fact_count} facts to identify #{entity_count} entities"
|
844
|
+
|
845
|
+
# Use the "ref" attribute of FactTypeRoleInstance:
|
846
|
+
x_fact_roles = @x_model.elements.to_a("orm:Facts/orm:Fact/orm:Instances/orm:FactTypeInstance/orm:RoleInstances/orm:FactTypeRoleInstance")
|
847
|
+
|
848
|
+
last_id = nil
|
849
|
+
last_fact_type = nil
|
850
|
+
fact_roles = []
|
851
|
+
x_fact_roles.each{|v|
|
852
|
+
fact_type_id = v.parent.parent.parent.parent.attributes['id']
|
853
|
+
id = v.parent.parent.attributes['id']
|
854
|
+
fact_type = @by_id[fact_type_id]
|
855
|
+
throw "Fact type #{fact_type_id} not found" unless fact_type
|
856
|
+
|
857
|
+
if (last_id && id != last_id)
|
858
|
+
# Process completed fact now we have all roles:
|
859
|
+
last_fact = Fact.new(population, last_fact_type, *fact_roles)
|
860
|
+
fact_roles = []
|
861
|
+
else
|
862
|
+
last_fact_type = fact_type
|
863
|
+
end
|
864
|
+
|
865
|
+
#show_xmlobj(v)
|
866
|
+
|
867
|
+
last_id = id
|
868
|
+
x_role_instance = @x_by_id[v.attributes['ref']]
|
869
|
+
x_role_id = x_role_instance.parent.parent.attributes['id']
|
870
|
+
role = @by_id[x_role_id]
|
871
|
+
throw "Role not found for instance #{x_role_id}" unless role
|
872
|
+
instance_id = x_role_instance.attributes['ref']
|
873
|
+
instance = @by_id[instance_id]
|
874
|
+
throw "Instance not found for FactRole #{instance_id}" unless instance
|
875
|
+
fact_roles << FactRole.new(role, instance)
|
876
|
+
}
|
877
|
+
|
878
|
+
if (last_id)
|
879
|
+
# Process final completed fact now we have all roles:
|
880
|
+
last_fact = Fact.new(population, last_fact_type, *fact_roles)
|
881
|
+
end
|
882
|
+
|
883
|
+
end
|
884
|
+
|
885
|
+
def read_rest
|
886
|
+
puts "Reading Implied Facts (not yet)"
|
887
|
+
=begin
|
888
|
+
x_implied_facts = @x_model.elements.to_a("orm:Facts/orm:ImpliedFact")
|
889
|
+
pp x_implied_facts
|
890
|
+
=end
|
891
|
+
puts "Reading Data Types (not yet)"
|
892
|
+
=begin
|
893
|
+
x_datatypes = @x_model.elements.to_a("orm:DataTypes/*")
|
894
|
+
pp x_datatypes
|
895
|
+
=end
|
896
|
+
puts "Reading Reference Mode Kinds (not yet)"
|
897
|
+
=begin
|
898
|
+
x_refmodekinds = @x_model.elements.to_a("orm:ReferenceModeKinds/*")
|
899
|
+
pp x_refmodekinds
|
900
|
+
=end
|
901
|
+
end
|
902
|
+
|
903
|
+
def show_xmlobj(x, indent = "")
|
904
|
+
parentage = []
|
905
|
+
p = x
|
906
|
+
while (p)
|
907
|
+
parentage.unshift(p)
|
908
|
+
p = p.parent
|
909
|
+
end
|
910
|
+
#parentage = parentage.shift
|
911
|
+
puts "#{indent}#{x.name} object has heritage {"
|
912
|
+
parentage.each{|p|
|
913
|
+
next if REXML::Document === p
|
914
|
+
puts "#{indent}\t#{p.name}#{
|
915
|
+
}#{(n = p.attributes['Name']) ? " Name='#{n}'" : ""
|
916
|
+
}#{(id = p.attributes['id']) ? " #{id}" : ""
|
917
|
+
}#{(ref = p.attributes['ref']) ? " -> #{ref}" : ""
|
918
|
+
}#{/\S/ === ((text = p.text)) ? " "+text.inspect : ""
|
919
|
+
}"
|
920
|
+
show_xmlobj(@x_by_id[ref], "\t#{indent}") if ref
|
921
|
+
}
|
922
|
+
puts "#{indent}}"
|
923
|
+
end
|
924
|
+
end
|
925
|
+
end
|
926
|
+
end
|