activerdf 1.3.1 → 1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +4 -0
- data/lib/active_rdf/objectmanager/namespace.rb +1 -0
- data/lib/active_rdf/objectmanager/resource.rb +95 -38
- data/lib/active_rdf/queryengine/ntriples_parser.rb +20 -10
- data/test/objectmanager/test_resource_reading.rb +14 -9
- data/test/queryengine/test_ntriples_parser.rb +31 -6
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
== activerdf (1.4) Tue, 27 Feb 2007 20:50:21 +0000
|
2
|
+
* dynamic finders support prefixes (find_by_foaf::name)
|
3
|
+
* ntriples parser supports encoded literals (HTML)
|
4
|
+
|
1
5
|
== activerdf (1.3.1) Mon, 19 Feb 2007 17:04:57 +0000
|
2
6
|
* fixed type-checking bug in query.rb
|
3
7
|
* added language support to literals
|
@@ -10,6 +10,7 @@ class Namespace
|
|
10
10
|
# e.g. :rdf and 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
|
11
11
|
def self.register(prefix, fullURI)
|
12
12
|
raise ActiveRdfError, 'prefix nor uri can be empty' if (prefix.to_s.empty? or fullURI.to_s.empty?)
|
13
|
+
raise ActiveRdfError, "namespace uri should end with # or /" unless /\/|#/ =~ fullURI.to_s[-1..-1]
|
13
14
|
$activerdflog.info "Namespace: registering #{fullURI} to #{prefix}"
|
14
15
|
@@namespaces[prefix.to_sym] = fullURI.to_s
|
15
16
|
@@inverted_namespaces[fullURI.to_s] = prefix.to_sym
|
@@ -67,42 +67,21 @@ module RDFS
|
|
67
67
|
Query.new.distinct(:p).where(:p, domain, class_uri).execute || []
|
68
68
|
end
|
69
69
|
|
70
|
-
# manages invocations such as Person.find_by_name
|
70
|
+
# manages invocations such as Person.find_by_name,
|
71
|
+
# Person.find_by_foaf::name, Person.find_by_foaf::name_and_foaf::knows, etc.
|
71
72
|
def Resource.method_missing(method, *args)
|
72
|
-
|
73
|
+
if /find_by_(.+)/.match(method.to_s)
|
74
|
+
$activerdflog.debug "constructing dynamic finder for #{method}"
|
73
75
|
|
74
|
-
|
76
|
+
# construct proxy to handle delayed lookups
|
77
|
+
# (find_by_foaf::name_and_foaf::age)
|
78
|
+
proxy = DynamicFinderProxy.new($1, nil, *args)
|
75
79
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
attributes = match[1].split('_and_')
|
81
|
-
|
82
|
-
# get list of possible predicates for this class
|
83
|
-
possible_predicates = predicates
|
84
|
-
|
85
|
-
# build query looking for all resources with the given parameters
|
86
|
-
query = Query.new.distinct(:s)
|
87
|
-
|
88
|
-
# add where clause for each attribute-value pair,
|
89
|
-
# looking into possible_predicates to figure out
|
90
|
-
# which full-URI to use for each given parameter (heuristic)
|
91
|
-
|
92
|
-
attributes.each_with_index do |atr,i|
|
93
|
-
possible_predicates.each do |pred|
|
94
|
-
query.where(:s, pred, args[i]) if Namespace.localname(pred) == atr
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# execute query
|
99
|
-
$activerdflog.debug "RDFS::Resource: method_missing on class: executing query: #{query}"
|
100
|
-
return query.execute(:flatten => true)
|
80
|
+
# if proxy already found a value (find_by_name) we will not get a more
|
81
|
+
# complex query, so return the value. Otherwise, return the proxy so that
|
82
|
+
# subsequent lookups are handled
|
83
|
+
return proxy.value || proxy
|
101
84
|
end
|
102
|
-
|
103
|
-
# otherwise, if no match found, raise NoMethodError (in superclass)
|
104
|
-
$activerdflog.warn 'RDFS::Resource: method_missing on class: method not matching find_by_*'
|
105
|
-
super
|
106
85
|
end
|
107
86
|
|
108
87
|
# returns array of all instances of this class (e.g. Person.find_all)
|
@@ -187,6 +166,7 @@ module RDFS
|
|
187
166
|
predicate = Namespace.lookup(@@uri, localname)
|
188
167
|
Query.new.distinct(:o).where(@@subject, predicate, :o).execute(:flatten => true)
|
189
168
|
end
|
169
|
+
private(:type)
|
190
170
|
end
|
191
171
|
return namespace
|
192
172
|
end
|
@@ -302,8 +282,8 @@ module RDFS
|
|
302
282
|
else
|
303
283
|
q = Query.new.select(:p)
|
304
284
|
end
|
305
|
-
|
306
|
-
return (direct + direct.collect {|d| ancestors(d)}).flatten.uniq
|
285
|
+
q.where(self,:p, :o).execute
|
286
|
+
#return (direct + direct.collect {|d| ancestors(d)}).flatten.uniq
|
307
287
|
end
|
308
288
|
|
309
289
|
def property_accessors
|
@@ -326,10 +306,10 @@ module RDFS
|
|
326
306
|
|
327
307
|
private
|
328
308
|
|
329
|
-
def ancestors(predicate)
|
330
|
-
subproperty = Namespace.lookup(:rdfs,:subPropertyOf)
|
331
|
-
Query.new.distinct(:p).where(predicate, subproperty, :p).execute
|
332
|
-
end
|
309
|
+
# def ancestors(predicate)
|
310
|
+
# subproperty = Namespace.lookup(:rdfs,:subPropertyOf)
|
311
|
+
# Query.new.distinct(:p).where(predicate, subproperty, :p).execute
|
312
|
+
# end
|
333
313
|
|
334
314
|
def predicate_invocation(predicate, args, update)
|
335
315
|
if update
|
@@ -366,3 +346,80 @@ module RDFS
|
|
366
346
|
end
|
367
347
|
end
|
368
348
|
end
|
349
|
+
|
350
|
+
# proxy to manage find_by_ invocations
|
351
|
+
class DynamicFinderProxy
|
352
|
+
@ns = nil
|
353
|
+
@where = nil
|
354
|
+
@value = nil
|
355
|
+
attr_reader :value
|
356
|
+
|
357
|
+
# construct proxy from find_by text
|
358
|
+
# foaf::name
|
359
|
+
def initialize(find_string, where, *args)
|
360
|
+
@where = where || []
|
361
|
+
parse_attributes(find_string, *args)
|
362
|
+
end
|
363
|
+
|
364
|
+
def method_missing(method, *args)
|
365
|
+
# we store the ns:name for later (we need to wait until we have the
|
366
|
+
# arguments before actually constructing the where clause): now we just
|
367
|
+
# store that a where clause should appear about foaf:name
|
368
|
+
|
369
|
+
# if this method is called name_and_foaf::age we add ourself to the query
|
370
|
+
# otherwise, the query is built: we execute it and return the results
|
371
|
+
if method.to_s.include?('_and_')
|
372
|
+
parse_attributes(method.to_s, *args)
|
373
|
+
else
|
374
|
+
@where << Namespace.lookup(@ns, method.to_s)
|
375
|
+
query(*args)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
private
|
380
|
+
# split find_string by occurrences of _and_
|
381
|
+
def parse_attributes string, *args
|
382
|
+
attributes = string.split('_and_')
|
383
|
+
attributes.each do |atr|
|
384
|
+
# attribute can be:
|
385
|
+
# - a namespace prefix (foaf): store prefix in @ns to prepare for method_missing
|
386
|
+
# - name (attribute name): store in where to prepare for method_missing
|
387
|
+
if Namespace.abbreviations.include?(atr.to_sym)
|
388
|
+
@ns = atr.to_s.downcase.to_sym
|
389
|
+
else
|
390
|
+
# found simple attribute label, e.g. 'name'
|
391
|
+
# find out candidate (full) predicate for this localname: investigate
|
392
|
+
# all possible predicates and select first one with matching localname
|
393
|
+
candidates = Query.new.distinct(:p).where(:s,:p,:o).execute
|
394
|
+
@where << candidates.select {|cand| Namespace.localname(cand) == atr}.first
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# if the last attribute was a prefix, return this dynamic finder (we'll
|
399
|
+
# catch the following method_missing and construct the real query then)
|
400
|
+
# if the last attribute was a localname, construct the query now and return
|
401
|
+
# the results
|
402
|
+
if Namespace.abbreviations.include?(attributes.last.to_sym)
|
403
|
+
return self
|
404
|
+
else
|
405
|
+
return query(*args)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# construct and execute finder query
|
410
|
+
def query(*args)
|
411
|
+
query = Query.new.distinct(:s)
|
412
|
+
@where.each_with_index do |predicate, i|
|
413
|
+
query.where(:s, predicate, args[i])
|
414
|
+
end
|
415
|
+
|
416
|
+
$activerdflog.debug "executing dynamic finder: #{query.to_sp}"
|
417
|
+
|
418
|
+
# store the query results so that caller (Resource.method_missing) can
|
419
|
+
# retrieve them (we cannot return them here, since we were invoked from
|
420
|
+
# the initialize method so all return values are ignored, instead the proxy
|
421
|
+
# itself is returned)
|
422
|
+
@value = query.execute(:flatten => true)
|
423
|
+
return value
|
424
|
+
end
|
425
|
+
end
|
@@ -3,38 +3,48 @@
|
|
3
3
|
# License:: LGPL
|
4
4
|
require 'active_rdf'
|
5
5
|
require 'uuidtools'
|
6
|
+
require 'strscan'
|
6
7
|
|
7
8
|
# ntriples parser
|
8
9
|
class NTriplesParser
|
9
10
|
# parses an input string of ntriples and returns a nested array of [s, p, o]
|
10
11
|
# (which are in turn ActiveRDF objects)
|
11
12
|
def self.parse(input)
|
12
|
-
|
13
13
|
# need unique identifier for this batch of triples (to detect occurence of
|
14
14
|
# same bnodes _:#1
|
15
15
|
uuid = UUID.random_create.to_s
|
16
16
|
|
17
17
|
input.collect do |triple|
|
18
|
-
|
18
|
+
nodes = []
|
19
|
+
scanner = StringScanner.new(triple)
|
20
|
+
scanner.skip(/\s+/)
|
21
|
+
while not scanner.eos?
|
22
|
+
nodes << scanner.scan(Node)
|
23
|
+
scanner.skip(/\s+/)
|
24
|
+
scanner.terminate if nodes.size == 3
|
25
|
+
end
|
19
26
|
|
20
27
|
# handle bnodes if necessary (bnodes need to have uri generated)
|
21
28
|
subject = case nodes[0]
|
22
29
|
when BNode
|
23
30
|
RDFS::Resource.new("http://www.activerdf.org/bnode/#{uuid}/#$1")
|
24
|
-
|
25
|
-
RDFS::Resource.new(
|
31
|
+
when Resource
|
32
|
+
RDFS::Resource.new($1)
|
26
33
|
end
|
27
34
|
|
28
|
-
|
35
|
+
predicate = case nodes[1]
|
36
|
+
when Resource
|
37
|
+
RDFS::Resource.new($1)
|
38
|
+
end
|
29
39
|
|
30
40
|
# handle bnodes and literals if necessary (literals need unicode fixing)
|
31
41
|
object = case nodes[2]
|
32
42
|
when BNode
|
33
43
|
RDFS::Resource.new("http://www.activerdf.org/bnode/#{uuid}/#$1")
|
34
44
|
when Literal
|
35
|
-
fix_unicode(
|
36
|
-
|
37
|
-
RDFS::
|
45
|
+
fix_unicode($1)
|
46
|
+
when Resource
|
47
|
+
RDFS::Resource.new($1)
|
38
48
|
end
|
39
49
|
|
40
50
|
# collect s, p, o into array to be returned
|
@@ -44,10 +54,10 @@ class NTriplesParser
|
|
44
54
|
|
45
55
|
private
|
46
56
|
# constants for extracting resources/literals from sql results
|
47
|
-
Node = Regexp.union(/_:\S*/,/<[^>]
|
57
|
+
Node = Regexp.union(/"(?:\\"|[^"])*"/,/_:\S*/,/<[^>]*>/)
|
48
58
|
BNode = /_:(\S*)/
|
49
59
|
Resource = /<([^>]*)>/
|
50
|
-
Literal = /"([^"]*)"/
|
60
|
+
Literal = /"((?:\\"|[^"])*)"/
|
51
61
|
|
52
62
|
# fixes unicode characters in literals (because we parse them wrongly somehow)
|
53
63
|
def self.fix_unicode(str)
|
@@ -11,7 +11,7 @@ class TestResourceReading < Test::Unit::TestCase
|
|
11
11
|
def setup
|
12
12
|
ConnectionPool.clear
|
13
13
|
@adapter = get_read_only_adapter
|
14
|
-
Namespace.register(:
|
14
|
+
Namespace.register(:test, 'http://activerdf.org/test/')
|
15
15
|
ObjectManager.construct_classes
|
16
16
|
|
17
17
|
@eyal = RDFS::Resource.new 'http://activerdf.org/test/eyal'
|
@@ -22,7 +22,7 @@ class TestResourceReading < Test::Unit::TestCase
|
|
22
22
|
|
23
23
|
def test_find_all_instances
|
24
24
|
assert_equal 36, RDFS::Resource.find_all.size
|
25
|
-
assert_equal [@eyal],
|
25
|
+
assert_equal [@eyal], TEST::Person.find_all
|
26
26
|
end
|
27
27
|
|
28
28
|
def test_class_predicates
|
@@ -47,7 +47,7 @@ class TestResourceReading < Test::Unit::TestCase
|
|
47
47
|
def test_eyal_types
|
48
48
|
types = @eyal.type
|
49
49
|
assert_equal 2, types.size
|
50
|
-
assert types.include?(
|
50
|
+
assert types.include?(TEST::Person)
|
51
51
|
assert types.include?(RDFS::Resource)
|
52
52
|
end
|
53
53
|
|
@@ -64,15 +64,20 @@ class TestResourceReading < Test::Unit::TestCase
|
|
64
64
|
|
65
65
|
def test_eyal_type
|
66
66
|
assert_instance_of RDFS::Resource, @eyal
|
67
|
-
assert_instance_of
|
67
|
+
assert_instance_of TEST::Person, @eyal
|
68
68
|
end
|
69
69
|
|
70
70
|
def test_find_methods
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
assert_equal
|
75
|
-
assert_equal @eyal, RDFS::Resource.
|
71
|
+
assert_equal @eyal, RDFS::Resource.find_by_eye('blue')
|
72
|
+
assert_equal @eyal, RDFS::Resource.find_by_test::eye('blue')
|
73
|
+
|
74
|
+
assert_equal @eyal, RDFS::Resource.find_by_age(27)
|
75
|
+
assert_equal @eyal, RDFS::Resource.find_by_test::age(27)
|
76
|
+
|
77
|
+
assert_equal @eyal, RDFS::Resource.find_by_age_and_eye(27, 'blue')
|
78
|
+
assert_equal @eyal, RDFS::Resource.find_by_test::age_and_test::eye(27, 'blue')
|
79
|
+
assert_equal @eyal, RDFS::Resource.find_by_test::age_and_eye(27, 'blue')
|
80
|
+
assert_equal @eyal, RDFS::Resource.find_by_age_and_test::eye(27, 'blue')
|
76
81
|
end
|
77
82
|
|
78
83
|
# test for writing if no write adapter is defined (like only sparqls)
|
@@ -14,18 +14,43 @@ class TestNTriplesParser < Test::Unit::TestCase
|
|
14
14
|
def teardown
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
17
|
+
def test_simple_triples
|
18
18
|
str = <<EOF
|
19
19
|
<http://www.johnbreslin.com/blog/author/cloud/#foaf> <http://xmlns.com/foaf/0.1/surname> "Breslin" .
|
20
20
|
<http://www.johnbreslin.com/blog/author/cloud/#foaf> <http://xmlns.com/foaf/0.1/firstName> "John" .
|
21
21
|
<http://www.johnbreslin.com/blog/author/cloud/> <http://purl.org/dc/terms/created> "1999-11-30T00:00:00" .
|
22
22
|
EOF
|
23
23
|
|
24
|
-
|
25
|
-
assert_equal 9,
|
26
|
-
assert_equal 3,
|
24
|
+
triples = NTriplesParser.parse(str)
|
25
|
+
assert_equal 9, triples.flatten.size
|
26
|
+
assert_equal 3, triples[0].size
|
27
27
|
|
28
|
-
assert_equal RDFS::Resource,
|
29
|
-
assert_equal
|
28
|
+
assert_equal RDFS::Resource.new('http://www.johnbreslin.com/blog/author/cloud/#foaf'), triples[0][0]
|
29
|
+
assert_equal RDFS::Resource.new('http://xmlns.com/foaf/0.1/surname'), triples[0][1]
|
30
|
+
assert_equal 'Breslin', triples[0][2]
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_encoded_content
|
34
|
+
str = <<'EOF'
|
35
|
+
<http://b4mad.net/datenbrei/archives/2004/07/15/brainstream-his-own-foafing-in-wordpress/#comment-10> <http://purl.org/rss/1.0/modules/content/encoded> "<p>Heh - excellent. Are we leaving Morten in the dust? :) I know he had some bu gs to fix in his version.</p>\n<p>Also, I think we should really add the foaf: in front of the foaf properties to ma ke it easier to read. </p>\n<p>Other hack ideas:</p>\n<p>* Birthdate in month/date/year (seperate fields) to add bio :Event/ bio:Birth and then say who can see the birth year, birth day/mo and full birth date.<br />\n* Add trust leve ls to friends<br />\n* Storing ones PGP key/key fingerprint in Wordpress and referencing it as user_pubkey/user_pubk eyprint respectively<br />\n* Add gender, depiction picture for profile, myers-brigs, astrological sign fields to Pr ofile.<br />\n* Add the option to create Projects/Groups user is involved with re: their Profile.<br />\n* Maybe add phone numbers/address/geo location? Essentially make it a VCard that can be foafified.\n</p>\n" .
|
36
|
+
EOF
|
37
|
+
literal = '<p>Heh - excellent. Are we leaving Morten in the dust? :) I know he had some bu gs to fix in his version.</p>\n<p>Also, I think we should really add the foaf: in front of the foaf properties to ma ke it easier to read. </p>\n<p>Other hack ideas:</p>\n<p>* Birthdate in month/date/year (seperate fields) to add bio :Event/ bio:Birth and then say who can see the birth year, birth day/mo and full birth date.<br />\n* Add trust leve ls to friends<br />\n* Storing ones PGP key/key fingerprint in Wordpress and referencing it as user_pubkey/user_pubk eyprint respectively<br />\n* Add gender, depiction picture for profile, myers-brigs, astrological sign fields to Pr ofile.<br />\n* Add the option to create Projects/Groups user is involved with re: their Profile.<br />\n* Maybe add phone numbers/address/geo location? Essentially make it a VCard that can be foafified.\n</p>\n'
|
38
|
+
|
39
|
+
triples = NTriplesParser.parse(str)
|
40
|
+
assert_equal 1, triples.size
|
41
|
+
|
42
|
+
encoded_content = triples.first[2]
|
43
|
+
assert_equal literal, encoded_content
|
44
|
+
assert_equal String, encoded_content.class
|
45
|
+
assert encoded_content.include?('PGP')
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_escaped_quotes
|
49
|
+
string = '<subject> <predicate> "test string with \n breaks and \" escaped quotes" .'
|
50
|
+
literal = 'test string with \n breaks and \" escaped quotes'
|
51
|
+
triples = NTriplesParser.parse(string)
|
52
|
+
|
53
|
+
assert_equal 1, triples.size
|
54
|
+
assert_equal literal, triples.first[2]
|
30
55
|
end
|
31
56
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
|
|
3
3
|
specification_version: 1
|
4
4
|
name: activerdf
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-02-
|
6
|
+
version: "1.4"
|
7
|
+
date: 2007-02-27 00:00:00 +00:00
|
8
8
|
summary: Offers object-oriented access to RDF (with adapters to several datastores).
|
9
9
|
require_paths:
|
10
10
|
- lib
|