rtm-activerecord 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/DISCLAIMER +13 -0
  2. data/LICENSE +201 -0
  3. data/README +4 -0
  4. data/lib/rtm/activerecord/001_initial_schema.rb +119 -0
  5. data/lib/rtm/activerecord/association_and_role.rb +54 -0
  6. data/lib/rtm/activerecord/base.rb +83 -0
  7. data/lib/rtm/activerecord/connect.rb +106 -0
  8. data/lib/rtm/activerecord/io/from_xtm2.rb +266 -0
  9. data/lib/rtm/activerecord/io/from_xtm2_libxml.rb +97 -0
  10. data/lib/rtm/activerecord/io/to_jtm.rb +141 -0
  11. data/lib/rtm/activerecord/io/to_string.rb +130 -0
  12. data/lib/rtm/activerecord/io/to_xtm1.rb +162 -0
  13. data/lib/rtm/activerecord/io/to_xtm2.rb +163 -0
  14. data/lib/rtm/activerecord/io/to_yaml.rb +132 -0
  15. data/lib/rtm/activerecord/literal_index.rb +38 -0
  16. data/lib/rtm/activerecord/locators.rb +58 -0
  17. data/lib/rtm/activerecord/merging.rb +310 -0
  18. data/lib/rtm/activerecord/name_variant_occurrence.rb +86 -0
  19. data/lib/rtm/activerecord/persistent_code_output.rb +22 -0
  20. data/lib/rtm/activerecord/quaaxtm2rtm.rb +116 -0
  21. data/lib/rtm/activerecord/quaaxtm2rtmviews.rb +137 -0
  22. data/lib/rtm/activerecord/set_wrapper.rb +101 -0
  23. data/lib/rtm/activerecord/sugar/association/hash_access.rb +22 -0
  24. data/lib/rtm/activerecord/sugar/role/counterparts.rb +56 -0
  25. data/lib/rtm/activerecord/sugar/topic/characteristics.rb +15 -0
  26. data/lib/rtm/activerecord/sugar/topic/counterparts.rb +18 -0
  27. data/lib/rtm/activerecord/sugar/topic/hash_access.rb +78 -0
  28. data/lib/rtm/activerecord/sugar/topic/identifier_direct.rb +14 -0
  29. data/lib/rtm/activerecord/sugar/topic/predefined_associations.rb +53 -0
  30. data/lib/rtm/activerecord/tm_construct.rb +66 -0
  31. data/lib/rtm/activerecord/tm_delegator.rb +417 -0
  32. data/lib/rtm/activerecord/tm_set_delegator.rb +226 -0
  33. data/lib/rtm/activerecord/tmdm.rb +309 -0
  34. data/lib/rtm/activerecord/topic.rb +204 -0
  35. data/lib/rtm/activerecord/topic_map.rb +435 -0
  36. data/lib/rtm/activerecord/traverse_associations.rb +90 -0
  37. data/lib/rtm/activerecord.rb +106 -0
  38. metadata +120 -0
@@ -0,0 +1,204 @@
1
+ # Copyright: Copyright 2009 Topic Maps Lab, University of Leipzig.
2
+ # License: Apache License, Version 2.0
3
+
4
+ module RTM::AR
5
+ class Topic < Construct
6
+ include RTM::Topic
7
+ wrapper_cache
8
+
9
+ # parent :topic_map
10
+ alias :parent :topic_map
11
+ property_set :subject_identifiers, :aka => [:si,:sid], :type => :SubjectIdentifier, :wrap => true,
12
+ #:create => :subject_identifier, :create_aka => [:csi,:csid],
13
+ #:create_args => [{:name => :reference, :type => :String}]
14
+ :add => :subject_identifier, :remove => :subject_identifier
15
+ alias :getSubjectIdentifiers :subject_identifiers
16
+
17
+ property_set :subject_locators, :aka => [:sl,:slo], :type => :SubjectLocator, :wrap => true,
18
+ #:create => :subject_locator, :create_aka => [:csl, :cslo],
19
+ #:create_args => [{:name => :reference, :type => :String}]
20
+ :add => :subject_locator, :remove => :subject_locator
21
+ alias :getSubjectLocators :subject_locators
22
+
23
+ property :reified, :computable => true, :type => :Reifiable, :wrap => true
24
+
25
+ property_set :names, :aka => [:n, :topic_names], :type => :Name, :wrap => true,
26
+ :create => :name_internal, # :create_aka => [:create_topic_name, :cn],
27
+ :create_args => [ {:name => :value, :type => :String}, {:name => :type, :type => :Topic, :optional => true}, {:name => :scope, :type => :Collection} ]
28
+
29
+ alias :names_by :names # intermediate solution, check real TMAPI usage
30
+
31
+
32
+ def create_name(arg1,arg2 = :nothing, arg3 = :nothing)
33
+ if (arg2 == :nothing) && (arg3 == :nothing) #arg1 = value
34
+ raise "create_name(value): value must be a String" unless arg1.is_a?(String)
35
+ return create_name_internal(:value => arg1, :type => self.parent._topic_by_locator!(RTM::PSI[:name_type]))
36
+ end
37
+ if (arg3 == :nothing)
38
+ if arg2.is_a?(Array) #arg1 = value, arg2 = scope
39
+ raise "create_name(value, scope): value must be a String" unless arg1.is_a?(String)
40
+ arg2.each do |theme|
41
+ raise "create_name(value, scope): scope must be an Array of Topics/Topic-References" unless (theme.is_a?(String) || theme.is_a?(RTM::Topic) || theme.is_a?(RTM::Locator))
42
+ end
43
+ return arg2.empty? ? create_name_internal(:value => arg1) : create_name_internal(:value => arg1, :scope => self.parent._topic_by_locator!(arg2))
44
+ elsif arg2.is_a?(String) #arg1 = type, arg2 = value
45
+ raise "create_name(type, value): type must be a Topic/Topic-Reference" unless (arg1.is_a?(String) || arg1.is_a?(RTM::Topic) || arg1.is_a?(RTM::Locator))
46
+ return create_name_internal(:type => self.parent._topic_by_locator!(arg1), :value => arg2)
47
+ else
48
+ raise "create_name(?,?): arguments don't match"
49
+ end
50
+ end
51
+ #arg1 = type, arg2 = value, arg3 = scope
52
+ raise "create_name(type, value, scope): type must be a Topic/Topic-Reference" unless (arg1.is_a?(String) || arg1.is_a?(RTM::Topic) || arg1.is_a?(RTM::Locator))
53
+ raise "create_name(type, value, scope): value must be a String" unless arg2.is_a?(String)
54
+ raise "create_name(type, value, scope): scope must be an Array" unless arg3.is_a?(Array)
55
+ arg3.each do |theme|
56
+ raise "create_name(type, value, scope): scope must be an Array of Topics/Topic-References" unless (theme.is_a?(String) || theme.is_a?(RTM::Topic) || theme.is_a?(RTM::Locator))
57
+ end
58
+ return create_name_internal(:type => self.parent._topic_by_locator!(arg1), :value => arg2, :scope => self.parent._topic_by_locator!(arg3))
59
+ end
60
+ alias :cn :create_name
61
+ alias :create_topic_name :create_name
62
+
63
+ property_set :occurrences, :aka => :o, :type => :Occurrence, :wrap => true,
64
+ :create => :occurrence_internal, :create_aka => :co,
65
+ :create_args => [ {:name => :type, :type => :Topic}, {:name => :value, :type => [:String, :Locator]}, {:name => :scope, :type => :Collection} ]
66
+
67
+ alias :occurrences_by :occurrences # intermediate solution, check real TMAPI usage
68
+
69
+ def create_occurrence(type,value,params={})
70
+ if params[:scope]
71
+ if value.is_a?(RTM::Locator)
72
+ return create_occurrence_internal(topic_map.get!(type),value,topic_map._topic_by_locator!(params[:scope]), :datatype => RTM::PSI[:IRI])
73
+ elsif !params[:datatype]
74
+ return create_occurrence_internal(topic_map.get!(type),value,topic_map._topic_by_locator!(params[:scope]))
75
+ else # datatype given, no locator
76
+ return create_occurrence_internal(topic_map.get!(type),value,topic_map.create_locator(params[:datatype]),topic_map._topic_by_locator!(params[:scope]))
77
+ end
78
+ else #no scope
79
+ if value.is_a?(RTM::Locator)
80
+ return create_occurrence_internal(topic_map.get!(type),value, :datatype => RTM::PSI[:IRI])
81
+ elsif !params[:datatype]
82
+ return create_occurrence_internal(topic_map.get!(type),value, params)
83
+ else #datatype given, no locator
84
+ return create_occurrence_internal(topic_map.get!(type),value,topic_map.create_locator(params[:datatype]))
85
+ end
86
+ end
87
+ end
88
+
89
+ def characteristics(*args)
90
+ names(*args).to_a + occurrences(*args).to_a
91
+ end
92
+ alias :children :characteristics
93
+ alias :characteristics_by :characteristics # intermediate solution, check real TMAPI usage
94
+
95
+ property_set :roles, :aka => [:r, :roles_played, :association_roles], :computable => true, :type => :Role, :wrap => true
96
+
97
+ def filter_roles(*args)
98
+ puts
99
+ puts "topic filter roles:"
100
+ puts args.inspect
101
+ if args.size == 1
102
+ a1 = args.shift
103
+ # if a1 == :any
104
+ # a1 = nil
105
+ # end
106
+ role_type = topic_map.get(a1)
107
+ return self.roles unless role_type
108
+ if role_type.respond_to?(:each)
109
+ query_conditions = (["ttype_id = ?"] * role_type.size).join(" OR ")
110
+ puts
111
+ puts "qq:"
112
+ puts query_conditions
113
+ puts role_type.map{|rt| rt.id}
114
+ puts
115
+ return self.roles.find(:all,:conditions => [query_conditions, role_type.map{|rt| rt.id}])
116
+ else
117
+ return self.roles.find(:all,:conditions => ["ttype_id = ?", role_type.id])
118
+ end
119
+ elsif args.size == 2
120
+ role_type = args.shift
121
+ # if role_type == :any
122
+ # role_type = nil
123
+ # end
124
+ role_type = topic_map.get(role_type)
125
+
126
+ assoc_type = args.shift
127
+ # if assoc_type == :any
128
+ # assoc_type = nil
129
+ # end
130
+ assoc_type = topic_map.get(assoc_type)
131
+
132
+ if role_type && assoc_type
133
+ puts "trying to return topic.roles for #{role_type.inspect} and #{assoc_type.inspect}"
134
+ res = self.roles.find(:all, :conditions => ["roles.ttype_id = ? and associations.ttype_id = ?", role_type.id, assoc_type.id])
135
+ puts res.inspect
136
+ return res
137
+ elsif assoc_type
138
+ return self.roles.find(:all, :conditions => ["associations.ttype_id = ?", assoc_type.id])
139
+ elsif role_type
140
+ return self.roles.find(:all,:conditions => ["ttype_id = ?", role_type.id])
141
+ else
142
+ warn("Topic#roles got 2 arguments but doesn't know how to handle (or referenced topics where not found)")
143
+ end
144
+ end
145
+ false
146
+ end
147
+
148
+ property_set :associations, :aka => [:a, :associations_played], :type => :Association, :wrap => true
149
+
150
+ property_set :scoped, :aka => :scoped_objects, :type => :Construct, :wrap => true
151
+ property_set :scoped_associations, :type => :Association, :wrap => true
152
+ property_set :scoped_names, :aka => :scoped_topic_names, :type => :Name, :wrap => true
153
+ property_set :scoped_variants, :type => :Variant, :wrap => true
154
+ property_set :scoped_occurrences, :type => :Occurrence, :wrap => true
155
+
156
+ property_set :typed, :aka => :typed_objects, :type => :Construct, :wrap => true
157
+ property_set :typed_associations, :aka => [:ta,:associations_typed,:at], :type => :Association, :wrap => true
158
+ property_set :typed_roles, :aka => [:rt,:roles_typed,:association_roles_typed,:art,:rt], :type => :Role, :wrap => true
159
+ property_set :typed_names, :aka => [:nt,:names_typed,:topic_names_typed,:tnt,:nt], :type => :Name, :wrap => true
160
+ property_set :typed_occurrences, :aka => [:to,:occurrences_typed,:ot], :type => :Occurrence, :wrap => true
161
+
162
+ def id
163
+ __getobj__.id
164
+ end
165
+
166
+ # class:Topic equality, http://www.isotopicmaps.org/sam/sam-model/#d0e1029
167
+ #
168
+ # This method is all crap. These ActiveRecord Arrays can't be intersected,
169
+ # so I map the identifiers to their reference which seems long winded.
170
+ # Still I find it better than using their ID in the long.
171
+ #
172
+ def ==(o)
173
+ return false unless o
174
+ return false unless o.respond_to?(:subject_identifiers)
175
+ return false unless o.respond_to?(:subject_locators)
176
+ return false unless o.respond_to?(:names)
177
+ # Two topic items are equal if they have:
178
+ # * at least one equal string in their [subject identifiers] properties,
179
+ # -> test if intersection are > 0
180
+ my_si = self.subject_identifiers.map{|si| si.reference}
181
+ ot_si = o.subject_identifiers.map{|si| si.reference}
182
+ return true if ( my_si & ot_si).size > 0
183
+
184
+ # * at least one equal string in their [item identifiers] properties,
185
+ my_ii = self.item_identifiers.map{|ii| ii.reference}
186
+ ot_ii = o.item_identifiers.map{|ii| ii.reference}
187
+ return true if (my_ii & ot_ii).size > 0
188
+
189
+ # * at least one equal string in their [subject locators] properties,
190
+ my_sl = self.subject_locators.map{|sl| sl.reference}
191
+ ot_sl = o.subject_locators.map{|sl| sl.reference}
192
+ return true if (my_sl & ot_sl).size > 0
193
+
194
+ # * an equal string in the [subject identifiers] property of the one topic item and the [item identifiers] property of the other, or
195
+ return true if (my_si & ot_ii).size > 0
196
+ return true if (my_ii & ot_si).size > 0
197
+
198
+ # * the same information item in their [reified] properties.
199
+ return true if self.reified != nil && o.reified != nil && (self.reified == o.reified)
200
+ # ... otherwise
201
+ false
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,435 @@
1
+ # Copyright: Copyright 2009 Topic Maps Lab, University of Leipzig.
2
+ # License: Apache License, Version 2.0
3
+
4
+ module RTM::AR
5
+ class TopicMap < Reifiable
6
+ include RTM::TopicMap
7
+
8
+ def close
9
+ end
10
+
11
+ wrapper_cache
12
+ property_set :topics, :aka => :t, :type => :Topic, :wrap => true,
13
+ :create => :topic, :create_aka => :ct
14
+
15
+ property_set :associations, :aka => [:a, :assocs], :type => :Association, :wrap => true,
16
+ :create => :association_internal, :create_aka => :ca,
17
+ :create_args => [{:name => :type, :type => :Topic}]
18
+
19
+ def create_association(type, scope = :default)
20
+ raise "type must be a Topic or Topic-Reference" unless type.is_a?(RTM::Topic) || type.is_a?(String) || type.is_a?(RTM::Locator)
21
+ if scope.eql? :default
22
+ return create_association_internal(get!(type))
23
+ else
24
+ raise "scope must be an Array" unless scope.is_a?(Array)
25
+ a = create_association_internal(get!(type))
26
+ scope.each {|s| a.add_scope(get!(s))}
27
+ return a
28
+ end
29
+ end
30
+
31
+ delegate :base_locator
32
+
33
+ property_set :association_types, :aka => :at, :type => :Topic, :wrap => true
34
+ property_set :role_types, :aka => [:association_role_types,:art,:rt], :type => :Topic, :wrap => true
35
+ property_set :name_types, :aka => [:topic_name_types,:tnt,:nt], :type => :Topic, :wrap => true
36
+ property_set :occurrence_types, :aka => :ot, :type => :Topic, :wrap => true
37
+
38
+ property_set :names, :aka => [:topic_names,:n], :type => :Name, :wrap => :true
39
+ property_set :occurrences, :aka => :o, :type => :Occurrence, :wrap => :true
40
+ property_set :roles, :aka => [:association_roles, :r], :type => :Role, :wrap => :true
41
+
42
+ def types
43
+ topics.select{|t| t.instances.size > 0}
44
+ end
45
+ alias :topic_types :types
46
+ def fast_types
47
+
48
+ end
49
+
50
+ # This fetches all topics who have no topic-instances (but they might be types for associations etc.).
51
+ # See indivduals
52
+ def non_types
53
+ topics.select{|t| t.instances.size == 0}
54
+ end
55
+
56
+ # This fetches all topics which are not type for something else (including topics, associations etc.).
57
+ # See non_types
58
+ def individuals
59
+ non_types.select{|t| t.associations_typed.size==0 && t.roles_typed.size==0 && t.names_typed.size==0 && t.occurrences_typed.size==0}
60
+ end
61
+ def instances
62
+ topics.select{|t| t.types.size > 0}
63
+ end
64
+ def non_instances
65
+ topics.reject{|t| t.types.size > 0}
66
+ end
67
+ def internal_occurrences
68
+ occurrences.select{|o| o.datatype != RTM::PSI[:IRI]}
69
+ end
70
+ def external_occurrences
71
+ occurrences.select{|o| o.datatype == RTM::PSI[:IRI]}
72
+ end
73
+
74
+ def self.create(base_locator, params={})
75
+ bl_uri = URI.parse(base_locator)
76
+ raise "The locator for the Topic Map must be absolute! \"#{base_locator}\" is not an absolute locator." unless bl_uri.absolute?
77
+ tm = self.wrap(TMDM::TopicMap.find_or_create_by_base_locator(base_locator))
78
+ yield tm if block_given?
79
+ tm
80
+ end
81
+
82
+ def self.topic_maps
83
+ TopicMaps.wrap(TMDM::TopicMap.find(:all))
84
+ end
85
+
86
+ # Resolves an IRI or fragment relative to the base_locator of this topic_map or an optional given alternative_base_locator
87
+ #
88
+ # Absolute IRIs are taken as is.
89
+ #
90
+ # Relative IRIs:
91
+ # If the base_locator is a directory (ends with "/") it is appended like a file, i.e. directly
92
+ # If the base_locator is a file it is appended as a fragment (with # in between)
93
+ def resolve(obj,alternative_base_locator=nil)
94
+ uri = obj.to_s
95
+ # TODO uri = URI.decode(obj.to_s) # this InvalidURIError somethimes :(
96
+ begin
97
+ uri_uri = URI.parse(uri)
98
+ rescue URI::InvalidComponentError => ice
99
+ warn "Catched an URI::InvalidComponentError for URI: #{uri}, message was \"#{ice.message}\"\n" +
100
+ "Will continue using the UNRESOLVED IRI, claiming it is absolute."
101
+ return uri
102
+ end
103
+ if uri_uri.absolute?
104
+ return uri_uri.to_s
105
+ end
106
+
107
+ uri = uri[1..-1] if uri[0] == "#"[0]
108
+ bl = alternative_base_locator || base_locator
109
+ if bl[-1,1] == "/"
110
+ return bl + uri
111
+ else
112
+ return bl + "#" + uri
113
+ end
114
+ end
115
+
116
+ #private
117
+
118
+ def _item_identifier(iid)
119
+ __getobj__.locators.find_by_reference(resolve(iid)) # doesn't work :( --> , :include => [ :construct ])
120
+ end
121
+ def _item_identifier!(iid)
122
+ __getobj__.locators.find_or_create_by_reference(resolve(iid)) # doesn't work :( --> , :include => [ :construct ])
123
+ end
124
+
125
+ def _subject_identifier(iid)
126
+ __getobj__.subject_identifiers.find_by_reference(iid, :include => :topic)
127
+ end
128
+ def _subject_identifier!(iid)
129
+ __getobj__.subject_identifiers.find_or_create_by_reference(iid, :include => :topic)
130
+ end
131
+
132
+ def _subject_locator(iid)
133
+ __getobj__.subject_locators.find_by_reference(iid, :include => :topic)
134
+ end
135
+ def _subject_locator!(iid)
136
+ __getobj__.subject_locators.find_or_create_by_reference(iid, :include => :topic)
137
+ end
138
+
139
+
140
+ # internal helper for by_item_identifier, doesn't wrap result
141
+ def _by_item_identifier(iid)
142
+ iid = RTM::LocatorHelpers.iid2iri(iid) if RTM::LocatorHelpers.is_a_iid?(iid)
143
+ ii = __getobj__.locators.find_by_reference(resolve(iid.to_s.sub(/^(\^|ii:)\s*/,""))) #, :include => [ :construct ])
144
+ return ii.construct if ii
145
+ nil
146
+ end
147
+ def _topic_by_item_identifier(iid)
148
+ t = _by_item_identifier(iid)
149
+ return nil unless t
150
+ return t if t.class.name =~ /::Topic$/ # only return something if the thing is a topic
151
+ nil
152
+ end
153
+
154
+ # internal helper for topic_by_item_identifier!, doesn't wrap result
155
+ def _topic_by_item_identifier!(iid)
156
+ iid = RTM::LocatorHelpers.iid2iri(iid) if RTM::LocatorHelpers.is_a_iid?(iid)
157
+ ii = __getobj__.locators.find_or_create_by_reference(resolve(iid.to_s.sub(/^(\^|ii:)\s*/,"")))#, :include => [ :construct ])
158
+ return ii.construct if ii.construct && ii.construct_type =~ /::Topic$/
159
+ top2 = _topic_by_subject_identifier(resolve(iid))
160
+ if top2
161
+ ii.construct = top2
162
+ else
163
+ ii.construct = create_topic.__getobj__
164
+ end
165
+ ii.save
166
+ ii.construct
167
+ end
168
+ # internal helper for topic_by_subject_identifier, doesn't wrap result
169
+ def _topic_by_subject_identifier(sid)
170
+ si = __getobj__.subject_identifiers.find_by_reference(resolve(sid.to_s.sub(/^(si:)\s*/,"")), :include => [ :topic ])
171
+ return si.topic if si
172
+ nil
173
+ end
174
+ # internal helper for topic_by_subject_identifier!, doesn't wrap result
175
+ def _topic_by_subject_identifier!(sid)
176
+ si = __getobj__.subject_identifiers.find_or_create_by_reference(resolve(sid.to_s.sub(/^(si:)\s*/,"")), :include => [ :topic ])
177
+ return si.topic if si.topic
178
+ begin
179
+ top2 = _by_item_identifier(sid)
180
+ rescue ActiveRecord::RecordNotFound => rnf
181
+ si.topic = create_topic.__getobj__
182
+ else
183
+ if top2 && top2.respond_to?(:subject_identifiers)
184
+ si.topic = top2
185
+ else
186
+ si.topic = create_topic.__getobj__
187
+ end
188
+ end
189
+ si.save
190
+ si.topic
191
+ end
192
+
193
+ # internal helper for topic_by_subject_locator, doesn't wrap result
194
+ def _topic_by_subject_locator(slo)
195
+ sl = __getobj__.subject_locators.find_by_reference(resolve(RTM::LocatorHelpers.slo2iri(slo))) #, :include => [ :topic ])
196
+ return sl.topic if sl
197
+ nil
198
+ end
199
+
200
+ # internal helper for topic_by_subject_locator!, doesn't wrap result
201
+ def _topic_by_subject_locator!(slo)
202
+ sl = __getobj__.subject_locators.find_or_create_by_reference(resolve(RTM::LocatorHelpers.slo2iri(slo)), :include => [ :topic ])
203
+ return sl.topic if sl.topic
204
+ sl.topic = create_topic.__getobj__
205
+ sl.save
206
+ sl.topic
207
+ end
208
+ def _topic_by_locator(iri)
209
+ raise "Locator may not be nil" unless iri
210
+ return iri if iri.is_a?(RTM::AR::TMDM::Topic)
211
+ return iri.__getobj__ if iri.is_a?(RTM::AR::Topic)
212
+ if iri.is_a?(Array)
213
+ return iri.map{|i| _topic_by_locator(i)}
214
+ end
215
+ if RTM::LocatorHelpers.is_a_slo?(iri)
216
+ _topic_by_subject_locator(iri)
217
+ elsif RTM::LocatorHelpers.is_a_iid?(iri)
218
+ _topic_by_item_identifier(iri)
219
+ #elsif URI.parse(iri).absolute?
220
+ # _topic_by_subject_identifier(iri)
221
+ else
222
+ _topic_by_subject_identifier(iri)
223
+ end
224
+ end
225
+
226
+ def _topic_by_locator!(iri)
227
+ raise "Locator may not be nil" unless iri
228
+ return iri if iri.is_a?(RTM::AR::TMDM::Topic)
229
+ return iri.__getobj__ if iri.is_a?(RTM::AR::Topic)
230
+ if iri.is_a?(Array)
231
+ return iri.map{|i| _topic_by_locator!(i)}
232
+ end
233
+ if RTM::LocatorHelpers.is_a_slo?(iri)
234
+ _topic_by_subject_locator!(iri)
235
+ elsif RTM::LocatorHelpers.is_a_iid?(iri)
236
+ _topic_by_item_identifier!(iri)
237
+ #elsif RTM::LocatorHelpers.is_a_sid?(iri)
238
+ # _topic_by_subject_identifier!(iri)
239
+ else
240
+ _topic_by_subject_identifier!(iri)
241
+ end
242
+ end
243
+
244
+ %w[topic association role name occurrence variant].each do |x|
245
+ eval <<-EOI
246
+ def _#{x}_by_id(id)
247
+ __getobj__.#{x}s.find(id)
248
+ end
249
+ EOI
250
+ end
251
+ public
252
+ # returns an item identifier from the topic_map if it exists
253
+ def item_identifier(iid)
254
+ ItemIdentifier.wrap(_item_identifier(iid))
255
+ end
256
+ # returns an item identififier from the topic_map or creates it if it doesn't exist
257
+ def item_identifier!(iid)
258
+ ItemIdentifier.wrap(_item_identifier!(iid))
259
+ end
260
+ alias :create_locator :item_identifier!
261
+
262
+ # Returns a topic map construct by it's item identifier or nil if not found.
263
+ # It's the equivalent to TMAPI's TopicMapObjectIndex.getTopicMapObjectBySourceLocator
264
+ def by_item_identifier(iid)
265
+ Construct.wrap(_by_item_identifier(iid))
266
+ end
267
+ alias :get_construct_by_item_identifier :by_item_identifier
268
+
269
+ # Returns a topic by it's item identifier. The topic will be created if not found.
270
+ def topic_by_item_identifier!(iid)
271
+ Topic.wrap(_topic_by_item_identifier!(iid))
272
+ end
273
+ alias :create_topic_by_item_identifier :topic_by_item_identifier!
274
+
275
+ # Returns a topic by it's subject identifier or nil if not found.
276
+ # It's the equivalent to TMAPI's TopicsIndex.getTopicBySubjectIdentifier.
277
+ def topic_by_subject_identifier(sid)
278
+ Topic.wrap(_topic_by_subject_identifier(sid))
279
+ end
280
+ alias :get_topic_by_subject_identifier :topic_by_subject_identifier
281
+
282
+ # returns a topic by it's subject identifier. The topic will be created if not found.
283
+ def topic_by_subject_identifier!(sid)
284
+ Topic.wrap(_topic_by_subject_identifier!(sid))
285
+ end
286
+ alias :create_topic_by_subject_identifier :topic_by_subject_identifier!
287
+
288
+ # Returns a topic by it's subject locator or nil if not found.
289
+ # It's the equivalent to TMAPI's TopicsIndex.getTopicBySubjectLocator
290
+ def topic_by_subject_locator(slo)
291
+ Topic.wrap(_topic_by_subject_locator(slo))
292
+ end
293
+ alias :get_topic_by_subject_locator :topic_by_subject_locator
294
+
295
+ # returns a topic by it's subject locator. The topic will be created if not found.
296
+ def topic_by_subject_locator!(slo)
297
+ Topic.wrap(_topic_by_subject_locator!(slo))
298
+ end
299
+ alias :create_topic_by_subject_locator :topic_by_subject_locator!
300
+
301
+ # Gets a topic from this topic map using its (relative) item identifier,
302
+ # its (absolute) subject identifier or its (absolute and by "=" prepended) subject locator)
303
+ #
304
+ # Returns nil if the Topic does not exist.
305
+ #
306
+ # It's also possible to pass a number, which is used to fetch a topic using the internal id. In this case, an exception is thrown, if the topic does not exist.
307
+ #
308
+ def topic_by_locator(iri)
309
+ return nil unless iri
310
+ return iri if iri.is_a? Topic
311
+ return topic_by_id(iri) if iri.is_a? Integer
312
+ if iri.is_a?(Array)
313
+ return iri.map{|i| topic_by_locator(i)}
314
+ end
315
+ # @tblc ||= {}
316
+ # t = @tblc[iri]
317
+ # return t if t
318
+ Topic.wrap(_topic_by_locator(iri)) # t = ...
319
+ # @tblc[iri] = t if t
320
+ # t
321
+ end
322
+ alias :get :topic_by_locator
323
+
324
+ # Gets a topic from this topic map using its (relative) item identifier,
325
+ # its (absolute) subject identifier or its (absolute and by "=" prepended) subject locator)
326
+ #
327
+ # If there is no topic with this item identifier, subject identifier or subject locator, it is created.
328
+ # Please be aware, the creation does not work with the internal (numeric) ids.
329
+ #
330
+ def topic_by_locator!(iri)
331
+ return nil unless iri
332
+ return iri if iri.is_a? Topic
333
+ return topic_by_id(iri) if iri.is_a? Integer
334
+ if iri.is_a?(Array)
335
+ return iri.map{|i| topic_by_locator!(i)}
336
+ end
337
+ # @tblc ||= {}
338
+ # t = @tblc[iri]
339
+ # return t if t
340
+ Topic.wrap(_topic_by_locator!(iri)) # t = ...
341
+ # @tblc[iri] = t
342
+ # t
343
+ end
344
+ alias :get! :topic_by_locator!
345
+
346
+ # Assumes identifier is an IRI (Locator or String) or an Array of IRIs.
347
+ # Returns an existing or created Topic or an Array of Topics according
348
+ # to the nature of the IRI.
349
+ #
350
+ # :call-seq:
351
+ # create_topic_by(identifier) -> Topic
352
+ # create_topic_by(identifier-Array) -> Array of Topics
353
+ #
354
+ def create_topic_by(identifier)
355
+ case identifier
356
+ when RTM::Locator
357
+ return create_topic_by_subject_identifier(identifier)
358
+ when String
359
+ reroute_for_create(identifier)
360
+ when Array
361
+ return identifier.map{|i| create_topic_by(i)}
362
+ else
363
+ return nil
364
+ end
365
+ end
366
+
367
+ # Identifies the identifier as item identifier, subject locator or
368
+ # subject identifier and calls create_topic_by_.. accordingly. Creates
369
+ # new topic if topic does not exist yet. Returns the topic.
370
+ #
371
+ # :call-seq:
372
+ # reroute_for_create(identifier) -> Topic
373
+ #
374
+ def reroute_for_create(identifier)
375
+ case identifier
376
+ when /^(\^|ii:)\s*(.*)/ #identifiers starts with ^ or ii:
377
+ create_topic_by_item_identifier(create_locator($2))
378
+ when /^(=|sl:)\s*(.*)/ #identifier starts with = or sl:
379
+ create_topic_by_subject_locator(create_locator($2))
380
+ when /^(si:)\s*(.*)/ #identifier starts with si:
381
+ create_topic_by_subject_identifier(create_locator($2))
382
+ else #identifier does not start with a special character
383
+ create_topic_by_subject_identifier(create_locator(identifier.lstrip))#lstrip: leading whitespace chars removed
384
+ end
385
+ end
386
+
387
+ # Identifies the identifier as item identifier, subject locator or
388
+ # subject identifier and calls get_construct/topic_by_.. accordingly.
389
+ # Returns the topic.
390
+ #
391
+ # :call-seq:
392
+ # reroute_for_get(identifier) -> Topic
393
+ #
394
+ def reroute_for_get(identifier)
395
+ case identifier
396
+ when /^(si:|sl:|ii:|=|^)$/
397
+ return nil
398
+ when /^(\^|ii:)\s*(.*)/ #identifiers starts with ^ or ii:
399
+ get_construct_by_item_identifier(create_locator($2))
400
+ when /^(=|sl:)\s*(.+)/ #identifier starts with = or sl:
401
+ get_topic_by_subject_locator(create_locator($2))
402
+ when /^(si:)\s*(.+)/ #identifier starts with si:
403
+ get_topic_by_subject_identifier(create_locator($2))
404
+ else #identifier does not start with a special character
405
+ get_topic_by_subject_identifier(create_locator(identifier.lstrip)) #lstrip: leading whitespace chars removed
406
+ end
407
+ end
408
+
409
+ %w[topic association role name occurrence variant].each do |x|
410
+ eval <<-EOI
411
+ def #{x}_by_id(id)
412
+ #{x.camelize}.wrap(_#{x}_by_id(id))
413
+ end
414
+ alias :#{x[0].chr}id :#{x}_by_id
415
+ EOI
416
+ end
417
+
418
+ # I am not sure if this is at all correct. TMDM doesn't say a word about it
419
+ # and the approach to compare topic maps is CXTM. I chose this because we
420
+ # should (at least at the time of writing) have only one topic with a given
421
+ # base locator in RTM. If you don't like it, drop me a mail and explain why
422
+ # and propose something better.
423
+ equality [:base_locator]
424
+
425
+ def children
426
+ topics.to_a + associations.to_a
427
+ end
428
+
429
+ def literal_index
430
+ LiteralIndex.new(self)
431
+ end
432
+ #def type_instance_index
433
+ #end
434
+ end
435
+ end