activefacts 0.8.10 → 0.8.12

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.
Files changed (52) hide show
  1. data/Rakefile +3 -2
  2. data/bin/afgen +25 -23
  3. data/bin/cql +9 -8
  4. data/css/orm2.css +23 -3
  5. data/examples/CQL/CompanyDirectorEmployee.cql +1 -1
  6. data/examples/CQL/Diplomacy.cql +3 -3
  7. data/examples/CQL/Insurance.cql +27 -21
  8. data/examples/CQL/Metamodel.cql +12 -8
  9. data/examples/CQL/MetamodelNext.cql +172 -149
  10. data/examples/CQL/ServiceDirector.cql +17 -17
  11. data/examples/CQL/Supervision.cql +3 -5
  12. data/examples/CQL/WaiterTips.cql +1 -1
  13. data/examples/CQL/unit.cql +1 -1
  14. data/index.html +0 -0
  15. data/lib/activefacts/cql/FactTypes.treetop +41 -8
  16. data/lib/activefacts/cql/Language/English.treetop +10 -0
  17. data/lib/activefacts/cql/ObjectTypes.treetop +3 -1
  18. data/lib/activefacts/cql/Terms.treetop +34 -53
  19. data/lib/activefacts/cql/compiler.rb +1 -1
  20. data/lib/activefacts/cql/compiler/clause.rb +21 -8
  21. data/lib/activefacts/cql/compiler/constraint.rb +3 -1
  22. data/lib/activefacts/cql/compiler/entity_type.rb +1 -1
  23. data/lib/activefacts/cql/compiler/fact_type.rb +9 -3
  24. data/lib/activefacts/cql/compiler/join.rb +3 -0
  25. data/lib/activefacts/cql/compiler/value_type.rb +9 -4
  26. data/lib/activefacts/cql/parser.rb +11 -3
  27. data/lib/activefacts/generate/oo.rb +3 -3
  28. data/lib/activefacts/generate/ordered.rb +0 -4
  29. data/lib/activefacts/input/orm.rb +305 -250
  30. data/lib/activefacts/persistence/tables.rb +6 -0
  31. data/lib/activefacts/support.rb +18 -0
  32. data/lib/activefacts/version.rb +1 -1
  33. data/lib/activefacts/vocabulary/extensions.rb +59 -20
  34. data/lib/activefacts/vocabulary/metamodel.rb +23 -13
  35. data/lib/activefacts/vocabulary/verbaliser.rb +5 -3
  36. data/spec/absorption_spec.rb +3 -2
  37. data/spec/cql/comparison_spec.rb +1 -3
  38. data/spec/cql/context_spec.rb +1 -1
  39. data/spec/cql/entity_type_spec.rb +2 -2
  40. data/spec/cql/expressions_spec.rb +2 -4
  41. data/spec/cql/fact_type_matching_spec.rb +55 -3
  42. data/spec/cql/parser/fact_types_spec.rb +3 -0
  43. data/spec/cql/role_matching_spec.rb +8 -7
  44. data/spec/cql/samples_spec.rb +10 -2
  45. data/spec/cql_dm_spec.rb +2 -1
  46. data/spec/helpers/array_matcher.rb +18 -35
  47. data/spec/helpers/diff_matcher.rb +34 -13
  48. data/spec/helpers/file_matcher.rb +27 -43
  49. data/spec/helpers/string_matcher.rb +23 -33
  50. data/spec/norma_cql_spec.rb +1 -0
  51. data/spec/norma_tables_spec.rb +1 -2
  52. metadata +95 -102
@@ -496,7 +496,9 @@ module ActiveFacts
496
496
 
497
497
  def compile
498
498
  @constraint = @constellation.ValueConstraint(:new)
499
- $stderr.puts "REVISIT: units on value constraints are not yet processed" if @units
499
+ raise "Units on value constraints are not yet processed (at line #{'REVISIT'})" if @units
500
+ # @string.line_of(node.interval.first)
501
+
500
502
  @value_ranges.each do |range|
501
503
  min, max = Array === range ? range : [range, range]
502
504
  v_range = @constellation.ValueRange(
@@ -412,7 +412,7 @@ module ActiveFacts
412
412
  debug :mode, "Creating new ValueType PresenceConstraint"
413
413
  else
414
414
  debug :mode, "Marking existing ValueType PresenceConstraint as preferred"
415
- rs1.all_presence_constraint[0].is_preferred_identifier = true
415
+ rs1.all_presence_constraint.single.is_preferred_identifier = true
416
416
  end
417
417
  end
418
418
 
@@ -138,6 +138,8 @@ module ActiveFacts
138
138
  if @fact_type.entity_type and @name != @fact_type.entity_type.name
139
139
  raise "Cannot objectify fact type as #{@name} and as #{@fact_type.entity_type.name}"
140
140
  end
141
+ e = @constellation.EntityType[[@vocabulary, @name]]
142
+ raise "You can't objectify #{@name}, it already exists" if e
141
143
  e = @constellation.EntityType(@vocabulary, @name, :fact_type => @fact_type)
142
144
  e.create_implicit_fact_types
143
145
  if @pragmas
@@ -193,9 +195,13 @@ module ActiveFacts
193
195
  sort_by{ |clause| clause.side_effects.cost }
194
196
  fact_types = @existing_clauses.map{ |clause| clause.fact_type }.uniq.compact
195
197
 
196
- return nil if fact_types.empty?
197
- # If there's only a single clause, the match must be exact:
198
- return nil if @clauses.size == 1 && @existing_clauses[0].side_effects.cost != 0
198
+ return nil if fact_types.empty? # There are no matched fact types
199
+
200
+ if @clauses.size == 1 && @existing_clauses[0].side_effects.cost != 0
201
+ debug :matching, "There's only a single clause, but it's not an exact match"
202
+ return nil
203
+ end
204
+
199
205
  if (fact_types.size > 1)
200
206
  # There must be only one fact type with exact matches:
201
207
  if @existing_clauses[0].side_effects.cost != 0 or
@@ -11,6 +11,9 @@ module ActiveFacts
11
11
  debug :join, "Creating join node #{join.all_join_node.size} for #{variable.inspect}"
12
12
  variable.join_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => variable.player)
13
13
  if literal = variable.refs.detect{|r| r.literal}
14
+ if literal.kind_of?(ActiveFacts::CQL::Compiler::VarRef)
15
+ literal = literal.literal
16
+ end
14
17
  unit = @constellation.Unit.detect{|k, v| [v.name, v.plural_name].include? literal.unit} if literal.unit
15
18
  variable.join_node.value = [literal.literal.to_s, literal.is_a?(String), unit]
16
19
  end
@@ -16,9 +16,13 @@ module ActiveFacts
16
16
  def compile
17
17
  if (@numerator.to_f / @denominator.to_i != 1.0)
18
18
  coefficient = @constellation.Coefficient(
19
- :numerator => @numerator,
20
- :denominator => @denominator.to_i,
21
- :is_precise => !@approximately
19
+ @numerator,
20
+ @denominator.to_i,
21
+ !@approximately
22
+ # REVISIT: Activefacts-api is complaining at present. The following is better and should work:
23
+ # :numerator => @numerator,
24
+ # :denominator => @denominator.to_i,
25
+ # :is_precise => !@approximately
22
26
  )
23
27
  else
24
28
  coefficient = nil
@@ -113,7 +117,8 @@ module ActiveFacts
113
117
 
114
118
  unless @unit.empty?
115
119
  unit_name, exponent = *@unit[0]
116
- unit = @constellation.Unit.detect{|k,v| v.name == unit_name }
120
+ unit = @constellation.Name[unit_name].unit ||
121
+ @constellation.Name[unit_name].unit_as_plural_name
117
122
  raise "Unit #{unit_name} for value type #{@name} is not defined" unless unit
118
123
  if exponent != 1
119
124
  base_unit = unit
@@ -18,6 +18,14 @@ require 'activefacts/cql/FactTypes'
18
18
  require 'activefacts/cql/Context'
19
19
  require 'activefacts/cql/CQLParser'
20
20
 
21
+ module ActiveFacts
22
+ module CQL
23
+ class Parser < CQLParser
24
+ end
25
+ end
26
+ end
27
+ require 'activefacts/cql/nodes'
28
+
21
29
  class Treetop::Runtime::SyntaxNode
22
30
  def node_type
23
31
  terminal? ? :keyword : :composite
@@ -33,7 +41,7 @@ module ActiveFacts
33
41
  end
34
42
 
35
43
  # Extend the generated parser:
36
- class Parser < CQLParser
44
+ class Parser
37
45
  include ActiveFacts
38
46
 
39
47
  # The Context manages some key information revealed or needed during parsing
@@ -63,12 +71,12 @@ module ActiveFacts
63
71
  end
64
72
 
65
73
  def new_leading_adjective_term(adj, term)
66
- index_name(@role_names, "#{adj} #{term}", term) && debug(:context, "new role '#{adj}- #{term}'")
74
+ index_name(@role_names, "#{adj} #{term}", term) && debug(:context, "new compound term '#{adj}- #{term}'")
67
75
  true
68
76
  end
69
77
 
70
78
  def new_trailing_adjective_term(adj, term)
71
- index_name(@role_names, "#{term} #{adj}", term) && debug(:context, "new role '#{term} -#{adj}'")
79
+ index_name(@role_names, "#{term} #{adj}", term) && debug(:context, "new compound term '#{term} -#{adj}'")
72
80
  true
73
81
  end
74
82
 
@@ -105,12 +105,12 @@ module ActiveFacts
105
105
 
106
106
  # REVISIT: Consider whether NOT to use the adjective if it's a prefix of the role_name
107
107
  la = preferred_role_ref.leading_adjective
108
- role_words << la.gsub(/ /,'_') if la && la != "" and !role.role_name
108
+ role_words << la.gsub(/[- ]/,'_') if la && la != "" and !role.role_name
109
109
 
110
- role_words << (role_name || role.object_type.name.gsub(/ /,'_'))
110
+ role_words << (role_name || role.object_type.name.gsub(/[- ]/,'_'))
111
111
  # REVISIT: Same when trailing_adjective is a suffix of the role_name
112
112
  ta = preferred_role_ref.trailing_adjective
113
- role_words << ta.gsub(/ /,'_') if ta && ta != "" and !role_name
113
+ role_words << ta.gsub(/[- ]/,'_') if ta && ta != "" and !role_name
114
114
  n = role_words.map{|w| w.gsub(/([a-z])([A-Z]+)/,'\1_\2').downcase}*"_"
115
115
  # debug "\tresult=#{n}"
116
116
  n.gsub(' ','_')
@@ -82,10 +82,6 @@ module ActiveFacts
82
82
  # Skip value-type derived units
83
83
  next if unit.name =~ /\^/
84
84
 
85
- # Don't dump fundamental units which have normal derived units, they are implied:
86
- next if unit.is_fundamental &&
87
- unit.all_derivation_as_base_unit.detect{|d| d.derived_unit.name !~ /\^/}
88
-
89
85
  if !done_banner
90
86
  done_banner = true
91
87
  units_banner
@@ -131,6 +131,7 @@ module ActiveFacts
131
131
  # Everything we build will be indexed here:
132
132
  @by_id = {}
133
133
 
134
+ list_subtypes
134
135
  read_entity_types
135
136
  read_value_types
136
137
  read_fact_types
@@ -171,56 +172,77 @@ module ActiveFacts
171
172
  # Now the value types:
172
173
  value_types = []
173
174
  x_value_types = @x_model.xpath("orm:Objects/orm:ValueType")
174
- #pp x_value_types
175
+ @value_type_id_read = {}
175
176
  x_value_types.each{|x|
176
- id = x['id']
177
- name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
178
- name = nil if name.size == 0
179
-
180
177
  next if x['IsImplicitBooleanValue']
178
+ value_types << read_value_type(x)
179
+ }
180
+ end
181
181
 
182
- cdt = x.xpath('orm:ConceptualDataType')[0]
183
- scale = cdt['Scale']
184
- scale = scale != "" && scale.to_i
185
- length = cdt['Length']
186
- length = length != "" && length.to_i
187
- length = nil if length <= 0
188
- base_type = @x_by_id[cdt['ref']]
189
- type_name = "#{base_type.name}"
190
- type_name.sub!(/^orm:/,'')
191
-
192
- type_name.sub!(/DataType\Z/,'')
193
- type_name = DataTypeMapping[type_name] || type_name
194
- if !length and type_name =~ /\(([0-9]+)\)/
195
- length = $1.to_i
196
- end
197
- type_name = type_name.sub(/\(([0-9]*)\)/,'')
198
-
182
+ def read_value_type x
183
+ id = x['id']
184
+ return if @value_type_id_read[id] # Don't read the same value type twice
185
+ @value_type_id_read[id] = true
186
+
187
+ name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip
188
+ name = nil if name.size == 0
189
+
190
+ cdt = x.xpath('orm:ConceptualDataType')[0]
191
+ scale = cdt['Scale']
192
+ scale = scale != "" && scale.to_i
193
+ length = cdt['Length']
194
+ length = length != "" && length.to_i
195
+ length = nil if length <= 0
196
+ base_type = @x_by_id[cdt['ref']]
197
+ type_name = "#{base_type.name}"
198
+ type_name.sub!(/^orm:/,'')
199
+
200
+ type_name.sub!(/DataType\Z/,'')
201
+ type_name = DataTypeMapping[type_name] || type_name
202
+ if !length and type_name =~ /\(([0-9]+)\)/
203
+ length = $1.to_i
204
+ end
205
+ type_name = type_name.sub(/\(([0-9]*)\)/,'')
206
+
207
+ subtype_roles = x.xpath("orm:PlayedRoles/orm:SubtypeMetaRole")
208
+ if !subtype_roles.empty?
209
+ subtype_role_id = subtype_roles[0]['ref']
210
+ subtype_role = @x_by_id[subtype_role_id]
211
+ subtyping_fact_roles = subtype_role.parent
212
+ supertype_id = subtyping_fact_roles.xpath("orm:SupertypeMetaRole/orm:RolePlayer")[0]['ref']
213
+ x_supertype = @x_by_id[supertype_id]
214
+ read_value_type x_supertype unless @value_type_id_read[supertype_id]
215
+ supertype = @by_id[supertype_id]
216
+ supertype_name = x_supertype['Name']
217
+ raise "Supertype of #{name} is post-defined but recursiving processing failed" unless supertype
218
+ raise "Supertype #{supertype_name} of #{name} is not a value type" unless supertype.kind_of? ActiveFacts::Metamodel::ValueType
219
+ value_super_type = @constellation.ValueType(@vocabulary, supertype_name)
220
+ else
199
221
  # REVISIT: Need to handle standard types better here:
200
222
  value_super_type = type_name != name ? @constellation.ValueType(@vocabulary, type_name) : nil
223
+ end
201
224
 
202
- value_types <<
203
- @by_id[id] =
204
- vt = @constellation.ValueType(@vocabulary, name)
205
- vt.supertype = value_super_type
206
- vt.length = length if length
207
- vt.scale = scale if scale && scale != 0
208
- independent = x['IsIndependent']
209
- vt.is_independent = true if independent && independent == 'true'
210
- personal = x['IsPersonal']
211
- vt.pronoun = 'personal' if personal && personal == 'true'
212
-
213
- x_vr = x.xpath("orm:ValueRestriction/orm:ValueConstraint")
214
- x_vr.each{|vr|
215
- x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange")
216
- next if x_ranges.size == 0
217
- vt.value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(:new)
218
- x_ranges.each{|x_range|
219
- v_range = value_range(x_range)
220
- ar = @constellation.AllowedRange(vt.value_constraint, v_range)
221
- }
225
+ @by_id[id] =
226
+ vt = @constellation.ValueType(@vocabulary, name)
227
+ vt.supertype = value_super_type
228
+ vt.length = length if length
229
+ vt.scale = scale if scale && scale != 0
230
+ independent = x['IsIndependent']
231
+ vt.is_independent = true if independent && independent == 'true'
232
+ personal = x['IsPersonal']
233
+ vt.pronoun = 'personal' if personal && personal == 'true'
234
+
235
+ x_vr = x.xpath("orm:ValueRestriction/orm:ValueConstraint")
236
+ x_vr.each{|vr|
237
+ x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange")
238
+ next if x_ranges.size == 0
239
+ vt.value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(:new)
240
+ x_ranges.each{|x_range|
241
+ v_range = value_range(x_range)
242
+ ar = @constellation.AllowedRange(vt.value_constraint, v_range)
222
243
  }
223
244
  }
245
+ vt
224
246
  end
225
247
 
226
248
  def value_range(x_range)
@@ -254,9 +276,7 @@ module ActiveFacts
254
276
  end
255
277
  end
256
278
 
257
- def read_subtypes
258
- # Handle the subtype fact types:
259
- facts = []
279
+ def list_subtypes
260
280
  @x_subtypes = @x_model.xpath("orm:Facts/orm:SubtypeFact")
261
281
  if @document.namespaces['xmlns:oialtocdb']
262
282
  oialtocdb = @document.xpath("ormRoot:ORM2/oialtocdb:MappingCustomization")
@@ -264,6 +284,11 @@ module ActiveFacts
264
284
  else
265
285
  @x_mappings = []
266
286
  end
287
+ end
288
+
289
+ def read_subtypes
290
+ # Handle the subtype fact types:
291
+ facts = []
267
292
 
268
293
  debug :orm, "Reading sub-types" do
269
294
  @x_subtypes.each{|x|
@@ -287,6 +312,10 @@ module ActiveFacts
287
312
  throw "For Subtype fact #{name}, the subtype #{subtype_id} was not found" if !subtype
288
313
  debug :orm, "#{subtype.name} is a subtype of #{supertype.name}"
289
314
 
315
+ # We already handled ValueType subtyping:
316
+ next if subtype.kind_of? ActiveFacts::Metamodel::ValueType or
317
+ supertype.kind_of? ActiveFacts::Metamodel::ValueType
318
+
290
319
  inheritance_fact = @constellation.TypeInheritance(subtype, supertype, :fact_type_id => :new)
291
320
  if x["IsPrimary"] == "true" or # Old way
292
321
  x["PreferredIdentificationPath"] == "true" # Newer
@@ -339,19 +368,14 @@ module ActiveFacts
339
368
  next if x.xpath("orm:DerivationRule").size > 0
340
369
  throw "Nested fact #{fact_id} not found" if !fact_type
341
370
 
342
- #if is_implied
343
- # puts "Implied type #{name} (#{id}) nests #{fact_type ? fact_type.fact_type_id : "unknown"}"
344
- # @by_id[id] = fact_type
345
- #else
346
- begin
347
- debug :orm, "NestedType #{name} is #{id}, nests #{fact_type.fact_type_id}"
348
- @nested_types <<
349
- @by_id[id] =
350
- nested_type = @constellation.EntityType(@vocabulary, name)
351
- independent = x['IsIndependent']
352
- nested_type.is_independent = true if independent && independent == 'true' && !is_implied
353
- nested_type.fact_type = fact_type
354
- end
371
+ debug :orm, "NestedType #{name} is #{id}, nests #{fact_type.fact_type_id}"
372
+ @nested_types <<
373
+ @by_id[id] =
374
+ nested_type = @constellation.EntityType(@vocabulary, name)
375
+ independent = x['IsIndependent']
376
+ nested_type.is_independent = true if independent && independent == 'true' && !is_implied
377
+ nested_type.is_implied_by_objectification = is_implied
378
+ nested_type.fact_type = fact_type
355
379
  }
356
380
  end
357
381
  end
@@ -477,16 +501,24 @@ module ActiveFacts
477
501
  role = role_ref.role
478
502
 
479
503
  word = '\b[A-Za-z_][A-Za-z0-9_]+\b'
480
- leading_adjectives_re = "#{word}-(?: +#{word})*"
481
- trailing_adjectives_re = "(?:#{word} +)*-#{word}"
504
+ leading_adjectives_re = "#{word}-+(?: +#{word})*"
505
+ trailing_adjectives_re = "(?:#{word} +)*-+#{word}"
482
506
  role_with_adjectives_re =
483
507
  %r| ?(#{leading_adjectives_re})? *\{#{i}\} *(#{trailing_adjectives_re})? ?|
484
508
 
509
+ # A hyphenated pre-bound reading looks like this:
510
+ # <orm:Data>{0} has pre-- bound {1}</orm:Data>
511
+
485
512
  text.gsub!(role_with_adjectives_re) {
486
513
  # REVISIT: Don't want to strip all spaces here any more:
487
514
  #puts "text=#{text.inspect}, la=#{$1.inspect}, ta=#{$2.inspect}" if $1 || $2
488
- la = ($1||'').gsub(/\s+/,' ').sub(/-/,'').strip
489
- ta = ($2||'').gsub(/\s+/,' ').sub(/-/,'').strip
515
+ la = ($1||'').gsub(/\s+/,' ') # Strip duplicate spaces
516
+ ta = ($2||'').gsub(/\s+/,' ')
517
+ # When we have "aaa-bbb" we want "aaa bbb"
518
+ # When we have "aaa- bbb" we want "aaa bbb"
519
+ # When we have "aaa-- bbb" we want "aaa-bbb"
520
+ la = la.sub(/(-)?- ?/,'\1').strip
521
+ ta = ta.sub(/ ?(-)?-/,'\1').strip
490
522
  #puts "Setting leading adj #{la.inspect} from #{text.inspect} for #{role_ref.role.object_type.name}" if la != ""
491
523
  # REVISIT: Dunno what's up here, but removing the "if" test makes this chuck exceptions:
492
524
  role_ref.leading_adjective = la if la != ""
@@ -575,7 +607,9 @@ module ActiveFacts
575
607
  # Talk about why this wasn't found - this shouldn't happen.
576
608
  if (!x_nests || !implied)
577
609
  #puts "="*60
578
- puts "Skipping #{why}, #{x_role.name} #{id} not found"
610
+ # We skip creating TypeInheritance implied fact types for ValueType inheritance
611
+ return nil if x_role.name = 'orm:SubtypeMetaRole' or x_role.name = 'orm:SupertypeMetaRole'
612
+ raise "Skipping #{why}, #{x_role.name} #{id} not found"
579
613
 
580
614
  if (x_nests)
581
615
  puts "Role is on #{implied ? "implied " : ""}objectification #{x_object}"
@@ -795,196 +829,204 @@ module ActiveFacts
795
829
  # object type, which might involve subtyping or objectification joins.
796
830
  #
797
831
  def make_joins(constraint_type, name, role_sequences)
798
- # Get the object types constrained for each position in the role sequences.
799
- # Supertyping joins may be needed to reach them.
800
- end_points = [] # An array of the common supertype for matching role_refs across the sequences
801
- end_joins = [] # An array of booleans indicating whether any role_sequence requires a subtyping join
802
- role_sequences[0].all_role_ref.size.times do |i|
803
- role_refs = role_sequences.map{|rs| rs.all_role_ref.detect{|rr| rr.ordinal == i}}
804
- if (fact_types = role_refs.map{|rr| rr.role.fact_type}).uniq.size == 1
805
- raise "In #{constraint_type} #{name}, there is a faulty join"
806
- next
807
- end
808
- if (players = role_refs.map{|rr| rr.role.object_type}).uniq.size == 1
809
- end_point = players[0]
810
- end_joins[i] = false
811
- else
812
- # Can the players be joined using a subtyping join?
813
- common_supertypes = players[1..-1].
814
- inject(players[0].supertypes_transitive) do |remaining, player|
815
- remaining & player.supertypes_transitive
816
- end
817
- end_point = common_supertypes[0]
832
+ begin
833
+ # Get the object types constrained for each position in the role sequences.
834
+ # Supertyping joins may be needed to reach them.
835
+ end_points = [] # An array of the common supertype for matching role_refs across the sequences
836
+ end_joins = [] # An array of booleans indicating whether any role_sequence requires a subtyping join
837
+ role_sequences[0].all_role_ref.size.times do |i|
838
+ role_refs = role_sequences.map{|rs| rs.all_role_ref.detect{|rr| rr.ordinal == i}}
839
+ if (fact_types = role_refs.map{|rr| rr.role.fact_type}).uniq.size == 1
840
+ # $stderr.puts(role_sequences.map{|rs| rs.all_role_ref.map{|rr| rr.role.fact_type.describe(rr.role)}}.inspect)
841
+ raise "In #{constraint_type} #{name} role sequence #{i}, there is a faulty join involving just 1 fact type: '#{fact_types[0].default_reading}'"
842
+ end
843
+ if (players = role_refs.map{|rr| rr.role.object_type}).uniq.size == 1
844
+ end_point = players[0]
845
+ end_joins[i] = false
846
+ else
847
+ # Can the players be joined using a subtyping join?
848
+ common_supertypes = players[1..-1].
849
+ inject(players[0].supertypes_transitive) do |remaining, player|
850
+ remaining & player.supertypes_transitive
851
+ end
852
+ end_point = common_supertypes[0]
818
853
 
819
- raise "constrained roles of #{constraint_type} constraint #{name} are incompatible (#{players.map(&:name)*', '})" if common_supertypes.size == 0
820
- end_joins[i] = true
854
+ raise "constrained roles of #{constraint_type} #{name} are incompatible (#{players.map(&:name)*', '})" if common_supertypes.size == 0
855
+ end_joins[i] = true
856
+ end
857
+ end_points[i] = end_point
821
858
  end
822
- end_points[i] = end_point
823
- end
824
859
 
825
- # For each role_sequence, find the object type over which the join is implied (nil if no join)
826
- sequence_join_over = []
827
- if role_sequences[0].all_role_ref.size > 1 # There are joins within each sequence.
860
+ # For each role_sequence, find the object type over which the join is implied (nil if no join)
828
861
  sequence_join_over = []
829
- sequence_joined_roles = []
830
- role_sequences.map do |rs|
831
- join_over, joined_roles = *ActiveFacts::Metamodel.join_roles_over(rs.all_role_ref.map{|rr| rr.role})
832
- sequence_join_over << join_over
833
- sequence_joined_roles << joined_roles
862
+ if role_sequences[0].all_role_ref.size > 1 # There are joins within each sequence.
863
+ sequence_join_over = []
864
+ sequence_joined_roles = []
865
+ role_sequences.map do |rs|
866
+ join_over, joined_roles = *ActiveFacts::Metamodel.join_roles_over(rs.all_role_ref.map{|rr| rr.role})
867
+ sequence_join_over << join_over
868
+ sequence_joined_roles << joined_roles
869
+ end
834
870
  end
835
- end
836
-
837
- # If there are no joins, we can drop out here.
838
- if sequence_join_over.compact.empty? && !end_joins.detect{|e| true}
839
- return
840
- end
841
871
 
842
- debug :join, "#{constraint_type} join constraint #{name} over #{role_sequences.map{|rs|rs.describe}*', '}"
843
-
844
- join = nil
845
- debug :join, "#{constraint_type} join constraint #{name} constrains #{
846
- end_points.zip(end_joins).map{|(p,j)| p.name+(j ? ' & subtypes':'')}*', '
847
- }#{
848
- if role_sequences[0].all_role_ref.size > 1
849
- ", joined over #{
850
- sequence_join_over.zip(sequence_joined_roles).map{|o, roles|
851
- (o ? o.name : '(none)') +
852
- (roles ? " to (#{roles.map{|role| role ? role.fact_type.default_reading : 'null'}*','})" : '')
853
- }*', '}"
854
- else
855
- ''
872
+ # If there are no joins, we can drop out here.
873
+ if sequence_join_over.compact.empty? && !end_joins.detect{|e| true}
874
+ return true
856
875
  end
857
- }" do
858
-
859
- # There may be one join per role sequence:
860
- role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles|
861
- # Skip if there's no join here (sequence join nor end-point subset join)
862
- role_refs = role_sequence.all_role_ref_in_order
863
- if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]}
864
- # No sequence join nor end_point join here
865
- next
866
- end
867
876
 
868
- # A RoleSequence for the actual join end-points
869
- replacement_rs = @constellation.RoleSequence(:new)
870
-
871
- join = @constellation.Join(:new)
872
- join_node = nil
873
- join_role = nil
874
- role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i|
875
-
876
- # Each role_ref is to an object joined via joined_role to join_node (or which will be the join_node)
877
-
878
- # Create a join node for the actual end-point (supertype of the constrained roles)
879
- end_point = end_points[i]
880
- raise "In #{constraint_type} #{name}, there is a faulty join" unless end_point
881
- debug :join, "Join Node #{join.all_join_node.size} is for #{end_point.name}"
882
- end_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => end_point)
883
-
884
- # We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same:
885
- role_node = end_node
886
- end_role = role_ref.role
887
-
888
- # Create subtyping join steps at the end-point, if needed:
889
- projecting_jr = nil
890
- constrained_join_role = nil
891
- if (subtype = role_ref.role.object_type) != end_point
892
- debug :join, "Making subtyping join steps from #{subtype.name} to #{end_point.name}" do
893
- # There may be more than one supertyping level. Make the steps:
894
- subtyping_steps = subtype_join_steps(join, subtype, end_point)
895
- js = subtyping_steps[0]
896
- constrained_join_role = subtyping_steps[-1].input_join_role
897
-
898
- # Replace the constrained role and node with the supertype ones:
899
- end_node = join.all_join_node.detect{|jn| jn.object_type == end_point }
900
- projecting_jr = js.output_join_role
901
- end_role = js.fact_type.all_role.detect{|r| r.object_type == end_point }
902
- role_node = join.all_join_node.detect{|jn| jn.object_type == role_ref.role.object_type }
903
- end
877
+ debug :join, "#{constraint_type} join constraint #{name} over #{role_sequences.map{|rs|rs.describe}*', '}"
878
+
879
+ join = nil
880
+ debug :join, "#{constraint_type} join constraint #{name} constrains #{
881
+ end_points.zip(end_joins).map{|(p,j)| (p ? p.name : 'NULL')+(j ? ' & subtypes':'')}*', '
882
+ }#{
883
+ if role_sequences[0].all_role_ref.size > 1
884
+ ", joined over #{
885
+ sequence_join_over.zip(sequence_joined_roles).map{|o, roles|
886
+ (o ? o.name : '(none)') +
887
+ (roles ? " to (#{roles.map{|role| role ? role.fact_type.default_reading : 'null'}*','})" : '')
888
+ }*', '}"
889
+ else
890
+ ''
891
+ end
892
+ }" do
893
+
894
+ # There may be one join per role sequence:
895
+ role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles|
896
+ # Skip if there's no join here (sequence join nor end-point subset join)
897
+ role_refs = role_sequence.all_role_ref_in_order
898
+ if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]}
899
+ # No sequence join nor end_point join here
900
+ next
904
901
  end
905
902
 
906
- raise "Internal error: making illegal reference to join node" if end_role.object_type != end_node.object_type
907
- rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => end_role)
908
- projecting_jr ||= (constrained_join_role = @constellation.JoinRole(end_node, end_role))
909
- projecting_jr.role_ref = rr # Project this RoleRef
903
+ # A RoleSequence for the actual join end-points
904
+ replacement_rs = @constellation.RoleSequence(:new)
905
+
906
+ join = @constellation.Join(:new)
907
+ join_node = nil
908
+ join_role = nil
909
+ role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i|
910
910
 
911
- if join_over
912
- if !join_node # Create the JoinNode when processing the first role
913
- debug :join, "Join Node #{join.all_join_node.size} is over #{join_over.name}"
914
- join_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => join_over)
911
+ # Each role_ref is to an object joined via joined_role to join_node (or which will be the join_node)
912
+
913
+ # Create a join node for the actual end-point (supertype of the constrained roles)
914
+ end_point = end_points[i]
915
+ unless end_point
916
+ raise "In #{constraint_type} #{name}, there is a faulty or non-translated join"
915
917
  end
916
- debug :join, "Making join step from #{end_point.name} to #{join_over.name}" do
917
- rs = @constellation.RoleSequence(:new)
918
- # Detect the fact type over which we're joining (may involve objectification)
919
- raise "Internal error: making illegal reference to join node" if role_ref.role.object_type != role_node.object_type
920
- @constellation.RoleRef(rs, 0, :role => role_ref.role)
921
- role_jr = @constellation.JoinRole(role_node, role_ref.role)
922
- raise "Internal error: making illegal reference to join node" if joined_role.object_type != join_node.object_type
923
- @constellation.RoleRef(rs, 1, :role => joined_role)
924
- join_jr = @constellation.JoinRole(join_node, joined_role)
925
-
926
- js = @constellation.JoinStep(role_jr, join_jr, :fact_type => joined_role.fact_type)
927
- debug :join, "New join step #{js.describe}"
918
+ debug :join, "Join Node #{join.all_join_node.size} is for #{end_point.name}"
919
+ end_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => end_point)
920
+
921
+ # We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same:
922
+ role_node = end_node
923
+ end_role = role_ref.role
924
+
925
+ # Create subtyping join steps at the end-point, if needed:
926
+ projecting_jr = nil
927
+ constrained_join_role = nil
928
+ if (subtype = role_ref.role.object_type) != end_point
929
+ debug :join, "Making subtyping join steps from #{subtype.name} to #{end_point.name}" do
930
+ # There may be more than one supertyping level. Make the steps:
931
+ subtyping_steps = subtype_join_steps(join, subtype, end_point)
932
+ js = subtyping_steps[0]
933
+ constrained_join_role = subtyping_steps[-1].input_join_role
934
+
935
+ # Replace the constrained role and node with the supertype ones:
936
+ end_node = join.all_join_node.detect{|jn| jn.object_type == end_point }
937
+ projecting_jr = js.output_join_role
938
+ end_role = js.fact_type.all_role.detect{|r| r.object_type == end_point }
939
+ role_node = join.all_join_node.detect{|jn| jn.object_type == role_ref.role.object_type }
940
+ end
928
941
  end
929
- else
930
- debug :join, "Need join step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
931
- if (roles = role_ref.role.fact_type.all_role.to_a).size > 1
932
- # Here we have an end join (step already created) but no sequence join
933
- if join_node
934
- raise "Internal error: making illegal join step" if role_ref.role.object_type != role_node.object_type
935
- join_jr = @constellation.JoinRole(join_node, join_role)
942
+
943
+ raise "Internal error in #{constraint_type} #{name}: making illegal reference to join node, end role mismatch" if end_role.object_type != end_node.object_type
944
+ rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => end_role)
945
+ projecting_jr ||= (constrained_join_role = @constellation.JoinRole(end_node, end_role))
946
+ projecting_jr.role_ref = rr # Project this RoleRef
947
+
948
+ if join_over
949
+ if !join_node # Create the JoinNode when processing the first role
950
+ debug :join, "Join Node #{join.all_join_node.size} is over #{join_over.name}"
951
+ join_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => join_over)
952
+ end
953
+ debug :join, "Making join step from #{end_point.name} to #{join_over.name}" do
954
+ rs = @constellation.RoleSequence(:new)
955
+ # Detect the fact type over which we're joining (may involve objectification)
956
+ raise "Internal error in #{constraint_type} #{name}: making illegal reference to join node, object type mismatch" if role_ref.role.object_type != role_node.object_type
957
+ @constellation.RoleRef(rs, 0, :role => role_ref.role)
936
958
  role_jr = @constellation.JoinRole(role_node, role_ref.role)
937
- js = @constellation.JoinStep(join_jr, role_jr, :fact_type => role_ref.role.fact_type)
938
- roles -= [join_role, role_ref.role]
939
- roles.each do |incidental_role|
940
- jn = @constellation.JoinNode(join, join.all_join_node.size, :object_type => incidental_role.object_type)
941
- jr = @constellation.JoinRole(jn, incidental_role, :join_step => js)
942
- end
943
- else
944
- if role_sequence.all_role_ref.size > 1
945
- join_node = role_node
946
- join_role = role_ref.role
947
- else
948
- # There's no join in this role sequence, so we'd drop of fthe bottom without doing the right things. Why?
949
- # Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why.
950
- # 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)
959
+ raise "Internal error in #{constraint_type} #{name}: making illegal reference to join node, joined_role mismatch" if joined_role.object_type != join_node.object_type
960
+ @constellation.RoleRef(rs, 1, :role => joined_role)
961
+ join_jr = @constellation.JoinRole(join_node, joined_role)
962
+
963
+ js = @constellation.JoinStep(role_jr, join_jr, :fact_type => joined_role.fact_type)
964
+ debug :join, "New join step #{js.describe}"
965
+ end
966
+ else
967
+ debug :join, "Need join step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}"
968
+ if (roles = role_ref.role.fact_type.all_role.to_a).size > 1
969
+ # Here we have an end join (step already created) but no sequence join
970
+ if join_node
971
+ raise "Internal error in #{constraint_type} #{name}: making illegal join step" if role_ref.role.object_type != role_node.object_type
972
+ join_jr = @constellation.JoinRole(join_node, join_role)
951
973
  role_jr = @constellation.JoinRole(role_node, role_ref.role)
952
- js = nil
953
- role_ref.role.fact_type.all_role.each do |role|
954
- next if role == role_jr.role
955
- next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
956
- jn = @constellation.JoinNode(join, join.all_join_node.size, :object_type => role.object_type)
957
- jr = @constellation.JoinRole(jn, role)
958
- if js
959
- jr.join_step = js # Incidental role
960
- else
961
- js = @constellation.JoinStep(role_jr, jr, :fact_type => role_ref.role.fact_type)
974
+ js = @constellation.JoinStep(join_jr, role_jr, :fact_type => role_ref.role.fact_type)
975
+ roles -= [join_role, role_ref.role]
976
+ roles.each do |incidental_role|
977
+ jn = @constellation.JoinNode(join, join.all_join_node.size, :object_type => incidental_role.object_type)
978
+ jr = @constellation.JoinRole(jn, incidental_role, :join_step => js)
979
+ end
980
+ else
981
+ if role_sequence.all_role_ref.size > 1
982
+ join_node = role_node
983
+ join_role = role_ref.role
984
+ else
985
+ # There's no join in this role sequence, so we'd drop of fthe bottom without doing the right things. Why?
986
+ # Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why.
987
+ # 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)
988
+ role_jr = @constellation.JoinRole(role_node, role_ref.role)
989
+ js = nil
990
+ role_ref.role.fact_type.all_role.each do |role|
991
+ next if role == role_jr.role
992
+ next if role_sequence.all_role_ref.detect{|rr| rr.role == role}
993
+ jn = @constellation.JoinNode(join, join.all_join_node.size, :object_type => role.object_type)
994
+ jr = @constellation.JoinRole(jn, role)
995
+ if js
996
+ jr.join_step = js # Incidental role
997
+ else
998
+ js = @constellation.JoinStep(role_jr, jr, :fact_type => role_ref.role.fact_type)
999
+ end
962
1000
  end
963
1001
  end
964
1002
  end
1003
+ else
1004
+ # Unary fact type, make a Join Step from and to the constrained_join_role
1005
+ jr = @constellation.JoinRole(constrained_join_role.join_node, role_ref.role)
1006
+ js = @constellation.JoinStep(jr, jr, :fact_type => role_ref.role.fact_type)
965
1007
  end
966
- else
967
- # Unary fact type, make a Join Step from and to the constrained_join_role
968
- jr = @constellation.JoinRole(constrained_join_role.join_node, role_ref.role)
969
- js = @constellation.JoinStep(jr, jr, :fact_type => role_ref.role.fact_type)
970
1008
  end
971
1009
  end
972
- end
973
1010
 
974
- # Thoroughly check that this is a valid join
975
- join.validate
976
- debug :join, "Join has projected nodes #{replacement_rs.describe}"
1011
+ # Thoroughly check that this is a valid join
1012
+ join.validate
1013
+ debug :join, "Join has projected nodes #{replacement_rs.describe}"
977
1014
 
978
- # Constrain the replacement role sequence, which has the attached join:
979
- role_sequences[role_sequences.index(role_sequence)] = replacement_rs
1015
+ # Constrain the replacement role sequence, which has the attached join:
1016
+ role_sequences[role_sequences.index(role_sequence)] = replacement_rs
1017
+ end
1018
+ return true
980
1019
  end
1020
+ rescue => e
1021
+ $stderr.puts '// '+e.to_s
1022
+ return false
981
1023
  end
982
1024
 
983
1025
  end
984
1026
 
985
1027
  def read_exclusion_constraints
986
1028
  x_exclusion_constraints = @x_model.xpath("orm:Constraints/orm:ExclusionConstraint")
987
- debug :orm, "Reading exclusion constraints" do
1029
+ debug :orm, "Reading #{x_exclusion_constraints.size} exclusion constraints" do
988
1030
  x_exclusion_constraints.each{|x|
989
1031
  id = x['id']
990
1032
  name = x["Name"] || ''
@@ -1008,7 +1050,7 @@ module ActiveFacts
1008
1050
 
1009
1051
  next if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role
1010
1052
 
1011
- make_joins('exclusion', name+(x_mandatory ? '/'+x_mandatory['Name'] : ''), role_sequences)
1053
+ next unless make_joins('exclusion', name+(x_mandatory ? '/'+x_mandatory['Name'] : ''), role_sequences)
1012
1054
 
1013
1055
  ec = @constellation.SetExclusionConstraint(:new)
1014
1056
  ec.vocabulary = @vocabulary
@@ -1040,8 +1082,10 @@ module ActiveFacts
1040
1082
  )
1041
1083
  }
1042
1084
 
1043
- next if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role
1044
- make_joins('equality', name, role_sequences)
1085
+ # Role sequence missing; includes a derived fact type role
1086
+ next if role_sequences.compact.size != role_sequences.size
1087
+
1088
+ next unless make_joins('equality', name, role_sequences)
1045
1089
 
1046
1090
  ec = @constellation.SetEqualityConstraint(:new)
1047
1091
  ec.vocabulary = @vocabulary
@@ -1071,7 +1115,7 @@ module ActiveFacts
1071
1115
  )
1072
1116
  }
1073
1117
  next if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role
1074
- make_joins('subset', name, role_sequences)
1118
+ next unless make_joins('subset', name, role_sequences)
1075
1119
 
1076
1120
  ec = @constellation.SubsetConstraint(:new)
1077
1121
  ec.vocabulary = @vocabulary
@@ -1296,17 +1340,20 @@ module ActiveFacts
1296
1340
  bounds = x_shape['AbsoluteBounds']
1297
1341
  case shape_type = x_shape.name
1298
1342
  when 'FactTypeShape'
1299
- read_fact_type_shape diagram, x_shape, is_expanded, bounds, subject
1343
+ if subject
1344
+ read_fact_type_shape diagram, x_shape, is_expanded, bounds, subject
1345
+ # else REVISIT: probably a derived fact type
1346
+ end
1300
1347
  when 'ExternalConstraintShape', 'FrequencyConstraintShape'
1301
1348
  # REVISIT: The offset might depend on the constraint type. This is right for subset and other round ones.
1302
- position = convert_position(bounds, Gravity::NW, 31, 31)
1349
+ position = convert_position(bounds, Gravity::C)
1303
1350
  shape = @constellation.ConstraintShape(
1304
1351
  :new, :diagram => diagram, :position => position, :is_expanded => is_expanded,
1305
1352
  :constraint => subject
1306
1353
  )
1307
1354
  when 'RingConstraintShape'
1308
1355
  # REVISIT: The offset might depend on the ring constraint type. This is right for basic round ones.
1309
- position = convert_position(bounds, Gravity::NW, 31, 31)
1356
+ position = convert_position(bounds, Gravity::C)
1310
1357
  shape = @constellation.RingConstraintShape(
1311
1358
  :new, :diagram => diagram, :position => position, :is_expanded => is_expanded,
1312
1359
  :constraint => subject
@@ -1315,10 +1362,12 @@ module ActiveFacts
1315
1362
  when 'ModelNoteShape'
1316
1363
  # REVISIT: Add model notes
1317
1364
  when 'ObjectTypeShape'
1365
+ position = convert_position(bounds, Gravity::C)
1366
+ # $stderr.puts "#{subject.name}: bounds=#{bounds} -> position = (#{position.x}, #{position.y})"
1318
1367
  shape = @constellation.ObjectTypeShape(
1319
1368
  :new, :diagram => diagram, :position => position, :is_expanded => is_expanded,
1320
1369
  :object_type => subject,
1321
- :has_expanded_reference_mode => false # REVISIT
1370
+ :position => position
1322
1371
  )
1323
1372
  else
1324
1373
  raise "Unknown shape #{x_shape.name}"
@@ -1342,21 +1391,22 @@ module ActiveFacts
1342
1391
  when 'VerticalRotatedRight'; 'right'
1343
1392
  else nil
1344
1393
  end
1345
- # Position of a fact type is the top-left of the first role box
1346
- offs_x = 0
1347
- offs_y = 0
1348
- if fact_type.entity_type # If objectified, move right 12, down 24
1349
- offs_x += 12
1350
- offs_y += 24
1394
+
1395
+ # Position of a fact type is the centre of the row of role boxes
1396
+ offs_x = 11
1397
+ offs_y = -12
1398
+ if fact_type.entity_type
1399
+ offs_x -= 12
1400
+ offs_y -= 9 if !fact_type.entity_type.is_implied_by_objectification
1351
1401
  end
1352
1402
 
1353
- # count internal UC's, add 27 Y units for each:
1354
- iucs = fact_type.internal_presence_constraints.select{|uc| uc.max_frequency == 1 }
1355
- offs_y += iucs.size*27
1356
- position = convert_position(bounds, Gravity::NW, offs_x, offs_y)
1403
+ position = convert_position(bounds, Gravity::S, offs_x, offs_y)
1404
+
1405
+ # $stderr.puts "#{fact_type.describe}: bounds=#{bounds} -> position = (#{position.x}, #{position.y})"
1406
+
1357
1407
  debug :orm, "REVISIT: Can't place rotated fact type correctly on diagram yet" if rotation_setting
1358
1408
 
1359
- debug :orm, "fact type at #{position.x},#{position.y} has display_role_names_setting=#{display_role_names_setting.inspect}, rotation_setting=#{rotation_setting.inspect}, #{iucs.size} IUC's"
1409
+ debug :orm, "fact type at #{position.x},#{position.y} has display_role_names_setting=#{display_role_names_setting.inspect}, rotation_setting=#{rotation_setting.inspect}"
1360
1410
  shape = @constellation.FactTypeShape(
1361
1411
  :new,
1362
1412
  :diagram => diagram,
@@ -1386,9 +1436,9 @@ module ActiveFacts
1386
1436
  position = convert_position(xr_shape['AbsoluteBounds'])
1387
1437
  case xr_shape.name
1388
1438
  when 'ObjectifiedFactTypeNameShape'
1389
- @constellation.ObjectifiedFactTypeNameShape(shape, :diagram => diagram, :position => position, :is_expanded => false)
1439
+ @constellation.ObjectifiedFactTypeNameShape(shape, :shape_id => :new, :diagram => diagram, :position => position, :is_expanded => false)
1390
1440
  when 'ReadingShape'
1391
- @constellation.ReadingShape(:new, :diagram => diagram, :position => position, :is_expanded => false, :reading => fact_type.preferred_reading)
1441
+ @constellation.ReadingShape(shape, :shape_id => :new, :fact_type_shape=>shape, :diagram => diagram, :position => position, :is_expanded => false, :reading => fact_type.preferred_reading)
1392
1442
  when 'RoleNameShape'
1393
1443
  role = @by_id[xr_shape.xpath("ormDiagram:Subject")[0]['ref']]
1394
1444
  role_display = role_display_for_role(shape, x_role_display, role)
@@ -1403,7 +1453,7 @@ module ActiveFacts
1403
1453
  debug :orm, "Fact type '#{fact_type.preferred_reading.expand}' has #{xr_shape.name} for #{constraint.inspect}"
1404
1454
 
1405
1455
  role_display = role_display_for_role(shape, x_role_display, constraint.role)
1406
- debug :orm, "ValueConstraintShape is on #{role_ordinal}'th role (by #{x_role_display.size > 0 ? 'role_display' : 'fact roles'})"
1456
+ debug :orm, "ValueConstraintShape is on #{role_display.ordinal}'th role (by #{x_role_display.size > 0 ? 'role_display' : 'fact roles'})"
1407
1457
  @constellation.ValueConstraintShape(
1408
1458
  :new, :diagram => diagram, :position => position, :is_expanded => false,
1409
1459
  :constraint => constraint,
@@ -1418,16 +1468,21 @@ module ActiveFacts
1418
1468
  # Find or create the RoleDisplay for this role in this fact_type_shape, given (possibly empty) x_role_display nodes:
1419
1469
  def role_display_for_role(fact_type_shape, x_role_display, role)
1420
1470
  if x_role_display.size == 0
1421
- role_ordinal = fact_type_shape.fact_type.all_role.to_a.index(role)
1471
+ # There's no x_role_display, which means the roles are in displayed
1472
+ # the same order as in the fact type. However, we need a RoleDisplay
1473
+ # to attach a ReadingShape or ValueConstraintShape, so make them all.
1474
+ fact_type_shape.fact_type.all_role.each{|r| @constellation.RoleDisplay(fact_type_shape, r.ordinal, :role => r) }
1475
+ role_ordinal = fact_type_shape.fact_type.all_role_in_order.index(role)
1422
1476
  else
1423
1477
  role_ordinal = x_role_display.map{|rd| @by_id[rd['ref']]}.index(role)
1424
1478
  end
1425
1479
  role_display = @constellation.RoleDisplay(fact_type_shape, role_ordinal, :role => role)
1426
1480
  end
1427
1481
 
1428
- DIAGRAM_SCALE = 384
1429
- def convert_position(bounds, gravity = Gravity::NW, xoffs = 0, yoffs = 0)
1482
+ DIAGRAM_SCALE = 96*1.5
1483
+ def convert_position(bounds, gravity = Gravity::C, xoffs = 0, yoffs = 0)
1430
1484
  return nil unless bounds
1485
+ # Bounds is top, left, width, height in inches
1431
1486
  bf = bounds.split(/, /).map{|b|b.to_f}
1432
1487
  sizefrax = [
1433
1488
  [0, 0], [1, 0], [2, 0],
@@ -1435,8 +1490,8 @@ module ActiveFacts
1435
1490
  [0, 2], [1, 2], [2, 2],
1436
1491
  ]
1437
1492
 
1438
- x = (DIAGRAM_SCALE * bf[0]+bf[2]*sizefrax[gravity][0]/2).round + xoffs
1439
- y = (DIAGRAM_SCALE * bf[1]+bf[3]*sizefrax[gravity][1]/2).round + yoffs
1493
+ x = (DIAGRAM_SCALE * (bf[0]+bf[2]*sizefrax[gravity][0]/2)).round + xoffs
1494
+ y = (DIAGRAM_SCALE * (bf[1]+bf[3]*sizefrax[gravity][1]/2)).round + yoffs
1440
1495
  @constellation.Position(x, y)
1441
1496
  end
1442
1497