activerdf 1.3.1 → 1.4

Sign up to get free protection for your applications and to get access to all the features.
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
- method_name = method.to_s
73
+ if /find_by_(.+)/.match(method.to_s)
74
+ $activerdflog.debug "constructing dynamic finder for #{method}"
73
75
 
74
- $activerdflog.debug "RDFS::Resource: method_missing on class: called with method name #{method}"
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
- # extract predicates on which to match
77
- # e.g. find_by_name, find_by_name_and_age
78
- if match = /find_by_(.+)/.match(method_name)
79
- # find searched attributes, e.g. name, age
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
- direct = q.where(self,:p, :o).execute
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
- nodes = triple.scan(Node)
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
- else
25
- RDFS::Resource.new(nodes[0])
31
+ when Resource
32
+ RDFS::Resource.new($1)
26
33
  end
27
34
 
28
- predicate = RDFS::Resource.new(nodes[1])
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(nodes[2])
36
- else
37
- RDFS::Resourec.new(nodes[2])
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(:ar, 'http://activerdf.org/test/')
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], AR::Person.find_all
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?(AR::Person)
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 AR::Person, @eyal
67
+ assert_instance_of TEST::Person, @eyal
68
68
  end
69
69
 
70
70
  def test_find_methods
71
- found_eyal = RDFS::Resource.find_by_eye('blue')
72
- assert_not_nil found_eyal
73
- assert_equal @eyal, found_eyal
74
- assert_equal 'blue', RDFS::Resource.find_by_age(27).eye
75
- assert_equal @eyal, RDFS::Resource.find_by_age_and_eye(27,'blue')
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 test_the_parser
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
- results = NTriplesParser.parse(str)
25
- assert_equal 9, results.flatten.size
26
- assert_equal 3, results[0].size
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, results[0][0].class
29
- assert_equal String, results[0][2].class
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.3.1
7
- date: 2007-02-19 00:00:00 +00:00
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