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 +15 -0
- data/README.rdoc +84 -8
- data/VERSION +1 -1
- data/bin/rdf_context +2 -6
- data/lib/rdf_context.rb +12 -2
- data/lib/rdf_context/bnode.rb +5 -1
- data/lib/rdf_context/graph.rb +97 -70
- data/lib/rdf_context/literal.rb +8 -3
- data/lib/rdf_context/parser.rb +4 -4
- data/lib/rdf_context/serializer/abstract_serializer.rb +26 -0
- data/lib/rdf_context/serializer/nt_serializer.rb +12 -0
- data/lib/rdf_context/serializer/recursive_serializer.rb +140 -0
- data/lib/rdf_context/serializer/turtle_serializer.rb +219 -0
- data/lib/rdf_context/serializer/xml_serializer.rb +236 -0
- data/lib/rdf_context/store/abstract_store.rb +1 -0
- data/lib/rdf_context/store/memory_store.rb +1 -1
- data/lib/rdf_context/string_hacks.rb +1 -1
- data/lib/rdf_context/uriref.rb +5 -2
- data/spec/bnode_spec.rb +1 -1
- data/spec/graph_spec.rb +77 -10
- data/spec/rdf_helper.rb +4 -1
- data/spec/turtle_serializer_spec.rb +227 -0
- data/spec/xml_serializer_spec.rb +378 -0
- metadata +12 -3
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
|
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
|
-
|
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
|
-
|
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
|
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.
|
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 =
|
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
|
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
|
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")
|
data/lib/rdf_context/bnode.rb
CHANGED
@@ -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.
|
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
|
|
data/lib/rdf_context/graph.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
306
|
-
|
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
|
|