activefacts-orm 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +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
|