activefacts-orm 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/activefacts-orm.gemspec +28 -0
- data/lib/activefacts/input/orm.rb +1636 -0
- data/lib/activefacts/orm/version.rb +5 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cd75d822b0249191ccb0a194683892d77c927002
|
4
|
+
data.tar.gz: 083c5a576d145726ca942393ad5827b4d5c57c8f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b0591d5466cbbdc9e212c827210c9a75bb5d90cd0c7b786977cac789eec5cbdc34379473bc9400556d6ef97a426821d270f365b18e6ef02b6057b4d442966d01
|
7
|
+
data.tar.gz: 045f05c2a13de02a8ec099bcf3d75c8de04cdee00a62bacdca0ffc09093a8f7690da3efc9ff9bab1eb1c230020d5e8683c49e90e46325c125a2357c39fc71120
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--format documentation
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
if ENV['PWD'] =~ %r{\A/Users/cjh/work/activefacts}
|
6
|
+
gem 'activefacts-api', path: '/Users/cjh/work/activefacts/api'
|
7
|
+
gem 'activefacts-metamodel', path: '/Users/cjh/work/activefacts/metamodel'
|
8
|
+
# gem 'activefacts-metamodel', git: 'git://github.com/cjheath/activefacts-metamodel.git'
|
9
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Clifford Heath
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Activefacts::Orm
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/activefacts/orm`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'activefacts-orm'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install activefacts-orm
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/activefacts-orm.
|
36
|
+
|
37
|
+
|
38
|
+
## License
|
39
|
+
|
40
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'activefacts/orm/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "activefacts-orm"
|
8
|
+
spec.version = ActiveFacts::ORM::VERSION
|
9
|
+
spec.authors = ["Clifford Heath"]
|
10
|
+
spec.email = ["clifford.heath@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{ORM format importer for the ActiveFacts fact modeling suite.}
|
13
|
+
spec.description = %q{ORM format importer for the ActiveFacts fact modeling suite.
|
14
|
+
Install the Natural ORM Architect from http://ormfoundation.org to produce input files.
|
15
|
+
}
|
16
|
+
spec.homepage = "http://github.com/cjheath/activefacts-orm"
|
17
|
+
spec.license = "MIT"
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.10.a"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
|
26
|
+
spec.add_runtime_dependency(%q<activefacts-metamodel>, [">= 1.7.0", "~> 1.7"])
|
27
|
+
spec.add_runtime_dependency(%q<nokogiri>)
|
28
|
+
end
|
@@ -0,0 +1,1636 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Vocabulary Input.
|
3
|
+
# Read a NORMA file into an ActiveFacts vocabulary
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
# This code uses variables prefixed with x_ when they refer to Rexml nodes.
|
8
|
+
# Every node having an id="..." is indexed in @x_by_id[] hash before processing.
|
9
|
+
# As we build ActiveFacts objects to match, we index those in @by_id[].
|
10
|
+
# Both these hashes may be looked up by any of the ref="..." values in the file.
|
11
|
+
#
|
12
|
+
require 'nokogiri'
|
13
|
+
require 'activefacts/metamodel'
|
14
|
+
|
15
|
+
module Nokogiri
|
16
|
+
module XML
|
17
|
+
class Node
|
18
|
+
def elements
|
19
|
+
children.select{|n|
|
20
|
+
Nokogiri::XML::Element === n
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ActiveFacts
|
28
|
+
module Input
|
29
|
+
# Compile a NORMA (.orm) file to an ActiveFacts vocabulary.
|
30
|
+
# Invoke as
|
31
|
+
# afgen --<generator> <file>.orm
|
32
|
+
# This parser uses Rexml so it's very slow.
|
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" => "Guid",
|
67
|
+
"ObjectIdOther" => "Guid"
|
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
|
+
|
73
|
+
private
|
74
|
+
def self.readfile(filename, *options)
|
75
|
+
if File.basename(filename, '.orm') == "-"
|
76
|
+
self.read(STDIN, "<standard input>", options)
|
77
|
+
else
|
78
|
+
File.open(filename) {|file|
|
79
|
+
self.read(file, filename, *options)
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.read(file, filename = "stdin", *options)
|
85
|
+
ORM.new(file, filename, *options).read
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(file, filename = "stdin", *options)
|
89
|
+
@file = file
|
90
|
+
@filename = filename
|
91
|
+
@options = options
|
92
|
+
end
|
93
|
+
|
94
|
+
public
|
95
|
+
def read #:nodoc:
|
96
|
+
begin
|
97
|
+
@document = Nokogiri::XML(@file)
|
98
|
+
rescue => e
|
99
|
+
puts "Failed to parse XML in #{@filename}: #{e.inspect}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Find the Vocabulary and do some setup:
|
103
|
+
root = @document.root
|
104
|
+
#p((root.methods-0.methods).sort.grep(/name/))
|
105
|
+
if root.name == "ORM2" && root.namespace.prefix == "ormRoot"
|
106
|
+
x_models = root.xpath('orm:ORMModel')
|
107
|
+
throw "No vocabulary found" unless x_models.size == 1
|
108
|
+
@x_model = x_models[0]
|
109
|
+
elsif root.name == "ORMModel"
|
110
|
+
p @document.children.map(&:name)
|
111
|
+
@x_model = @document.children[0]
|
112
|
+
else
|
113
|
+
throw "NORMA model not found in #{@filename}"
|
114
|
+
end
|
115
|
+
|
116
|
+
read_vocabulary
|
117
|
+
@vocabulary
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def read_vocabulary
|
123
|
+
@constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
|
124
|
+
vocabulary_name = @x_model['Name']
|
125
|
+
@vocabulary = @constellation.Vocabulary(vocabulary_name)
|
126
|
+
|
127
|
+
# Find all elements having an "id" attribute and index them
|
128
|
+
x_identified = @x_model.xpath(".//*[@id]")
|
129
|
+
@x_by_id = x_identified.inject({}){|h, x|
|
130
|
+
id = x['id']
|
131
|
+
h[id] = x
|
132
|
+
h
|
133
|
+
}
|
134
|
+
|
135
|
+
# Everything we build will be indexed here:
|
136
|
+
@by_id = {}
|
137
|
+
|
138
|
+
list_subtypes
|
139
|
+
read_entity_types
|
140
|
+
read_value_types
|
141
|
+
read_fact_types
|
142
|
+
read_nested_types
|
143
|
+
read_subtypes
|
144
|
+
read_roles
|
145
|
+
complete_nested_types
|
146
|
+
read_constraints
|
147
|
+
read_instances if @options.include?("instances")
|
148
|
+
read_diagrams if @options.include?("diagrams")
|
149
|
+
end
|
150
|
+
|
151
|
+
def id_of(x)
|
152
|
+
x['id'][1..-1]
|
153
|
+
end
|
154
|
+
|
155
|
+
def read_entity_types
|
156
|
+
# get and process all the entity types:
|
157
|
+
entity_types = []
|
158
|
+
x_entity_types = @x_model.xpath("orm:Objects/orm:EntityType")
|
159
|
+
x_entity_types.each do |x|
|
160
|
+
id = x['id']
|
161
|
+
name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
|
162
|
+
name = nil if name.size == 0
|
163
|
+
entity_type =
|
164
|
+
@by_id[id] =
|
165
|
+
trace :orm, "Asserting new EntityType #{name.inspect}" do
|
166
|
+
@vocabulary.valid_entity_type_name(name) ||
|
167
|
+
@constellation.EntityType(@vocabulary, name, :concept => id_of(x))
|
168
|
+
end
|
169
|
+
entity_types << entity_type
|
170
|
+
independent = x['IsIndependent']
|
171
|
+
entity_type.is_independent = true if independent && independent == 'true'
|
172
|
+
personal = x['IsPersonal']
|
173
|
+
entity_type.pronoun = 'personal' if personal && personal == 'true'
|
174
|
+
# x_pref = x.xpath("orm:PreferredIdentifier")[0]
|
175
|
+
# if x_pref
|
176
|
+
# pi_id = x_pref['ref']
|
177
|
+
# @pref_id_for[pi_id] = x
|
178
|
+
# end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def read_value_types
|
183
|
+
# Now the value types:
|
184
|
+
value_types = []
|
185
|
+
x_value_types = @x_model.xpath("orm:Objects/orm:ValueType")
|
186
|
+
@value_type_id_read = {}
|
187
|
+
x_value_types.each{|x|
|
188
|
+
next if x['IsImplicitBooleanValue']
|
189
|
+
value_types << read_value_type(x)
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
def read_value_type x
|
194
|
+
id = x['id']
|
195
|
+
return if @value_type_id_read[id] # Don't read the same value type twice
|
196
|
+
@value_type_id_read[id] = true
|
197
|
+
|
198
|
+
name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
|
199
|
+
name = nil if name.size == 0
|
200
|
+
|
201
|
+
cdt = x.xpath('orm:ConceptualDataType')[0]
|
202
|
+
scale = cdt['Scale']
|
203
|
+
scale = scale != "" && scale.to_i
|
204
|
+
length = cdt['Length']
|
205
|
+
length = length != "" && length.to_i
|
206
|
+
length = nil if length <= 0
|
207
|
+
base_type = @x_by_id[cdt['ref']]
|
208
|
+
type_name = "#{base_type.name}"
|
209
|
+
type_name.sub!(/^orm:/,'')
|
210
|
+
|
211
|
+
type_name.sub!(/DataType\Z/,'')
|
212
|
+
if t = DataTypeMapping[type_name]
|
213
|
+
existing = @constellation.ObjectType[[@vocabulary.identifying_role_values, t]]
|
214
|
+
if !existing || existing.is_a?(ActiveFacts::Metamodel::ValueType)
|
215
|
+
# There's no type conflict, so use the mapped name.
|
216
|
+
type_name = t
|
217
|
+
end
|
218
|
+
end
|
219
|
+
trace :orm, "Using #{type_name.inspect} as supertype for new #{name}"
|
220
|
+
if !length and type_name =~ /\(([0-9]+)\)/
|
221
|
+
length = $1.to_i
|
222
|
+
end
|
223
|
+
type_name = type_name.sub(/\(([0-9]*)\)/,'')
|
224
|
+
|
225
|
+
subtype_roles = x.xpath("orm:PlayedRoles/orm:SubtypeMetaRole")
|
226
|
+
value_super_type = nil
|
227
|
+
if !subtype_roles.empty?
|
228
|
+
subtype_role_id = subtype_roles[0]['ref']
|
229
|
+
subtype_role = @x_by_id[subtype_role_id]
|
230
|
+
subtyping_fact_roles = subtype_role.parent
|
231
|
+
supertype_id = subtyping_fact_roles.xpath("orm:SupertypeMetaRole/orm:RolePlayer")[0]['ref']
|
232
|
+
x_supertype = @x_by_id[supertype_id]
|
233
|
+
read_value_type x_supertype unless @value_type_id_read[supertype_id]
|
234
|
+
supertype = @by_id[supertype_id]
|
235
|
+
supertype_name = x_supertype['Name']
|
236
|
+
raise "Supertype of #{name} is post-defined but recursiving processing failed" unless supertype
|
237
|
+
raise "Supertype #{supertype_name} of #{name} is not a value type" unless supertype.kind_of? ActiveFacts::Metamodel::ValueType
|
238
|
+
trace :orm, "Asserting new ValueType #{supertype_name.inspect} for supertype" do
|
239
|
+
value_super_type =
|
240
|
+
@vocabulary.valid_value_type_name(supertype_name) ||
|
241
|
+
@constellation.ValueType(@vocabulary, supertype_name, :concept => id_of(x_supertype))
|
242
|
+
end
|
243
|
+
else
|
244
|
+
# REVISIT: Need to handle standard types better here:
|
245
|
+
value_super_type =
|
246
|
+
if type_name != name
|
247
|
+
trace :orm, "Asserting new ValueType #{type_name.inspect} for supertype" do
|
248
|
+
@vocabulary.valid_value_type_name(type_name) ||
|
249
|
+
@constellation.ValueType(@vocabulary.identifying_role_values, type_name, :concept => :new)
|
250
|
+
end
|
251
|
+
else
|
252
|
+
nil
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
vt =
|
257
|
+
trace :orm, "Asserting new ValueType #{name.inspect}" do
|
258
|
+
@by_id[id] =
|
259
|
+
@vocabulary.valid_value_type_name(name) ||
|
260
|
+
@constellation.ValueType(@vocabulary.identifying_role_values, name, :concept => id_of(x))
|
261
|
+
end
|
262
|
+
vt.supertype = value_super_type
|
263
|
+
vt.length = length if length
|
264
|
+
vt.scale = scale if scale && scale != 0
|
265
|
+
independent = x['IsIndependent']
|
266
|
+
vt.is_independent = true if independent && independent == 'true'
|
267
|
+
personal = x['IsPersonal']
|
268
|
+
vt.pronoun = 'personal' if personal && personal == 'true'
|
269
|
+
|
270
|
+
x_vr = x.xpath("orm:ValueRestriction/orm:ValueConstraint")
|
271
|
+
x_vr.each{|vr|
|
272
|
+
x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange")
|
273
|
+
next if x_ranges.size == 0
|
274
|
+
vt.value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(id_of(vr))
|
275
|
+
x_ranges.each{|x_range|
|
276
|
+
v_range = value_range(x_range)
|
277
|
+
ar = @constellation.AllowedRange(vt.value_constraint, v_range)
|
278
|
+
}
|
279
|
+
}
|
280
|
+
vt
|
281
|
+
end
|
282
|
+
|
283
|
+
def assert_value(val, string)
|
284
|
+
if val
|
285
|
+
@constellation.Value(val.to_s, string, nil)
|
286
|
+
else
|
287
|
+
nil
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def value_range(x_range)
|
292
|
+
min = x_range['MinValue']
|
293
|
+
max = x_range['MaxValue']
|
294
|
+
|
295
|
+
strings = is_literal_string(min) || is_literal_string(max)
|
296
|
+
# ValueRange takes a minimum and/or a maximum Bound, each takes value and whether inclusive
|
297
|
+
@constellation.ValueRange(
|
298
|
+
min.length > 0 ? @constellation.Bound(assert_value(min, strings), true) : nil,
|
299
|
+
max.length > 0 ? @constellation.Bound(assert_value(max, strings), true) : nil)
|
300
|
+
end
|
301
|
+
|
302
|
+
def read_fact_types
|
303
|
+
# Handle the fact types:
|
304
|
+
facts = []
|
305
|
+
@x_facts = @x_model.xpath("orm:Facts/orm:Fact")
|
306
|
+
trace :orm, "Reading fact types" do
|
307
|
+
@x_facts.each{|x|
|
308
|
+
id = x['id']
|
309
|
+
name = x['Name'] || x['_Name']
|
310
|
+
name = "<unnamed>" if !name
|
311
|
+
name = "" if !name || name.size == 0
|
312
|
+
# Note that the new metamodel doesn't have a name for a facttype unless it's objectified
|
313
|
+
|
314
|
+
trace :orm, "FactType #{name || id}"
|
315
|
+
facts << @by_id[id] = fact_type = @constellation.FactType(id_of(x))
|
316
|
+
}
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def list_subtypes
|
321
|
+
@x_subtypes = @x_model.xpath("orm:Facts/orm:SubtypeFact")
|
322
|
+
if @document.namespaces['xmlns:oialtocdb']
|
323
|
+
oialtocdb = @document.xpath("ormRoot:ORM2/oialtocdb:MappingCustomization")
|
324
|
+
@x_mappings = oialtocdb.xpath(".//oialtocdb:AssimilationMappings/oialtocdb:AssimilationMapping/oialtocdb:FactType")
|
325
|
+
else
|
326
|
+
@x_mappings = []
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def read_subtypes
|
331
|
+
# Handle the subtype fact types:
|
332
|
+
facts = []
|
333
|
+
|
334
|
+
trace :orm, "Reading sub-types" do
|
335
|
+
@x_subtypes.each{|x|
|
336
|
+
id = x['id']
|
337
|
+
name = (x['Name'] || x['_Name'] || '').gsub(/\s+/,' ').gsub(/-/,'_').strip
|
338
|
+
name = nil if name.size == 0
|
339
|
+
trace :orm, "FactType #{name || id}"
|
340
|
+
|
341
|
+
x_subtype_role = x.xpath('orm:FactRoles/orm:SubtypeMetaRole')[0]
|
342
|
+
subtype_role_id = x_subtype_role['id']
|
343
|
+
subtype_id = x_subtype_role.xpath('orm:RolePlayer')[0]['ref']
|
344
|
+
subtype = @by_id[subtype_id]
|
345
|
+
# REVISIT: Provide a way in the metamodel of handling Partition, (and mapping choices that vary for each supertype?)
|
346
|
+
|
347
|
+
x_supertype_role = x.xpath('orm:FactRoles/orm:SupertypeMetaRole')[0]
|
348
|
+
supertype_role_id = x_supertype_role['id']
|
349
|
+
supertype_id = x_supertype_role.xpath('orm:RolePlayer')[0]['ref']
|
350
|
+
supertype = @by_id[supertype_id]
|
351
|
+
|
352
|
+
throw "For Subtype fact #{name}, the supertype #{supertype_id} was not found" if !supertype
|
353
|
+
throw "For Subtype fact #{name}, the subtype #{subtype_id} was not found" if !subtype
|
354
|
+
trace :orm, "#{subtype.name} is a subtype of #{supertype.name}"
|
355
|
+
|
356
|
+
# We already handled ValueType subtyping:
|
357
|
+
next if subtype.kind_of? ActiveFacts::Metamodel::ValueType or
|
358
|
+
supertype.kind_of? ActiveFacts::Metamodel::ValueType
|
359
|
+
|
360
|
+
inheritance_fact = @constellation.TypeInheritance(subtype, supertype, :concept => id_of(x))
|
361
|
+
if x["IsPrimary"] == "true" or # Old way
|
362
|
+
x["PreferredIdentificationPath"] == "true" # Newer
|
363
|
+
trace :orm, "#{supertype.name} is primary supertype of #{subtype.name}"
|
364
|
+
inheritance_fact.provides_identification = true
|
365
|
+
end
|
366
|
+
mapping = @x_mappings.detect{ |m| m['ref'] == id }
|
367
|
+
mapping_choice = mapping ? mapping.parent['AbsorptionChoice'] : 'Absorbed'
|
368
|
+
inheritance_fact.assimilation = mapping_choice.downcase.sub(/partition/, 'partitioned') if mapping_choice != 'Absorbed'
|
369
|
+
facts << @by_id[id] = inheritance_fact
|
370
|
+
|
371
|
+
# Create the new Roles so we can find constraints on them:
|
372
|
+
subtype_role = @by_id[subtype_role_id] = @constellation.Role(inheritance_fact, 0, :object_type => subtype, :concept => id_of(x_subtype_role))
|
373
|
+
supertype_role = @by_id[supertype_role_id] = @constellation.Role(inheritance_fact, 1, :object_type => supertype, :concept => id_of(x_supertype_role))
|
374
|
+
|
375
|
+
# Create readings, so constraints can be verbalised for example:
|
376
|
+
rs = @constellation.RoleSequence(:new)
|
377
|
+
@constellation.RoleRef(rs, 0, :role => subtype_role)
|
378
|
+
@constellation.RoleRef(rs, 1, :role => supertype_role)
|
379
|
+
@constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}", :is_negative => false)
|
380
|
+
@constellation.Reading(inheritance_fact, 1, :role_sequence => rs, :text => "{0} is a subtype of {1}", :is_negative => false)
|
381
|
+
|
382
|
+
rs2 = @constellation.RoleSequence(:new)
|
383
|
+
@constellation.RoleRef(rs2, 0, :role => supertype_role)
|
384
|
+
@constellation.RoleRef(rs2, 1, :role => subtype_role)
|
385
|
+
n = 'aeioh'.include?(subtype_role.object_type.name.downcase[0]) ? 1 : 0
|
386
|
+
@constellation.Reading(inheritance_fact, 2+n, :role_sequence => rs2, :text => "{0} is a {1}", :is_negative => false)
|
387
|
+
@constellation.Reading(inheritance_fact, 3-n, :role_sequence => rs2, :text => "{0} is an {1}", :is_negative => false)
|
388
|
+
}
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def read_nested_types
|
393
|
+
# Process NestedTypes, but ignore ones having a NestedPredicate with IsImplied="true"
|
394
|
+
# We'll ignore the fact roles (and constraints) that implied objectifications have.
|
395
|
+
# This happens for all ternaries and higher order facts
|
396
|
+
@nested_types = []
|
397
|
+
x_nested_types = @x_model.xpath("orm:Objects/orm:ObjectifiedType")
|
398
|
+
trace :orm, "Reading objectified types" do
|
399
|
+
x_nested_types.each{|x|
|
400
|
+
id = x['id']
|
401
|
+
name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
|
402
|
+
name = nil if name.size == 0
|
403
|
+
|
404
|
+
x_fact_type = x.xpath('orm:NestedPredicate')[0]
|
405
|
+
is_implied = x_fact_type['IsImplied'] == "true"
|
406
|
+
|
407
|
+
fact_id = x_fact_type['ref']
|
408
|
+
fact_type = @by_id[fact_id]
|
409
|
+
next unless fact_type # "Nested fact #{fact_id} not found; objectification of a derived fact type?"
|
410
|
+
|
411
|
+
trace :orm, "NestedType #{name} is #{id}, nests #{fact_type.concept.guid}"
|
412
|
+
@nested_types <<
|
413
|
+
@by_id[id] =
|
414
|
+
nested_type = @vocabulary.valid_entity_type_name(name) ||
|
415
|
+
@constellation.EntityType(@vocabulary, name, :concept => id_of(x))
|
416
|
+
independent = x['IsIndependent']
|
417
|
+
nested_type.is_independent = true if independent && independent == 'true' && !is_implied
|
418
|
+
nested_type.concept.implication_rule = 'objectification' if is_implied
|
419
|
+
nested_type.fact_type = fact_type
|
420
|
+
}
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def complete_nested_types
|
425
|
+
@nested_types.each do |nested_type|
|
426
|
+
# Create the phantom roles here. These will be used later when we create objectification steps,
|
427
|
+
# but for now there's nothing we import from NORMA which requires objectification steps.
|
428
|
+
# Consequently there's no need to index them against NORMA's phantom roles.
|
429
|
+
nested_type.create_implicit_fact_types
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def read_roles
|
434
|
+
trace :orm, "Reading roles and readings" do
|
435
|
+
@x_facts.each{|x|
|
436
|
+
id = x['id']
|
437
|
+
fact_type = @by_id[id]
|
438
|
+
fact_name = x['Name'] || x['_Name'] || ''
|
439
|
+
#fact_name.gsub!(/\s/,'')
|
440
|
+
fact_name = nil if fact_name == ''
|
441
|
+
|
442
|
+
x_fact_roles = x.xpath('orm:FactRoles/*')
|
443
|
+
|
444
|
+
# Deal with FactRoles (Roles):
|
445
|
+
trace :orm, "Reading fact roles" do
|
446
|
+
x_fact_roles.each{|x|
|
447
|
+
name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
|
448
|
+
name = nil if name.size == 0
|
449
|
+
|
450
|
+
# _IsMandatory = x['_IsMandatory']
|
451
|
+
# _Multiplicity = x['_Multiplicity]
|
452
|
+
id = x['id']
|
453
|
+
rp = x.xpath('orm:RolePlayer')[0]
|
454
|
+
raise "Invalid ORM file; fact has missing player (RolePlayer id=#{id})" unless rp
|
455
|
+
ref = rp['ref']
|
456
|
+
|
457
|
+
# Find the object_type that plays the role:
|
458
|
+
object_type = @by_id[ref]
|
459
|
+
|
460
|
+
# Skip implicit roles added by NORMA to make unaries into binaries.
|
461
|
+
# This would make constraints over the deleted roles impossible,
|
462
|
+
# so as a SPECIAL CASE we index the unary role by the id of the
|
463
|
+
# implicit role. That means care is needed when handling unary FTs.
|
464
|
+
if (ox = @x_by_id[ref]) && ox['IsImplicitBooleanValue']
|
465
|
+
x_other_role = x.parent.xpath('orm:Role').reject{|x_role|
|
466
|
+
x_role == x
|
467
|
+
}[0]
|
468
|
+
other_role_id = x_other_role["id"]
|
469
|
+
other_role = @by_id[other_role_id]
|
470
|
+
trace :orm, "Indexing unary FT role #{other_role_id} by implicit boolean role #{id}"
|
471
|
+
@by_id[id] = other_role
|
472
|
+
|
473
|
+
# The role name of the ignored role is the one that applies:
|
474
|
+
role_name = x['Name']
|
475
|
+
other_role.role_name = role_name if role_name && role_name != ''
|
476
|
+
|
477
|
+
@by_id.delete(ref) # and de-index it from our list
|
478
|
+
next
|
479
|
+
end
|
480
|
+
if !object_type
|
481
|
+
throw "RolePlayer for '#{name}' #{ref} in fact type #{x.parent.parent['_Name']} was not found"
|
482
|
+
end
|
483
|
+
|
484
|
+
trace :orm, "#{@vocabulary.name}, RoleName=#{x['Name'].inspect} played by object_type=#{object_type.name}"
|
485
|
+
throw "Role is played by #{object_type.class} not ObjectType" if !(@constellation.vocabulary.object_type(:ObjectType) === object_type)
|
486
|
+
|
487
|
+
trace :orm, "Creating role #{name} nr#{fact_type.all_role.size} of #{fact_type.concept.guid} played by #{object_type.name}"
|
488
|
+
|
489
|
+
role = @by_id[id] = @constellation.Role(fact_type, fact_type.all_role.size, :object_type => object_type, :concept => id_of(x))
|
490
|
+
role.role_name = name if name && name != object_type.name
|
491
|
+
trace :orm, "Fact #{fact_name} (id #{fact_type.concept.guid}) role #{x['Name']} is played by #{object_type.name}, role is #{role.concept.guid}"
|
492
|
+
|
493
|
+
x_vr = x.xpath("orm:ValueRestriction/orm:RoleValueConstraint")
|
494
|
+
x_vr.each{|vr|
|
495
|
+
x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange")
|
496
|
+
next if x_ranges.size == 0
|
497
|
+
role.role_value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(id_of(vr))
|
498
|
+
x_ranges.each{|x_range|
|
499
|
+
v_range = value_range(x_range)
|
500
|
+
ar = @constellation.AllowedRange(role.role_value_constraint, v_range)
|
501
|
+
}
|
502
|
+
}
|
503
|
+
|
504
|
+
trace :orm, "Adding Role #{role.role_name || role.object_type.name} to #{fact_type.describe}"
|
505
|
+
#fact_type.add_role(role)
|
506
|
+
trace :orm, "Role #{role} is #{id}"
|
507
|
+
}
|
508
|
+
end
|
509
|
+
|
510
|
+
# Deal with Readings:
|
511
|
+
trace :orm, "Reading fact readings" do
|
512
|
+
x_reading_orders = x.xpath('orm:ReadingOrders/*')
|
513
|
+
x_reading_orders.each{|x|
|
514
|
+
x_role_sequence = x.xpath('orm:RoleSequence/*')
|
515
|
+
x_readings = x.xpath('orm:Readings/orm:Reading/orm:Data')
|
516
|
+
|
517
|
+
# Build an array of the Roles needed:
|
518
|
+
role_array = x_role_sequence.map{|x| @by_id[x['ref']] }
|
519
|
+
|
520
|
+
trace :orm, "Reading #{x_readings.map(&:text).inspect}"
|
521
|
+
role_sequence = get_role_sequence(role_array)
|
522
|
+
|
523
|
+
#role_sequence.all_role_ref.each_with_index{|rr, i|
|
524
|
+
# # REVISIT: rr.leading_adjective = ...; Add adjectives here
|
525
|
+
# }
|
526
|
+
|
527
|
+
x_readings.each_with_index{|x, i|
|
528
|
+
reading = @constellation.Reading(fact_type, fact_type.all_reading.size, :is_negative => false)
|
529
|
+
reading.role_sequence = role_sequence
|
530
|
+
# REVISIT: The downcase here only needs to be the initial letter of each word, but be safe:
|
531
|
+
reading.text = extract_adjectives(x.text, role_sequence)
|
532
|
+
}
|
533
|
+
}
|
534
|
+
end
|
535
|
+
}
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
def extract_adjectives(text, role_sequence)
|
540
|
+
all_role_refs = role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
|
541
|
+
(0...all_role_refs.size).each{|i|
|
542
|
+
role_ref = all_role_refs[i]
|
543
|
+
role = role_ref.role
|
544
|
+
|
545
|
+
word = '\b[A-Za-z_][A-Za-z0-9_]+\b'
|
546
|
+
leading_adjectives_re = "#{word}-+(?: +#{word})*"
|
547
|
+
trailing_adjectives_re = "(?:#{word} +)*-+#{word}"
|
548
|
+
role_with_adjectives_re =
|
549
|
+
%r| ?(#{leading_adjectives_re})? *\{#{i}\} *(#{trailing_adjectives_re})? ?|
|
550
|
+
|
551
|
+
# A hyphenated pre-bound reading looks like this:
|
552
|
+
# <orm:Data>{0} has pre-- bound {1}</orm:Data>
|
553
|
+
|
554
|
+
text.gsub!(role_with_adjectives_re) {
|
555
|
+
# REVISIT: Don't want to strip all spaces here any more:
|
556
|
+
#puts "text=#{text.inspect}, la=#{$1.inspect}, ta=#{$2.inspect}" if $1 || $2
|
557
|
+
la = ($1||'').gsub(/\s+/,' ') # Strip duplicate spaces
|
558
|
+
ta = ($2||'').gsub(/\s+/,' ')
|
559
|
+
# When we have "aaa-bbb" we want "aaa bbb"
|
560
|
+
# When we have "aaa- bbb" we want "aaa bbb"
|
561
|
+
# When we have "aaa-- bbb" we want "aaa-bbb"
|
562
|
+
la = la.sub(/(-)?- ?/,'\1').strip
|
563
|
+
ta = ta.sub(/ ?(-)?-/,'\1').strip
|
564
|
+
#puts "Setting leading adj #{la.inspect} from #{text.inspect} for #{role_ref.role.object_type.name}" if la != ""
|
565
|
+
# REVISIT: Dunno what's up here, but removing the "if" test makes this chuck exceptions:
|
566
|
+
role_ref.leading_adjective = la if la != ""
|
567
|
+
role_ref.trailing_adjective = ta if ta != ""
|
568
|
+
|
569
|
+
#puts "Reading '#{text}' has role #{i} adjectives '#{la}' '#{ta}'" if la != "" || ta != ""
|
570
|
+
|
571
|
+
" {#{i}} "
|
572
|
+
}
|
573
|
+
}
|
574
|
+
text.sub!(/\s\s*/, ' ') # Compress extra spaces
|
575
|
+
text.strip!
|
576
|
+
text.downcase! # Check for reserved words and object type names *after* downcasing
|
577
|
+
elided = ''
|
578
|
+
text.gsub!(/( |[a-z]+(-[a-z]+)+|-?\b[A-Za-z_][A-Za-z0-9_]*\b-?|\{\d\})|./) do |w|
|
579
|
+
case w
|
580
|
+
when /[A-Za-z]/
|
581
|
+
if RESERVED_WORDS.include?(w)
|
582
|
+
$stderr.puts "Masking reserved word '#{w}' in reading '#{text}'"
|
583
|
+
next "_#{w}"
|
584
|
+
elsif @constellation.ObjectType[[[@vocabulary.name], w]]
|
585
|
+
$stderr.puts "Masking object type name '#{w}' in reading '#{text}'"
|
586
|
+
next "_#{w}"
|
587
|
+
elsif all_role_refs.detect{|rr| rr.role.role_name == w}
|
588
|
+
$stderr.puts "Masking role name '#{w}' in reading '#{text}'"
|
589
|
+
next "_#{w}"
|
590
|
+
end
|
591
|
+
next w
|
592
|
+
when /\{\d\}/
|
593
|
+
next w
|
594
|
+
when / /
|
595
|
+
next w
|
596
|
+
else
|
597
|
+
elided << w
|
598
|
+
next ''
|
599
|
+
end
|
600
|
+
end
|
601
|
+
$stderr.puts "Elided illegal characters '#{elided}' from reading #{text.inspect}" unless elided.empty?
|
602
|
+
text
|
603
|
+
end
|
604
|
+
|
605
|
+
def get_role_sequence(role_array)
|
606
|
+
# puts "Getting RoleSequence [#{role_array.map{|r| "#{r.object_type.name} (role #{r.concept.guid})" }*", "}]"
|
607
|
+
|
608
|
+
# Look for an existing RoleSequence
|
609
|
+
# REVISIT: This searches all role sequences. Perhaps we could narrow it down first instead?
|
610
|
+
role_sequence = @constellation.RoleSequence.values.detect{|c|
|
611
|
+
#puts "Checking RoleSequence [#{c.all_role_ref.map{|rr| rr.role.object_type.name}*", "}]"
|
612
|
+
role_array == c.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role }
|
613
|
+
}
|
614
|
+
# puts "Found matching RoleSequence!" if role_sequence
|
615
|
+
return role_sequence if role_sequence
|
616
|
+
|
617
|
+
# Make a new RoleSequence:
|
618
|
+
role_sequence = @constellation.RoleSequence(:new) unless role_sequence
|
619
|
+
role_array.each_with_index do |r, i|
|
620
|
+
role_ref = @constellation.RoleRef(role_sequence, i)
|
621
|
+
role_ref.role = r
|
622
|
+
end
|
623
|
+
|
624
|
+
role_sequence
|
625
|
+
end
|
626
|
+
|
627
|
+
def map_roles(x_roles, why = nil)
|
628
|
+
role_array = x_roles.map do |x|
|
629
|
+
id = x['ref']
|
630
|
+
role = @by_id[id]
|
631
|
+
if (why && !role)
|
632
|
+
# We didn't make Implied objects, so some constraints are unconnectable
|
633
|
+
x_role = @x_by_id[id]
|
634
|
+
x_player = x_role.xpath('orm:RolePlayer')[0]
|
635
|
+
x_object = @x_by_id[x_player['ref']]
|
636
|
+
x_nests = nil
|
637
|
+
if (x_object.name.to_s == 'ObjectifiedType')
|
638
|
+
x_nests = x_object.xpath('orm:NestedPredicate')[0]
|
639
|
+
implied = x_nests['IsImplied']
|
640
|
+
# x_fact is the fact of which the role player is an objectification, not the fact this role belongs to
|
641
|
+
x_fact = @x_by_id[x_nests['ref']]
|
642
|
+
end
|
643
|
+
|
644
|
+
# This might have been a role of an ImpliedFact, which makes it safe to ignore.
|
645
|
+
next if 'ImpliedFact' == x_role.parent.parent.name
|
646
|
+
|
647
|
+
# Talk about why this wasn't found - this shouldn't happen.
|
648
|
+
if (!x_nests || !implied)
|
649
|
+
#puts "="*60
|
650
|
+
# We skip creating TypeInheritance implied fact types for ValueType inheritance
|
651
|
+
return nil if x_role.name = 'orm:SubtypeMetaRole' or x_role.name = 'orm:SupertypeMetaRole'
|
652
|
+
raise "Skipping #{why}, #{x_role.name} #{id} not found"
|
653
|
+
|
654
|
+
if (x_nests)
|
655
|
+
puts "Role is on #{implied ? "implied " : ""}objectification #{x_object}"
|
656
|
+
puts "which objectifies #{x_fact}"
|
657
|
+
end
|
658
|
+
puts x_object.to_s
|
659
|
+
end
|
660
|
+
end
|
661
|
+
role
|
662
|
+
end
|
663
|
+
return nil if role_array.include?(nil)
|
664
|
+
|
665
|
+
get_role_sequence(role_array)
|
666
|
+
end
|
667
|
+
|
668
|
+
def read_constraints
|
669
|
+
@constraints_by_rs = {}
|
670
|
+
|
671
|
+
read_mandatory_constraints
|
672
|
+
read_uniqueness_constraints
|
673
|
+
read_exclusion_constraints
|
674
|
+
read_subset_constraints
|
675
|
+
read_ring_constraints
|
676
|
+
read_equality_constraints
|
677
|
+
read_frequency_constraints
|
678
|
+
read_residual_mandatory_constraints
|
679
|
+
end
|
680
|
+
|
681
|
+
def read_mandatory_constraints
|
682
|
+
x_mandatory_constraints = @x_model.xpath("orm:Constraints/orm:MandatoryConstraint")
|
683
|
+
@mandatory_constraints_by_rs = {}
|
684
|
+
@mandatory_constraint_rs_by_id = {}
|
685
|
+
trace :orm, "Scanning mandatory constraints" do
|
686
|
+
x_mandatory_constraints.each{|x|
|
687
|
+
name = x["Name"] || ''
|
688
|
+
name = nil if name.size == 0
|
689
|
+
|
690
|
+
# As of Feb 2008, all NORMA ValueTypes have an implied mandatory constraint.
|
691
|
+
next if x.xpath("orm:ImpliedByObjectType").size > 0
|
692
|
+
|
693
|
+
x_roles = x.xpath("orm:RoleSequence/orm:Role")
|
694
|
+
role_sequence = map_roles(x_roles, "mandatory constraint #{name}")
|
695
|
+
next if !role_sequence
|
696
|
+
|
697
|
+
trace :orm, "New MC #{x['Name']} over #{role_sequence.describe}"
|
698
|
+
@mandatory_constraints_by_rs[role_sequence] = x
|
699
|
+
@mandatory_constraint_rs_by_id[x['id']] = role_sequence
|
700
|
+
}
|
701
|
+
end
|
702
|
+
end
|
703
|
+
|
704
|
+
# Mandatory constraints that didn't get merged with an exclusion constraint or a uniqueness constraint are simple mandatories
|
705
|
+
def read_residual_mandatory_constraints
|
706
|
+
trace :orm, "Processing non-absorbed mandatory constraints" do
|
707
|
+
@mandatory_constraints_by_rs.each { |role_sequence, x|
|
708
|
+
id = x['id']
|
709
|
+
# Create a simply-mandatory PresenceConstraint for each mandatory constraint
|
710
|
+
name = x["Name"] || ''
|
711
|
+
name = nil if name.size == 0
|
712
|
+
#puts "Residual Mandatory #{name}: #{role_sequence.to_s}"
|
713
|
+
|
714
|
+
if (players = role_sequence.all_role_ref.map{|rr| rr.role.object_type}).uniq.size > 1
|
715
|
+
join_over, = *ActiveFacts::Metamodel.plays_over(role_sequence.all_role_ref.map{|rr| rr.role}, :proximate)
|
716
|
+
raise "Mandatory join constraint #{name} has incompatible players #{players.map{|o| o.name}.inspect}" unless join_over
|
717
|
+
if players.detect{|p| p != join_over}
|
718
|
+
trace :query, "subtyping step simple mandatory constraint #{name} over #{join_over.name}"
|
719
|
+
players.each_with_index do |player, i|
|
720
|
+
next if player != join_over
|
721
|
+
# REVISIT: We don't need to make a subtyping step here (from join_over to player)
|
722
|
+
end
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
pc = @constellation.PresenceConstraint(id_of(x))
|
727
|
+
pc.vocabulary = @vocabulary
|
728
|
+
pc.name = name
|
729
|
+
pc.role_sequence = role_sequence
|
730
|
+
pc.is_mandatory = true
|
731
|
+
pc.min_frequency = 1
|
732
|
+
pc.max_frequency = nil
|
733
|
+
pc.is_preferred_identifier = false
|
734
|
+
|
735
|
+
(@constraints_by_rs[role_sequence] ||= []) << pc
|
736
|
+
@by_id[id] = pc
|
737
|
+
}
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
def read_uniqueness_constraints
|
742
|
+
x_uniqueness_constraints = @x_model.xpath("orm:Constraints/orm:UniquenessConstraint")
|
743
|
+
trace :orm, "Reading uniqueness constraints" do
|
744
|
+
x_uniqueness_constraints.each{|x|
|
745
|
+
name = x["Name"] || ''
|
746
|
+
name = nil if name.size == 0
|
747
|
+
uc_id = x["id"]
|
748
|
+
x_pi = x.xpath("orm:PreferredIdentifierFor")[0]
|
749
|
+
pi = x_pi ? @by_id[eref = x_pi['ref']] : nil
|
750
|
+
|
751
|
+
# Skip uniqueness constraints on implied object_types
|
752
|
+
next if x_pi && !pi
|
753
|
+
|
754
|
+
# Get the RoleSequence:
|
755
|
+
x_roles = x.xpath("orm:RoleSequence/orm:Role")
|
756
|
+
next if x_roles.size == 0
|
757
|
+
role_sequence = map_roles(x_roles, "uniqueness constraint #{name}")
|
758
|
+
next if !role_sequence
|
759
|
+
#puts "uc: #{role_sequence.all_role_ref.map{|rr|rr.role.fact_type.default_reading}*', '}"
|
760
|
+
|
761
|
+
# Check for a query
|
762
|
+
if (fact_types = role_sequence.all_role_ref.map{|rr| rr.role.fact_type}).uniq.size > 1
|
763
|
+
join_over, = *ActiveFacts::Metamodel.plays_over(role_sequence.all_role_ref.map{|rr| rr.role}, :counterpart)
|
764
|
+
|
765
|
+
players = role_sequence.all_role_ref.map{|rr| rr.role.object_type.name}.uniq
|
766
|
+
unless join_over
|
767
|
+
if x.xpath("orm:RoleSequence/orm:JoinRule").size > 0
|
768
|
+
$stderr.puts "Ignoring Join #{name} because its query is not understood"
|
769
|
+
next
|
770
|
+
end
|
771
|
+
raise "Uniqueness join constraint #{name} has incompatible players #{players.inspect}"
|
772
|
+
end
|
773
|
+
subtyping = players.size > 1 ? 'subtyping ' : ''
|
774
|
+
# REVISIT: Create the Query, the Variable for join_over, and steps from each role_ref to join_over
|
775
|
+
trace :query, "#{subtyping}join uniqueness constraint over #{join_over.name} in #{fact_types.map(&:default_reading)*', '}"
|
776
|
+
end
|
777
|
+
|
778
|
+
# There is an implicit uniqueness constraint when any object plays a unary. Skip it.
|
779
|
+
if (x_roles.size == 1 &&
|
780
|
+
(id = x_roles[0]['ref']) &&
|
781
|
+
(x_role = @x_by_id[id]) &&
|
782
|
+
(nodes = x_role.parent.elements).size == 2 &&
|
783
|
+
(sibling = nodes[1]) &&
|
784
|
+
(ib_id = sibling.elements[0]['ref']) &&
|
785
|
+
(ib = @x_by_id[ib_id]) &&
|
786
|
+
ib['IsImplicitBooleanValue'])
|
787
|
+
unary_identifier = true
|
788
|
+
end
|
789
|
+
|
790
|
+
mc_id = nil
|
791
|
+
|
792
|
+
if (mc = @mandatory_constraints_by_rs[role_sequence])
|
793
|
+
# Remove absorbed mandatory constraints, leaving residual ones.
|
794
|
+
trace :orm, "Absorbing MC #{mc['Name']} over #{role_sequence.describe}"
|
795
|
+
@mandatory_constraints_by_rs.delete(role_sequence)
|
796
|
+
mc_id = mc['id']
|
797
|
+
@mandatory_constraint_rs_by_id.delete(mc['id'])
|
798
|
+
elsif (fts = role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq).size == 1 and
|
799
|
+
fts[0].entity_type
|
800
|
+
# this uniqueness constraint is an internal UC on an objectified fact type,
|
801
|
+
# so the covered roles are always mandatory (wrt the OFT)
|
802
|
+
# That is, the phantom roles are mandatory, even if the visible roles are not.
|
803
|
+
mc = true
|
804
|
+
else
|
805
|
+
trace :orm, "No MC to absorb over #{role_sequence.describe}"
|
806
|
+
end
|
807
|
+
|
808
|
+
# A TypeInheritance fact type has a uniqueness constraint on each role.
|
809
|
+
# If this UC is on the supertype and identifies the subtype, it's preferred:
|
810
|
+
is_supertype_constraint =
|
811
|
+
(rr = role_sequence.all_role_ref.single) &&
|
812
|
+
(role = rr.role) &&
|
813
|
+
(fact_type = role.fact_type) &&
|
814
|
+
fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) &&
|
815
|
+
role.object_type == fact_type.supertype &&
|
816
|
+
fact_type.provides_identification
|
817
|
+
|
818
|
+
pc = @constellation.PresenceConstraint(id_of(x))
|
819
|
+
pc.vocabulary = @vocabulary
|
820
|
+
pc.name = name
|
821
|
+
pc.role_sequence = role_sequence
|
822
|
+
pc.is_mandatory = true if mc
|
823
|
+
pc.min_frequency = mc ? 1 : 0
|
824
|
+
pc.max_frequency = 1
|
825
|
+
pc.is_preferred_identifier = true if pi || unary_identifier || is_supertype_constraint
|
826
|
+
trace :orm, "#{name} covers #{role_sequence.describe} has min=#{pc.min_frequency}, max=1, preferred=#{pc.is_preferred_identifier.inspect}"
|
827
|
+
|
828
|
+
trace :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 }
|
829
|
+
|
830
|
+
(@constraints_by_rs[role_sequence] ||= []) << pc
|
831
|
+
@by_id[uc_id] = pc
|
832
|
+
@by_id[mc_id] = pc if mc_id
|
833
|
+
}
|
834
|
+
end
|
835
|
+
end
|
836
|
+
|
837
|
+
def subtype_step(query, ti)
|
838
|
+
subtype_node = query.all_variable.detect{|jn| jn.object_type == ti.subtype } ||
|
839
|
+
@constellation.Variable(query, query.all_variable.size, :object_type => ti.subtype)
|
840
|
+
supertype_node = query.all_variable.detect{|jn| jn.object_type == ti.supertype } ||
|
841
|
+
@constellation.Variable(query, query.all_variable.size, :object_type => ti.supertype)
|
842
|
+
step = @constellation.Step(:guid => :new, :fact_type => ti)
|
843
|
+
rs = @constellation.RoleSequence(:new)
|
844
|
+
@constellation.RoleRef(rs, 0, :role => ti.subtype_role)
|
845
|
+
sub_play = @constellation.Play(:step => step, :variable => subtype_node, :role => ti.subtype_role)
|
846
|
+
@constellation.RoleRef(rs, 1, :role => ti.supertype_role)
|
847
|
+
sup_play = @constellation.Play(:step => step, :variable => supertype_node, :role => ti.supertype_role, :is_input => true)
|
848
|
+
trace :query, "New subtyping step #{step.describe}"
|
849
|
+
step
|
850
|
+
end
|
851
|
+
|
852
|
+
# Make as many steps as it takes to get from subtype to supertype
|
853
|
+
def subtype_steps(query, subtype, supertype)
|
854
|
+
primary_ti = nil
|
855
|
+
other_ti = nil
|
856
|
+
subtype.all_type_inheritance_as_subtype.each do |ti|
|
857
|
+
next unless ti.supertype.supertypes_transitive.include? supertype
|
858
|
+
if ti.provides_identification
|
859
|
+
primary_ti ||= ti
|
860
|
+
else
|
861
|
+
other_ti ||= ti
|
862
|
+
end
|
863
|
+
end
|
864
|
+
ti = primary_ti || other_ti
|
865
|
+
# Make supertype steps first:
|
866
|
+
(ti.supertype == supertype ? [] : subtype_steps(query, ti.supertype, supertype)) +
|
867
|
+
[subtype_step(query, ti)]
|
868
|
+
end
|
869
|
+
|
870
|
+
# If there's a query, build it and return a new RoleSequence containing the projected roles:
|
871
|
+
def query_over_role_sequence(role_sequence, join_over, joined_roles, end_points)
|
872
|
+
# Skip if there's no query here (sequence join nor end-point subset join)
|
873
|
+
role_refs = role_sequence.all_role_ref_in_order
|
874
|
+
if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]}
|
875
|
+
# No sequence join nor end_point join here
|
876
|
+
return role_sequence
|
877
|
+
end
|
878
|
+
|
879
|
+
# A RoleSequence for the actual query end-points
|
880
|
+
replacement_rs = @constellation.RoleSequence(:new)
|
881
|
+
|
882
|
+
query = @constellation.Query(:new)
|
883
|
+
variable = nil
|
884
|
+
query_role = nil
|
885
|
+
role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i|
|
886
|
+
|
887
|
+
# Each role_ref is to an object joined via joined_role to variable (or which will be the variable)
|
888
|
+
|
889
|
+
# Create a variable for the actual end-point (supertype of the constrained roles)
|
890
|
+
end_point = end_points[i]
|
891
|
+
unless end_point
|
892
|
+
raise "In #{constraint_type} #{name}, there is a faulty or non-translated query"
|
893
|
+
end
|
894
|
+
trace :query, "Variable #{query.all_variable.size} is for #{end_point.name}"
|
895
|
+
end_node = @constellation.Variable(query, query.all_variable.size, :object_type => end_point)
|
896
|
+
|
897
|
+
# We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same:
|
898
|
+
role_node = end_node
|
899
|
+
end_role = role_ref.role
|
900
|
+
|
901
|
+
# Create subtyping steps at the end-point, if needed:
|
902
|
+
projecting_play = nil
|
903
|
+
constrained_play = nil
|
904
|
+
if (subtype = role_ref.role.object_type) != end_point
|
905
|
+
trace :query, "Making subtyping steps from #{subtype.name} to #{end_point.name}" do
|
906
|
+
# There may be more than one supertyping level. Make the steps:
|
907
|
+
subtyping_steps = subtype_steps(query, subtype, end_point)
|
908
|
+
step = subtyping_steps[0]
|
909
|
+
#constrained_play = subtyping_steps[-1].all_play.detect{|p| p.role.object_type == end_point}
|
910
|
+
subtyping_steps.detect{|s| constrained_play = s.all_play.detect{|p| p.role.object_type == end_point}}
|
911
|
+
|
912
|
+
# Replace the constrained role and node with the supertype ones:
|
913
|
+
end_node = query.all_variable.detect{|jn| jn.object_type == end_point }
|
914
|
+
#projecting_play = step.all_play.detect{|p| p.role.object_type == subtype}
|
915
|
+
subtyping_steps.detect{|s| projecting_play = s.all_play.detect{|p| p.role.object_type == subtype}}
|
916
|
+
end_role = step.fact_type.all_role.detect{|r| r.object_type == end_point }
|
917
|
+
role_node = query.all_variable.detect{|jn| jn.object_type == role_ref.role.object_type }
|
918
|
+
end
|
919
|
+
end
|
920
|
+
|
921
|
+
if end_role.object_type != end_node.object_type
|
922
|
+
raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, end role mismatch"
|
923
|
+
end
|
924
|
+
|
925
|
+
if join_over
|
926
|
+
if !variable # Create the Variable when processing the first role
|
927
|
+
trace :query, "Variable #{query.all_variable.size} is over #{join_over.name}"
|
928
|
+
variable = @constellation.Variable(query, query.all_variable.size, :object_type => join_over)
|
929
|
+
end
|
930
|
+
trace :query, "Making step from #{end_point.name} to #{join_over.name}" do
|
931
|
+
rs = @constellation.RoleSequence(:new)
|
932
|
+
# Detect the fact type over which we're stepping (may involve objectification)
|
933
|
+
raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, object type mismatch" if role_ref.role.object_type != role_node.object_type
|
934
|
+
step = @constellation.Step(:guid => :new, :fact_type => joined_role.fact_type)
|
935
|
+
@constellation.RoleRef(rs, 0, :role => role_ref.role)
|
936
|
+
role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
|
937
|
+
# Make the projected RoleRef:
|
938
|
+
rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => role_play)
|
939
|
+
raise "Internal error in #{constraint_type} #{name}: making illegal reference to variable, joined_role mismatch" if joined_role.object_type != variable.object_type
|
940
|
+
@constellation.RoleRef(rs, 1, :role => joined_role)
|
941
|
+
join_play = @constellation.Play(:step => step, :variable => variable, :role => joined_role)
|
942
|
+
trace :query, "New step #{step.describe}"
|
943
|
+
end
|
944
|
+
else
|
945
|
+
trace :query, "Need step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
|
946
|
+
if (roles = role_ref.role.fact_type.all_role.to_a).size > 1
|
947
|
+
# Here we have an end join (step already created) but no sequence join
|
948
|
+
if variable
|
949
|
+
raise "Internal error in #{constraint_type} #{name}: making illegal step" if role_ref.role.object_type != role_node.object_type
|
950
|
+
step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
|
951
|
+
join_play = @constellation.Play(:step => step, :variable => variable, :role => query_role, :is_input => true)
|
952
|
+
role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role)
|
953
|
+
# Make the projected RoleRef:
|
954
|
+
rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => role_play)
|
955
|
+
roles -= [query_role, role_ref.role]
|
956
|
+
roles.each do |incidental_role|
|
957
|
+
jn = @constellation.Variable(query, query.all_variable.size, :object_type => incidental_role.object_type)
|
958
|
+
play = @constellation.Play(:step => step, :variable => jn, :role => incidental_role, :step => step)
|
959
|
+
end
|
960
|
+
else
|
961
|
+
|
962
|
+
if role_sequence.all_role_ref.size > 1
|
963
|
+
variable = role_node
|
964
|
+
query_role = role_ref.role
|
965
|
+
|
966
|
+
# Make the projected RoleRef:
|
967
|
+
rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => constrained_play.role, :play => constrained_play)
|
968
|
+
else
|
969
|
+
# We enter this fact type (requiring that a role be played) but don't exit it.
|
970
|
+
# I think this can only happen where we have subtyping steps, above.
|
971
|
+
|
972
|
+
# There's no query in this role sequence, so we'd drop off the bottom without doing the right things. Why?
|
973
|
+
# Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why.
|
974
|
+
# I think the "then" code causes it to drop out the bottom without making the step (which is otherwise made in every case, see CompanyDirectorEmployee for example)
|
975
|
+
step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
|
976
|
+
|
977
|
+
# p constrained_play.role.object_type.name
|
978
|
+
# p projecting_play.role.object_type.name
|
979
|
+
# debugger
|
980
|
+
|
981
|
+
role_play = @constellation.Play(:step => step, :variable => role_node, :role => role_ref.role, :is_input => true)
|
982
|
+
|
983
|
+
# Make the projected RoleRef:
|
984
|
+
rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => constrained_play.role, :play => constrained_play)
|
985
|
+
|
986
|
+
# role_ref.role.fact_type.all_role.each do |role|
|
987
|
+
# next if role == role_play.role
|
988
|
+
# next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
|
989
|
+
# jn = @constellation.Variable(query, query.all_variable.size, :object_type => role.object_type)
|
990
|
+
# play = @constellation.Play(:step => step, :variable => jn, :role => role)
|
991
|
+
# if role == role_ref.role
|
992
|
+
# # Make the projected RoleRef:
|
993
|
+
# rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role, :play => play)
|
994
|
+
# end
|
995
|
+
# end
|
996
|
+
|
997
|
+
end
|
998
|
+
end
|
999
|
+
else
|
1000
|
+
# Unary fact type, make a Step from and to the constrained_play
|
1001
|
+
step = @constellation.Step(:guid => :new, :fact_type => role_ref.role.fact_type)
|
1002
|
+
play = @constellation.Play(:step => step, :variable => constrained_play.variable, :role => role_ref.role, :is_input => true)
|
1003
|
+
# Make the projected RoleRef:
|
1004
|
+
rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => role_ref.role, :play => play)
|
1005
|
+
end
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
raise "hell" if replacement_rs.all_role_ref.size != role_sequence.all_role_ref.size
|
1009
|
+
|
1010
|
+
# Thoroughly check that this is a valid query
|
1011
|
+
query.validate
|
1012
|
+
trace :query, "Query has projected nodes #{replacement_rs.describe}"
|
1013
|
+
replacement_rs
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
# Equality and subset join constraints involve two or more role sequences,
|
1017
|
+
# and the respective roles from each sequence must be compatible,
|
1018
|
+
# Compatibility might involve subtyping steps but not objectification steps
|
1019
|
+
# to the respective end-point (constrained object type).
|
1020
|
+
# Also, all roles in each sequence constitute a join over a single
|
1021
|
+
# object type, which might involve subtyping or objectification steps.
|
1022
|
+
#
|
1023
|
+
def make_queries(constraint_type, name, role_sequences)
|
1024
|
+
begin
|
1025
|
+
# Get the object types constrained for each position in the role sequences.
|
1026
|
+
# Supertyping steps may be needed to reach them.
|
1027
|
+
end_points = [] # An array of the common supertype for matching role_refs across the sequences
|
1028
|
+
end_step_needed = [] # An array of booleans indicating whether any role_sequence requires subtyping steps
|
1029
|
+
role_sequences[0].all_role_ref.size.times do |i|
|
1030
|
+
role_refs = role_sequences.map{|rs| rs.all_role_ref.detect{|rr| rr.ordinal == i}}
|
1031
|
+
if (fact_types = role_refs.map{|rr| rr.role.fact_type}).uniq.size == 1
|
1032
|
+
# $stderr.puts(role_sequences.map{|rs| rs.all_role_ref.map{|rr| rr.role.fact_type.describe(rr.role)}}.inspect)
|
1033
|
+
raise "In #{constraint_type} #{name} role sequence #{i}, there is a faulty join involving just 1 fact type: '#{fact_types[0].default_reading}'"
|
1034
|
+
end
|
1035
|
+
if (players = role_refs.map{|rr| rr.role.object_type}).uniq.size == 1
|
1036
|
+
# All roles in this set are played by the same object type
|
1037
|
+
end_point = players[0]
|
1038
|
+
end_step_needed[i] = false
|
1039
|
+
else
|
1040
|
+
# Can the players be joined using subtyping steps?
|
1041
|
+
common_supertypes = players[1..-1].
|
1042
|
+
inject(players[0].supertypes_transitive) do |remaining, player|
|
1043
|
+
remaining & player.supertypes_transitive
|
1044
|
+
end
|
1045
|
+
end_point = common_supertypes[0]
|
1046
|
+
|
1047
|
+
raise "constrained roles of #{constraint_type} #{name} are incompatible (#{players.map(&:name)*', '})" if common_supertypes.size == 0
|
1048
|
+
end_step_needed[i] = true
|
1049
|
+
end
|
1050
|
+
end_points[i] = end_point
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
# For each role_sequence, find the object type over which the join is implied (nil if no join)
|
1054
|
+
sequence_join_over = []
|
1055
|
+
if role_sequences[0].all_role_ref.size > 1 # There are queries within each sequence.
|
1056
|
+
sequence_join_over = []
|
1057
|
+
sequence_joined_roles = []
|
1058
|
+
role_sequences.map do |rs|
|
1059
|
+
join_over, joined_roles = *ActiveFacts::Metamodel.plays_over(rs.all_role_ref.map{|rr| rr.role})
|
1060
|
+
sequence_join_over << join_over
|
1061
|
+
sequence_joined_roles << joined_roles
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
# If there are no queries, we can drop out here.
|
1066
|
+
if sequence_join_over.compact.empty? && !end_step_needed.detect{|e| e}
|
1067
|
+
return true
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
trace :query, "#{constraint_type} join constraint #{name} over #{role_sequences.map{|rs|rs.describe}*', '}"
|
1071
|
+
|
1072
|
+
query = nil
|
1073
|
+
trace :query, "#{constraint_type} join constraint #{name} constrains #{
|
1074
|
+
end_points.zip(end_step_needed).map{|(p,j)| (p ? p.name : 'NULL')+(j ? ' & subtypes':'')}*', '
|
1075
|
+
}#{
|
1076
|
+
if role_sequences[0].all_role_ref.size > 1
|
1077
|
+
", joined over #{
|
1078
|
+
sequence_join_over.zip(sequence_joined_roles).map{|o, roles|
|
1079
|
+
(o ? o.name : '(none)') +
|
1080
|
+
(roles ? " to (#{roles.map{|role| role ? role.fact_type.default_reading : 'null'}*','})" : '')
|
1081
|
+
}*', '}"
|
1082
|
+
else
|
1083
|
+
''
|
1084
|
+
end
|
1085
|
+
}" do
|
1086
|
+
|
1087
|
+
# There may be one query per role sequence:
|
1088
|
+
role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles|
|
1089
|
+
position = role_sequences.index(role_sequence)
|
1090
|
+
replacement_rs = query_over_role_sequence(role_sequence, join_over, joined_roles, end_points)
|
1091
|
+
if role_sequence != replacement_rs
|
1092
|
+
role_sequences[position] = replacement_rs
|
1093
|
+
end
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
return true
|
1097
|
+
end
|
1098
|
+
rescue => e
|
1099
|
+
debugger if trace :debug
|
1100
|
+
$stderr.puts "// #{e.to_s}: #{e.backtrace[0]}"
|
1101
|
+
return false
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
def read_exclusion_constraints
|
1107
|
+
x_exclusion_constraints = @x_model.xpath("orm:Constraints/orm:ExclusionConstraint")
|
1108
|
+
trace :orm, "Reading #{x_exclusion_constraints.size} exclusion constraints" do
|
1109
|
+
x_exclusion_constraints.each{|x|
|
1110
|
+
id = x['id']
|
1111
|
+
name = x["Name"] || ''
|
1112
|
+
name = nil if name.size == 0
|
1113
|
+
x_mandatory = (m = x.xpath("orm:ExclusiveOrMandatoryConstraint")[0]) &&
|
1114
|
+
@x_by_id[mc_id = m['ref']]
|
1115
|
+
role_sequences =
|
1116
|
+
x.xpath("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
|
1117
|
+
x_role_refs = x_rs.xpath("orm:Role")
|
1118
|
+
map_roles(
|
1119
|
+
x_role_refs , # .map{|xr| @x_by_id[xr['ref']] },
|
1120
|
+
"exclusion constraint #{name}"
|
1121
|
+
)
|
1122
|
+
}
|
1123
|
+
if x_mandatory
|
1124
|
+
# Remove absorbed mandatory constraints, leaving residual ones.
|
1125
|
+
mc_rs = @mandatory_constraint_rs_by_id[mc_id]
|
1126
|
+
@mandatory_constraint_rs_by_id.delete(mc_id)
|
1127
|
+
@mandatory_constraints_by_rs.delete(mc_rs)
|
1128
|
+
end
|
1129
|
+
|
1130
|
+
if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role
|
1131
|
+
trace :orm, "skipped exclusion constraint #{id}, missing role sequence"
|
1132
|
+
next
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
unless make_queries('exclusion', name+(x_mandatory ? '/'+x_mandatory['Name'] : ''), role_sequences)
|
1136
|
+
trace :orm, "skipped exclusion constraint #{id}, can't make_queries"
|
1137
|
+
next
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
ec = @constellation.SetExclusionConstraint(id_of(x))
|
1141
|
+
ec.vocabulary = @vocabulary
|
1142
|
+
ec.name = name
|
1143
|
+
# ec.enforcement =
|
1144
|
+
role_sequences.each_with_index do |rs, i|
|
1145
|
+
@constellation.SetComparisonRoles(ec, i, :role_sequence => rs)
|
1146
|
+
end
|
1147
|
+
ec.is_mandatory = true if x_mandatory
|
1148
|
+
@by_id[id] = ec
|
1149
|
+
@by_id[mc_id] = ec if mc_id
|
1150
|
+
}
|
1151
|
+
end
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
def read_equality_constraints
|
1155
|
+
x_equality_constraints = @x_model.xpath("orm:Constraints/orm:EqualityConstraint")
|
1156
|
+
trace :orm, "Reading equality constraints" do
|
1157
|
+
x_equality_constraints.each{|x|
|
1158
|
+
id = x['id']
|
1159
|
+
name = x["Name"] || ''
|
1160
|
+
name = nil if name.size == 0
|
1161
|
+
role_sequences =
|
1162
|
+
x.xpath("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
|
1163
|
+
x_role_refs = x_rs.xpath("orm:Role")
|
1164
|
+
map_roles(
|
1165
|
+
x_role_refs , # .map{|xr| @x_by_id[xr['ref']] },
|
1166
|
+
"equality constraint #{name}"
|
1167
|
+
)
|
1168
|
+
}
|
1169
|
+
|
1170
|
+
# Role sequence missing; includes a derived fact type role
|
1171
|
+
next if role_sequences.compact.size != role_sequences.size
|
1172
|
+
|
1173
|
+
next unless make_queries('equality', name, role_sequences)
|
1174
|
+
|
1175
|
+
ec = @constellation.SetEqualityConstraint(id_of(x))
|
1176
|
+
ec.vocabulary = @vocabulary
|
1177
|
+
ec.name = name
|
1178
|
+
# ec.enforcement =
|
1179
|
+
role_sequences.each_with_index do |rs, i|
|
1180
|
+
@constellation.SetComparisonRoles(ec, i, :role_sequence => rs)
|
1181
|
+
end
|
1182
|
+
@by_id[id] = ec
|
1183
|
+
}
|
1184
|
+
end
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
def read_subset_constraints
|
1188
|
+
x_subset_constraints = @x_model.xpath("orm:Constraints/orm:SubsetConstraint")
|
1189
|
+
trace :orm, "Reading subset constraints" do
|
1190
|
+
x_subset_constraints.each{|x|
|
1191
|
+
id = x['id']
|
1192
|
+
name = x["Name"] || ''
|
1193
|
+
name = nil if name.size == 0
|
1194
|
+
role_sequences =
|
1195
|
+
x.xpath("orm:RoleSequences/orm:RoleSequence").map{|x_rs|
|
1196
|
+
x_role_refs = x_rs.xpath("orm:Role")
|
1197
|
+
map_roles(
|
1198
|
+
x_role_refs , # .map{|xr| @x_by_id[xr['ref']] },
|
1199
|
+
"equality constraint #{name}"
|
1200
|
+
)
|
1201
|
+
}
|
1202
|
+
next if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role
|
1203
|
+
next unless make_queries('subset', name, role_sequences)
|
1204
|
+
|
1205
|
+
ec = @constellation.SubsetConstraint(id_of(x))
|
1206
|
+
ec.vocabulary = @vocabulary
|
1207
|
+
ec.name = name
|
1208
|
+
# ec.enforcement =
|
1209
|
+
ec.subset_role_sequence = role_sequences[0]
|
1210
|
+
ec.superset_role_sequence = role_sequences[1]
|
1211
|
+
@by_id[id] = ec
|
1212
|
+
}
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
def read_ring_constraints
|
1217
|
+
x_ring_constraints = @x_model.xpath("orm:Constraints/orm:RingConstraint")
|
1218
|
+
trace :orm, "Reading ring constraints" do
|
1219
|
+
x_ring_constraints.each{|x|
|
1220
|
+
id = x['id']
|
1221
|
+
name = x["Name"] || ''
|
1222
|
+
name = nil if name.size == 0
|
1223
|
+
ring_type = x["Type"]
|
1224
|
+
|
1225
|
+
from, to = *x.xpath("orm:RoleSequence/orm:Role").
|
1226
|
+
map do |xr|
|
1227
|
+
@by_id[xr['ref']]
|
1228
|
+
end
|
1229
|
+
next unless from && to # Roles missing; covers a derived fact type
|
1230
|
+
if from.object_type != to.object_type
|
1231
|
+
join_over, = *ActiveFacts::Metamodel.plays_over([from, to], :counterpart)
|
1232
|
+
raise "Ring constraint has incompatible players #{from.object_type.name}, #{to.object_type.name}" if !join_over
|
1233
|
+
trace :query, "join ring constraint over #{join_over.name}"
|
1234
|
+
end
|
1235
|
+
rc = @constellation.RingConstraint(id_of(x))
|
1236
|
+
rc.vocabulary = @vocabulary
|
1237
|
+
rc.name = name
|
1238
|
+
# rc.enforcement =
|
1239
|
+
rc.role = from
|
1240
|
+
rc.other_role = to
|
1241
|
+
rc.ring_type = ring_type.gsub(/PurelyReflexive/,'Reflexive')
|
1242
|
+
@by_id[id] = rc
|
1243
|
+
}
|
1244
|
+
end
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
def read_frequency_constraints
|
1248
|
+
x_frequency_constraints = @x_model.xpath("orm:Constraints/orm:FrequencyConstraint")
|
1249
|
+
trace :orm, "Reading frequency constraints" do
|
1250
|
+
x_frequency_constraints.each do |x_frequency_constraint|
|
1251
|
+
id = x_frequency_constraint['id']
|
1252
|
+
min_frequency = x_frequency_constraint["MinFrequency"].to_i
|
1253
|
+
min_frequency = nil if min_frequency == 0
|
1254
|
+
max_frequency = x_frequency_constraint["MaxFrequency"].to_i
|
1255
|
+
max_frequency = nil if max_frequency == 0
|
1256
|
+
x_roles = x_frequency_constraint.xpath("orm:RoleSequence/orm:Role")
|
1257
|
+
role = @by_id[x_roles[0]["ref"]]
|
1258
|
+
role_sequence = @constellation.RoleSequence(:new)
|
1259
|
+
role_ref = @constellation.RoleRef(role_sequence, 0, :role => role)
|
1260
|
+
next unless role # Role missing; belongs to a derived fact type
|
1261
|
+
trace :orm, "FrequencyConstraint(min #{min_frequency.inspect} max #{max_frequency.inspect} over #{role.fact_type.describe(role)} #{id} role ref = #{x_roles[0]["ref"]}"
|
1262
|
+
@by_id[id] = @constellation.PresenceConstraint(
|
1263
|
+
id_of(x_frequency_constraint),
|
1264
|
+
:vocabulary => @vocabulary,
|
1265
|
+
:name => name = x_frequency_constraint["Name"] || '',
|
1266
|
+
:role_sequence => role_sequence,
|
1267
|
+
:is_mandatory => false,
|
1268
|
+
:min_frequency => min_frequency,
|
1269
|
+
:max_frequency => max_frequency,
|
1270
|
+
:is_preferred_identifier => false
|
1271
|
+
)
|
1272
|
+
end
|
1273
|
+
end
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
def read_instances
|
1277
|
+
trace :orm, "Reading sample data" do
|
1278
|
+
population = @constellation.Population(@vocabulary, "sample", :concept => :new)
|
1279
|
+
|
1280
|
+
# Value instances first, then entities then facts:
|
1281
|
+
|
1282
|
+
x_values = @x_model.xpath("orm:Objects/orm:ValueType/orm:Instances/orm:ValueTypeInstance/orm:Value")
|
1283
|
+
#pp x_values.map{|v| [ v.parent['id'], v.text ] }
|
1284
|
+
trace :orm, "Reading sample values" do
|
1285
|
+
x_values.each{|v|
|
1286
|
+
id = v.parent['id']
|
1287
|
+
# Get details of the ValueType:
|
1288
|
+
xvt = v.parent.parent.parent
|
1289
|
+
vt_id = xvt['id']
|
1290
|
+
vtname = xvt['Name'] || ''
|
1291
|
+
#vtname.gsub!(/\s/,'')
|
1292
|
+
vtname = nil if vtname.size == 0
|
1293
|
+
vt = @by_id[vt_id]
|
1294
|
+
throw "ValueType #{vtname} not found" unless vt
|
1295
|
+
|
1296
|
+
i = @constellation.Instance(id_of(v.parent), :population => population, :object_type => vt, :value => [v.text, is_literal_string(v.text), nil])
|
1297
|
+
@by_id[id] = i
|
1298
|
+
# show_xmlobj(v)
|
1299
|
+
}
|
1300
|
+
end
|
1301
|
+
|
1302
|
+
# Use the "id" attribute of EntityTypeInstance
|
1303
|
+
x_entities = @x_model.xpath("orm:Objects/orm:EntityType/orm:Instances/orm:EntityTypeInstance")
|
1304
|
+
#pp x_entities
|
1305
|
+
# x_entities.each{|v| show_xmlobj(v) }
|
1306
|
+
last_et_id = nil
|
1307
|
+
last_et = nil
|
1308
|
+
et = nil
|
1309
|
+
trace :orm, "Reading sample entities" do
|
1310
|
+
x_entities.each{|v|
|
1311
|
+
id = v['id']
|
1312
|
+
|
1313
|
+
# Get details of the EntityType:
|
1314
|
+
xet = v.parent.parent
|
1315
|
+
et_id = xet['id']
|
1316
|
+
if (et_id != last_et_id)
|
1317
|
+
etname = xet['Name'] || ''
|
1318
|
+
#etname.gsub!(/\s/,'')
|
1319
|
+
etname = nil if etname.size == 0
|
1320
|
+
last_et = et = @by_id[et_id]
|
1321
|
+
last_et_id = et_id
|
1322
|
+
throw "EntityType #{etname} not found" unless et
|
1323
|
+
end
|
1324
|
+
|
1325
|
+
instance = @constellation.Instance(id_of(v), :population => population, :object_type => et, :value => nil)
|
1326
|
+
@by_id[id] = instance
|
1327
|
+
trace :orm, "Made new EntityType #{etname}"
|
1328
|
+
}
|
1329
|
+
end
|
1330
|
+
|
1331
|
+
# The EntityType instances have implicit facts for the PI facts.
|
1332
|
+
# These are in the ORM file, but instead of using those,
|
1333
|
+
# We create implicit PI facts after all the instances.
|
1334
|
+
entity_count = 0
|
1335
|
+
pi_fact_count = 0
|
1336
|
+
trace :orm, "Creating identifying facts for entities" do
|
1337
|
+
x_entities.each do |v|
|
1338
|
+
id = v['id']
|
1339
|
+
instance = @by_id[id]
|
1340
|
+
et = @by_id[v.parent.parent['id']]
|
1341
|
+
next unless (preferred_id = et.preferred_identifier)
|
1342
|
+
|
1343
|
+
trace :orm, "Create identifying facts using #{preferred_id}"
|
1344
|
+
|
1345
|
+
# Collate the referenced objects by role:
|
1346
|
+
role_instances = v.elements[0].elements.inject({}){|h, v|
|
1347
|
+
etri = @x_by_id[v['ref']]
|
1348
|
+
x_role_id = etri.parent.parent['id']
|
1349
|
+
role = @by_id[x_role_id]
|
1350
|
+
object = @by_id[object_id = etri['ref']]
|
1351
|
+
h[role] = object
|
1352
|
+
h
|
1353
|
+
}
|
1354
|
+
|
1355
|
+
# Create an instance of each required fact type, for compound identification:
|
1356
|
+
identifying_fact_types =
|
1357
|
+
preferred_id.role_sequence.all_role_ref.map { |rr| rr.role.fact_type }.uniq
|
1358
|
+
identifying_fact_types.
|
1359
|
+
each do |ft|
|
1360
|
+
trace :orm, "For FactType #{ft}" do
|
1361
|
+
fact = @constellation.Fact(:new, :population => population, :fact_type => ft)
|
1362
|
+
fact_roles = ft.all_role.map do |role|
|
1363
|
+
if role.object_type == et
|
1364
|
+
object = instance
|
1365
|
+
else
|
1366
|
+
object = role_instances[role]
|
1367
|
+
trace :orm, "instance for role #{role} is #{object}"
|
1368
|
+
end
|
1369
|
+
@constellation.RoleValue(:instance => object, :population => population, :fact => fact, :role => role)
|
1370
|
+
end
|
1371
|
+
end
|
1372
|
+
pi_fact_count += 1
|
1373
|
+
end
|
1374
|
+
|
1375
|
+
entity_count += 1
|
1376
|
+
end
|
1377
|
+
end
|
1378
|
+
trace :orm, "Created #{pi_fact_count} facts to identify #{entity_count} entities"
|
1379
|
+
|
1380
|
+
# Use the "ref" attribute of FactTypeRoleInstance:
|
1381
|
+
x_fact_roles = @x_model.xpath("orm:Facts/orm:Fact/orm:Instances/orm:FactTypeInstance/orm:RoleInstances/orm:FactTypeRoleInstance")
|
1382
|
+
|
1383
|
+
# REVISIT: This presumably duplicates the identifying fact instances for the above entities. Hmmm.
|
1384
|
+
last_id = nil
|
1385
|
+
fact = nil
|
1386
|
+
fact_roles = []
|
1387
|
+
trace :orm, "Reading sample facts" do
|
1388
|
+
x_fact_roles.each do |v|
|
1389
|
+
fact_type_id = v.parent.parent.parent.parent['id']
|
1390
|
+
id = v.parent.parent['id']
|
1391
|
+
fact_type = @by_id[fact_type_id]
|
1392
|
+
throw "Fact type #{fact_type_id} not found" unless fact_type
|
1393
|
+
|
1394
|
+
# Create initial and subsequent Fact objects:
|
1395
|
+
fact = @constellation.Fact(:new, :population => population, :fact_type => fact_type) unless fact && last_id == id
|
1396
|
+
last_id = id
|
1397
|
+
|
1398
|
+
# REVISIT: This doesn't handle instances of objectified fact types (where a RoleValue.instance objectifies Fact)
|
1399
|
+
|
1400
|
+
x_role_instance = @x_by_id[v['ref']]
|
1401
|
+
x_role_id = x_role_instance.parent.parent['id']
|
1402
|
+
role = @by_id[x_role_id]
|
1403
|
+
throw "Role not found for instance #{x_role_id}" unless role
|
1404
|
+
instance_id = x_role_instance['ref']
|
1405
|
+
instance = @by_id[instance_id]
|
1406
|
+
throw "Instance not found for FactRole #{instance_id}" unless instance
|
1407
|
+
@constellation.RoleValue(:instance => instance, :population => population, :fact => fact, :role => role)
|
1408
|
+
end
|
1409
|
+
end
|
1410
|
+
|
1411
|
+
end
|
1412
|
+
end
|
1413
|
+
|
1414
|
+
def read_diagrams
|
1415
|
+
x_diagrams = @document.root.xpath("ormDiagram:ORMDiagram")
|
1416
|
+
trace :orm, "Reading diagrams" do
|
1417
|
+
x_diagrams.each do |x|
|
1418
|
+
name = (x["Name"] || '').strip
|
1419
|
+
diagram = @constellation.ORMDiagram(@vocabulary, name)
|
1420
|
+
trace :diagram, "Starting to read diagram #{name}"
|
1421
|
+
shapes = x.xpath("ormDiagram:Shapes/*")
|
1422
|
+
trace :orm, "Reading shapes" do
|
1423
|
+
shapes.map do |x_shape|
|
1424
|
+
x_subject = x_shape.xpath("ormDiagram:Subject")[0]
|
1425
|
+
subject = @by_id[x_subject["ref"]]
|
1426
|
+
is_expanded = v = x_shape['IsExpanded'] and v == 'true'
|
1427
|
+
bounds = x_shape['AbsoluteBounds']
|
1428
|
+
case shape_type = x_shape.name
|
1429
|
+
when 'FactTypeShape'
|
1430
|
+
if subject
|
1431
|
+
read_fact_type_shape diagram, x_shape, is_expanded, bounds, subject
|
1432
|
+
# else REVISIT: probably a derived fact type
|
1433
|
+
end
|
1434
|
+
when 'ExternalConstraintShape', 'FrequencyConstraintShape'
|
1435
|
+
# REVISIT: The offset might depend on the constraint type. This is right for subset and other round ones.
|
1436
|
+
location = convert_location(bounds, Gravity::C)
|
1437
|
+
shape = @constellation.ConstraintShape(
|
1438
|
+
:guid => id_of(x_shape), :orm_diagram => diagram, :location => location, :is_expanded => is_expanded,
|
1439
|
+
:constraint => subject
|
1440
|
+
)
|
1441
|
+
when 'RingConstraintShape'
|
1442
|
+
# REVISIT: The offset might depend on the ring constraint type. This is right for basic round ones.
|
1443
|
+
location = convert_location(bounds, Gravity::C)
|
1444
|
+
shape = @constellation.RingConstraintShape(
|
1445
|
+
:guid => id_of(x_shape), :orm_diagram => diagram, :location => location, :is_expanded => is_expanded,
|
1446
|
+
:constraint => subject
|
1447
|
+
)
|
1448
|
+
shape.fact_type_shape = subject.role.fact_type.all_fact_type_shape.to_a[0]
|
1449
|
+
when 'ModelNoteShape'
|
1450
|
+
# REVISIT: Add model notes
|
1451
|
+
when 'ObjectTypeShape'
|
1452
|
+
location = convert_location(bounds, Gravity::C)
|
1453
|
+
# $stderr.puts "#{subject.name}: bounds=#{bounds} -> location = (#{location.x}, #{location.y})"
|
1454
|
+
shape = @constellation.ObjectTypeShape(
|
1455
|
+
:guid => id_of(x_shape), :orm_diagram => diagram, :location => location, :is_expanded => is_expanded,
|
1456
|
+
:object_type => subject,
|
1457
|
+
:location => location
|
1458
|
+
)
|
1459
|
+
else
|
1460
|
+
raise "Unknown shape #{x_shape.name}"
|
1461
|
+
end
|
1462
|
+
end
|
1463
|
+
end
|
1464
|
+
end
|
1465
|
+
end
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
def read_fact_type_shape diagram, x_shape, is_expanded, bounds, fact_type
|
1469
|
+
display_role_names_setting = v = x_shape["DisplayRoleNames"] and
|
1470
|
+
case v
|
1471
|
+
when 'Off'; 'false'
|
1472
|
+
when 'On'; 'true'
|
1473
|
+
else nil
|
1474
|
+
end
|
1475
|
+
rotation_setting = v = x_shape['DisplayOrientation'] and
|
1476
|
+
case v
|
1477
|
+
when 'VerticalRotatedLeft'; 'left'
|
1478
|
+
when 'VerticalRotatedRight'; 'right'
|
1479
|
+
else nil
|
1480
|
+
end
|
1481
|
+
|
1482
|
+
# Location of a fact type is the centre of the row of role boxes
|
1483
|
+
offs_x = 11
|
1484
|
+
offs_y = -12
|
1485
|
+
if fact_type.entity_type
|
1486
|
+
offs_x -= 12
|
1487
|
+
offs_y -= 9 if !fact_type.entity_type.concept.implication_rule # .implication_rule_name == 'objectification'
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
location = convert_location(bounds, Gravity::S, offs_x, offs_y)
|
1491
|
+
|
1492
|
+
# $stderr.puts "#{fact_type.describe}: bounds=#{bounds} -> location = (#{location.x}, #{location.y})"
|
1493
|
+
|
1494
|
+
trace :orm, "REVISIT: Can't place rotated fact type correctly on diagram yet" if rotation_setting
|
1495
|
+
|
1496
|
+
trace :orm, "fact type at #{location.x},#{location.y} has display_role_names_setting=#{display_role_names_setting.inspect}, rotation_setting=#{rotation_setting.inspect}"
|
1497
|
+
shape = @constellation.FactTypeShape(
|
1498
|
+
:guid => id_of(x_shape),
|
1499
|
+
:orm_diagram => diagram,
|
1500
|
+
:location => location,
|
1501
|
+
:is_expanded => is_expanded,
|
1502
|
+
:display_role_names_setting => display_role_names_setting,
|
1503
|
+
:rotation_setting => rotation_setting,
|
1504
|
+
:fact_type => fact_type
|
1505
|
+
)
|
1506
|
+
# Create RoleDisplay objects if necessary
|
1507
|
+
x_role_display = x_shape.xpath("ormDiagram:RoleDisplayOrder/ormDiagram:Role")
|
1508
|
+
# print "Fact type '#{fact_type.preferred_reading.expand}' (#{fact_type.all_role.map{|r|r.object_type.name}*' '})"
|
1509
|
+
if x_role_display.size > 0
|
1510
|
+
trace :orm, " has roleDisplay (#{x_role_display.map{|rd| @by_id[rd['ref']].object_type.name}*','})'"
|
1511
|
+
x_role_display.each_with_index do |rd, ordinal|
|
1512
|
+
role_display = @constellation.RoleDisplay(shape, ordinal, :role => @by_id[rd['ref']])
|
1513
|
+
end
|
1514
|
+
else
|
1515
|
+
# Decide whether to create all RoleDisplay objects for this fact type, which is in role order
|
1516
|
+
# Omitting this here might lead to incomplete RoleDisplay sequences,
|
1517
|
+
# because each RoleNameShape or ValueConstraintShape creates just one.
|
1518
|
+
trace :orm, " has no roleDisplay"
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
relative_shapes = x_shape.xpath('ormDiagram:RelativeShapes/*')
|
1522
|
+
relative_shapes.each do |xr_shape|
|
1523
|
+
location = convert_location(xr_shape['AbsoluteBounds'])
|
1524
|
+
case xr_shape.name
|
1525
|
+
when 'ObjectifiedFactTypeNameShape'
|
1526
|
+
@constellation.ObjectifiedFactTypeNameShape(:guid => id_of(xr_shape), :fact_type_shape => shape, :orm_diagram => diagram, :location => location, :is_expanded => false)
|
1527
|
+
when 'ReadingShape'
|
1528
|
+
begin
|
1529
|
+
@constellation.ReadingShape(:guid => id_of(xr_shape), :fact_type_shape => shape, :orm_diagram => diagram, :location => location, :is_expanded => false, :reading => fact_type.preferred_reading)
|
1530
|
+
rescue =>e
|
1531
|
+
debugger
|
1532
|
+
@constellation.ReadingShape(:guid => id_of(xr_shape), :fact_type_shape => shape, :orm_diagram => diagram, :location => location, :is_expanded => false, :reading => fact_type.preferred_reading)
|
1533
|
+
end
|
1534
|
+
when 'RoleNameShape'
|
1535
|
+
role = @by_id[xr_shape.xpath("ormDiagram:Subject")[0]['ref']]
|
1536
|
+
role_display = role_display_for_role(shape, x_role_display, role)
|
1537
|
+
trace :orm, "Fact type '#{fact_type.preferred_reading.expand}' has #{xr_shape.name}"
|
1538
|
+
@constellation.RoleNameShape(
|
1539
|
+
:guid => id_of(xr_shape), :orm_diagram => diagram, :location => location, :is_expanded => false,
|
1540
|
+
:role_display => role_display
|
1541
|
+
)
|
1542
|
+
when 'ValueConstraintShape'
|
1543
|
+
vc_subject_id = xr_shape.xpath("ormDiagram:Subject")[0]['ref']
|
1544
|
+
constraint = @by_id[vc_subject_id]
|
1545
|
+
trace :orm, "Fact type '#{fact_type.preferred_reading.expand}' has #{xr_shape.name} for #{constraint.inspect}"
|
1546
|
+
|
1547
|
+
role_display = role_display_for_role(shape, x_role_display, constraint.role)
|
1548
|
+
trace :orm, "ValueConstraintShape is on #{role_display.ordinal}'th role (by #{x_role_display.size > 0 ? 'role_display' : 'fact roles'})"
|
1549
|
+
@constellation.ValueConstraintShape(
|
1550
|
+
:guid => id_of(xr_shape), :orm_diagram => diagram, :location => location, :is_expanded => false,
|
1551
|
+
:constraint => constraint,
|
1552
|
+
:object_type_shape => nil, # This constraint is relative to a Fact Type, so must be on a role
|
1553
|
+
:role_display => role_display
|
1554
|
+
)
|
1555
|
+
else raise "Unknown relative shape #{xr_shape.name}"
|
1556
|
+
end
|
1557
|
+
end
|
1558
|
+
end
|
1559
|
+
|
1560
|
+
# Find or create the RoleDisplay for this role in this fact_type_shape, given (possibly empty) x_role_display nodes:
|
1561
|
+
def role_display_for_role(fact_type_shape, x_role_display, role)
|
1562
|
+
if x_role_display.size == 0
|
1563
|
+
# There's no x_role_display, which means the roles are in displayed
|
1564
|
+
# the same order as in the fact type. However, we need a RoleDisplay
|
1565
|
+
# to attach a ReadingShape or ValueConstraintShape, so make them all.
|
1566
|
+
fact_type_shape.fact_type.all_role.each{|r| @constellation.RoleDisplay(fact_type_shape, r.ordinal, :role => r) }
|
1567
|
+
role_ordinal = fact_type_shape.fact_type.all_role_in_order.index(role)
|
1568
|
+
else
|
1569
|
+
role_ordinal = x_role_display.map{|rd| @by_id[rd['ref']]}.index(role)
|
1570
|
+
end
|
1571
|
+
role_display = @constellation.RoleDisplay(fact_type_shape, role_ordinal, :role => role)
|
1572
|
+
end
|
1573
|
+
|
1574
|
+
DIAGRAM_SCALE = 96*1.5
|
1575
|
+
def convert_location(bounds, gravity = Gravity::C, xoffs = 0, yoffs = 0)
|
1576
|
+
return nil unless bounds
|
1577
|
+
# Bounds is top, left, width, height in inches
|
1578
|
+
bf = bounds.split(/, /).map{|b|b.to_f}
|
1579
|
+
sizefrax = [
|
1580
|
+
[0, 0], [1, 0], [2, 0],
|
1581
|
+
[0, 1], [1, 1], [2, 2],
|
1582
|
+
[0, 2], [1, 2], [2, 2],
|
1583
|
+
]
|
1584
|
+
|
1585
|
+
x = (DIAGRAM_SCALE * (bf[0]+bf[2]*sizefrax[gravity][0]/2)).round + xoffs
|
1586
|
+
y = (DIAGRAM_SCALE * (bf[1]+bf[3]*sizefrax[gravity][1]/2)).round + yoffs
|
1587
|
+
@constellation.Location(x, y)
|
1588
|
+
end
|
1589
|
+
|
1590
|
+
# Detect numeric data and denote it as a string:
|
1591
|
+
def is_literal_string(value)
|
1592
|
+
value =~ /[^ \d.]/
|
1593
|
+
end
|
1594
|
+
|
1595
|
+
def read_rest
|
1596
|
+
puts "Reading Implied Facts (not yet)"
|
1597
|
+
=begin
|
1598
|
+
x_implied_facts = @x_model.xpath("orm:Facts/orm:ImpliedFact")
|
1599
|
+
pp x_implied_facts
|
1600
|
+
=end
|
1601
|
+
puts "Reading Data Types (not yet)"
|
1602
|
+
=begin
|
1603
|
+
x_datatypes = @x_model.xpath("orm:DataTypes/*")
|
1604
|
+
pp x_datatypes
|
1605
|
+
=end
|
1606
|
+
puts "Reading Reference Mode Kinds (not yet)"
|
1607
|
+
=begin
|
1608
|
+
x_refmodekinds = @x_model.xpath("orm:ReferenceModeKinds/*")
|
1609
|
+
pp x_refmodekinds
|
1610
|
+
=end
|
1611
|
+
end
|
1612
|
+
|
1613
|
+
def show_xmlobj(x, indent = "")
|
1614
|
+
parentage = []
|
1615
|
+
p = x
|
1616
|
+
while (p)
|
1617
|
+
parentage.unshift(p)
|
1618
|
+
p = p.parent
|
1619
|
+
end
|
1620
|
+
#parentage = parentage.shift
|
1621
|
+
puts "#{indent}#{x.name} object has heritage {"
|
1622
|
+
parentage.each{|p|
|
1623
|
+
next if REXML::Document === p
|
1624
|
+
puts "#{indent}\t#{p.name}#{
|
1625
|
+
}#{(n = p['Name']) ? " Name='#{n}'" : ""
|
1626
|
+
}#{(id = p['id']) ? " #{id}" : ""
|
1627
|
+
}#{(ref = p['ref']) ? " -> #{ref}" : ""
|
1628
|
+
}#{/\S/ === ((text = p.text)) ? " "+text.inspect : ""
|
1629
|
+
}"
|
1630
|
+
show_xmlobj(@x_by_id[ref], "\t#{indent}") if ref
|
1631
|
+
}
|
1632
|
+
puts "#{indent}}"
|
1633
|
+
end
|
1634
|
+
end
|
1635
|
+
end
|
1636
|
+
end
|