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