rdf_context 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,18 @@
1
+ === 0.5.1
2
+ * Avoid stack-overflow when checking graph size (if $DEBUG == true)
3
+ * Refactor serializers to be based on AbstractSerializer
4
+ * Graph
5
+ * Add Graph#serialize taking a serializer or symbol identifing a serialzer and a base URI
6
+ * Add Graph#add_seq to add the list of Resources as to an RDF Container (i.e., rdf:first/rdf:rest)
7
+ * Add Graph#sync_properties to write properties cached from Graph#properties back to the store.
8
+ * Add TurtleSerializer, along with AbstractSerializer, RecusiveSerializer, NTSerializer and XMLSerializer
9
+ * Update XmlSerializer, and Graph#to_xml to do more intelligent generation of RDF/XML
10
+ * Control depth of recursive node generation with :max_depth
11
+ * Serialize rdf:type and untyped literals as attributes with :attributes => :untyped
12
+ * Serialize typed literals as attributes with :attributes => :typed
13
+ * Added Literal#typed? and Literal#untyped? in addition to Literal#xmlliteral?
14
+ * BNode.new("") is not a named identifier, it's equivalent to an anonymous BNode
15
+
1
16
  === 0.5.0
2
17
  * Support for Ruby 1.9, 1.8.7 and 1.8.6.
3
18
  * Unicode escapes and URIRefs only work properly in Ruby 1.9
data/README.rdoc CHANGED
@@ -19,7 +19,7 @@ RdfContext parses RDF/XML, RDFa and N3-rdf into a Graph object. It also serializ
19
19
  * Fully compliant RDF/XML parser.
20
20
  * Fully compliant XHTML/RDFa 1.0 parser.
21
21
  * Fully compliant N3-rdf parser (N3-rdf level)
22
- * N-Triples and RDF/XML serializer
22
+ * Turtle, N-Triples and RDF/XML serializer
23
23
  * RDFa tests use SPARQL for most tests due to Rasqal limitations. Other tests compare directly against N-triples.
24
24
  * Graph serializes into RDF/XML and N-Triples.
25
25
  * ConjunctiveGraph, named Graphs and contextual storage modules.
@@ -40,7 +40,7 @@ a :store (defaults to :list_store)
40
40
 
41
41
  g.add(Triple.new(subject, predicate, object))
42
42
 
43
- Graphs also store namespace associations, and can serialize graphs to RDF/XML or N-triples
43
+ Graphs also store namespace associations, and can serialize graphs to Turtle, RDF/XML, N-triples or a custom serializer
44
44
 
45
45
  g.bind(Namespace.new("http://example.com", "ex"))
46
46
  g.namespace("ex")
@@ -48,6 +48,12 @@ Graphs also store namespace associations, and can serialize graphs to RDF/XML or
48
48
 
49
49
  g.to_rdfxml
50
50
  g.to_ntriples
51
+ g.serialize(:format => :xml, :io => StringIO.new, :base => "http://base.example.com/") => IO Object, or string if none specified
52
+
53
+ or, pass an instantiated Serializer object
54
+
55
+ ser = TurtleSerializer.new(g)
56
+ g.serialize(:format => ser, :io => StringIO.new, :base => "http://base.example.com/")
51
57
 
52
58
  Resource properties
53
59
 
@@ -63,9 +69,47 @@ Instantiate an existing graph from a datastore
63
69
  s = SQLIte3Store.new(:path => "store.db")
64
70
  g = Graph.new(:store => s, :identifier => "http://example.com/context")
65
71
 
66
- QuotedGraph implements N3 Formulae semantics, by creating a graph within a store that is formula_aware. QuotedGraph triples are not returned in a query to a ConjunctiveGraph.
72
+ ==== Named Graphs / Conjunctive Graphs
73
+ RdfContext defines the following kinds of Graphs:
74
+ Graph:: Basic graph, associated with a Store and an identifier
75
+ QuotedGraph:: implements N3 Formulae semantics, by creating a graph within a store that is formula_aware. QuotedGraph triples are not returned in a query to a ConjunctiveGraph.
76
+ ConjunctiveGraph:: A Conjunctive Graph is the most relevant collection of graphs that are considered to be the boundary for closed world assumptions. This boundary is equivalent to that of the store instance (which is itself uniquely identified and distinct from other instances of Store that signify other Conjunctive Graphs). It is equivalent to all the named graphs within it and associated with a _default_ graph which is automatically assigned a BNode for an identifier - if one isn't given.
77
+ AggregateGraph:: allow multiple graphs from a given context_aware store to be combined into a single read-only graph.
78
+
79
+ === Terminology
80
+ _Context_: A named, unordered set of statements. Also could be called a sub-graph. The named graphs literature and ontology are relevant to this concept. A context could be thought of as only the relationship between an RDF triple and a sub-graph (this is how the term context is used in the Notation 3 Design Issues page) in which it is found or the sub-graph itself.
81
+
82
+ It's worth noting that the concept of logically grouping triples within an addressable 'set' or 'subgraph' is just barely beyond the scope of the RDF model. The RDF model defines a graph as an arbitrary collection of triples and the semantics of these triples, but doesn't give guidance on how to consistently address such arbitrary collections. Though a collection of triples can be thought of as a resource itself, the association between a triple and the collection it is a part of is not covered.
83
+
84
+ <em>Conjunctive Graph</em>: This refers to the 'top-level' Graph. It is the aggregation of all the contexts within it and is also the appropriate, absolute boundary for closed world assumptions / models. This distinction is the low-hanging fruit of RDF along the path to the semantic web and most of its value is in (corporate/enterprise) real-world problems:
85
+
86
+ For the sake of persistence, Conjunctive Graphs must be distinguished by identifiers (that may not necessarily be RDF identifiers or may be an RDF identifier normalized - SHA1/MD5 perhaps - for database naming purposes ) which could be referenced to indicate conjunctive queries (queries made across the entire conjunctive graph) or appear as nodes in asserted statements. In this latter case, such statements could be interpreted as being made about the entire 'known' universe. For example:
87
+
88
+ <urn:uuid:conjunctive-graph-foo> rdf:type :ConjunctiveGraph
89
+ <urn:uuid:conjunctive-graph-foo> rdf:type log:Truth
90
+ <urn:uuid:conjunctive-graph-foo> :persistedBy :MySQL
91
+
92
+ _Terms_: Terms are the kinds of objects that can appear in a quoted/asserted triple. This includes those that are core to RDF:
93
+
94
+ * Blank Nodes
95
+ * URI References
96
+ * Literals (which consist of a literal value, datatype and language tag)
97
+
98
+ _Nodes_: Nodes are a subset of the Terms that the underlying store actually persists. The set of such Terms depends on whether or not the store is formula-aware. Stores that aren't formula-aware would only persist those terms core to the RDF Model, and those that are formula-aware would be able to persist the N3 extensions as well. However, utility terms that only serve the purpose for matching nodes by term-patterns probably will only be terms and not nodes.
99
+
100
+ The set of nodes of an RDF graph is the set of subjects and objects of triples in the graph.
101
+
102
+ <em>Context-aware</em>: An RDF store capable of storing statements within contexts is considered context-aware. Essentially, such a store is able to partition the RDF model it represents into individual, named, and addressable sub-graphs.
103
+
104
+ <em>Formula-aware</em>: An RDF store capable of distinguishing between statements that are asserted and statements that are quoted is considered formula-aware.
105
+
106
+ Such a store is responsible for maintaining this separation and ensuring that queries against the entire model (the aggregation of all the contexts - specified by not limiting a 'query' to a specifically name context) do not include quoted statements. Also, it is responsible for distinguishing universal quantifiers (variables).
67
107
 
68
- AggregateGraphs allow multiple graphs from a given context_aware store to be combined into a single read-only graph.
108
+ These 2 additional concepts (formulae and variables) must be thought of as core extensions and distinguishable from the other terms of a triple (for the sake of the persistence roundtrip - at the very least). It's worth noting that the 'scope' of universal quantifiers (variables) and existential quantifiers (BNodes) is the formula (or context - to be specific) in which their statements reside. Beyond this, a Formula-aware store behaves the same as a Context-aware store.
109
+
110
+ <em>Conjunctive Query</em>: Any query that doesn't limit the store to search within a named context only. Such a query expects a context-aware store to search the entire asserted universe (the conjunctive graph). A formula-aware store is expected not to include quoted statements when matching such a query.
111
+
112
+ <em>Transactional Store</em>: An RDF store capable of providing transactional integrity to the RDF operations performed on it.
69
113
 
70
114
  === Parsers
71
115
  Instantiate a parser and parse source, specifying type and base-URL
@@ -84,11 +128,46 @@ or, parse directly into a graph
84
128
  g = Graph.new(:identifier => "http://example.com", :store => :sqlite3_store)
85
129
  g.parse(input, "http://example.com", :type => :rdfxml)
86
130
 
131
+ === Serializers
132
+ AbstractSerializer class for XML, N-Triples and Turtle serializers. Turtle based on RecursiveSerializer.
133
+ May be called from Graph via Graph#serialize, or instantiated and run separately:
134
+
135
+ g = Graph.new
136
+ s = TurtleSerializer.new(g)
137
+ stream = StringIO.new
138
+ s.serialize(stream, , "http://base.example.com/")
139
+
87
140
  === Data Stores
141
+ RdfContext defines three datastores:
142
+
88
143
  ListStore:: simple non-context aware datastore based on Array
89
144
  MemoryStore:: context aware datastore using multiple hashes to optimize triple lookkup
90
145
  SQLite3Store:: context aware datastore using a SQLite3 database to create a persistent storage model
91
146
 
147
+ Additional stores may be created by sub-classing _AbstractStore_ or _AbstractSQLStore_.
148
+
149
+ ==== Database/Transactional stores
150
+ An RDF store should provide standard interfaces for the management of database connections. Such interfaces are standard to most database management systems (Oracle, MySQL, Berkeley DB, Postgres, etc..) The following methods are defined to provide this capability:
151
+
152
+ *open*:: Opens the store specified by the options. If :create is true a store will be created if it does not already exist. If *create* is false and a store does not already exist an exception is raised. An exception is also raised if a store exists, but there is insufficient permissions to open the store.
153
+ *close*:: This closes the database connection. The commit_pending_transaction parameter specifies whether to commit all pending transactions before closing (if the store is transactional).
154
+ *destroy*:: This destroys the instance of the store identified by the options.
155
+
156
+ The configuration hash is understood by the store implementation and represents all the necessary parameters needed to locate an individual instance of a store. The open function needs to fail intelligently in order to clearly express that a store (identified by the given configuration string) already exists or that there is no store (at the location specified by the configuration string) depending on the value of create.
157
+
158
+ ==== Triple Interfaces
159
+ An RDF store could provide a standard set of interfaces for the manipulation, management, and/or retrieval of its contained triples (asserted or quoted):
160
+
161
+ *add*:: Adds the given statement to a specific context or to the model. The quoted argument is interpreted by formula-aware stores to indicate this statement is quoted/hypothetical. It should be an error to not specify a context and have the quoted argument be True. It should also be an error for the quoted argument to be True when the store is not formula-aware.
162
+ *remove*:: Remove a triple, or pattern from a specific or all contexts.
163
+ *triples*:: Returns an closure over all the triples (within the conjunctive graph or just the given context) matching the given pattern or an array of triples. The pattern is specified by providing explicit statement terms (which are used to match against nodes in the underlying store), or nil - which indicates a wildcard. This function can be thought of as the primary mechanism for producing triples with nodes that match the corresponding terms and term pattern provided. A conjunctive query can be indicated by either providing a value of nil for context or the identifier associated with the Conjunctive Graph.
164
+ *size*:: Number of statements in the store. This should only account for non-quoted (asserted) statements if the context is not specified, otherwise it should return the number of statements in the formula or context given.
165
+
166
+ ==== Formula / Context Interfaces
167
+
168
+ These interfaces work on contexts and formulae (for stores that are formula-aware) interchangeably.
169
+ *contexts*:: Closure or list over all contexts in the graph. If triple is specified, it returns all contexts the triple is in.
170
+
92
171
  == Dependencies:
93
172
 
94
173
  * Addressable
@@ -109,13 +188,10 @@ SQLite3Store:: context aware datastore using a SQLite3 database to create a pers
109
188
  * Testing
110
189
  * RDFa updates for new tests and non XHTML representations.
111
190
  * Graphs
112
- * n3/turtle serialization
191
+ * n3 semantics including variables and formulae
113
192
  * Reasoner/inference engine
114
193
  * SPARQL
115
194
  * RDFS logic and RDF entailment tests
116
- * Ruby objects
117
- * ActiveRDF-like class support
118
- * Integrate with RDFObjects (http://github.com/rsinger/RDFObjects)
119
195
 
120
196
  == Resources:
121
197
  * Distiller[http://kellogg-assoc/distiller]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.5.1
data/bin/rdf_context CHANGED
@@ -10,12 +10,7 @@ class Parse
10
10
  graph_opts[:store] = store if store
11
11
  parser = Parser.new(:graph => Graph.new(graph_opts))
12
12
  parser.parse(file.respond_to?(:read) ? file : File.open(file), base_uri, :strict => true)
13
- output = case $format
14
- when "xml"
15
- parser.graph.to_rdfxml
16
- else
17
- parser.graph.to_ntriples
18
- end
13
+ output = parser.graph.serialize(:format => $format.to_sym, :base => base_uri)
19
14
  puts output unless $quiet
20
15
 
21
16
  puts parser.debug.join("\n\t") if $verbose
@@ -34,6 +29,7 @@ mode = ARGV.shift
34
29
  raise "Mode must be one of 'parse'" unless mode == "parse"
35
30
 
36
31
  $verbose = false
32
+ $format = "ttl"
37
33
  base_uri = "http://example.com"
38
34
  store = :list_store
39
35
  opts = GetoptLong.new(
data/lib/rdf_context.rb CHANGED
@@ -15,10 +15,19 @@ rescue LoadError
15
15
  require 'treetop'
16
16
  end
17
17
 
18
- Dir.glob(File.join(File.dirname(__FILE__), 'rdf_context/**.rb')).sort.each { |f| require f }
18
+ Dir.glob(File.join(File.dirname(__FILE__), 'rdf_context/*.rb')).sort.each { |f| require f }
19
19
 
20
20
  # Include Storage types, but be tollerant of failure to load as dependencies might not be available
21
- Dir.glob(File.join(File.dirname(__FILE__), "rdf_context/store/**.rb")).each do |f|
21
+ Dir.glob(File.join(File.dirname(__FILE__), "rdf_context/store/*.rb")).each do |f|
22
+ begin
23
+ require f
24
+ rescue LoadError
25
+ puts "Error loading #{f}: #{$!}"
26
+ end
27
+ end
28
+
29
+ # Include Serializer types, but be tollerant of failure to load as dependencies might not be available
30
+ Dir.glob(File.join(File.dirname(__FILE__), "rdf_context/serializer/*.rb")).each do |f|
22
31
  begin
23
32
  require f
24
33
  rescue LoadError
@@ -51,6 +60,7 @@ module RdfContext
51
60
  RDF_TYPE = URIRef.new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
52
61
  XML_LITERAL = Literal::Encoding.xmlliteral
53
62
 
63
+ DC_NS = Namespace.new("http://purl.org/dc/elements/1.1/", "dc")
54
64
  OWL_NS = Namespace.new("http://www.w3.org/2002/07/owl#", "owl")
55
65
  LOG_NS = Namespace.new("http://www.w3.org/2000/10/swap/log#", "log")
56
66
  RDF_NS = Namespace.new("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf")
@@ -16,7 +16,7 @@ module RdfContext
16
16
  # @param [String] identifier:: Legal NCName or nil for a named BNode
17
17
  # @param [Hash] context:: Context used to store named BNodes
18
18
  def initialize(identifier = nil, context = {})
19
- if identifier.nil?
19
+ if identifier.to_s.empty?
20
20
  @identifier = generate_bn_identifier
21
21
  elsif identifier.match(/n?bn\d+[a-z]+(N\w+)?$/)
22
22
  @identifier = context[identifier] || identifier
@@ -70,6 +70,10 @@ module RdfContext
70
70
  end
71
71
  alias_method :==, :eql?
72
72
 
73
+ def <=>(other)
74
+ self.to_s <=> other.to_s
75
+ end
76
+
73
77
  # Needed for uniq
74
78
  def hash; self.to_s.hash; end
75
79
 
@@ -3,6 +3,9 @@ require File.join(File.dirname(__FILE__), 'triple')
3
3
  require File.join(File.dirname(__FILE__), 'array_hacks')
4
4
  require File.join(File.dirname(__FILE__), 'store', 'list_store')
5
5
  require File.join(File.dirname(__FILE__), 'store', 'memory_store')
6
+ require File.join(File.dirname(__FILE__), 'serializer', 'nt_serializer')
7
+ require File.join(File.dirname(__FILE__), 'serializer', 'turtle_serializer')
8
+ require File.join(File.dirname(__FILE__), 'serializer', 'xml_serializer')
6
9
 
7
10
  module RdfContext
8
11
  # A simple graph to hold triples.
@@ -10,7 +13,6 @@ module RdfContext
10
13
  # Graphs store triples, and the namespaces associated with those triples, where defined
11
14
  class Graph
12
15
  attr_reader :triples
13
- attr_reader :nsbinding
14
16
  attr_reader :identifier
15
17
  attr_reader :store
16
18
  attr_accessor :allow_n3
@@ -34,8 +36,6 @@ module RdfContext
34
36
  # <em>options[:identifier]</em>:: Identifier for this graph, BNode or URIRef
35
37
  # <em>options[:allow_n3]</em>:: Allow N3-specific triples: Literals as subject, BNodes as predicate
36
38
  def initialize(options = {})
37
- @nsbinding = {}
38
-
39
39
  # Instantiate triple store
40
40
  @store = case options[:store]
41
41
  when AbstractStore then options[:store]
@@ -60,9 +60,6 @@ module RdfContext
60
60
 
61
61
  def context_aware?; @store.context_aware?; end
62
62
 
63
- # Data Store interface
64
- def nsbinding; @store.nsbinding; end
65
-
66
63
  # Destroy the store identified by _configuration_ if supported
67
64
  # If configuration is nil, remove the graph context
68
65
  def destroy(configuration = nil)
@@ -94,9 +91,34 @@ module RdfContext
94
91
  # Might be necessary for stores that require closing a connection to a
95
92
  # database or releasing some resource.
96
93
  def close(commit_pending_transaction=false)
97
- @store.open(commit_pending_transaction)
94
+ @store.close(commit_pending_transaction)
98
95
  end
99
96
 
97
+ # Serialize graph using specified serializer class.
98
+ #
99
+ # @param [Hash] options:: Options
100
+ # <em>options[:format]</em>:: serializer, defaults to a new NTSerializer instance. Otherwise may be a symbol from :nt, :turtle, :xml
101
+ # <em>options[:io]</em>:: IO (or StringIO) object, otherwise serializes to a string
102
+ # <em>options[:base]</em>:: Base URI for output
103
+ #
104
+ # Other options are parser specific.
105
+ #
106
+ # @returns [IO, String]:: Passed IO/StringIO object or a string
107
+ def serialize(options)
108
+ serializer = case options[:format].to_sym
109
+ when AbstractSerializer then options[:serializer]
110
+ when :nt, :ntriples then NTSerializer.new(self)
111
+ when :ttl, :turtle, :n3 then TurtleSerializer.new(self)
112
+ when :rdf, :xml, :rdfxml then XmlSerializer.new(self)
113
+ else NTSerializer.new(self)
114
+ end
115
+
116
+ io = options[:io] || StringIO.new
117
+
118
+ serializer.serialize(io, options)
119
+ options[:io] ? io : (io.rewind; io.read)
120
+ end
121
+
100
122
  ##
101
123
  # Exports the graph to RDF in N-Triples form.
102
124
  #
@@ -107,9 +129,7 @@ module RdfContext
107
129
  #
108
130
  # @author Tom Morris
109
131
  def to_ntriples
110
- triples.collect do |t|
111
- t.to_ntriples
112
- end * "\n" + "\n"
132
+ serialize(:format => :nt)
113
133
  end
114
134
 
115
135
  # Output graph using to_ntriples
@@ -120,59 +140,7 @@ module RdfContext
120
140
  #
121
141
  # @return [String]:: The RDF/XML graph
122
142
  def to_rdfxml
123
- replace_text = {}
124
- rdfxml = ""
125
- xml = builder = Builder::XmlMarkup.new(:target => rdfxml, :indent => 2)
126
-
127
- extended_bindings = nsbinding.merge(
128
- "rdf" => RDF_NS,
129
- "rdfs" => RDFS_NS,
130
- "xhv" => XHV_NS,
131
- "xml" => XML_NS
132
- )
133
- rdf_attrs = extended_bindings.values.inject({}) { |hash, ns| hash.merge(ns.xmlns_attr => ns.uri.to_s)}
134
- uri_bindings = self.uri_binding.merge(
135
- RDF_NS.uri.to_s => RDF_NS,
136
- RDFS_NS.uri.to_s => RDFS_NS,
137
- XHV_NS.uri.to_s => XHV_NS,
138
- XML_NS.uri.to_s => XML_NS
139
- )
140
-
141
- # Add bindings for predicates not already having bindings
142
- tmp_ns = "ns0"
143
- predicates.each do |p|
144
- raise "Attempt to serialize graph containing non-strict RDF compiant BNode as predicate" unless p.is_a?(URIRef)
145
- if !p.namespace(uri_bindings)
146
- uri_bindings[p.base] = Namespace.new(p.base, tmp_ns)
147
- rdf_attrs["xmlns:#{tmp_ns}"] = p.base
148
- tmp_ns = tmp_ns.succ
149
- end
150
- end
151
-
152
- xml.instruct!
153
- xml.rdf(:RDF, rdf_attrs) do
154
- # Add statements for each subject
155
- subjects.each do |s|
156
- xml.rdf(:Description, (s.is_a?(BNode) ? "rdf:nodeID" : "rdf:about") => s) do
157
- triples(Triple.new(s, nil, nil)) do |triple, context|
158
- xml_args = triple.object.xml_args
159
- qname = triple.predicate.to_qname(uri_bindings)
160
- if triple.object.is_a?(Literal) && triple.object.xmlliteral?
161
- replace_text["__replace_with_#{triple.object.object_id}__"] = xml_args[0]
162
- xml_args[0] = "__replace_with_#{triple.object.object_id}__"
163
- end
164
- xml.tag!(qname, *xml_args)
165
- end
166
- end
167
- end
168
- end
169
-
170
- # Perform literal substitutions
171
- replace_text.each_pair do |match, value|
172
- rdfxml.sub!(match, value)
173
- end
174
-
175
- rdfxml
143
+ serialize(:format => :rdfxml)
176
144
  end
177
145
 
178
146
  ##
@@ -268,11 +236,45 @@ module RdfContext
268
236
  ctx = options[:context] || @default_context || self
269
237
  triples.each do |t|
270
238
  t.validate_rdf unless @allow_n3 # Only add triples if n3-mode is set
239
+ #puts "Add #{t.inspect}, ctx: #{ctx.identifier}" if $verbose
271
240
  @store.add(t, ctx)
272
241
  end
273
242
  self
274
243
  end
275
244
 
245
+ ##
246
+ # Adds a list of resources as an RDF list by creating bnodes and first/rest triples
247
+ # @param [URIRef, BNode] subject:: the subject of the triple
248
+ # @param [URIRef] predicate:: the predicate of the triple
249
+ # @param [Array] objects:: List of objects to serialize
250
+ # @return [Graph]:: Returns the graph
251
+ # @raise [Error]:: Checks parameter types and raises if they are incorrect.
252
+ def add_seq(subject, predicate, objects)
253
+ if objects.empty?
254
+ add_triple(subject, predicate, RDF_NS.nil)
255
+ return self
256
+ end
257
+
258
+ if RDF_NS.first != predicate
259
+ bn = BNode.new
260
+ add_triple(subject, predicate, bn)
261
+ subject = bn
262
+ end
263
+
264
+ last = objects.pop
265
+
266
+ objects.each do |o|
267
+ add_triple(subject, RDF_NS.first, o)
268
+ bn = BNode.new
269
+ add_triple(subject, RDF_NS.rest, bn)
270
+ subject = bn
271
+ end
272
+
273
+ # Last item in list
274
+ add_triple(subject, RDF_NS.first, last)
275
+ add_triple(subject, RDF_NS.rest, RDF_NS.nil)
276
+ end
277
+
276
278
  # Remove a triple from the graph. Delegates to store.
277
279
  # Nil matches all triples and thus empties the graph
278
280
  def remove(triple); @store.remove(triple, self); end
@@ -290,20 +292,26 @@ module RdfContext
290
292
  # Returns ordered rdf:_n objects or rdf:first, rdf:rest for a given subject
291
293
  def seq(subject)
292
294
  props = properties(subject)
293
- rdf_type = props[RDF_TYPE.to_s] || []
295
+ rdf_type = (props[RDF_TYPE.to_s] || []).map {|t| t.to_s}
294
296
 
295
- if rdf_type.include?(RDF_NS.Seq)
297
+ #puts "seq; #{rdf_type} #{rdf_type - [RDF_NS.Seq, RDF_NS.Bag, RDF_NS.Alt]}"
298
+ if !(rdf_type - [RDF_NS.Seq, RDF_NS.Bag, RDF_NS.Alt]).empty?
296
299
  props.keys.select {|k| k.match(/#{RDF_NS.uri}_(\d)$/)}.
297
300
  sort_by {|i| i.sub(RDF_NS._.to_s, "").to_i}.
298
301
  map {|key| props[key]}.
299
302
  flatten
300
- elsif self.triples(Triple.new(subject, RDF_NS.first, nil))
303
+ elsif !self.triples(Triple.new(subject, RDF_NS.first, nil)).empty?
301
304
  # N3-style first/rest chain
302
305
  list = []
303
306
  while subject != RDF_NS.nil
304
307
  props = properties(subject)
305
- list += props[RDF_NS.first.to_s]
306
- subject = props[RDF_NS.rest.to_s].first
308
+ f = props[RDF_NS.first.to_s]
309
+ if f.to_s.empty? || f.first == RDF_NS.nil
310
+ subject = RDF_NS.nil
311
+ else
312
+ list += f
313
+ subject = props[RDF_NS.rest.to_s].first
314
+ end
307
315
  end
308
316
  list
309
317
  else
@@ -322,8 +330,9 @@ module RdfContext
322
330
  # "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" => [<http://example.com/#bar>],
323
331
  # "http://example.com/#label" => ["An example"]
324
332
  # }
325
- def properties(subject)
333
+ def properties(subject, recalc = false)
326
334
  @properties ||= {}
335
+ @properties.delete(subject.to_s) if recalc
327
336
  @properties[subject.to_s] ||= begin
328
337
  hash = Hash.new
329
338
  self.triples(Triple.new(subject, nil, nil)).map do |t, ctx|
@@ -336,6 +345,24 @@ module RdfContext
336
345
  end
337
346
  end
338
347
 
348
+
349
+ # Synchronize properties to graph
350
+ def sync_properties(subject)
351
+ props = properties(subject)
352
+
353
+ # First remove all properties for subject
354
+ remove(Triple.new(subject, nil, nil))
355
+
356
+ # Iterate through and add properties to graph
357
+ props.each_pair do |pred, list|
358
+ predicate = URIRef.new(pred)
359
+ [list].flatten.compact.each do |object|
360
+ add(Triple.new(subject, predicate, object))
361
+ end
362
+ end
363
+ @properties.delete(subject.to_s) # Read back in from graph
364
+ end
365
+
339
366
  # Return an n3 identifier for the Graph
340
367
  def n3
341
368
  "[#{self.identifier.to_n3}]"
@@ -401,7 +428,7 @@ module RdfContext
401
428
  #
402
429
  # We just follow Python RDFlib's lead and do a simple comparison
403
430
  def eql?(other)
404
- puts "eql? size #{self.size} vs #{other.size}" if $DEBUG
431
+ #puts "eql? size #{self.size} vs #{other.size}" if $DEBUG
405
432
  return false if !other.is_a?(Graph) || self.size != other.size
406
433
  return false unless other.identifier.to_s == identifier.to_s unless other.identifier.is_a?(BNode) && identifier.is_a?(BNode)
407
434