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 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