activefacts 0.8.6 → 0.8.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +33 -2
- data/README.rdoc +30 -36
- data/Rakefile +16 -20
- data/bin/afgen +17 -11
- data/bin/cql +313 -36
- data/download.html +43 -19
- data/examples/CQL/Address.cql +15 -15
- data/examples/CQL/Blog.cql +8 -8
- data/examples/CQL/CompanyDirectorEmployee.cql +6 -5
- data/examples/CQL/Death.cql +3 -3
- data/examples/CQL/Diplomacy.cql +48 -0
- data/examples/CQL/Genealogy.cql +41 -41
- data/examples/CQL/Insurance.cql +311 -0
- data/examples/CQL/JoinEquality.cql +35 -0
- data/examples/CQL/Marriage.cql +1 -1
- data/examples/CQL/Metamodel.cql +290 -185
- data/examples/CQL/MetamodelNext.cql +420 -0
- data/examples/CQL/Monogamy.cql +24 -0
- data/examples/CQL/MonthInSeason.cql +27 -0
- data/examples/CQL/Moon.cql +23 -0
- data/examples/CQL/MultiInheritance.cql +4 -4
- data/examples/CQL/NonRoleId.cql +14 -0
- data/examples/CQL/OddIdentifier.cql +18 -0
- data/examples/CQL/OilSupply.cql +24 -24
- data/examples/CQL/OneToOnes.cql +17 -0
- data/examples/CQL/Orienteering.cql +55 -55
- data/examples/CQL/OrienteeringER.cql +58 -0
- data/examples/CQL/PersonPlaysGame.cql +2 -2
- data/examples/CQL/RedundantDependency.cql +34 -0
- data/examples/CQL/SchoolActivities.cql +5 -5
- data/examples/CQL/SeparateSubtype.cql +28 -0
- data/examples/CQL/ServiceDirector.cql +283 -0
- data/examples/CQL/SimplestUnary.cql +2 -2
- data/examples/CQL/SubtypePI.cql +11 -11
- data/examples/CQL/Supervision.cql +38 -0
- data/examples/CQL/Tests.Test5.Load.cql +38 -0
- data/examples/CQL/WaiterTips.cql +33 -0
- data/examples/CQL/Warehousing.cql +55 -53
- data/examples/CQL/WindowInRoomInBldg.cql +9 -9
- data/examples/CQL/unit.cql +433 -544
- data/examples/index.html +314 -170
- data/examples/intro.html +6 -176
- data/examples/local.css +8 -4
- data/index.html +40 -25
- data/lib/activefacts/api/concept.rb +2 -2
- data/lib/activefacts/api/constellation.rb +4 -4
- data/lib/activefacts/api/instance.rb +2 -2
- data/lib/activefacts/api/instance_index.rb +4 -0
- data/lib/activefacts/api/numeric.rb +3 -1
- data/lib/activefacts/api/role.rb +1 -1
- data/lib/activefacts/api/standard_types.rb +23 -16
- data/lib/activefacts/api/support.rb +3 -1
- data/lib/activefacts/api/vocabulary.rb +4 -0
- data/lib/activefacts/cql/CQLParser.treetop +87 -39
- data/lib/activefacts/cql/Concepts.treetop +95 -69
- data/lib/activefacts/cql/Context.treetop +11 -2
- data/lib/activefacts/cql/Expressions.treetop +23 -59
- data/lib/activefacts/cql/FactTypes.treetop +141 -95
- data/lib/activefacts/cql/Language/English.treetop +33 -21
- data/lib/activefacts/cql/LexicalRules.treetop +6 -1
- data/lib/activefacts/cql/Terms.treetop +75 -26
- data/lib/activefacts/cql/ValueTypes.treetop +52 -54
- data/lib/activefacts/cql/compiler.rb +46 -1691
- data/lib/activefacts/cql/compiler/constraint.rb +602 -0
- data/lib/activefacts/cql/compiler/entity_type.rb +425 -0
- data/lib/activefacts/cql/compiler/fact.rb +300 -0
- data/lib/activefacts/cql/compiler/fact_type.rb +230 -0
- data/lib/activefacts/cql/compiler/reading.rb +832 -0
- data/lib/activefacts/cql/compiler/shared.rb +109 -0
- data/lib/activefacts/cql/compiler/value_type.rb +104 -0
- data/lib/activefacts/cql/parser.rb +132 -81
- data/lib/activefacts/generate/cql.rb +397 -274
- data/lib/activefacts/generate/oo.rb +13 -12
- data/lib/activefacts/generate/ordered.rb +107 -117
- data/lib/activefacts/generate/ruby.rb +34 -38
- data/lib/activefacts/generate/sql/mysql.rb +62 -45
- data/lib/activefacts/generate/sql/server.rb +59 -42
- data/lib/activefacts/input/cql.rb +6 -3
- data/lib/activefacts/input/orm.rb +991 -557
- data/lib/activefacts/persistence/columns.rb +16 -12
- data/lib/activefacts/persistence/foreignkey.rb +7 -4
- data/lib/activefacts/persistence/index.rb +3 -4
- data/lib/activefacts/persistence/reference.rb +5 -2
- data/lib/activefacts/support.rb +20 -14
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary.rb +1 -0
- data/lib/activefacts/vocabulary/extensions.rb +328 -44
- data/lib/activefacts/vocabulary/metamodel.rb +145 -20
- data/lib/activefacts/vocabulary/verbaliser.rb +621 -0
- data/spec/absorption_spec.rb +4 -4
- data/spec/api/value_type.rb +1 -1
- data/spec/cql/context_spec.rb +45 -22
- data/spec/cql/deontic_spec.rb +88 -0
- data/spec/cql/matching_spec.rb +517 -0
- data/spec/cql/samples_spec.rb +88 -31
- data/spec/cql/unit_spec.rb +58 -37
- data/spec/cql_cql_spec.rb +12 -7
- data/spec/cql_mysql_spec.rb +3 -7
- data/spec/cql_parse_spec.rb +0 -4
- data/spec/cql_ruby_spec.rb +1 -4
- data/spec/cql_sql_spec.rb +5 -18
- data/spec/cql_symbol_tables_spec.rb +3 -0
- data/spec/cqldump_spec.rb +0 -2
- data/spec/helpers/array_matcher.rb +35 -0
- data/spec/helpers/ctrl_c_support.rb +52 -0
- data/spec/helpers/diff_matcher.rb +38 -0
- data/spec/helpers/file_matcher.rb +5 -3
- data/spec/helpers/string_matcher.rb +39 -0
- data/spec/helpers/test_parser.rb +13 -0
- data/spec/norma_cql_spec.rb +13 -5
- data/spec/norma_ruby_spec.rb +11 -3
- data/spec/{absorption_ruby_spec.rb → norma_ruby_sql_spec.rb} +37 -32
- data/spec/norma_sql_spec.rb +11 -5
- data/spec/norma_tables_spec.rb +33 -29
- data/spec/spec_helper.rb +4 -1
- data/status.html +92 -23
- metadata +102 -36
- data/lib/activefacts/generate/cql/html.rb +0 -403
@@ -15,7 +15,6 @@ module ActiveFacts
|
|
15
15
|
# afgen --sql/mysql[=options] <file>.cql
|
16
16
|
# Options are comma or space separated:
|
17
17
|
# * delay_fks Leave all foreign keys until the end, not just those that contain forward-references
|
18
|
-
# * norma Translate valuetypes from NORMA to SQL Server
|
19
18
|
class MYSQL
|
20
19
|
private
|
21
20
|
include Persistence
|
@@ -61,7 +60,6 @@ module ActiveFacts
|
|
61
60
|
@vocabulary = vocabulary
|
62
61
|
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
63
62
|
@delay_fks = options.include? "delay_fks"
|
64
|
-
@norma = options.include? "norma"
|
65
63
|
end
|
66
64
|
|
67
65
|
def puts s
|
@@ -82,56 +80,75 @@ module ActiveFacts
|
|
82
80
|
end
|
83
81
|
end
|
84
82
|
|
85
|
-
# Return SQL type and (modified?) length for the passed
|
86
|
-
def
|
83
|
+
# Return SQL type and (modified?) length for the passed base type
|
84
|
+
def normalise_type(type, length)
|
87
85
|
sql_type = case type
|
88
|
-
when
|
89
|
-
|
90
|
-
|
86
|
+
when /^Auto ?Counter$/
|
87
|
+
'int'
|
88
|
+
|
89
|
+
when /^Signed ?Integer$/,
|
90
|
+
/^Signed ?Small ?Integer$/
|
91
91
|
s = case
|
92
|
-
when length <= 8
|
93
|
-
|
94
|
-
when length <=
|
95
|
-
|
92
|
+
when length <= 8
|
93
|
+
'tinyint'
|
94
|
+
when length <= 16
|
95
|
+
'shortint'
|
96
|
+
when length <= 32
|
97
|
+
'int'
|
98
|
+
else 'bigint'
|
96
99
|
end
|
97
100
|
length = nil
|
98
101
|
s
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
+
|
103
|
+
when /^Unsigned ?Integer$/,
|
104
|
+
/^Unsigned ?Small ?Integer$/,
|
105
|
+
/^Unsigned ?Tiny ?Integer$/
|
102
106
|
s = case
|
103
|
-
when length <= 8
|
104
|
-
|
105
|
-
when length <=
|
106
|
-
|
107
|
-
|
107
|
+
when length <= 8
|
108
|
+
'tinyint unsigned'
|
109
|
+
when length <= 16
|
110
|
+
'shortint unsigned'
|
111
|
+
when length <= 32
|
112
|
+
'int unsigned'
|
113
|
+
else 'bigint'
|
108
114
|
end
|
109
115
|
length = nil
|
110
116
|
s
|
111
|
-
when "Decimal"; "DECIMAL"
|
112
117
|
|
113
|
-
when
|
118
|
+
when /^Decimal$/
|
119
|
+
'decimal'
|
120
|
+
|
121
|
+
when /^Fixed ?Length ?Text$/, /^Char$/
|
114
122
|
length ||= DefaultCharColLength
|
115
|
-
"
|
116
|
-
when
|
123
|
+
"char"
|
124
|
+
when /^Variable ?Length ?Text$/, /^String$/
|
117
125
|
length ||= DefaultCharColLength
|
118
|
-
"
|
126
|
+
"varchar"
|
119
127
|
# There are several large length text types; If you need to store more than 65k chars, look at using MEDIUMTEXT or LONGTEXT
|
120
128
|
# CQL does not yet allow you to specify a length for LargeLengthText.
|
121
|
-
when
|
129
|
+
when /^Large ?Length ?Text$/, /^Text$/
|
130
|
+
'text'
|
122
131
|
|
123
|
-
when
|
124
|
-
|
125
|
-
when
|
126
|
-
|
132
|
+
when /^Date ?And ?Time$/, /^Date ?Time$/
|
133
|
+
'datetime'
|
134
|
+
when /^Date$/
|
135
|
+
'date'
|
136
|
+
when /^Time$/
|
137
|
+
'time'
|
138
|
+
when /^Auto ?Time ?Stamp$/
|
139
|
+
'timestamp'
|
127
140
|
|
128
|
-
when
|
141
|
+
when /^Money$/
|
142
|
+
'decimal'
|
129
143
|
# Warning: Max 65 kbytes. To use larger types, try MediumBlob (16mb) or LongBlob (4gb)
|
130
|
-
when
|
131
|
-
|
144
|
+
when /^Picture ?Raw ?Data$/, /^Image$/
|
145
|
+
'blob'
|
146
|
+
when /^Variable ?Length ?Raw ?Data$/, /^Blob$/
|
147
|
+
'blob'
|
132
148
|
# Assuming you only want a boolean out of this. Should we specify length instead?
|
133
|
-
when
|
134
|
-
|
149
|
+
when /^BIT$/
|
150
|
+
'bit'
|
151
|
+
else type # raise "SQL type unknown for standard type #{type}"
|
135
152
|
end
|
136
153
|
[sql_type, length]
|
137
154
|
end
|
@@ -145,7 +162,7 @@ module ActiveFacts
|
|
145
162
|
delayed_foreign_keys = []
|
146
163
|
|
147
164
|
@vocabulary.tables.each do |table|
|
148
|
-
puts "CREATE TABLE #{escape table.name} ("
|
165
|
+
puts "CREATE TABLE #{escape table.name.gsub(' ','')} ("
|
149
166
|
|
150
167
|
pk = table.identifier_columns
|
151
168
|
identity_column = pk[0] if pk.size == 1 && pk[0].is_auto_assigned
|
@@ -160,13 +177,13 @@ module ActiveFacts
|
|
160
177
|
columns = table.columns.sort_by { |column| column.name(nil) }.map do |column|
|
161
178
|
name = escape column.name("")
|
162
179
|
padding = " "*(name.size >= ColumnNameMax ? 1 : ColumnNameMax-name.size)
|
163
|
-
type, params,
|
164
|
-
|
180
|
+
type, params, constraints = column.type
|
181
|
+
constraints = [] if (fk_columns.include?(column)) # Don't enforce VT constraints on FK columns
|
165
182
|
length = params[:length]
|
166
183
|
length &&= length.to_i
|
167
184
|
scale = params[:scale]
|
168
185
|
scale &&= scale.to_i
|
169
|
-
type, length =
|
186
|
+
type, length = normalise_type(type, length)
|
170
187
|
sql_type = "#{type}#{
|
171
188
|
if !length
|
172
189
|
""
|
@@ -176,7 +193,7 @@ module ActiveFacts
|
|
176
193
|
}"
|
177
194
|
identity = column == identity_column ? " AUTO_INCREMENT" : ""
|
178
195
|
null = (column.is_mandatory ? "NOT " : "") + "NULL"
|
179
|
-
check = check_clause(name,
|
196
|
+
check = check_clause(name, constraints)
|
180
197
|
comment = column.comment
|
181
198
|
[ "-- #{comment}", "#{name}#{padding}#{sql_type}#{identity} #{null}#{check}" ]
|
182
199
|
end.flatten
|
@@ -189,7 +206,7 @@ module ActiveFacts
|
|
189
206
|
table.foreign_keys.each do |fk|
|
190
207
|
fk_text = "FOREIGN KEY (" +
|
191
208
|
fk.from_columns.map{|column| column.name}*", " +
|
192
|
-
") REFERENCES #{escape fk.to.name} (" +
|
209
|
+
") REFERENCES #{escape fk.to.name.gsub(' ','')} (" +
|
193
210
|
fk.to_columns.map{|column| column.name}*", " +
|
194
211
|
")"
|
195
212
|
if !@delay_fks and # We don't want to delay all Fks
|
@@ -197,7 +214,7 @@ module ActiveFacts
|
|
197
214
|
fk.to == table && !fk.to_columns.detect{|column| !column.is_mandatory}) # The reference columns already have the required indexes
|
198
215
|
inline_fks << fk_text
|
199
216
|
else
|
200
|
-
delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name}\n\tADD " + fk_text)
|
217
|
+
delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name.gsub(' ','')}\n\tADD " + fk_text)
|
201
218
|
end
|
202
219
|
end
|
203
220
|
|
@@ -235,11 +252,11 @@ module ActiveFacts
|
|
235
252
|
"'" + str.gsub(/'/,"''") + "'"
|
236
253
|
end
|
237
254
|
|
238
|
-
def check_clause(column_name,
|
239
|
-
return "" if
|
240
|
-
# REVISIT: Merge all
|
255
|
+
def check_clause(column_name, constraints)
|
256
|
+
return "" if constraints.empty?
|
257
|
+
# REVISIT: Merge all constraints (later; now just use the first)
|
241
258
|
" CHECK(" +
|
242
|
-
|
259
|
+
constraints[0].all_allowed_range_sorted.map do |ar|
|
243
260
|
vr = ar.value_range
|
244
261
|
min = vr.minimum_bound
|
245
262
|
max = vr.maximum_bound
|
@@ -15,7 +15,6 @@ module ActiveFacts
|
|
15
15
|
# afgen --sql/server[=options] <file>.cql
|
16
16
|
# Options are comma or space separated:
|
17
17
|
# * delay_fks Leave all foreign keys until the end, not just those that contain forward-references
|
18
|
-
# * norma Translate valuetypes from NORMA to SQL Server
|
19
18
|
class SERVER
|
20
19
|
private
|
21
20
|
include Persistence
|
@@ -48,7 +47,6 @@ module ActiveFacts
|
|
48
47
|
@vocabulary = vocabulary
|
49
48
|
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
50
49
|
@delay_fks = options.include? "delay_fks"
|
51
|
-
@norma = options.include? "norma"
|
52
50
|
@underscore = options.include?("underscore") ? "_" : ""
|
53
51
|
end
|
54
52
|
|
@@ -71,39 +69,58 @@ module ActiveFacts
|
|
71
69
|
end
|
72
70
|
end
|
73
71
|
|
74
|
-
# Return SQL type and (modified?) length for the passed
|
75
|
-
def
|
72
|
+
# Return SQL type and (modified?) length for the passed base type
|
73
|
+
def normalise_type(type, length)
|
76
74
|
sql_type = case type
|
77
|
-
when
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
75
|
+
when /^Auto ?Counter$/
|
76
|
+
'int'
|
77
|
+
|
78
|
+
when /^Unsigned ?Integer$/,
|
79
|
+
/^Signed ?Integer$/,
|
80
|
+
/^Unsigned ?Small ?Integer$/,
|
81
|
+
/^Signed ?Small ?Integer$/,
|
82
|
+
/^Unsigned ?Tiny ?Integer$/
|
83
83
|
s = case
|
84
|
-
when length <= 8
|
85
|
-
|
86
|
-
when length <=
|
87
|
-
|
84
|
+
when length <= 8
|
85
|
+
'tinyint'
|
86
|
+
when length <= 16
|
87
|
+
'shortint'
|
88
|
+
when length <= 32
|
89
|
+
'int'
|
90
|
+
else
|
91
|
+
'bigint'
|
88
92
|
end
|
89
93
|
length = nil
|
90
94
|
s
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
when
|
96
|
-
|
97
|
-
when
|
98
|
-
|
99
|
-
when
|
100
|
-
|
101
|
-
|
102
|
-
when
|
103
|
-
|
104
|
-
when
|
105
|
-
|
106
|
-
|
95
|
+
|
96
|
+
when /^Decimal$/
|
97
|
+
'decimal'
|
98
|
+
|
99
|
+
when /^Fixed ?Length ?Text$/, /^Char$/
|
100
|
+
'char'
|
101
|
+
when /^Variable ?Length ?Text$/, /^String$/
|
102
|
+
'varchar'
|
103
|
+
when /^Large ?Length ?Text$/, /^Text$/
|
104
|
+
'text'
|
105
|
+
|
106
|
+
when /^Date ?And ?Time$/, /^Date ?Time$/
|
107
|
+
'datetime'
|
108
|
+
when /^Date$/
|
109
|
+
'datetime' # SQLSVR 2K5: 'date'
|
110
|
+
when /^Time$/
|
111
|
+
'datetime' # SQLSVR 2K5: 'time'
|
112
|
+
when /^Auto ?Time ?Stamp$/
|
113
|
+
'timestamp'
|
114
|
+
|
115
|
+
when /^Money$/
|
116
|
+
'decimal'
|
117
|
+
when /^Picture ?Raw ?Data$/, /^Image$/
|
118
|
+
'image'
|
119
|
+
when /^Variable ?Length ?Raw ?Data$/, /^Blob$/
|
120
|
+
'varbinary'
|
121
|
+
when /^BIT$/
|
122
|
+
'bit'
|
123
|
+
else type # raise "SQL type unknown for standard type #{type}"
|
107
124
|
end
|
108
125
|
[sql_type, length]
|
109
126
|
end
|
@@ -117,7 +134,7 @@ module ActiveFacts
|
|
117
134
|
delayed_foreign_keys = []
|
118
135
|
|
119
136
|
@vocabulary.tables.each do |table|
|
120
|
-
puts "CREATE TABLE #{escape table.name(@underscore)} ("
|
137
|
+
puts "CREATE TABLE #{escape table.name(@underscore).gsub(' ',@underscore)} ("
|
121
138
|
|
122
139
|
pk = table.identifier_columns
|
123
140
|
identity_column = pk[0] if pk.size == 1 && pk[0].is_auto_assigned
|
@@ -132,13 +149,13 @@ module ActiveFacts
|
|
132
149
|
columns = table.columns.sort_by { |column| column.name(@underscore) }.map do |column|
|
133
150
|
name = escape column.name(@underscore)
|
134
151
|
padding = " "*(name.size >= ColumnNameMax ? 1 : ColumnNameMax-name.size)
|
135
|
-
type, params,
|
136
|
-
|
152
|
+
type, params, constraints = column.type
|
153
|
+
constraints = [] if (fk_columns.include?(column)) # Don't enforce VT constraints on FK columns
|
137
154
|
length = params[:length]
|
138
155
|
length &&= length.to_i
|
139
156
|
scale = params[:scale]
|
140
157
|
scale &&= scale.to_i
|
141
|
-
type, length =
|
158
|
+
type, length = normalise_type(type, length)
|
142
159
|
sql_type = "#{type}#{
|
143
160
|
if !length
|
144
161
|
""
|
@@ -148,7 +165,7 @@ module ActiveFacts
|
|
148
165
|
}"
|
149
166
|
identity = column == identity_column ? " IDENTITY" : ""
|
150
167
|
null = (column.is_mandatory ? "NOT " : "") + "NULL"
|
151
|
-
check = check_clause(name,
|
168
|
+
check = check_clause(name, constraints)
|
152
169
|
comment = column.comment
|
153
170
|
[ "-- #{comment}", "#{name}#{padding}#{sql_type}#{identity} #{null}#{check}" ]
|
154
171
|
end.flatten
|
@@ -161,7 +178,7 @@ module ActiveFacts
|
|
161
178
|
table.foreign_keys.each do |fk|
|
162
179
|
fk_text = "FOREIGN KEY (" +
|
163
180
|
fk.from_columns.map{|column| column.name(@underscore)}*", " +
|
164
|
-
") REFERENCES #{escape fk.to.name(@underscore)} (" +
|
181
|
+
") REFERENCES #{escape fk.to.name(@underscore).gsub(' ',@underscore)} (" +
|
165
182
|
fk.to_columns.map{|column| column.name(@underscore)}*", " +
|
166
183
|
")"
|
167
184
|
if !@delay_fks and # We don't want to delay all Fks
|
@@ -169,7 +186,7 @@ module ActiveFacts
|
|
169
186
|
fk.to == table && !fk.to_columns.detect{|column| !column.is_mandatory}) # The reference columns already have the required indexes
|
170
187
|
inline_fks << fk_text
|
171
188
|
else
|
172
|
-
delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name(@underscore)}\n\tADD " + fk_text)
|
189
|
+
delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name(@underscore).gsub(' ',@underscore)}\n\tADD " + fk_text)
|
173
190
|
end
|
174
191
|
end
|
175
192
|
|
@@ -187,7 +204,7 @@ module ActiveFacts
|
|
187
204
|
view_name = escape "#{index.view_name}_#{abbreviated_column_names}"
|
188
205
|
delayed_indices <<
|
189
206
|
%Q{CREATE VIEW dbo.#{view_name} (#{column_name_list}) WITH SCHEMABINDING AS
|
190
|
-
\tSELECT #{column_name_list} FROM dbo.#{escape index.on.name(@underscore)}
|
207
|
+
\tSELECT #{column_name_list} FROM dbo.#{escape index.on.name(@underscore).gsub(' ',@underscore)}
|
191
208
|
\tWHERE\t#{
|
192
209
|
index.columns.
|
193
210
|
select{|column| !column.is_mandatory }.
|
@@ -225,11 +242,11 @@ CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.c
|
|
225
242
|
"'" + str.gsub(/'/,"''") + "'"
|
226
243
|
end
|
227
244
|
|
228
|
-
def check_clause(column_name,
|
229
|
-
return "" if
|
230
|
-
# REVISIT: Merge all
|
245
|
+
def check_clause(column_name, constraints)
|
246
|
+
return "" if constraints.empty?
|
247
|
+
# REVISIT: Merge all constraints (later; now just use the first)
|
231
248
|
" CHECK(" +
|
232
|
-
|
249
|
+
constraints[0].all_allowed_range_sorted.map do |ar|
|
233
250
|
vr = ar.value_range
|
234
251
|
min = vr.minimum_bound
|
235
252
|
max = vr.maximum_bound
|
@@ -18,8 +18,10 @@ module ActiveFacts
|
|
18
18
|
read(file, filename)
|
19
19
|
}
|
20
20
|
rescue => e
|
21
|
-
|
22
|
-
|
21
|
+
# Augment the exception message, but preserve the backtrace
|
22
|
+
ne = StandardError.new("In #{filename} #{e.message.strip}")
|
23
|
+
ne.set_backtrace(e.backtrace)
|
24
|
+
raise ne
|
23
25
|
end
|
24
26
|
|
25
27
|
# Read the specified input stream
|
@@ -29,7 +31,8 @@ module ActiveFacts
|
|
29
31
|
|
30
32
|
# Read the specified input string
|
31
33
|
def self.readstring(str, filename = "string")
|
32
|
-
ActiveFacts::CQL::Compiler.new(
|
34
|
+
compiler = ActiveFacts::CQL::Compiler.new(filename)
|
35
|
+
compiler.compile(str)
|
33
36
|
end
|
34
37
|
end
|
35
38
|
end
|
@@ -31,20 +31,60 @@ module ActiveFacts
|
|
31
31
|
# afgen --<generator> <file>.orm
|
32
32
|
# This parser uses Rexml so it's very slow.
|
33
33
|
class ORM
|
34
|
+
module Gravity
|
35
|
+
%w{NW N NE W C E SW S SE}.each_with_index { |dir, i| const_set(dir, i) }
|
36
|
+
end
|
37
|
+
|
38
|
+
DataTypeMapping = {
|
39
|
+
"FixedLengthText" => "Char",
|
40
|
+
"VariableLengthText" => "String",
|
41
|
+
"LargeLengthText" => "Text",
|
42
|
+
"SignedIntegerNumeric" => "Signed Integer(32)",
|
43
|
+
"SignedSmallIntegerNumeric" => "Signed Integer(16)",
|
44
|
+
"SignedLargeIntegerNumeric" => "Signed Integer(64)",
|
45
|
+
"UnsignedIntegerNumeric" => "Unsigned Integer(32)",
|
46
|
+
"UnsignedTinyIntegerNumeric" => "Unsigned Integer(8)",
|
47
|
+
"UnsignedSmallIntegerNumeric" => "Unsigned Integer(16)",
|
48
|
+
"UnsignedLargeIntegerNumeric" => "Unsigned Integer(64)",
|
49
|
+
"AutoCounterNumeric" => "Auto Counter",
|
50
|
+
"FloatingPointNumeric" => "Real(64)",
|
51
|
+
"SinglePrecisionFloatingPointNumeric" => " Real(32)",
|
52
|
+
"DoublePrecisionFloatingPointNumeric" => " Real(32)",
|
53
|
+
"DecimalNumeric" => "Decimal",
|
54
|
+
"MoneyNumeric" => "Money",
|
55
|
+
"FixedLengthRawData" => "Blob",
|
56
|
+
"VariableLengthRawData" => "Blob",
|
57
|
+
"LargeLengthRawData" => "Blob",
|
58
|
+
"PictureRawData" => "Image",
|
59
|
+
"OleObjectRawData" => "Blob",
|
60
|
+
"AutoTimestampTemporal" => "Auto Time Stamp",
|
61
|
+
"TimeTemporal" => "Time",
|
62
|
+
"DateTemporal" => "Date",
|
63
|
+
"DateAndTimeTemporal" => "Date Time",
|
64
|
+
"TrueOrFalseLogical" => "Boolean",
|
65
|
+
"YesOrNoLogical" => "Boolean",
|
66
|
+
"RowIdOther" => "Integer(8)",
|
67
|
+
"ObjectIdOther" => "Integer(8)"
|
68
|
+
}
|
69
|
+
RESERVED_WORDS = %w{
|
70
|
+
and but each each either false if maybe no none not one or some that true where
|
71
|
+
}
|
72
|
+
|
34
73
|
private
|
35
|
-
def self.readfile(filename)
|
74
|
+
def self.readfile(filename, *options)
|
36
75
|
File.open(filename) {|file|
|
37
|
-
self.read(file, filename)
|
76
|
+
self.read(file, filename, *options)
|
38
77
|
}
|
39
78
|
end
|
40
79
|
|
41
|
-
def self.read(file, filename = "stdin")
|
42
|
-
ORM.new(file, filename).read
|
80
|
+
def self.read(file, filename = "stdin", *options)
|
81
|
+
ORM.new(file, filename, *options).read
|
43
82
|
end
|
44
83
|
|
45
|
-
def initialize(file, filename = "stdin")
|
84
|
+
def initialize(file, filename = "stdin", *options)
|
46
85
|
@file = file
|
47
86
|
@filename = filename
|
87
|
+
@options = options
|
48
88
|
end
|
49
89
|
|
50
90
|
public
|
@@ -78,7 +118,7 @@ module ActiveFacts
|
|
78
118
|
def read_vocabulary
|
79
119
|
@constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
|
80
120
|
vocabulary_name = @x_model['Name']
|
81
|
-
@vocabulary = @constellation.Vocabulary(
|
121
|
+
@vocabulary = @constellation.Vocabulary(vocabulary_name)
|
82
122
|
|
83
123
|
# Find all elements having an "id" attribute and index them
|
84
124
|
x_identified = @x_model.xpath(".//*[@id]")
|
@@ -97,9 +137,10 @@ module ActiveFacts
|
|
97
137
|
read_nested_types
|
98
138
|
read_subtypes
|
99
139
|
read_roles
|
140
|
+
complete_nested_types
|
100
141
|
read_constraints
|
101
|
-
|
102
|
-
|
142
|
+
read_instances if @options.include?("instances")
|
143
|
+
read_diagrams if @options.include?("diagrams")
|
103
144
|
end
|
104
145
|
|
105
146
|
def read_entity_types
|
@@ -108,8 +149,7 @@ module ActiveFacts
|
|
108
149
|
x_entity_types = @x_model.xpath("orm:Objects/orm:EntityType")
|
109
150
|
x_entity_types.each{|x|
|
110
151
|
id = x['id']
|
111
|
-
name = x['Name'] || ""
|
112
|
-
name.gsub!(/\s/,'')
|
152
|
+
name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
|
113
153
|
name = nil if name.size == 0
|
114
154
|
entity_types <<
|
115
155
|
@by_id[id] =
|
@@ -134,8 +174,7 @@ module ActiveFacts
|
|
134
174
|
#pp x_value_types
|
135
175
|
x_value_types.each{|x|
|
136
176
|
id = x['id']
|
137
|
-
name = x['Name'] || ""
|
138
|
-
name.gsub!(/\s/,'')
|
177
|
+
name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
|
139
178
|
name = nil if name.size == 0
|
140
179
|
|
141
180
|
cdt = x.xpath('orm:ConceptualDataType')[0]
|
@@ -143,13 +182,17 @@ module ActiveFacts
|
|
143
182
|
scale = scale != "" && scale.to_i
|
144
183
|
length = cdt['Length']
|
145
184
|
length = length != "" && length.to_i
|
185
|
+
length = nil if length <= 0
|
146
186
|
base_type = @x_by_id[cdt['ref']]
|
147
187
|
type_name = "#{base_type.name}"
|
148
188
|
type_name.sub!(/^orm:/,'')
|
189
|
+
|
149
190
|
type_name.sub!(/DataType\Z/,'')
|
150
|
-
type_name
|
151
|
-
type_name
|
152
|
-
|
191
|
+
type_name = DataTypeMapping[type_name] || type_name
|
192
|
+
if !length and type_name =~ /\(([0-9]+)\)/
|
193
|
+
length = $1.to_i
|
194
|
+
end
|
195
|
+
type_name = type_name.sub(/\(([0-9]*)\)/,'')
|
153
196
|
|
154
197
|
# REVISIT: Need to handle standard types better here:
|
155
198
|
value_super_type = type_name != name ? @constellation.ValueType(@vocabulary, type_name) : nil
|
@@ -159,18 +202,21 @@ module ActiveFacts
|
|
159
202
|
vt = @constellation.ValueType(@vocabulary, name)
|
160
203
|
vt.supertype = value_super_type
|
161
204
|
vt.length = length if length
|
162
|
-
vt.scale = scale if scale
|
205
|
+
vt.scale = scale if scale && scale != 0
|
163
206
|
independent = x['IsIndependent']
|
164
207
|
vt.is_independent = true if independent && independent == 'true'
|
165
208
|
personal = x['IsPersonal']
|
166
209
|
vt.pronoun = 'personal' if personal && personal == 'true'
|
167
210
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
211
|
+
x_vr = x.xpath("orm:ValueRestriction/orm:ValueConstraint")
|
212
|
+
x_vr.each{|vr|
|
213
|
+
x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange")
|
214
|
+
next if x_ranges.size == 0
|
215
|
+
vt.value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(:new)
|
216
|
+
x_ranges.each{|x_range|
|
217
|
+
v_range = value_range(x_range)
|
218
|
+
ar = @constellation.AllowedRange(vt.value_constraint, v_range)
|
219
|
+
}
|
174
220
|
}
|
175
221
|
}
|
176
222
|
end
|
@@ -191,17 +237,18 @@ module ActiveFacts
|
|
191
237
|
# Handle the fact types:
|
192
238
|
facts = []
|
193
239
|
@x_facts = @x_model.xpath("orm:Facts/orm:Fact")
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
240
|
+
debug :orm, "Reading fact types" do
|
241
|
+
@x_facts.each{|x|
|
242
|
+
id = x['id']
|
243
|
+
name = x['Name'] || x['_Name']
|
244
|
+
name = "<unnamed>" if !name
|
245
|
+
name = "" if !name || name.size == 0
|
246
|
+
# Note that the new metamodel doesn't have a name for a facttype unless it's objectified
|
247
|
+
|
248
|
+
debug :orm, "FactType #{name || id}"
|
249
|
+
facts << @by_id[id] = fact_type = @constellation.FactType(:new)
|
250
|
+
}
|
251
|
+
end
|
205
252
|
end
|
206
253
|
|
207
254
|
def read_subtypes
|
@@ -215,220 +262,207 @@ module ActiveFacts
|
|
215
262
|
@x_mappings = []
|
216
263
|
end
|
217
264
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
=begin
|
272
|
-
# Create uniqueness constraints over the subtyping fact type
|
273
|
-
p1rs = @constellation.RoleSequence(:new)
|
274
|
-
@constellation.RoleRef(p1rs, 0).role = subtype_role
|
275
|
-
pc1 = @constellation.PresenceConstraint(:new)
|
276
|
-
pc1.name = "#{subtype.name}MustHaveSupertype#{supertype.name}"
|
277
|
-
pc1.vocabulary = @vocabulary
|
278
|
-
pc1.role_sequence = p1rs
|
279
|
-
pc1.is_mandatory = true # A subtype instance must have a supertype instance
|
280
|
-
pc1.min_frequency = 1
|
281
|
-
pc1.max_frequency = 1
|
282
|
-
pc1.is_preferred_identifier = false
|
283
|
-
|
284
|
-
# The supertype role often identifies the subtype:
|
285
|
-
p2rs = @constellation.RoleSequence(:new)
|
286
|
-
@constellation.RoleRef(p2rs, 0).role = supertype_role
|
287
|
-
pc2 = @constellation.PresenceConstraint(:new)
|
288
|
-
pc2.name = "#{supertype.name}MayBeA#{subtype.name}"
|
289
|
-
pc2.vocabulary = @vocabulary
|
290
|
-
pc2.role_sequence = p2rs
|
291
|
-
pc2.is_mandatory = false
|
292
|
-
pc2.min_frequency = 0
|
293
|
-
pc2.max_frequency = 1
|
294
|
-
pc2.is_preferred_identifier = inheritance_fact.provides_identification
|
295
|
-
=end
|
296
|
-
}
|
265
|
+
debug :orm, "Reading sub-types" do
|
266
|
+
@x_subtypes.each{|x|
|
267
|
+
id = x['id']
|
268
|
+
name = (x['Name'] || x['_Name'] || '').gsub(/\s+/,' ').gsub(/-/,'_').strip
|
269
|
+
name = nil if name.size == 0
|
270
|
+
debug :orm, "FactType #{name || id}"
|
271
|
+
|
272
|
+
x_subtype_role = x.xpath('orm:FactRoles/orm:SubtypeMetaRole')[0]
|
273
|
+
subtype_role_id = x_subtype_role['id']
|
274
|
+
subtype_id = x_subtype_role.xpath('orm:RolePlayer')[0]['ref']
|
275
|
+
subtype = @by_id[subtype_id]
|
276
|
+
# REVISIT: Provide a way in the metamodel of handling Partition, (and mapping choices that vary for each supertype?)
|
277
|
+
|
278
|
+
x_supertype_role = x.xpath('orm:FactRoles/orm:SupertypeMetaRole')[0]
|
279
|
+
supertype_role_id = x_supertype_role['id']
|
280
|
+
supertype_id = x_supertype_role.xpath('orm:RolePlayer')[0]['ref']
|
281
|
+
supertype = @by_id[supertype_id]
|
282
|
+
|
283
|
+
throw "For Subtype fact #{name}, the supertype #{supertype_id} was not found" if !supertype
|
284
|
+
throw "For Subtype fact #{name}, the subtype #{subtype_id} was not found" if !subtype
|
285
|
+
debug :orm, "#{subtype.name} is a subtype of #{supertype.name}"
|
286
|
+
|
287
|
+
inheritance_fact = @constellation.TypeInheritance(subtype, supertype)
|
288
|
+
inheritance_fact.fact_type_id = :new
|
289
|
+
if x["IsPrimary"] == "true" or # Old way
|
290
|
+
x["PreferredIdentificationPath"] == "true" # Newer
|
291
|
+
debug :orm, "#{supertype.name} is primary supertype of #{subtype.name}"
|
292
|
+
inheritance_fact.provides_identification = true
|
293
|
+
end
|
294
|
+
mapping = @x_mappings.detect{ |m| m['ref'] == id }
|
295
|
+
mapping_choice = mapping ? mapping.parent['AbsorptionChoice'] : 'Absorbed'
|
296
|
+
inheritance_fact.assimilation = mapping_choice.downcase.sub(/partition/, 'partitioned') if mapping_choice != 'Absorbed'
|
297
|
+
facts << @by_id[id] = inheritance_fact
|
298
|
+
|
299
|
+
# Create the new Roles so we can find constraints on them:
|
300
|
+
subtype_role = @by_id[subtype_role_id] = @constellation.Role(inheritance_fact, 0, :concept => subtype)
|
301
|
+
supertype_role = @by_id[supertype_role_id] = @constellation.Role(inheritance_fact, 1, :concept => supertype)
|
302
|
+
|
303
|
+
# Create readings, so constraints can be verbalised for example:
|
304
|
+
rs = @constellation.RoleSequence(:new)
|
305
|
+
@constellation.RoleRef(rs, 0, :role => subtype_role)
|
306
|
+
@constellation.RoleRef(rs, 1, :role => supertype_role)
|
307
|
+
@constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}")
|
308
|
+
@constellation.Reading(inheritance_fact, 1, :role_sequence => rs, :text => "{0} is a subtype of {1}")
|
309
|
+
|
310
|
+
rs2 = @constellation.RoleSequence(:new)
|
311
|
+
@constellation.RoleRef(rs2, 0, :role => supertype_role)
|
312
|
+
@constellation.RoleRef(rs2, 1, :role => subtype_role)
|
313
|
+
n = 'aeiouh'.include?(subtype_role.concept.name.downcase[0]) ? 1 : 0
|
314
|
+
@constellation.Reading(inheritance_fact, 2+n, :role_sequence => rs2, :text => "{0} is a {1}")
|
315
|
+
@constellation.Reading(inheritance_fact, 3-n, :role_sequence => rs2, :text => "{0} is an {1}")
|
316
|
+
}
|
317
|
+
end
|
297
318
|
end
|
298
319
|
|
299
320
|
def read_nested_types
|
300
321
|
# Process NestedTypes, but ignore ones having a NestedPredicate with IsImplied="true"
|
301
322
|
# We'll ignore the fact roles (and constraints) that implied objectifications have.
|
302
323
|
# This happens for all ternaries and higher order facts
|
303
|
-
nested_types = []
|
324
|
+
@nested_types = []
|
304
325
|
x_nested_types = @x_model.xpath("orm:Objects/orm:ObjectifiedType")
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
326
|
+
debug :orm, "Reading objectified types" do
|
327
|
+
x_nested_types.each{|x|
|
328
|
+
id = x['id']
|
329
|
+
name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
|
330
|
+
name = nil if name.size == 0
|
310
331
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
332
|
+
x_fact_type = x.xpath('orm:NestedPredicate')[0]
|
333
|
+
is_implied = x_fact_type['IsImplied'] == "true"
|
334
|
+
|
335
|
+
fact_id = x_fact_type['ref']
|
336
|
+
fact_type = @by_id[fact_id]
|
337
|
+
throw "Nested fact #{fact_id} not found" if !fact_type
|
338
|
+
|
339
|
+
#if is_implied
|
340
|
+
# puts "Implied type #{name} (#{id}) nests #{fact_type ? fact_type.fact_type_id : "unknown"}"
|
341
|
+
# @by_id[id] = fact_type
|
342
|
+
#else
|
343
|
+
begin
|
344
|
+
debug :orm, "NestedType #{name} is #{id}, nests #{fact_type.fact_type_id}"
|
345
|
+
@nested_types <<
|
346
|
+
@by_id[id] =
|
347
|
+
nested_type = @constellation.EntityType(@vocabulary, name)
|
348
|
+
nested_type.fact_type = fact_type
|
349
|
+
end
|
350
|
+
}
|
351
|
+
end
|
330
352
|
end
|
331
353
|
|
332
|
-
def
|
333
|
-
@
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
x_fact_roles = x.xpath('orm:FactRoles/*')
|
341
|
-
x_reading_orders = x.xpath('orm:ReadingOrders/*')
|
342
|
-
|
343
|
-
# Deal with FactRoles (Roles):
|
344
|
-
x_fact_roles.each{|x|
|
345
|
-
name = x['Name'] || ""
|
346
|
-
name.gsub!(/\s/,'')
|
347
|
-
name = nil if name.size == 0
|
354
|
+
def complete_nested_types
|
355
|
+
@nested_types.each do |nested_type|
|
356
|
+
# Create the phantom roles here. These will be used later when we create objectification joins,
|
357
|
+
# but for now there's nothing we import from NORMA which requires objectification joins.
|
358
|
+
# Consequently there's no need to index them against NORMA's phantom roles.
|
359
|
+
nested_type.create_implicit_fact_types
|
360
|
+
end
|
361
|
+
end
|
348
362
|
|
349
|
-
|
350
|
-
|
363
|
+
def read_roles
|
364
|
+
debug :orm, "Reading roles and readings" do
|
365
|
+
@x_facts.each{|x|
|
351
366
|
id = x['id']
|
352
|
-
|
353
|
-
|
354
|
-
#
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
#
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
367
|
+
fact_type = @by_id[id]
|
368
|
+
fact_name = x['Name'] || x['_Name'] || ''
|
369
|
+
#fact_name.gsub!(/\s/,'')
|
370
|
+
fact_name = nil if fact_name == ''
|
371
|
+
|
372
|
+
x_fact_roles = x.xpath('orm:FactRoles/*')
|
373
|
+
x_reading_orders = x.xpath('orm:ReadingOrders/*')
|
374
|
+
|
375
|
+
# Deal with FactRoles (Roles):
|
376
|
+
debug :orm, "Reading fact roles" do
|
377
|
+
x_fact_roles.each{|x|
|
378
|
+
name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
|
379
|
+
name = nil if name.size == 0
|
380
|
+
|
381
|
+
# _IsMandatory = x['_IsMandatory']
|
382
|
+
# _Multiplicity = x['_Multiplicity]
|
383
|
+
id = x['id']
|
384
|
+
rp = x.xpath('orm:RolePlayer')[0]
|
385
|
+
raise "Invalid ORM file; fact has missing player (RolePlayer id=#{id})" unless rp
|
386
|
+
ref = rp['ref']
|
387
|
+
|
388
|
+
# Find the concept that plays the role:
|
389
|
+
concept = @by_id[ref]
|
390
|
+
throw "RolePlayer for '#{name}' #{ref} was not found" if !concept
|
391
|
+
|
392
|
+
# Skip implicit roles added by NORMA to make unaries into binaries.
|
393
|
+
# This would make constraints over the deleted roles impossible,
|
394
|
+
# so as a SPECIAL CASE we index the unary role by the id of the
|
395
|
+
# implicit role. That means care is needed when handling unary FTs.
|
396
|
+
if (ox = @x_by_id[ref]) && ox['IsImplicitBooleanValue']
|
397
|
+
x_other_role = x.parent.xpath('orm:Role').reject{|x_role|
|
398
|
+
x_role == x
|
399
|
+
}[0]
|
400
|
+
other_role_id = x_other_role["id"]
|
401
|
+
other_role = @by_id[other_role_id]
|
402
|
+
debug :orm, "Indexing unary FT role #{other_role_id} by implicit boolean role #{id}"
|
403
|
+
@by_id[id] = other_role
|
404
|
+
|
405
|
+
# The role name of the ignored role is the one that applies:
|
406
|
+
role_name = x['Name']
|
407
|
+
other_role.role_name = role_name if role_name && role_name != ''
|
408
|
+
|
409
|
+
concept.retract # Delete our object for the implicit boolean ValueType
|
410
|
+
@by_id.delete(ref) # and de-index it from our list
|
411
|
+
next
|
412
|
+
end
|
413
|
+
|
414
|
+
debug :orm, "#{@vocabulary.name}, RoleName=#{x['Name'].inspect} played by concept=#{concept.name}"
|
415
|
+
throw "Role is played by #{concept.class} not Concept" if !(@constellation.vocabulary.concept(:Concept) === concept)
|
416
|
+
|
417
|
+
debug :orm, "Creating role #{name} nr#{fact_type.all_role.size} of #{fact_type.fact_type_id} played by #{concept.name}"
|
418
|
+
|
419
|
+
role = @by_id[id] = @constellation.Role(fact_type, fact_type.all_role.size, :concept => concept)
|
420
|
+
role.role_name = name if name
|
421
|
+
debug :orm, "Fact #{fact_name} (id #{fact_type.fact_type_id.object_id}) role #{x['Name']} is played by #{concept.name}, role is #{role.object_id}"
|
422
|
+
|
423
|
+
x_vr = x.xpath("orm:ValueRestriction/orm:RoleValueConstraint")
|
424
|
+
x_vr.each{|vr|
|
425
|
+
x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange")
|
426
|
+
next if x_ranges.size == 0
|
427
|
+
role.role_value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(:new)
|
428
|
+
x_ranges.each{|x_range|
|
429
|
+
v_range = value_range(x_range)
|
430
|
+
ar = @constellation.AllowedRange(role.role_value_constraint, v_range)
|
431
|
+
}
|
432
|
+
}
|
382
433
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
#puts "Creating role #{name} nr#{fact_type.all_role.size} of #{fact_type.fact_type_id} played by #{concept.name}"
|
387
|
-
|
388
|
-
role = @by_id[id] = @constellation.Role(fact_type, fact_type.all_role.size, :concept => concept)
|
389
|
-
role.role_name = name if name
|
390
|
-
# puts "Fact #{fact_name} (id #{fact_type.fact_type_id.object_id}) role #{x['Name']} is played by #{concept.name}, role is #{role.object_id}"
|
391
|
-
|
392
|
-
x_vr = x.xpath("orm:ValueRestriction")
|
393
|
-
x_vr.each{|vr|
|
394
|
-
x_ranges = vr.xpath("orm:RoleValueConstraint/orm:ValueRanges/orm:ValueRange")
|
395
|
-
next if x_ranges.size == 0
|
396
|
-
role.role_value_restriction = @constellation.ValueRestriction(:new)
|
397
|
-
x_ranges.each{|x_range|
|
398
|
-
v_range = value_range(x_range)
|
399
|
-
ar = @constellation.AllowedRange(role.role_value_restriction, v_range)
|
434
|
+
debug :orm, "Adding Role #{role.role_name || role.concept.name} to #{fact_type.describe}"
|
435
|
+
#fact_type.add_role(role)
|
436
|
+
debug :orm, "Role #{role} is #{id}"
|
400
437
|
}
|
401
|
-
|
402
|
-
|
403
|
-
# puts "Adding Role #{role.name} to #{fact_type.name}"
|
404
|
-
#fact_type.add_role(role)
|
405
|
-
# puts "\tRole #{role} is #{id}"
|
406
|
-
}
|
438
|
+
end
|
407
439
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
440
|
+
# Deal with Readings:
|
441
|
+
debug :orm, "Reading fact readings" do
|
442
|
+
x_reading_orders.each{|x|
|
443
|
+
x_role_sequence = x.xpath('orm:RoleSequence/*')
|
444
|
+
x_readings = x.xpath('orm:Readings/orm:Reading/orm:Data')
|
412
445
|
|
413
|
-
|
414
|
-
|
446
|
+
# Build an array of the Roles needed:
|
447
|
+
role_array = x_role_sequence.map{|x| @by_id[x['ref']] }
|
415
448
|
|
416
|
-
|
417
|
-
|
449
|
+
debug :orm, "Reading #{x_readings.map(&:text).inspect}"
|
450
|
+
role_sequence = get_role_sequence(role_array)
|
418
451
|
|
419
|
-
|
420
|
-
|
421
|
-
|
452
|
+
#role_sequence.all_role_ref.each_with_index{|rr, i|
|
453
|
+
# # REVISIT: rr.leading_adjective = ...; Add adjectives here
|
454
|
+
# }
|
422
455
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
456
|
+
x_readings.each_with_index{|x, i|
|
457
|
+
reading = @constellation.Reading(fact_type, fact_type.all_reading.size)
|
458
|
+
reading.role_sequence = role_sequence
|
459
|
+
# REVISIT: The downcase here only needs to be the initial letter of each word, but be safe:
|
460
|
+
reading.text = extract_adjectives(x.text, role_sequence)
|
461
|
+
}
|
462
|
+
}
|
463
|
+
end
|
429
464
|
}
|
430
|
-
|
431
|
-
# @vocabulary.fact_types.each{|ft| puts ft }
|
465
|
+
end
|
432
466
|
end
|
433
467
|
|
434
468
|
def extract_adjectives(text, role_sequence)
|
@@ -437,15 +471,17 @@ module ActiveFacts
|
|
437
471
|
role_ref = all_role_refs[i]
|
438
472
|
role = role_ref.role
|
439
473
|
|
440
|
-
word = '\b
|
441
|
-
leading_adjectives_re = "
|
442
|
-
trailing_adjectives_re = "(
|
474
|
+
word = '\b[A-Za-z_][A-Za-z0-9_]+\b'
|
475
|
+
leading_adjectives_re = "#{word}-(?: +#{word})*"
|
476
|
+
trailing_adjectives_re = "(?:#{word} +)*-#{word}"
|
443
477
|
role_with_adjectives_re =
|
444
|
-
%r|
|
478
|
+
%r| ?(#{leading_adjectives_re})? *\{#{i}\} *(#{trailing_adjectives_re})? ?|
|
445
479
|
|
446
480
|
text.gsub!(role_with_adjectives_re) {
|
447
|
-
|
448
|
-
|
481
|
+
# REVISIT: Don't want to strip all spaces here any more:
|
482
|
+
#puts "text=#{text.inspect}, la=#{$1.inspect}, ta=#{$2.inspect}" if $1 || $2
|
483
|
+
la = ($1||'').gsub(/\s+/,' ').sub(/-/,'').strip
|
484
|
+
ta = ($2||'').gsub(/\s+/,' ').sub(/-/,'').strip
|
449
485
|
#puts "Setting leading adj #{la.inspect} from #{text.inspect} for #{role_ref.role.concept.name}" if la != ""
|
450
486
|
# REVISIT: Dunno what's up here, but removing the "if" test makes this chuck exceptions:
|
451
487
|
role_ref.leading_adjective = la if la != ""
|
@@ -456,8 +492,34 @@ module ActiveFacts
|
|
456
492
|
" {#{i}} "
|
457
493
|
}
|
458
494
|
}
|
459
|
-
text.sub!(/\
|
460
|
-
text.
|
495
|
+
text.sub!(/\s\s*/, ' ') # Compress extra spaces
|
496
|
+
text.strip!
|
497
|
+
text.downcase! # Check for reserved words and object type names *after* downcasing
|
498
|
+
elided = ''
|
499
|
+
text.gsub!(/( |-?\b[A-Za-z_][A-Za-z0-9_]*\b-?|\{\d\})|./) do |w|
|
500
|
+
case w
|
501
|
+
when /[A-Za-z]/
|
502
|
+
if RESERVED_WORDS.include?(w)
|
503
|
+
$stderr.puts "Masking reserved word '#{w}' in reading '#{text}'"
|
504
|
+
next "_#{w}"
|
505
|
+
elsif @constellation.Concept[[[@vocabulary.name], w]]
|
506
|
+
$stderr.puts "Masking object type name '#{w}' in reading '#{text}'"
|
507
|
+
next "_#{w}"
|
508
|
+
elsif all_role_refs.detect{|rr| rr.role.role_name == w}
|
509
|
+
$stderr.puts "Masking role name '#{w}' in reading '#{text}'"
|
510
|
+
next "_#{w}"
|
511
|
+
end
|
512
|
+
next w
|
513
|
+
when /\{\d\}/
|
514
|
+
next w
|
515
|
+
when / /
|
516
|
+
next w
|
517
|
+
else
|
518
|
+
elided << w
|
519
|
+
next ''
|
520
|
+
end
|
521
|
+
end
|
522
|
+
$stderr.puts "Elided illegal characters '#{elided}' from reading" unless elided.empty?
|
461
523
|
text
|
462
524
|
end
|
463
525
|
|
@@ -475,15 +537,16 @@ module ActiveFacts
|
|
475
537
|
|
476
538
|
# Make a new RoleSequence:
|
477
539
|
role_sequence = @constellation.RoleSequence(:new) unless role_sequence
|
478
|
-
role_array.each_with_index
|
540
|
+
role_array.each_with_index do |r, i|
|
479
541
|
role_ref = @constellation.RoleRef(role_sequence, i)
|
480
542
|
role_ref.role = r
|
481
|
-
|
543
|
+
end
|
544
|
+
|
482
545
|
role_sequence
|
483
546
|
end
|
484
547
|
|
485
548
|
def map_roles(x_roles, why = nil)
|
486
|
-
role_array = x_roles.map
|
549
|
+
role_array = x_roles.map do |x|
|
487
550
|
id = x['ref']
|
488
551
|
role = @by_id[id]
|
489
552
|
if (why && !role)
|
@@ -514,8 +577,10 @@ module ActiveFacts
|
|
514
577
|
end
|
515
578
|
end
|
516
579
|
role
|
517
|
-
|
518
|
-
role_array.include?(nil)
|
580
|
+
end
|
581
|
+
return nil if role_array.include?(nil)
|
582
|
+
|
583
|
+
get_role_sequence(role_array)
|
519
584
|
end
|
520
585
|
|
521
586
|
def read_constraints
|
@@ -535,378 +600,747 @@ module ActiveFacts
|
|
535
600
|
x_mandatory_constraints = @x_model.xpath("orm:Constraints/orm:MandatoryConstraint")
|
536
601
|
@mandatory_constraints_by_rs = {}
|
537
602
|
@mandatory_constraint_rs_by_id = {}
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
# As of Feb 2008, all NORMA ValueTypes have an implied mandatory constraint.
|
544
|
-
if x.xpath("orm:ImpliedByObjectType").size > 0
|
545
|
-
# $stderr.puts "Skipping ImpliedMandatoryConstraint #{name} over #{roles}"
|
546
|
-
next
|
547
|
-
end
|
603
|
+
debug :orm, "Scanning mandatory constraints" do
|
604
|
+
x_mandatory_constraints.each{|x|
|
605
|
+
name = x["Name"] || ''
|
606
|
+
name = nil if name.size == 0
|
548
607
|
|
549
|
-
|
550
|
-
|
551
|
-
next if !roles
|
608
|
+
# As of Feb 2008, all NORMA ValueTypes have an implied mandatory constraint.
|
609
|
+
next if x.xpath("orm:ImpliedByObjectType").size > 0
|
552
610
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
# puts "Mandatory #{name}(#{roles}) is paired with exclusive #{x_exclusion['Name']}" if x_exclusion
|
611
|
+
x_roles = x.xpath("orm:RoleSequence/orm:Role")
|
612
|
+
role_sequence = map_roles(x_roles, "mandatory constraint #{name}")
|
613
|
+
next if !role_sequence
|
557
614
|
|
558
|
-
|
559
|
-
|
560
|
-
|
615
|
+
debug :orm, "New MC #{x['Name']} over #{role_sequence.describe}"
|
616
|
+
@mandatory_constraints_by_rs[role_sequence] = x
|
617
|
+
@mandatory_constraint_rs_by_id[x['id']] = role_sequence
|
618
|
+
}
|
619
|
+
end
|
561
620
|
end
|
562
621
|
|
622
|
+
# Mandatory constraints that didn't get merged with an exclusion constraint or a uniqueness constraint are simple mandatories
|
563
623
|
def read_residual_mandatory_constraints
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
624
|
+
debug :orm, "Processing non-absorbed mandatory constraints" do
|
625
|
+
@mandatory_constraints_by_rs.each { |role_sequence, x|
|
626
|
+
id = x['id']
|
627
|
+
# Create a simply-mandatory PresenceConstraint for each mandatory constraint
|
628
|
+
name = x["Name"] || ''
|
629
|
+
name = nil if name.size == 0
|
630
|
+
#puts "Residual Mandatory #{name}: #{role_sequence.to_s}"
|
631
|
+
|
632
|
+
if (players = role_sequence.all_role_ref.map{|rr| rr.role.concept}).uniq.size > 1
|
633
|
+
join_over, = *ActiveFacts::Metamodel.join_roles_over(role_sequence.all_role_ref.map{|rr| rr.role}, :proximate)
|
634
|
+
raise "Mandatory join constraint #{name} has incompatible players #{players.map{|o| o.name}.inspect}" unless join_over
|
635
|
+
if players.detect{|p| p != join_over}
|
636
|
+
debug :join, "subtyping join simple mandatory constraint #{name} over #{join_over.name}"
|
637
|
+
players.each_with_index do |player, i|
|
638
|
+
next if player != join_over
|
639
|
+
# REVISIT: We don't need to make a subtyping join here (from join_over to player)
|
640
|
+
end
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
pc = @constellation.PresenceConstraint(:new)
|
645
|
+
pc.vocabulary = @vocabulary
|
646
|
+
pc.name = name
|
647
|
+
pc.role_sequence = role_sequence
|
648
|
+
pc.is_mandatory = true
|
649
|
+
pc.min_frequency = 1
|
650
|
+
pc.max_frequency = nil
|
651
|
+
pc.is_preferred_identifier = false
|
652
|
+
|
653
|
+
(@constraints_by_rs[role_sequence] ||= []) << pc
|
654
|
+
@by_id[id] = pc
|
655
|
+
}
|
656
|
+
end
|
582
657
|
end
|
583
658
|
|
584
659
|
def read_uniqueness_constraints
|
585
660
|
x_uniqueness_constraints = @x_model.xpath("orm:Constraints/orm:UniquenessConstraint")
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
661
|
+
debug :orm, "Reading uniqueness constraints" do
|
662
|
+
x_uniqueness_constraints.each{|x|
|
663
|
+
name = x["Name"] || ''
|
664
|
+
name = nil if name.size == 0
|
665
|
+
uc_id = x["id"]
|
666
|
+
x_pi = x.xpath("orm:PreferredIdentifierFor")[0]
|
667
|
+
pi = x_pi ? @by_id[eref = x_pi['ref']] : nil
|
668
|
+
|
669
|
+
# Skip uniqueness constraints on implied concepts
|
670
|
+
next if x_pi && !pi
|
671
|
+
|
672
|
+
# Get the RoleSequence:
|
673
|
+
x_roles = x.xpath("orm:RoleSequence/orm:Role")
|
674
|
+
next if x_roles.size == 0
|
675
|
+
role_sequence = map_roles(x_roles, "uniqueness constraint #{name}")
|
676
|
+
next if !role_sequence
|
677
|
+
#puts "uc: #{role_sequence.all_role_ref.map{|rr|rr.role.fact_type.default_reading}*', '}"
|
678
|
+
|
679
|
+
# Check for a join
|
680
|
+
if (fact_types = role_sequence.all_role_ref.map{|rr| rr.role.fact_type}).uniq.size > 1
|
681
|
+
join_over, = *ActiveFacts::Metamodel.join_roles_over(role_sequence.all_role_ref.map{|rr| rr.role}, :counterpart)
|
682
|
+
|
683
|
+
players = role_sequence.all_role_ref.map{|rr| rr.role.concept.name}.uniq
|
684
|
+
raise "Uniqueness join constraint #{name} has incompatible players #{players.inspect}" unless join_over
|
685
|
+
subtyping = players.size > 1 ? 'subtyping ' : ''
|
686
|
+
# REVISIT: Create the Join, the JoinNode for join_over, and steps from each role_ref to join_over
|
687
|
+
debug :join, "#{subtyping}join uniqueness constraint over #{join_over.name} in #{fact_types.map(&:default_reading)*', '}"
|
688
|
+
end
|
689
|
+
|
690
|
+
# There is an implicit uniqueness constraint when any object plays a unary. Skip it.
|
691
|
+
if (x_roles.size == 1 &&
|
692
|
+
(id = x_roles[0]['ref']) &&
|
693
|
+
(x_role = @x_by_id[id]) &&
|
694
|
+
(nodes = x_role.parent.elements).size == 2 &&
|
695
|
+
(sibling = nodes[1]) &&
|
696
|
+
(ib_id = sibling.elements[0]['ref']) &&
|
697
|
+
(ib = @x_by_id[ib_id]) &&
|
698
|
+
ib['IsImplicitBooleanValue'])
|
699
|
+
unary_identifier = true
|
700
|
+
end
|
701
|
+
|
702
|
+
mc_id = nil
|
703
|
+
if (mc = @mandatory_constraints_by_rs[role_sequence])
|
704
|
+
# Remove absorbed mandatory constraints, leaving residual ones.
|
705
|
+
debug :orm, "Absorbing MC #{mc['Name']} over #{role_sequence.describe}"
|
706
|
+
@mandatory_constraints_by_rs.delete(role_sequence)
|
707
|
+
mc_id = mc['id']
|
708
|
+
@mandatory_constraint_rs_by_id.delete(mc['id'])
|
709
|
+
else
|
710
|
+
debug :orm, "No MC to absorb over #{role_sequence.describe}"
|
711
|
+
end
|
712
|
+
|
713
|
+
# A TypeInheritance fact type has a uniqueness constraint on each role.
|
714
|
+
# If this UC is on the supertype and identifies the subtype, it's preferred:
|
715
|
+
is_supertype_constraint =
|
716
|
+
(rr = role_sequence.all_role_ref.single) &&
|
717
|
+
(role = rr.role) &&
|
718
|
+
(fact_type = role.fact_type) &&
|
719
|
+
fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) &&
|
720
|
+
role.concept == fact_type.supertype &&
|
721
|
+
fact_type.provides_identification
|
722
|
+
|
723
|
+
pc = @constellation.PresenceConstraint(:new)
|
724
|
+
pc.vocabulary = @vocabulary
|
725
|
+
pc.name = name
|
726
|
+
pc.role_sequence = role_sequence
|
727
|
+
pc.is_mandatory = true if mc
|
728
|
+
pc.min_frequency = mc ? 1 : 0
|
729
|
+
pc.max_frequency = 1
|
730
|
+
pc.is_preferred_identifier = true if pi || unary_identifier || is_supertype_constraint
|
731
|
+
debug :orm, "#{name} covers #{role_sequence.describe} has min=#{pc.min_frequency}, max=1, preferred=#{pc.is_preferred_identifier.inspect}"
|
732
|
+
|
733
|
+
debug :orm, role_sequence.all_role_ref.to_a[0].role.fact_type.describe + " is subject to " + pc.describe if role_sequence.all_role_ref.all?{|r| r.role.fact_type.is_a? ActiveFacts::Metamodel::TypeInheritance }
|
734
|
+
|
735
|
+
(@constraints_by_rs[role_sequence] ||= []) << pc
|
736
|
+
@by_id[uc_id] = pc
|
737
|
+
@by_id[mc_id] = pc if mc_id
|
738
|
+
}
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
742
|
+
# Equality and subset joins involve two or more role sequences,
|
743
|
+
# and the respective roles from each sequence must be compatible,
|
744
|
+
# Compatibility might involve subtyping joins but not objectification joins
|
745
|
+
# to the respective end-point (constrained object type).
|
746
|
+
# Also, all roles in each sequence constitute a join over a single
|
747
|
+
# object type, which might involve subtyping or objectification joins.
|
748
|
+
#
|
749
|
+
def make_joins(constraint_type, name, role_sequences)
|
750
|
+
# Get the object types constrained for each position in the role sequences.
|
751
|
+
# Supertyping joins may be needed to reach them.
|
752
|
+
end_points = [] # An array of the common supertype for matching role_refs across the sequences
|
753
|
+
end_joins = [] # An array of booleans indicating whether any role_sequence requires a subtyping join
|
754
|
+
role_sequences[0].all_role_ref.size.times do |i|
|
755
|
+
role_refs = role_sequences.map{|rs| rs.all_role_ref.detect{|rr| rr.ordinal == i}}
|
756
|
+
next if (fact_types = role_refs.map{|rr| rr.role.fact_type}).uniq.size == 1
|
757
|
+
if (players = role_refs.map{|rr| rr.role.concept}).uniq.size == 1
|
758
|
+
end_point = players[0]
|
759
|
+
end_joins[i] = false
|
760
|
+
else
|
761
|
+
# Can the players be joined using a subtyping join?
|
762
|
+
common_supertypes = players[1..-1].
|
763
|
+
inject(players[0].supertypes_transitive) do |remaining, player|
|
764
|
+
remaining & player.supertypes_transitive
|
765
|
+
end
|
766
|
+
end_point = common_supertypes[0]
|
767
|
+
|
768
|
+
raise "constrained roles of #{constraint_type} constraint #{name} are incompatible (#{names*', '})" if common_supertypes.size == 0
|
769
|
+
end_joins[i] = true
|
770
|
+
end
|
771
|
+
end_points[i] = end_point
|
772
|
+
end
|
773
|
+
|
774
|
+
# For each role_sequence, find the object type over which the join is implied (nil if no join)
|
775
|
+
sequence_join_over = []
|
776
|
+
if role_sequences[0].all_role_ref.size > 1 # There are joins within each sequence.
|
777
|
+
sequence_join_over = role_sequences.map do |rs|
|
778
|
+
join_over, joined_roles = *ActiveFacts::Metamodel.join_roles_over(rs.all_role_ref.map{|rr| rr.role})
|
779
|
+
join_over
|
598
780
|
end
|
781
|
+
end
|
599
782
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
# end
|
783
|
+
# If there are no joins, we can drop out here.
|
784
|
+
if sequence_join_over.compact.empty? && !end_joins.detect{|e| true}
|
785
|
+
return
|
786
|
+
end
|
787
|
+
|
788
|
+
debug :join, "#{constraint_type} join constraint #{name} over #{role_sequences.map{|rs|rs.describe}*', '}"
|
607
789
|
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
(id = x_roles[0]['ref']) &&
|
617
|
-
(x_role = @x_by_id[id]) &&
|
618
|
-
(nodes = x_role.parent.elements).size == 2 &&
|
619
|
-
(sibling = nodes[1]) &&
|
620
|
-
# x_role.parent.children.size == 2 &&
|
621
|
-
# (sibling = x_role.parent.children[1]) &&
|
622
|
-
(ib_id = sibling.elements[0]['ref']) &&
|
623
|
-
(ib = @x_by_id[ib_id]) &&
|
624
|
-
ib['IsImplicitBooleanValue'])
|
625
|
-
unary_identifier = true
|
790
|
+
join = nil
|
791
|
+
debug :join, "#{constraint_type} join constraint #{name} constrains #{
|
792
|
+
end_points.zip(end_joins).map{|(p,j)| p.name+(j ? ' & subtypes':'')}*', '
|
793
|
+
}#{
|
794
|
+
if role_sequences[0].all_role_ref.size > 1
|
795
|
+
", joined over #{sequence_join_over.map{|o| o ? o.name : '(none)'}*', '}"
|
796
|
+
else
|
797
|
+
''
|
626
798
|
end
|
799
|
+
}" do
|
800
|
+
|
801
|
+
end
|
802
|
+
return # REVISIT: Disabled until we get the join verbaliser working.
|
803
|
+
while false
|
804
|
+
|
805
|
+
# There may be one join per role sequence:
|
806
|
+
role_sequences.zip(sequence_join_over).map do |role_sequence, join_over|
|
807
|
+
# Skip if there's no join here (sequence join nor end-point subset join)
|
808
|
+
role_refs = role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
|
809
|
+
next unless join_over or role_refs.detect{|rr| rr.role.concept != end_points[rr.ordinal]}
|
810
|
+
|
811
|
+
replacement_rs = @constellation.RoleSequence(:new)
|
812
|
+
join = @constellation.Join(:new)
|
813
|
+
join_node = nil
|
814
|
+
role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.each_with_index do |role_ref, i|
|
815
|
+
end_point = end_points[i]
|
816
|
+
debug :join, "Join Node #{join.all_join_node.size} is for #{end_point.name}"
|
817
|
+
end_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => end_point)
|
818
|
+
role_node = end_node
|
819
|
+
|
820
|
+
if role_ref.role.concept != end_point
|
821
|
+
subtype = role_ref.role.concept
|
822
|
+
debug :join, "Making subtyping join step #{join.all_join_node.size} from #{subtype.name} to #{end_point.name}" do
|
823
|
+
role_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => role_ref.role.concept)
|
824
|
+
ti = role_ref.role.concept.all_type_inheritance_as_subtype.detect{|ti| ti.supertype == end_point}
|
825
|
+
raise "REVISIT: Can't yet handle multiple subtyping levels in subtyping join" unless ti
|
826
|
+
|
827
|
+
# Make a new role sequence over the subtyping fact type:
|
828
|
+
rs = @constellation.RoleSequence(:new)
|
829
|
+
@constellation.RoleRef(rs, 0, :role => ti.all_role.detect{|r| r.concept == role_ref.role.concept}, :join_node => role_node)
|
830
|
+
@constellation.RoleRef(rs, 1, :role => ti.all_role.detect{|r| r.concept == end_point}, :join_node => end_node)
|
831
|
+
|
832
|
+
js = @constellation.JoinStep(role_node, end_node, :fact_type => ti)
|
833
|
+
debug :join, "New subtyping join step #{js.describe}"
|
834
|
+
end
|
835
|
+
end
|
627
836
|
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
837
|
+
@constellation.RoleRef(replacement_rs, 0, :role => role_ref.role, :join_node => end_node)
|
838
|
+
|
839
|
+
# REVISIT: Something here needs to handle unaries, which have a join step over two copies of the same JoinNode
|
840
|
+
|
841
|
+
if join_over
|
842
|
+
if !join_node
|
843
|
+
debug :join, "Join Node #{join.all_join_node.size} is over #{join_over.name}"
|
844
|
+
join_node = @constellation.JoinNode(join, join.all_join_node.size, :concept => join_over)
|
845
|
+
end
|
846
|
+
debug :join, "Making join step from #{end_point.name} to #{join_over.name}" do
|
847
|
+
role = role_ref.role
|
848
|
+
# Detect an objectification step. Here, the role is in an objectified type, but the end_point isn't another role player of that fact type... or is there a better test?
|
849
|
+
if join_over == role.fact_type.entity_type
|
850
|
+
p join_over.name
|
851
|
+
p role.fact_type.default_reading
|
852
|
+
p end_point.name
|
853
|
+
raise "REVISIT: Incomplete"
|
854
|
+
elsif end_point == role.fact_type.entity_type
|
855
|
+
fact_type = role.implicit_fact_type
|
856
|
+
# REVISIT: Might these be reversed... sometimes?
|
857
|
+
end_role = fact_type.all_role.single
|
858
|
+
join_role = role
|
859
|
+
else
|
860
|
+
fact_type = role.fact_type
|
861
|
+
end_role = role
|
862
|
+
# This is unambiguous, since it's come from NORMA which only supports (unambiguous) implicit joins:
|
863
|
+
join_role = role.fact_type.all_role.detect{|r| r.concept == join_over}
|
864
|
+
# debugger unless join_role && end_role
|
865
|
+
end
|
866
|
+
|
867
|
+
rs = @constellation.RoleSequence(:new)
|
868
|
+
# Detect the fact type over which we're joining (may involve objectification)
|
869
|
+
@constellation.RoleRef(rs, 0, :role => end_role, :join_node => end_node)
|
870
|
+
@constellation.RoleRef(rs, 1, :role => join_role, :join_node => join_node)
|
871
|
+
js = @constellation.JoinStep(end_node, join_node, :fact_type => fact_type)
|
872
|
+
debug :join, "New join step #{js.describe}"
|
873
|
+
end
|
874
|
+
else
|
875
|
+
debug :join, "Need join step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
|
876
|
+
if role_ref.role.fact_type.all_role.size > 1
|
877
|
+
raise unimplemented
|
878
|
+
else
|
879
|
+
js = @constellation.JoinStep(role_node, role_node, :fact_type => role_ref.role.fact_type)
|
880
|
+
end
|
881
|
+
end
|
882
|
+
end
|
883
|
+
|
884
|
+
# Thoroughly check that this is a valid join
|
885
|
+
join.validate
|
886
|
+
|
887
|
+
# Constrain the replacement role sequence, which has the attached join:
|
888
|
+
role_sequences[role_sequences.index(role_sequence)] = replacement_rs
|
633
889
|
end
|
890
|
+
end
|
634
891
|
|
635
|
-
# A UC that spans more than one Role of a fact will be a Preferred Id for the implied object
|
636
|
-
#puts "Unique" + rs.to_s +
|
637
|
-
# (pi ? " (preferred id for #{pi.name})" : "") +
|
638
|
-
# (mc ? " (mandatory)" : "") if pi && !mc
|
639
|
-
|
640
|
-
# A TypeInheritance fact type has a uniqueness constraint on each role.
|
641
|
-
# If this UC is on the supertype and identifies the subtype, it's preferred:
|
642
|
-
is_supertype_constraint =
|
643
|
-
(rr = roles.all_role_ref.single) &&
|
644
|
-
(role = rr.role) &&
|
645
|
-
(fact_type = role.fact_type) &&
|
646
|
-
fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) &&
|
647
|
-
role.concept == fact_type.supertype &&
|
648
|
-
fact_type.provides_identification
|
649
|
-
|
650
|
-
pc = @constellation.PresenceConstraint(:new)
|
651
|
-
pc.vocabulary = @vocabulary
|
652
|
-
pc.name = name
|
653
|
-
pc.role_sequence = roles
|
654
|
-
pc.is_mandatory = true if mc
|
655
|
-
pc.min_frequency = mc ? 1 : 0
|
656
|
-
pc.max_frequency = 1
|
657
|
-
pc.is_preferred_identifier = true if pi || unary_identifier || is_supertype_constraint
|
658
|
-
#puts "#{name} covers #{roles.describe} has min=#{pc.min_frequency}, max=1, preferred=#{pc.is_preferred_identifier.inspect}" if emit_special_debug
|
659
|
-
|
660
|
-
#puts roles.all_role_ref.to_a[0].role.fact_type.describe + " is subject to " + pc.describe if roles.all_role_ref.all?{|r| r.role.fact_type.is_a? ActiveFacts::Metamodel::TypeInheritance }
|
661
|
-
|
662
|
-
(@constraints_by_rs[roles] ||= []) << pc
|
663
|
-
}
|
664
892
|
end
|
665
893
|
|
666
894
|
def read_exclusion_constraints
|
667
895
|
x_exclusion_constraints = @x_model.xpath("orm:Constraints/orm:ExclusionConstraint")
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
896
|
+
debug :orm, "Reading exclusion constraints" do
|
897
|
+
x_exclusion_constraints.each{|x|
|
898
|
+
id = x['id']
|
899
|
+
name = x["Name"] || ''
|
900
|
+
name = nil if name.size == 0
|
901
|
+
x_mandatory = (m = x.xpath("orm:ExclusiveOrMandatoryConstraint")[0]) &&
|
902
|
+
@x_by_id[mc_id = m['ref']]
|
903
|
+
role_sequences =
|
904
|
+
x.xpath("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
|
905
|
+
x_role_refs = x_rs.xpath("orm:Role")
|
906
|
+
map_roles(
|
907
|
+
x_role_refs , # .map{|xr| @x_by_id[xr['ref']] },
|
908
|
+
"exclusion constraint #{name}"
|
909
|
+
)
|
910
|
+
}
|
911
|
+
if x_mandatory
|
912
|
+
# Remove absorbed mandatory constraints, leaving residual ones.
|
913
|
+
mc_rs = @mandatory_constraint_rs_by_id[mc_id]
|
914
|
+
@mandatory_constraint_rs_by_id.delete(mc_id)
|
915
|
+
@mandatory_constraints_by_rs.delete(mc_rs)
|
916
|
+
end
|
688
917
|
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
918
|
+
make_joins('exclusion', name+(x_mandatory ? '/'+x_mandatory['Name'] : ''), role_sequences)
|
919
|
+
|
920
|
+
ec = @constellation.SetExclusionConstraint(:new)
|
921
|
+
ec.vocabulary = @vocabulary
|
922
|
+
ec.name = name
|
923
|
+
# ec.enforcement =
|
924
|
+
role_sequences.each_with_index do |rs, i|
|
925
|
+
@constellation.SetComparisonRoles(ec, i, :role_sequence => rs)
|
926
|
+
end
|
927
|
+
ec.is_mandatory = true if x_mandatory
|
928
|
+
@by_id[id] = ec
|
929
|
+
@by_id[mc_id] = ec if mc_id
|
930
|
+
}
|
931
|
+
end
|
698
932
|
end
|
699
933
|
|
700
934
|
def read_equality_constraints
|
701
935
|
x_equality_constraints = @x_model.xpath("orm:Constraints/orm:EqualityConstraint")
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
936
|
+
debug :orm, "Reading equality constraints" do
|
937
|
+
x_equality_constraints.each{|x|
|
938
|
+
id = x['id']
|
939
|
+
name = x["Name"] || ''
|
940
|
+
name = nil if name.size == 0
|
941
|
+
role_sequences =
|
942
|
+
x.xpath("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
|
943
|
+
x_role_refs = x_rs.xpath("orm:Role")
|
944
|
+
map_roles(
|
945
|
+
x_role_refs , # .map{|xr| @x_by_id[xr['ref']] },
|
946
|
+
"equality constraint #{name}"
|
947
|
+
)
|
948
|
+
}
|
714
949
|
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
950
|
+
make_joins('equality', name, role_sequences)
|
951
|
+
|
952
|
+
ec = @constellation.SetEqualityConstraint(:new)
|
953
|
+
ec.vocabulary = @vocabulary
|
954
|
+
ec.name = name
|
955
|
+
# ec.enforcement =
|
956
|
+
role_sequences.each_with_index do |rs, i|
|
957
|
+
@constellation.SetComparisonRoles(ec, i, :role_sequence => rs)
|
958
|
+
end
|
959
|
+
@by_id[id] = ec
|
960
|
+
}
|
961
|
+
end
|
723
962
|
end
|
724
963
|
|
725
964
|
def read_subset_constraints
|
726
965
|
x_subset_constraints = @x_model.xpath("orm:Constraints/orm:SubsetConstraint")
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
966
|
+
debug :orm, "Reading subset constraints" do
|
967
|
+
x_subset_constraints.each{|x|
|
968
|
+
id = x['id']
|
969
|
+
name = x["Name"] || ''
|
970
|
+
name = nil if name.size == 0
|
971
|
+
role_sequences =
|
972
|
+
x.xpath("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
|
973
|
+
x_role_refs = x_rs.xpath("orm:Role")
|
974
|
+
map_roles(
|
975
|
+
x_role_refs , # .map{|xr| @x_by_id[xr['ref']] },
|
976
|
+
"equality constraint #{name}"
|
977
|
+
)
|
978
|
+
}
|
979
|
+
make_joins('subset', name, role_sequences)
|
980
|
+
|
981
|
+
ec = @constellation.SubsetConstraint(:new)
|
982
|
+
ec.vocabulary = @vocabulary
|
983
|
+
ec.name = name
|
984
|
+
# ec.enforcement =
|
985
|
+
ec.subset_role_sequence = role_sequences[0]
|
986
|
+
ec.superset_role_sequence = role_sequences[1]
|
987
|
+
@by_id[id] = ec
|
988
|
+
}
|
989
|
+
end
|
747
990
|
end
|
748
991
|
|
749
992
|
def read_ring_constraints
|
750
993
|
x_ring_constraints = @x_model.xpath("orm:Constraints/orm:RingConstraint")
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
# # Convert the RingConstraint name to a number:
|
758
|
-
# type_num = eval("::ActiveFacts::RingConstraint::#{type}")
|
759
|
-
# rescue => e
|
760
|
-
# throw "RingConstraint type #{type} isn't known"
|
761
|
-
# end
|
994
|
+
debug :orm, "Reading ring constraints" do
|
995
|
+
x_ring_constraints.each{|x|
|
996
|
+
id = x['id']
|
997
|
+
name = x["Name"] || ''
|
998
|
+
name = nil if name.size == 0
|
999
|
+
ring_type = x["Type"]
|
762
1000
|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
1001
|
+
from, to = *x.xpath("orm:RoleSequence/orm:Role").
|
1002
|
+
map do |xr|
|
1003
|
+
@by_id[xr['ref']]
|
1004
|
+
end
|
1005
|
+
if from.concept != to.concept
|
1006
|
+
join_over, = *ActiveFacts::Metamodel.join_roles_over([from, to], :counterpart)
|
1007
|
+
raise "Ring constraint has incompatible players #{from.concept.name}, #{to.concept.name}" if !join_over
|
1008
|
+
debug :join, "join ring constraint over #{join_over.name}"
|
1009
|
+
end
|
1010
|
+
rc = @constellation.RingConstraint(:new)
|
1011
|
+
rc.vocabulary = @vocabulary
|
1012
|
+
rc.name = name
|
1013
|
+
# rc.enforcement =
|
1014
|
+
rc.role = from
|
1015
|
+
rc.other_role = to
|
1016
|
+
rc.ring_type = ring_type.gsub(/PurelyReflexive/,'Reflexive')
|
1017
|
+
@by_id[id] = rc
|
1018
|
+
}
|
1019
|
+
end
|
774
1020
|
end
|
775
1021
|
|
776
1022
|
def read_frequency_constraints
|
777
1023
|
x_frequency_constraints = @x_model.xpath("orm:Constraints/orm:FrequencyConstraint")
|
778
|
-
|
1024
|
+
debug :orm, "Reading frequency constraints" do
|
1025
|
+
x_frequency_constraints.each do |x_frequency_constraint|
|
1026
|
+
id = x_frequency_constraint['id']
|
1027
|
+
min_frequency = x_frequency_constraint["MinFrequency"].to_i
|
1028
|
+
min_frequency = nil if min_frequency == 0
|
1029
|
+
max_frequency = x_frequency_constraint["MaxFrequency"].to_i
|
1030
|
+
max_frequency = nil if max_frequency == 0
|
1031
|
+
x_roles = x_frequency_constraint.xpath("orm:RoleSequence/orm:Role")
|
1032
|
+
role = @by_id[x_roles[0]["ref"]]
|
1033
|
+
role_sequence = @constellation.RoleSequence(:new)
|
1034
|
+
role_ref = @constellation.RoleRef(role_sequence, 0, :role => role)
|
1035
|
+
debug :orm, "FrequencyConstraint(min #{min_frequency.inspect} max #{max_frequency.inspect} over #{role.fact_type.describe(role)} #{id} role ref = #{x_roles[0]["ref"]}"
|
1036
|
+
@by_id[id] = @constellation.PresenceConstraint(
|
1037
|
+
:new,
|
1038
|
+
:vocabulary => @vocabulary,
|
1039
|
+
:name => name = x_frequency_constraint["Name"] || '',
|
1040
|
+
:role_sequence => role_sequence,
|
1041
|
+
:is_mandatory => false,
|
1042
|
+
:min_frequency => min_frequency,
|
1043
|
+
:max_frequency => max_frequency,
|
1044
|
+
:is_preferred_identifier => false
|
1045
|
+
)
|
1046
|
+
end
|
1047
|
+
end
|
779
1048
|
end
|
780
1049
|
|
781
1050
|
def read_instances
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
1051
|
+
debug :orm, "Reading sample data" do
|
1052
|
+
population = @constellation.Population(@vocabulary, "sample")
|
1053
|
+
|
1054
|
+
# Value instances first, then entities then facts:
|
1055
|
+
|
1056
|
+
x_values = @x_model.xpath("orm:Objects/orm:ValueType/orm:Instances/orm:ValueTypeInstance/orm:Value")
|
1057
|
+
#pp x_values.map{|v| [ v.parent['id'], v.text ] }
|
1058
|
+
debug :orm, "Reading sample values" do
|
1059
|
+
x_values.each{|v|
|
1060
|
+
id = v.parent['id']
|
1061
|
+
# Get details of the ValueType:
|
1062
|
+
xvt = v.parent.parent.parent
|
1063
|
+
vt_id = xvt['id']
|
1064
|
+
vtname = xvt['Name'] || ''
|
1065
|
+
#vtname.gsub!(/\s/,'')
|
1066
|
+
vtname = nil if vtname.size == 0
|
1067
|
+
vt = @by_id[vt_id]
|
1068
|
+
throw "ValueType #{vtname} not found" unless vt
|
1069
|
+
|
1070
|
+
i = @constellation.Instance(:new, :population => population, :concept => vt, :value => [v.text, is_a_string(v.text), nil])
|
1071
|
+
@by_id[id] = i
|
1072
|
+
# show_xmlobj(v)
|
1073
|
+
}
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
# Use the "id" attribute of EntityTypeInstance
|
1077
|
+
x_entities = @x_model.xpath("orm:Objects/orm:EntityType/orm:Instances/orm:EntityTypeInstance")
|
1078
|
+
#pp x_entities
|
1079
|
+
# x_entities.each{|v| show_xmlobj(v) }
|
1080
|
+
last_et_id = nil
|
1081
|
+
last_et = nil
|
1082
|
+
et = nil
|
1083
|
+
debug :orm, "Reading sample entities" do
|
1084
|
+
x_entities.each{|v|
|
1085
|
+
id = v['id']
|
1086
|
+
|
1087
|
+
# Get details of the EntityType:
|
1088
|
+
xet = v.parent.parent
|
1089
|
+
et_id = xet['id']
|
1090
|
+
if (et_id != last_et_id)
|
1091
|
+
etname = xet['Name'] || ''
|
1092
|
+
#etname.gsub!(/\s/,'')
|
1093
|
+
etname = nil if etname.size == 0
|
1094
|
+
last_et = et = @by_id[et_id]
|
1095
|
+
last_et_id = et_id
|
1096
|
+
throw "EntityType #{etname} not found" unless et
|
1097
|
+
end
|
803
1098
|
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
last_et_id = nil
|
809
|
-
last_et = nil
|
810
|
-
et = nil
|
811
|
-
x_entities.each{|v|
|
812
|
-
id = v['id']
|
813
|
-
|
814
|
-
# Get details of the EntityType:
|
815
|
-
xet = v.parent.parent
|
816
|
-
et_id = xet['id']
|
817
|
-
if (et_id != last_et_id)
|
818
|
-
etname = xet['Name'] || ''
|
819
|
-
etname.gsub!(/\s/,'')
|
820
|
-
etname = nil if name.size == 0
|
821
|
-
last_et = et = @by_id[et_id]
|
822
|
-
last_et_id = et_id
|
823
|
-
throw "EntityType #{etname} not found" unless et
|
1099
|
+
instance = @constellation.Instance(:new, :population => population, :concept => et, :value => nil)
|
1100
|
+
@by_id[id] = instance
|
1101
|
+
debug :orm, "Made new EntityType #{etname}"
|
1102
|
+
}
|
824
1103
|
end
|
825
1104
|
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
1105
|
+
# The EntityType instances have implicit facts for the PI facts.
|
1106
|
+
# We must create implicit PI facts after all the instances.
|
1107
|
+
entity_count = 0
|
1108
|
+
pi_fact_count = 0
|
1109
|
+
debug :orm, "Creating identifying facts for entities" do
|
1110
|
+
x_entities.each do |v|
|
1111
|
+
id = v['id']
|
1112
|
+
instance = @by_id[id]
|
1113
|
+
et = @by_id[v.parent.parent['id']]
|
1114
|
+
next unless (preferred_id = et.preferred_identifier)
|
1115
|
+
|
1116
|
+
debug :orm, "Create identifying facts using #{preferred_id}"
|
1117
|
+
|
1118
|
+
# Collate the referenced objects by role:
|
1119
|
+
role_instances = v.elements[0].elements.inject({}){|h, v|
|
1120
|
+
etri = @x_by_id[v['ref']]
|
1121
|
+
x_role_id = etri.parent.parent['id']
|
1122
|
+
role = @by_id[x_role_id]
|
1123
|
+
object = @by_id[object_id = etri['ref']]
|
1124
|
+
h[role] = object
|
1125
|
+
h
|
1126
|
+
}
|
1127
|
+
|
1128
|
+
# Create an instance of each required fact type, for compound identification:
|
1129
|
+
identifying_fact_types =
|
1130
|
+
preferred_id.role_sequence.all_role_ref.map { |rr| rr.role.fact_type }.uniq
|
1131
|
+
identifying_fact_types.
|
1132
|
+
each do |ft|
|
1133
|
+
debug :orm, "For FactType #{ft}" do
|
1134
|
+
fact = @constellation.Fact(:new, :population => population, :fact_type => ft)
|
1135
|
+
fact_roles = ft.all_role.map do |role|
|
1136
|
+
if role.concept == et
|
1137
|
+
object = instance
|
1138
|
+
else
|
1139
|
+
object = role_instances[role]
|
1140
|
+
debug :orm, "instance for role #{role} is #{object}"
|
1141
|
+
end
|
1142
|
+
@constellation.RoleValue(:instance => object, :population => population, :fact => fact, :role => role)
|
1143
|
+
end
|
1144
|
+
end
|
1145
|
+
pi_fact_count += 1
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
entity_count += 1
|
1149
|
+
end
|
1150
|
+
end
|
1151
|
+
debug :orm, "Created #{pi_fact_count} facts to identify #{entity_count} entities"
|
1152
|
+
|
1153
|
+
# Use the "ref" attribute of FactTypeRoleInstance:
|
1154
|
+
x_fact_roles = @x_model.xpath("orm:Facts/orm:Fact/orm:Instances/orm:FactTypeInstance/orm:RoleInstances/orm:FactTypeRoleInstance")
|
1155
|
+
|
1156
|
+
last_id = nil
|
1157
|
+
fact = nil
|
1158
|
+
fact_roles = []
|
1159
|
+
debug :orm, "Reading sample facts" do
|
1160
|
+
x_fact_roles.each do |v|
|
1161
|
+
fact_type_id = v.parent.parent.parent.parent['id']
|
1162
|
+
id = v.parent.parent['id']
|
1163
|
+
fact_type = @by_id[fact_type_id]
|
1164
|
+
throw "Fact type #{fact_type_id} not found" unless fact_type
|
830
1165
|
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
next unless (preferred_id = et.preferred_identifier)
|
840
|
-
|
841
|
-
# puts "Create identifying facts using #{preferred_id}"
|
842
|
-
|
843
|
-
# Collate the referenced objects by role:
|
844
|
-
role_instances = v.elements[0].elements.inject({}){|h, v|
|
845
|
-
etri = @x_by_id[v['ref']]
|
846
|
-
x_role_id = etri.parent.parent['id']
|
1166
|
+
# Create initial and subsequent Fact objects:
|
1167
|
+
fact = @constellation.Fact(:new, :population => population, :fact_type => fact_type) unless fact && last_id == id
|
1168
|
+
last_id = id
|
1169
|
+
|
1170
|
+
# REVISIT: This doesn't handle instances of objectified fact types (where a RoleValue.instance objectifies Fact)
|
1171
|
+
|
1172
|
+
x_role_instance = @x_by_id[v['ref']]
|
1173
|
+
x_role_id = x_role_instance.parent.parent['id']
|
847
1174
|
role = @by_id[x_role_id]
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
1175
|
+
throw "Role not found for instance #{x_role_id}" unless role
|
1176
|
+
instance_id = x_role_instance['ref']
|
1177
|
+
instance = @by_id[instance_id]
|
1178
|
+
throw "Instance not found for FactRole #{instance_id}" unless instance
|
1179
|
+
@constellation.RoleValue(:instance => instance, :population => population, :fact => fact, :role => role)
|
1180
|
+
end
|
1181
|
+
end
|
852
1182
|
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
1183
|
+
end
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
def read_diagrams
|
1187
|
+
x_diagrams = @document.root.xpath("ormDiagram:ORMDiagram")
|
1188
|
+
debug :orm, "Reading diagrams" do
|
1189
|
+
x_diagrams.each do |x|
|
1190
|
+
name = (x["Name"] || '').strip
|
1191
|
+
diagram = @constellation.Diagram(@vocabulary, name)
|
1192
|
+
debug :diagram, "Starting to read diagram #{name}"
|
1193
|
+
shapes = x.xpath("ormDiagram:Shapes/*")
|
1194
|
+
debug :orm, "Reading shapes" do
|
1195
|
+
shapes.map do |x_shape|
|
1196
|
+
x_subject = x_shape.xpath("ormDiagram:Subject")[0]
|
1197
|
+
subject = @by_id[x_subject["ref"]]
|
1198
|
+
is_expanded = v = x_shape['IsExpanded'] and v == 'true'
|
1199
|
+
bounds = x_shape['AbsoluteBounds']
|
1200
|
+
case shape_type = x_shape.name
|
1201
|
+
when 'FactTypeShape'
|
1202
|
+
read_fact_type_shape diagram, x_shape, is_expanded, bounds, subject
|
1203
|
+
when 'ExternalConstraintShape', 'FrequencyConstraintShape'
|
1204
|
+
# REVISIT: The offset might depend on the constraint type. This is right for subset and other round ones.
|
1205
|
+
position = convert_position(bounds, Gravity::NW, 31, 31)
|
1206
|
+
shape = @constellation.ConstraintShape(
|
1207
|
+
:new, :diagram => diagram, :position => position, :is_expanded => is_expanded,
|
1208
|
+
:constraint => subject
|
1209
|
+
)
|
1210
|
+
when 'RingConstraintShape'
|
1211
|
+
# REVISIT: The offset might depend on the ring constraint type. This is right for basic round ones.
|
1212
|
+
position = convert_position(bounds, Gravity::NW, 31, 31)
|
1213
|
+
shape = @constellation.RingConstraintShape(
|
1214
|
+
:new, :diagram => diagram, :position => position, :is_expanded => is_expanded,
|
1215
|
+
:constraint => subject
|
1216
|
+
)
|
1217
|
+
shape.fact_type = subject.role.fact_type
|
1218
|
+
when 'ModelNoteShape'
|
1219
|
+
# REVISIT: Add model notes
|
1220
|
+
when 'ObjectTypeShape'
|
1221
|
+
shape = @constellation.ObjectTypeShape(
|
1222
|
+
:new, :diagram => diagram, :position => position, :is_expanded => is_expanded,
|
1223
|
+
:concept => subject,
|
1224
|
+
:has_expanded_reference_mode => false # REVISIT
|
1225
|
+
)
|
1226
|
+
else
|
1227
|
+
raise "Unknown shape #{x_shape.name}"
|
1228
|
+
end
|
862
1229
|
end
|
863
|
-
|
864
|
-
}
|
865
|
-
f = Fact.new(population, ft, *fact_roles)
|
866
|
-
pi_fact_count += 1
|
867
|
-
}
|
868
|
-
entity_count += 1
|
869
|
-
}
|
870
|
-
# puts "Created #{pi_fact_count} facts to identify #{entity_count} entities"
|
871
|
-
|
872
|
-
# Use the "ref" attribute of FactTypeRoleInstance:
|
873
|
-
x_fact_roles = @x_model.xpath("orm:Facts/orm:Fact/orm:Instances/orm:FactTypeInstance/orm:RoleInstances/orm:FactTypeRoleInstance")
|
874
|
-
|
875
|
-
last_id = nil
|
876
|
-
last_fact_type = nil
|
877
|
-
fact_roles = []
|
878
|
-
x_fact_roles.each{|v|
|
879
|
-
fact_type_id = v.parent.parent.parent.parent['id']
|
880
|
-
id = v.parent.parent['id']
|
881
|
-
fact_type = @by_id[fact_type_id]
|
882
|
-
throw "Fact type #{fact_type_id} not found" unless fact_type
|
883
|
-
|
884
|
-
if (last_id && id != last_id)
|
885
|
-
# Process completed fact now we have all roles:
|
886
|
-
last_fact = Fact.new(population, last_fact_type, *fact_roles)
|
887
|
-
fact_roles = []
|
888
|
-
else
|
889
|
-
last_fact_type = fact_type
|
1230
|
+
end
|
890
1231
|
end
|
1232
|
+
end
|
1233
|
+
end
|
891
1234
|
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
1235
|
+
def read_fact_type_shape diagram, x_shape, is_expanded, bounds, fact_type
|
1236
|
+
display_role_names_setting = v = x_shape["DisplayRoleNames"] and
|
1237
|
+
case v
|
1238
|
+
when 'Off'; 'false'
|
1239
|
+
when 'On'; 'true'
|
1240
|
+
else nil
|
1241
|
+
end
|
1242
|
+
rotation_setting = v = x_shape['DisplayOrientation'] and
|
1243
|
+
case v
|
1244
|
+
when 'VerticalRotatedLeft'; 'left'
|
1245
|
+
when 'VerticalRotatedRight'; 'right'
|
1246
|
+
else nil
|
1247
|
+
end
|
1248
|
+
# Position of a fact type is the top-left of the first role box
|
1249
|
+
offs_x = 0
|
1250
|
+
offs_y = 0
|
1251
|
+
if fact_type.entity_type # If objectified, move right 12, down 24
|
1252
|
+
offs_x += 12
|
1253
|
+
offs_y += 24
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
# count internal UC's, add 27 Y units for each:
|
1257
|
+
iucs = fact_type.internal_presence_constraints.select{|uc| uc.max_frequency == 1 }
|
1258
|
+
offs_y += iucs.size*27
|
1259
|
+
position = convert_position(bounds, Gravity::NW, offs_x, offs_y)
|
1260
|
+
debug :orm, "REVISIT: Can't place rotated fact type correctly on diagram yet" if rotation_setting
|
1261
|
+
|
1262
|
+
debug :orm, "fact type at #{position.x},#{position.y} has display_role_names_setting=#{display_role_names_setting.inspect}, rotation_setting=#{rotation_setting.inspect}, #{iucs.size} IUC's"
|
1263
|
+
shape = @constellation.FactTypeShape(
|
1264
|
+
:new,
|
1265
|
+
:diagram => diagram,
|
1266
|
+
:position => position,
|
1267
|
+
:is_expanded => is_expanded,
|
1268
|
+
:display_role_names_setting => display_role_names_setting,
|
1269
|
+
:rotation_setting => rotation_setting,
|
1270
|
+
:fact_type => fact_type
|
1271
|
+
)
|
1272
|
+
# Create RoleDisplay objects if necessary
|
1273
|
+
x_role_display = x_shape.xpath("ormDiagram:RoleDisplayOrder/ormDiagram:Role")
|
1274
|
+
# print "Fact type '#{fact_type.preferred_reading.expand}' (#{fact_type.all_role.map{|r|r.concept.name}*' '})"
|
1275
|
+
if x_role_display.size > 0
|
1276
|
+
debug :orm, " has roleDisplay (#{x_role_display.map{|rd| @by_id[rd['ref']].concept.name}*','})'"
|
1277
|
+
x_role_display.each_with_index do |rd, ordinal|
|
1278
|
+
role_display = @constellation.RoleDisplay(shape, ordinal, :role => @by_id[rd['ref']])
|
1279
|
+
end
|
1280
|
+
else
|
1281
|
+
# Decide whether to create all RoleDisplay objects for this fact type, which is in role order
|
1282
|
+
# Omitting this here might lead to incomplete RoleDisplay sequences,
|
1283
|
+
# because each RoleNameShape or ValueConstraintShape creates just one.
|
1284
|
+
debug :orm, " has no roleDisplay"
|
1285
|
+
end
|
904
1286
|
|
905
|
-
|
906
|
-
|
907
|
-
|
1287
|
+
relative_shapes = x_shape.xpath('ormDiagram:RelativeShapes/*')
|
1288
|
+
relative_shapes.each do |xr_shape|
|
1289
|
+
position = convert_position(xr_shape['AbsoluteBounds'])
|
1290
|
+
case xr_shape.name
|
1291
|
+
when 'ObjectifiedFactTypeNameShape'
|
1292
|
+
@constellation.ObjectifiedFactTypeNameShape(shape, :diagram => diagram, :position => position, :is_expanded => false)
|
1293
|
+
when 'ReadingShape'
|
1294
|
+
@constellation.ReadingShape(:new, :diagram => diagram, :position => position, :is_expanded => false, :reading => fact_type.preferred_reading)
|
1295
|
+
when 'RoleNameShape'
|
1296
|
+
role = @by_id[xr_shape.xpath("ormDiagram:Subject")[0]['ref']]
|
1297
|
+
role_display = role_display_for_role(shape, x_role_display, role)
|
1298
|
+
debug :orm, "Fact type '#{fact_type.preferred_reading.expand}' has #{xr_shape.name}"
|
1299
|
+
@constellation.RoleNameShape(
|
1300
|
+
:new, :diagram => diagram, :position => position, :is_expanded => false,
|
1301
|
+
:role_display => role_display
|
1302
|
+
)
|
1303
|
+
when 'ValueConstraintShape'
|
1304
|
+
vc_subject_id = xr_shape.xpath("ormDiagram:Subject")[0]['ref']
|
1305
|
+
constraint = @by_id[vc_subject_id]
|
1306
|
+
debug :orm, "Fact type '#{fact_type.preferred_reading.expand}' has #{xr_shape.name} for #{constraint.inspect}"
|
1307
|
+
|
1308
|
+
role_display = role_display_for_role(shape, x_role_display, constraint.role)
|
1309
|
+
debug :orm, "ValueConstraintShape is on #{role_ordinal}'th role (by #{x_role_display.size > 0 ? 'role_display' : 'fact roles'})"
|
1310
|
+
@constellation.ValueConstraintShape(
|
1311
|
+
:new, :diagram => diagram, :position => position, :is_expanded => false,
|
1312
|
+
:constraint => constraint,
|
1313
|
+
:object_type_shape => nil, # This constraint is relative to a Fact Type, so must be on a role
|
1314
|
+
:role_display => role_display
|
1315
|
+
)
|
1316
|
+
else raise "Unknown relative shape #{xr_shape.name}"
|
1317
|
+
end
|
1318
|
+
end
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
# Find or create the RoleDisplay for this role in this fact_type_shape, given (possibly empty) x_role_display nodes:
|
1322
|
+
def role_display_for_role(fact_type_shape, x_role_display, role)
|
1323
|
+
if x_role_display.size == 0
|
1324
|
+
role_ordinal = fact_type_shape.fact_type.all_role.to_a.index(role)
|
1325
|
+
else
|
1326
|
+
role_ordinal = x_role_display.map{|rd| @by_id[rd['ref']]}.index(role)
|
908
1327
|
end
|
1328
|
+
role_display = @constellation.RoleDisplay(fact_type_shape, role_ordinal, :role => role)
|
1329
|
+
end
|
909
1330
|
|
1331
|
+
DIAGRAM_SCALE = 384
|
1332
|
+
def convert_position(bounds, gravity = Gravity::NW, xoffs = 0, yoffs = 0)
|
1333
|
+
return nil unless bounds
|
1334
|
+
bf = bounds.split(/, /).map{|b|b.to_f}
|
1335
|
+
sizefrax = [
|
1336
|
+
[0, 0], [1, 0], [2, 0],
|
1337
|
+
[0, 1], [1, 1], [2, 2],
|
1338
|
+
[0, 2], [1, 2], [2, 2],
|
1339
|
+
]
|
1340
|
+
|
1341
|
+
x = (DIAGRAM_SCALE * bf[0]+bf[2]*sizefrax[gravity][0]/2).round + xoffs
|
1342
|
+
y = (DIAGRAM_SCALE * bf[1]+bf[3]*sizefrax[gravity][1]/2).round + yoffs
|
1343
|
+
@constellation.Position(x, y)
|
910
1344
|
end
|
911
1345
|
|
912
1346
|
# Detect numeric data and denote it as a string:
|