module RTM::AR class TopicMap < Reifiable include RTM::TopicMap wrapper_cache property_set :topics, :aka => :t, :type => :Topic, :wrap => true, :create => :topic, :create_aka => :ct property_set :associations, :aka => [:a, :assocs], :type => :Association, :wrap => true, :create => :association, :create_aka => :ca, :create_args => [{:name => :type, :type => :Topic}] delegate :base_locator property_set :association_types, :aka => :at, :type => :Topic, :wrap => true property_set :association_role_types, :aka => [:role_types,:art,:rt], :type => :Topic, :wrap => true property_set :topic_name_types, :aka => [:name_types,:tnt,:nt], :type => :Topic, :wrap => true property_set :occurrence_types, :aka => :ot, :type => :Topic, :wrap => true property_set :topic_names, :aka => [:names,:n], :type => :TopicName, :wrap => :true property_set :occurrences, :aka => :o, :type => :Occurrence, :wrap => :true property_set :association_roles, :aka => [:roles, :r], :type => :AssociationRole, :wrap => :true def types topics.select{|t| t.instances.size > 0} end alias :topic_types :types def fast_types end # This fetches all topics who have no topic-instances (but they might be types for associations etc.). # See indivduals def non_types topics.select{|t| t.instances.size == 0} end # This fetches all topics which are not type for something else (including topics, associations etc.). # See non_types def individuals non_types.select{|t| t.associations_typed.size==0 && t.roles_typed.size==0 && t.names_typed.size==0 && t.occurrences_typed.size==0} end def instances topics.select{|t| t.types.size > 0} end def non_instances topics.reject{|t| t.types.size > 0} end def internal_occurrences occurrences.select{|o| o.datatype != RTM::PSI[:IRI]} end def external_occurrences occurrences.select{|o| o.datatype == RTM::PSI[:IRI]} end def self.create(base_locator, params={}) bl_uri = URI.parse(base_locator) raise "The locator for the Topic Map must be absolute! \"#{base_locator}\" is not an absolute locator." unless bl_uri.absolute? tm = self.wrap(TMDM::TopicMap.find_or_create_by_base_locator(base_locator)) yield tm if block_given? tm end def self.topic_maps TopicMaps.wrap(TMDM::TopicMap.find(:all)) end # Resolves an IRI or fragment relative to the base_locator of this topic_map or an optional given alternative_base_locator # # Absolute IRIs are taken as is. # # Relative IRIs: # If the base_locator is a directory (ends with "/") it is appended like a file, i.e. directly # If the base_locator is a file it is appended as a fragment (with # in between) def resolve(obj,alternative_base_locator=nil) uri = obj.to_s # TODO uri = URI.decode(obj.to_s) # this InvalidURIError somethimes :( begin uri_uri = URI.parse(uri) rescue URI::InvalidComponentError => ice warn "Catched an URI::InvalidComponentError for URI: #{uri}, message was \"#{ice.message}\"\n" + "Will continue using the UNRESOLVED IRI, claiming it is absolute." return uri end if uri_uri.absolute? return uri_uri.to_s end uri = uri[1..-1] if uri[0] == "#"[0] bl = alternative_base_locator || base_locator if bl[-1,1] == "/" return bl + uri else return bl + "#" + uri end end #private def _item_identifier(iid) __getobj__.locators.find_by_reference(resolve(iid)) # doesn't work :( --> , :include => [ :topic_map_construct ]) end def _item_identifier!(iid) __getobj__.locators.find_or_create_by_reference(resolve(iid)) # doesn't work :( --> , :include => [ :topic_map_construct ]) end def _subject_identifier(iid) __getobj__.subject_identifiers.find_by_reference(iid, :include => :topic) end def _subject_identifier!(iid) __getobj__.subject_identifiers.find_or_create_by_reference(iid, :include => :topic) end def _subject_locator(iid) __getobj__.subject_locators.find_by_reference(iid, :include => :topic) end def _subject_locator!(iid) __getobj__.subject_locators.find_or_create_by_reference(iid, :include => :topic) end # internal helper for by_item_identifier, doesn't wrap result def _by_item_identifier(iid) iid = RTM::LocatorHelpers.iid2iri(iid) if RTM::LocatorHelpers.is_a_iid?(iid) ii = __getobj__.locators.find_by_reference(resolve(iid)) #, :include => [ :topic_map_construct ]) return ii.topic_map_construct if ii nil end def _topic_by_item_identifier(iid) t = _by_item_identifier(iid) return nil unless t return t if t.class.name =~ /::Topic$/ # only return something if the thing is a topic nil end # internal helper for topic_by_item_identifier!, doesn't wrap result def _topic_by_item_identifier!(iid) iid = RTM::LocatorHelpers.iid2iri(iid) if RTM::LocatorHelpers.is_a_iid?(iid) ii = __getobj__.locators.find_or_create_by_reference(resolve(iid))#, :include => [ :topic_map_construct ]) return ii.topic_map_construct if ii.topic_map_construct && ii.topic_map_construct_type =~ /::Topic$/ top2 = _topic_by_subject_identifier(resolve(iid)) if top2 ii.topic_map_construct = top2 else ii.topic_map_construct = create_topic.__getobj__ end ii.save ii.topic_map_construct end # internal helper for topic_by_subject_identifier, doesn't wrap result def _topic_by_subject_identifier(sid) si = __getobj__.subject_identifiers.find_by_reference(sid, :include => [ :topic ]) return si.topic if si nil end # internal helper for topic_by_subject_identifier!, doesn't wrap result def _topic_by_subject_identifier!(sid) si = __getobj__.subject_identifiers.find_or_create_by_reference(sid, :include => [ :topic ]) return si.topic if si.topic begin top2 = _by_item_identifier(sid) rescue ActiveRecord::RecordNotFound => rnf si.topic = create_topic.__getobj__ else if top2 && top2.respond_to?(:subject_identifiers) si.topic = top2 else si.topic = create_topic.__getobj__ end end si.save si.topic end # internal helper for topic_by_subject_locator, doesn't wrap result def _topic_by_subject_locator(slo) sl = __getobj__.subject_locators.find_by_reference(RTM::LocatorHelpers.slo2iri(slo)) #, :include => [ :topic ]) return sl.topic if sl nil end # internal helper for topic_by_subject_locator!, doesn't wrap result def _topic_by_subject_locator!(slo) sl = __getobj__.subject_locators.find_or_create_by_reference(RTM::LocatorHelpers.slo2iri(slo), :include => [ :topic ]) return sl.topic if sl.topic sl.topic = create_topic.__getobj__ sl.save sl.topic end def _topic_by_locator(iri) raise "Locator may not be nil" unless iri if RTM::LocatorHelpers.is_a_slo?(iri) _topic_by_subject_locator(iri) elsif RTM::LocatorHelpers.is_a_iid?(iri) _topic_by_item_identifier(iri) elsif URI.parse(iri).absolute? _topic_by_subject_identifier(iri) else _topic_by_item_identifier(iri) end end def _topic_by_locator!(iri) raise "Locator may not be nil" unless iri if RTM::LocatorHelpers.is_a_slo?(iri) _topic_by_subject_locator!(iri) elsif RTM::LocatorHelpers.is_a_iid?(iri) _topic_by_item_identifier!(iri) elsif URI.parse(iri).absolute? _topic_by_subject_identifier!(iri) else _topic_by_item_identifier!(iri) end end %w[topic association role name occurrence variant].each do |x| eval <<-EOI def _#{x}_by_id(id) __getobj__.#{x}s.find(id) end EOI end public # returns an item identifier from the topic_map if it exists def item_identifier(iid) ItemIdentifier.wrap(_item_identifier(iid)) end # returns an item identififier from the topic_map or creates it if it doesn't exist def item_identifier!(iid) ItemIdentifier.wrap(_item_identifier!(iid)) end alias :create_locator :item_identifier! # Returns a topic map construct by it's item identifier or nil if not found. # It's the equivalent to TMAPI's TopicMapObjectIndex.getTopicMapObjectBySourceLocator def by_item_identifier(iid) TopicMapConstruct.wrap(_by_item_identifier(iid)) end # Returns a topic by it's item identifier. The topic will be created if not found. def topic_by_item_identifier!(iid) Topic.wrap(_topic_by_item_identifier!(iid)) end # Returns a topic by it's subject identifier or nil if not found. # It's the equivalent to TMAPI's TopicsIndex.getTopicBySubjectIdentifier. def topic_by_subject_identifier(sid) Topic.wrap(_topic_by_subject_identifier(sid)) end # returns a topic by it's subject identifier. The topic will be created if not found. def topic_by_subject_identifier!(sid) Topic.wrap(_topic_by_subject_identifier!(sid)) end # Returns a topic by it's subject locator or nil if not found. # It's the equivalent to TMAPI's TopicsIndex.getTopicBySubjectLocator def topic_by_subject_locator(slo) Topic.wrap(_topic_by_subject_locator(slo)) end # returns a topic by it's subject locator. The topic will be created if not found. def topic_by_subject_locator!(slo) Topic.wrap(_topic_by_subject_locator!(slo)) end # Gets a topic from this topic map using its (relative) item identifier, # its (absolute) subject identifier or its (absolute and by "=" prepended) subject locator) # # Returns nil if the Topic does not exist. # # 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. # def topic_by_locator(iri) return iri if iri.is_a? Topic return topic_by_id(iri) if iri.is_a? Integer # @tblc ||= {} # t = @tblc[iri] # return t if t t = Topic.wrap(_topic_by_locator(iri)) # @tblc[iri] = t if t # t end alias :get :topic_by_locator # Gets a topic from this topic map using its (relative) item identifier, # its (absolute) subject identifier or its (absolute and by "=" prepended) subject locator) # # If there is no topic with this item identifier, subject identifier or subject locator, it is created. # Please be aware, the creation does not work with the internal (numeric) ids. # def topic_by_locator!(iri) return iri if iri.is_a? Topic return topic_by_id(iri) if iri.is_a? Integer # @tblc ||= {} # t = @tblc[iri] # return t if t t = Topic.wrap(_topic_by_locator!(iri)) # @tblc[iri] = t # t end alias :get! :topic_by_locator! %w[topic association role name occurrence variant].each do |x| eval <<-EOI def #{x}_by_id(id) #{x.camelize}.wrap(_#{x}_by_id(id)) end alias :#{x[0].chr}id :#{x}_by_id EOI end # I am not sure if this is at all correct. TMDM doesn't say a word about it # and the approach to compare topic maps is CXTM. I chose this because we # should (at least at the time of writing) have only one topic with a given # base locator in RTM. If you don't like it, drop me a mail and explain why # and propose something better. equality [:base_locator] end end