lightrdf 0.1
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/History.txt +3 -0
- data/Manifest.txt +12 -0
- data/README.rdoc +163 -0
- data/Rakefile +22 -0
- data/bin/yarfp +23 -0
- data/lib/lightrdf/graph.rb +52 -0
- data/lib/lightrdf/node.rb +84 -0
- data/lib/lightrdf/parser.rb +225 -0
- data/lib/lightrdf/quri.rb +81 -0
- data/lib/lightrdf.rb +17 -0
- data/test/test_helper.rb +3 -0
- data/test/test_lightrdf.rb +125 -0
- metadata +117 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
= LightRDF
|
2
|
+
|
3
|
+
* http://github.com/josei/lightrdf
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
LightRDF is a gem that makes managing RDF data and graphs easily with a Ruby interface.
|
8
|
+
|
9
|
+
== INSTALL:
|
10
|
+
|
11
|
+
gem install lightrdf
|
12
|
+
|
13
|
+
The gem also requires Raptor library (in Debian systems: sudo aptitude install raptor-utils), which is used
|
14
|
+
for outputting different RDF serialization formats.
|
15
|
+
|
16
|
+
Additionally, PNG output of RDF graphs requires Graphviz (in Debian systems: sudo aptitude install graphviz).
|
17
|
+
|
18
|
+
== RDF NODES:
|
19
|
+
|
20
|
+
RDF nodes can be created using the Node method:
|
21
|
+
|
22
|
+
a = Node('http://www.example.com/ontology#test')
|
23
|
+
|
24
|
+
Namespaces can be defined in order to simplify URI manipulation:
|
25
|
+
|
26
|
+
Namespace :ex, 'http://www.example.com/ontology#'
|
27
|
+
a = Node('ex:test')
|
28
|
+
|
29
|
+
BNodes can be created too:
|
30
|
+
|
31
|
+
bnode = Node('_:bnode1')
|
32
|
+
|
33
|
+
BNode identifiers can be created automatically if a nil ID is supplied:
|
34
|
+
|
35
|
+
bnode = Node(nil)
|
36
|
+
puts bnode # Outputs: _:1
|
37
|
+
|
38
|
+
LightRDF's nodes are Ruby hashes that are evaluated as equal whenever their IDs are the same:
|
39
|
+
|
40
|
+
Node('_:bnode1') == Node('_:bnode1') # => true
|
41
|
+
Node('ex:example') == Node('ex:example') # => true
|
42
|
+
|
43
|
+
== RDF TRIPLES:
|
44
|
+
|
45
|
+
Triples can be created by creating relations between nodes.
|
46
|
+
|
47
|
+
user = Node('ex:bob')
|
48
|
+
user.foaf::name = "Bob"
|
49
|
+
|
50
|
+
If you want to get the property, an array will be returned, as many foaf::name's can be associated to it:
|
51
|
+
|
52
|
+
user.foaf::name # => ["Bob"]
|
53
|
+
|
54
|
+
Therefore, we can add multiple values to a property:
|
55
|
+
|
56
|
+
user.foaf::weblog = Node('http://www.awesomeweblogfordummies.com')
|
57
|
+
user.foaf::weblog << Node('http://www.anotherawesomeweblogfordummies.com')
|
58
|
+
user.foaf::weblog.size # => 2
|
59
|
+
user.foaf::weblog?(Node('http://www.awesomeweblogfordummies.com')) # => true
|
60
|
+
|
61
|
+
== RDF GRAPHS:
|
62
|
+
|
63
|
+
Every node has an associated graph, where all its relations are stored.
|
64
|
+
A node's own graph can be accessed by using node.graph.
|
65
|
+
|
66
|
+
Graphs can also be created and merged:
|
67
|
+
|
68
|
+
alice = Node("ex:alice")
|
69
|
+
alice.foaf::name = "Alice"
|
70
|
+
|
71
|
+
bob = Node("ex:bob")
|
72
|
+
bob.foaf::name = "Bob"
|
73
|
+
|
74
|
+
graph = Graph.new # Creates a new empty graph
|
75
|
+
graph << alice # Adds alice to the graph
|
76
|
+
graph[Node("ex:alice")].foaf::name # => ["Alice"]
|
77
|
+
graph[Node("ex:bob")].foaf::name # => []
|
78
|
+
|
79
|
+
merged_graph = graph.merge(bob.graph)
|
80
|
+
graph[Node("ex:alice")].foaf::name # => ["Alice"]
|
81
|
+
graph[Node("ex:bob")].foaf::name # => ["Bob"]
|
82
|
+
merged_graph.triples.size # => 2
|
83
|
+
|
84
|
+
Also, queries for triples can be performed on a graph:
|
85
|
+
|
86
|
+
a = Node('ex:bob')
|
87
|
+
a.foaf::name = "Bob"
|
88
|
+
a.foaf::age = "24"
|
89
|
+
|
90
|
+
b = Node('ex:alice')
|
91
|
+
b.foaf::name = "Alice"
|
92
|
+
b.foaf::age = "22"
|
93
|
+
|
94
|
+
g = RDF::Graph.new
|
95
|
+
g << a
|
96
|
+
g << b
|
97
|
+
|
98
|
+
# Find a person with age == 22
|
99
|
+
g.find(nil, Node('foaf:age'), "22") # => [Node('ex:bob')]
|
100
|
+
|
101
|
+
# Find all predicates bob has
|
102
|
+
g.find(Node('ex:bob'), nil, []) # => [Node('foaf:name'), Node('foaf:age')]
|
103
|
+
|
104
|
+
== PARSING AND SERIALIZING:
|
105
|
+
|
106
|
+
LightRDF uses Raptor library internally to support many RDF formats. It also introduces
|
107
|
+
YARF (Yet Another RDF Format) for serializing and parsing. A YARF document is as follows:
|
108
|
+
|
109
|
+
# Namespaces
|
110
|
+
rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
|
111
|
+
rdfs: http://www.w3.org/2000/01/rdf-schema#
|
112
|
+
ex: http://www.example.com/ontology#
|
113
|
+
foaf: http://xmlns.com/foaf/0.1/
|
114
|
+
geo: http://www.w3.org/2003/01/geo/wgs84_pos#
|
115
|
+
|
116
|
+
# RDF data
|
117
|
+
ex:bob:
|
118
|
+
rdf:type: rdf:Resource
|
119
|
+
foaf:name: "Bob"
|
120
|
+
foaf:weblog:
|
121
|
+
http://www.awesomeblog.com:
|
122
|
+
dc:title: "Awesome blog"
|
123
|
+
http://www.anotherawesomeblog.com:
|
124
|
+
dc:title: "Another awesome blog"
|
125
|
+
foaf:based_near:
|
126
|
+
_:spain: # Blank node with a specified id
|
127
|
+
geo:lat: "37.0625"
|
128
|
+
*: # Blank node with auto generated id
|
129
|
+
geo:lat: "55.701"
|
130
|
+
geo:lng: "12.552"
|
131
|
+
_:spain: # Blank node with an id can be referenced in the doc
|
132
|
+
geo:lng: "-95.677068"
|
133
|
+
|
134
|
+
Documents in either yarf, rdf, json, ejson (for easyJSON), dot, png, ntriples format can be parsed into
|
135
|
+
graphs and serialized using the parse and serialize methods:
|
136
|
+
|
137
|
+
graph = RDF::Parser.parse(:rdf, open('http://planetrdf.com/guide/rss.rdf').read) # Builds a graph
|
138
|
+
graph.serialize(:ntriples) # Outputs a string with triples
|
139
|
+
|
140
|
+
== LICENSE:
|
141
|
+
|
142
|
+
(The MIT License)
|
143
|
+
|
144
|
+
Copyright (c) 2010 José Ignacio Fernández (joseignacio.fernandez <at> gmail.com)
|
145
|
+
|
146
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
147
|
+
a copy of this software and associated documentation files (the
|
148
|
+
'Software'), to deal in the Software without restriction, including
|
149
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
150
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
151
|
+
permit persons to whom the Software is furnished to do so, subject to
|
152
|
+
the following conditions:
|
153
|
+
|
154
|
+
The above copyright notice and this permission notice shall be
|
155
|
+
included in all copies or substantial portions of the Software.
|
156
|
+
|
157
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
158
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
159
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
160
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
161
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
162
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
163
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/lightrdf'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'lightrdf' do
|
14
|
+
self.developer 'José Ignacio', 'joseignacio.fernandez@gmail.com'
|
15
|
+
self.summary = "Light and easy library for managing RDF data and graphs"
|
16
|
+
self.post_install_message = '**Remember to install raptor RDF tools and (optionally for RDF PNG output) Graphviz**'
|
17
|
+
self.rubyforge_name = self.name # TODO this is default value
|
18
|
+
self.extra_deps = [['activesupport','>= 2.0.2']]
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'newgem/tasks'
|
22
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
data/bin/yarfp
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'lightrdf'
|
5
|
+
require 'open-uri'
|
6
|
+
|
7
|
+
if $*.size < 1
|
8
|
+
puts 'YARF Parser'
|
9
|
+
puts 'Usage: yarfp URI_or_file [output_format] [input_format] [namespaces]'
|
10
|
+
puts ''
|
11
|
+
puts 'Available formats: yarf (default input), ntriples (default output), rdfxml, turtle, rdfa, dot, png'
|
12
|
+
puts 'Namespaces: prefix1,uri1,prefix2,uri2... e.g.: sc,http://lab.gsi.dit.upm.es/scrappy/schema.rdf#'
|
13
|
+
exit
|
14
|
+
else
|
15
|
+
uri = $*[0]
|
16
|
+
output_format = ($*[1] || :ntriples).to_sym
|
17
|
+
input_format = ($*[2] || :yarf).to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
$*[3].split(',').each_slice(2) { |prefix, url| Namespace prefix, url } if $*[3]
|
21
|
+
|
22
|
+
data = open(uri).read
|
23
|
+
puts RDF::Parser.parse(input_format.to_sym, data).serialize(output_format.to_sym)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module RDF
|
2
|
+
class Graph < Hash
|
3
|
+
include Parser
|
4
|
+
# Namespace set stored when parsing a file. Can be used for reference
|
5
|
+
attr_accessor :ns
|
6
|
+
def initialize triples=[]
|
7
|
+
super(nil)
|
8
|
+
@ns = {}
|
9
|
+
self.triples = triples
|
10
|
+
end
|
11
|
+
|
12
|
+
def << node
|
13
|
+
self[node] = node
|
14
|
+
end
|
15
|
+
|
16
|
+
def [] id
|
17
|
+
super(ID(id)) || Node.new(id, self)
|
18
|
+
end
|
19
|
+
def []= id, node
|
20
|
+
node.graph = self
|
21
|
+
super ID(id), node
|
22
|
+
end
|
23
|
+
def nodes; values; end
|
24
|
+
def inspect
|
25
|
+
"{" + (values.map{|v| v.inspect} * ", ") + "}"
|
26
|
+
end
|
27
|
+
def merge graph
|
28
|
+
new_graph = Graph.new
|
29
|
+
new_graph.triples = triples + graph.triples
|
30
|
+
new_graph
|
31
|
+
end
|
32
|
+
def triples
|
33
|
+
triples = []; values.each { |n| triples += n.triples }
|
34
|
+
triples
|
35
|
+
end
|
36
|
+
def triples= triples
|
37
|
+
self.clear
|
38
|
+
triples.each { |s, p, o| self[s][p] = self[s][p] + [o] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def find subject, predicate, object
|
42
|
+
matches = triples.select { |s,p,o| (subject.nil? or subject==[] or s==subject) and
|
43
|
+
(predicate.nil? or predicate==[] or p==predicate) and
|
44
|
+
(object.nil? or object==[] or o==object) }
|
45
|
+
result = []
|
46
|
+
result += matches.map {|t| t[0] } if subject.nil?
|
47
|
+
result += matches.map {|t| t[1] } if predicate.nil?
|
48
|
+
result += matches.map {|t| t[2] } if object.nil?
|
49
|
+
result.uniq
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module RDF
|
2
|
+
class NsProxy
|
3
|
+
undef :type
|
4
|
+
undef :class
|
5
|
+
undef :id
|
6
|
+
def initialize ns, object
|
7
|
+
@object = object
|
8
|
+
@ns = ns
|
9
|
+
end
|
10
|
+
def method_missing method, *args
|
11
|
+
if method.to_s =~ /.*=\Z/
|
12
|
+
@object["#{@ns}:#{method.to_s[0..-2]}"] = args.first
|
13
|
+
elsif method.to_s =~ /.*\?\Z/
|
14
|
+
@object["#{@ns}:#{method.to_s[0..-2]}"].include?(args.first)
|
15
|
+
else
|
16
|
+
@object["#{@ns}:#{method}"]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Node < Hash
|
22
|
+
include Parser
|
23
|
+
attr_accessor :graph, :id
|
24
|
+
|
25
|
+
def initialize id=nil, graph=nil
|
26
|
+
@id = ID::parse(id)
|
27
|
+
@graph = graph || Graph.new
|
28
|
+
@graph << self
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing method, *args
|
32
|
+
QURI.ns[method] ? NsProxy.new(method, self) : super
|
33
|
+
end
|
34
|
+
def inspect; "<#{self.class} #{id} #{super}>"; end
|
35
|
+
def to_s; id.to_s; end
|
36
|
+
|
37
|
+
def [] name
|
38
|
+
self[Node(name)] = [] if super(Node(name)).nil?
|
39
|
+
super(Node(name)).map! {|n| n.is_a?(Node) ? @graph[n] : n}
|
40
|
+
end
|
41
|
+
def []= name, values
|
42
|
+
super(Node(name), [values].flatten.map { |node| node.is_a?(Node) ? @graph[node] : node })
|
43
|
+
end
|
44
|
+
|
45
|
+
def == b
|
46
|
+
eql? b
|
47
|
+
end
|
48
|
+
def eql? b
|
49
|
+
b.is_a?(Node) and self.id == b.id
|
50
|
+
end
|
51
|
+
def hash # Hack for Ruby 1.8.6
|
52
|
+
id.hash + self.class.hash
|
53
|
+
end
|
54
|
+
|
55
|
+
def predicates
|
56
|
+
keys.map { |p| [ @graph[p], self[p] ] }
|
57
|
+
end
|
58
|
+
|
59
|
+
def triples
|
60
|
+
triples = []; each { |k, v| v.each { |o| triples << [self, Node(k), o] } }
|
61
|
+
triples
|
62
|
+
end
|
63
|
+
|
64
|
+
def merge node
|
65
|
+
new_node = clone
|
66
|
+
(self.keys + node.keys).uniq.each do |k|
|
67
|
+
new_node[k] = (node[k] + self[k]).uniq
|
68
|
+
end
|
69
|
+
new_node
|
70
|
+
end
|
71
|
+
|
72
|
+
def clone
|
73
|
+
Node.new self
|
74
|
+
end
|
75
|
+
|
76
|
+
def bnode?
|
77
|
+
id.is_a?(BNodeID)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def Node id, graph=nil
|
83
|
+
graph.nil? ? RDF::Node.new(id, graph) : graph[id]
|
84
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
module RDF
|
2
|
+
module Parser
|
3
|
+
def to_ntriples; serialize :ntriples; end
|
4
|
+
|
5
|
+
def serialize format, header=true
|
6
|
+
nodes = if self.is_a?(Graph)
|
7
|
+
nodes_as_objects = find([], [], nil).select{|n| n.is_a?(Node)}
|
8
|
+
values - nodes_as_objects + nodes_as_objects # Put them at the end
|
9
|
+
else [self]
|
10
|
+
end
|
11
|
+
|
12
|
+
case format.to_sym
|
13
|
+
when :ntriples
|
14
|
+
triples.map { |s,p,o| "#{serialize_chunk_ntriples(s)} #{serialize_chunk_ntriples(p)} #{serialize_chunk_ntriples(o)} ." } * "\n"
|
15
|
+
when :yarf
|
16
|
+
ns = respond_to?(:ns) ? QURI.ns.merge(self.ns) : QURI.ns
|
17
|
+
if header
|
18
|
+
(ns.map{|k,v| "#{k}: #{v}\n"} * '') + serialize_yarf(nodes, ns)
|
19
|
+
else
|
20
|
+
serialize_yarf(nodes, ns)
|
21
|
+
end
|
22
|
+
when :ejson
|
23
|
+
stdin, stdout, stderr = Open3.popen3("python -mjson.tool")
|
24
|
+
stdin.puts ActiveSupport::JSON.encode(serialize_ejson(nodes))
|
25
|
+
stdin.close
|
26
|
+
stdout.read
|
27
|
+
when :png
|
28
|
+
dot = serialize(:dot)
|
29
|
+
ns = respond_to?(:ns) ? QURI.ns.merge(self.ns) : QURI.ns
|
30
|
+
ns.each { |k,v| dot.gsub!(v, "#{k}:") }
|
31
|
+
dot.gsub!(/label=\"\\n\\nModel:.*\)\";/, '')
|
32
|
+
stdin, stdout, stderr = Open3.popen3("dot -o/dev/stdout -Tpng")
|
33
|
+
stdin.puts dot
|
34
|
+
stdin.close
|
35
|
+
stdout.read
|
36
|
+
when :rdf
|
37
|
+
serialize(:rdfxml)
|
38
|
+
else
|
39
|
+
stdin, stdout, stderr = Open3.popen3("rapper -q -i ntriples -o #{format} /dev/stdin")
|
40
|
+
stdin.puts serialize(:ntriples)
|
41
|
+
stdin.close
|
42
|
+
stdout.read
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.parse format, text
|
47
|
+
case format
|
48
|
+
when :ntriples
|
49
|
+
graph = RDF::Graph.new
|
50
|
+
graph.triples = text.split(".\n").map do |l|
|
51
|
+
s,p,o = l.strip.match(/(<.+>|".*"|_:.*)\W(<.+>|".*"|_:.*)\W(<.+>|".*"|_:.*)/).captures
|
52
|
+
[parse_chunk_ntriples(s), parse_chunk_ntriples(p), parse_chunk_ntriples(o)]
|
53
|
+
end
|
54
|
+
graph
|
55
|
+
when :yarf
|
56
|
+
graph = RDF::Graph.new
|
57
|
+
ns = {}
|
58
|
+
# Preprocessing - Extract namespaces, remove comments, get indent levels
|
59
|
+
lines = []
|
60
|
+
text.split("\n").each_with_index do |line, n|
|
61
|
+
if line =~ /(\A\s*#|\A\w*\Z)/ # Comment or blank line - do nothing
|
62
|
+
elsif line =~ /\A(\w+):\s+(.+)/ # Namespace
|
63
|
+
ns[$1.to_sym] = $2
|
64
|
+
else # Normal line - store line number, get indent level and strip line
|
65
|
+
lines << [n+1, (line.size - line.lstrip.size)/2, line.strip]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
parse_yarf_nodes lines, graph, ns
|
69
|
+
graph.ns = ns
|
70
|
+
graph
|
71
|
+
when :rdf
|
72
|
+
parse :rdfxml, text
|
73
|
+
when :ejson
|
74
|
+
raise Exception, "eJSON format cannot be parsed (yet)"
|
75
|
+
else
|
76
|
+
begin
|
77
|
+
stdin, stdout, stderr = Open3.popen3("rapper -q -i #{format} -o ntriples /dev/stdin")
|
78
|
+
stdin.puts text
|
79
|
+
stdin.close
|
80
|
+
rescue
|
81
|
+
end
|
82
|
+
parse :ntriples, stdout.read
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def self.parse_yarf_nodes lines, graph, ns, base_level=0, i=0
|
88
|
+
nodes = []
|
89
|
+
while i < lines.size
|
90
|
+
number, level, line = lines[i]
|
91
|
+
if level == base_level
|
92
|
+
nodes << if line =~ /\A(".*")\Z/ # Literal
|
93
|
+
i += 1
|
94
|
+
ActiveSupport::JSON.decode($1)
|
95
|
+
elsif line =~ /\A(.+):\Z/ # Node with relations
|
96
|
+
node = Node(ID($1, ns), graph)
|
97
|
+
i, relations = parse_yarf_relations(lines, graph, ns, level+1, i+1)
|
98
|
+
relations.each { |predicate, object| node[predicate] = node[predicate] + [object] }
|
99
|
+
node
|
100
|
+
elsif line =~ /\A(.+)\Z/ # Node
|
101
|
+
i += 1
|
102
|
+
Node(ID($1, ns), graph)
|
103
|
+
else
|
104
|
+
raise Exception, "Syntax error on line #{number}"
|
105
|
+
end
|
106
|
+
elsif level < base_level
|
107
|
+
break
|
108
|
+
else
|
109
|
+
raise Exception, "Indentation error on line #{number}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
[i, nodes]
|
113
|
+
end
|
114
|
+
def self.parse_yarf_relations lines, graph, ns, base_level, i
|
115
|
+
relations = []
|
116
|
+
while i < lines.size
|
117
|
+
number, level, line = lines[i]
|
118
|
+
if level == base_level
|
119
|
+
relations += if line =~ /\A(.+):\s+(".+")\Z/ # Predicate and literal
|
120
|
+
i += 1
|
121
|
+
[[Node(ID($1, ns), graph), ActiveSupport::JSON.decode($2)]]
|
122
|
+
elsif line =~ /\A(.+):\s+(.+)\Z/ # Predicate and node
|
123
|
+
i += 1
|
124
|
+
[[Node(ID($1, ns), graph), Node(ID($2, ns), graph)]]
|
125
|
+
elsif line =~ /\A(.+):\Z/ # Just the predicate
|
126
|
+
predicate = Node(ID($1, ns), graph)
|
127
|
+
i, objects = parse_yarf_nodes(lines, graph, ns, level+1, i+1)
|
128
|
+
objects.map {|n| [predicate, n]}
|
129
|
+
end
|
130
|
+
elsif level < base_level
|
131
|
+
break
|
132
|
+
else
|
133
|
+
raise Exception, "Indentation error on line #{number}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
[i, relations]
|
137
|
+
end
|
138
|
+
|
139
|
+
def serialize_yarf nodes, ns=QURI.ns, level=0, already_serialized=[]
|
140
|
+
text = ""
|
141
|
+
|
142
|
+
for node in nodes
|
143
|
+
next if level == 0 and (node.triples.size == 0 or already_serialized.include?(node))
|
144
|
+
text += " " *level*2
|
145
|
+
text += serialize_chunk_yarf(node, ns)
|
146
|
+
if already_serialized.include?(node) or !node.is_a?(Node) or node.triples.size == 0
|
147
|
+
text += "\n"
|
148
|
+
else
|
149
|
+
already_serialized << node
|
150
|
+
text += ":\n"
|
151
|
+
node.predicates.each do |p, o| # Predicate and object
|
152
|
+
text += " " *(level+1)*2
|
153
|
+
text += p.id.compressed(ns)
|
154
|
+
text += ":"
|
155
|
+
if o.size == 1 and (already_serialized.include?(o.first) or !o.first.is_a?(Node) or o.first.triples.size==0)
|
156
|
+
text += " " + serialize_chunk_yarf(o.first, ns)
|
157
|
+
text += "\n"
|
158
|
+
else
|
159
|
+
text += "\n" + serialize_yarf(o, ns, level+2, already_serialized)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
text
|
165
|
+
end
|
166
|
+
|
167
|
+
def serialize_chunk_yarf node, ns=QURI.ns
|
168
|
+
if node.is_a?(Node)
|
169
|
+
if node.bnode?
|
170
|
+
if node.graph.find(nil, [], node).size > 1 # Only use a bnode-id if it appears again as object
|
171
|
+
node.id.to_s
|
172
|
+
else
|
173
|
+
"*"
|
174
|
+
end
|
175
|
+
else
|
176
|
+
node.id.compressed ns
|
177
|
+
end
|
178
|
+
else
|
179
|
+
ActiveSupport::JSON.encode(node)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def serialize_ejson nodes, already_serialized=[], level=0
|
184
|
+
list = []
|
185
|
+
|
186
|
+
nodes.each do |n|
|
187
|
+
if n.is_a?(Node)
|
188
|
+
if already_serialized.include?(n)
|
189
|
+
list << { 'id' => n.to_s } unless level == 0
|
190
|
+
else
|
191
|
+
already_serialized << n
|
192
|
+
hash = { 'id' => n.to_s }
|
193
|
+
n.predicates.each do |k,v|
|
194
|
+
hash[k.to_s] = serialize_ejson(v, already_serialized, level+1)
|
195
|
+
end
|
196
|
+
list << hash
|
197
|
+
end
|
198
|
+
else
|
199
|
+
list << n.to_s
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
list
|
204
|
+
end
|
205
|
+
|
206
|
+
def serialize_chunk_ntriples n
|
207
|
+
if n.is_a? Node
|
208
|
+
n.bnode? ? n.to_s : "<#{n}>"
|
209
|
+
else
|
210
|
+
ActiveSupport::JSON.encode(n)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.parse_chunk_ntriples c
|
215
|
+
case c[0..0]
|
216
|
+
when '<' then Node c[1..-2]
|
217
|
+
when '_' then Node c
|
218
|
+
when '"' then
|
219
|
+
ActiveSupport::JSON.decode(c)
|
220
|
+
else
|
221
|
+
raise Exception, "Parsing error: #{c}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module RDF
|
2
|
+
module QURI
|
3
|
+
@ns = { :rdf => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
4
|
+
:rdfs => 'http://www.w3.org/2000/01/rdf-schema#',
|
5
|
+
:dc => 'http://purl.org/dc/elements/1.1/',
|
6
|
+
:owl => 'http://www.w3.org/2002/07/owl#' }
|
7
|
+
def self.ns; @ns; end
|
8
|
+
|
9
|
+
def self.parse uri, ns={}
|
10
|
+
URI.parse( (uri.to_s =~ /(\w+):(.*)/ and uri.to_s[0..6]!='http://' and uri.to_s[0..7]!='https://') ?
|
11
|
+
"#{QURI.ns.merge(ns)[$1.to_sym]}#{$2}" :
|
12
|
+
uri.to_s )
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.compress uri, ns={}
|
16
|
+
QURI.ns.merge(ns).map.sort_by{|k,v| -v.to_s.size}.each do |k,v|
|
17
|
+
if uri.to_s.index(v) == 0
|
18
|
+
return "#{k}:#{uri.to_s[v.size..-1]}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
uri.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ID
|
26
|
+
def self.ns; QURI.ns; end
|
27
|
+
|
28
|
+
def compressed ns={}
|
29
|
+
RDF::ID.compress self, ns
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.compress id, ns={}
|
33
|
+
bnode?(id) ? id.to_s : RDF::QURI.compress(id, ns)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.parse id, ns={}
|
37
|
+
bnode?(id) ? RDF::BNodeID.parse(id) : RDF::QURI.parse(id, ns)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.bnode?(id)
|
41
|
+
id.nil? or id == '*' or id.to_s[0..0] == '_'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class BNodeID
|
46
|
+
include RDF::ID
|
47
|
+
@count = 0
|
48
|
+
def self.count; @count; end
|
49
|
+
def self.count=c; @count=c; end
|
50
|
+
|
51
|
+
attr_reader :id
|
52
|
+
def initialize id=nil
|
53
|
+
@id = (id || "_:bnode#{RDF::BNodeID.count+=1}").to_s
|
54
|
+
end
|
55
|
+
def self.parse id
|
56
|
+
new(id=='*' ? nil : id)
|
57
|
+
end
|
58
|
+
def to_s; id.to_s; end
|
59
|
+
|
60
|
+
def == b
|
61
|
+
eql? b
|
62
|
+
end
|
63
|
+
def eql? b
|
64
|
+
b.is_a?(BNodeID) and self.id == b.id
|
65
|
+
end
|
66
|
+
def hash # Hack for Ruby 1.8.6
|
67
|
+
id.hash + self.class.hash
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class URI::Generic
|
73
|
+
include RDF::ID # URIs are RDF IDs
|
74
|
+
end
|
75
|
+
|
76
|
+
def ID uri, ns={} # Shortcut to parse IDs
|
77
|
+
RDF::ID.parse uri, ns
|
78
|
+
end
|
79
|
+
def Namespace prefix, uri
|
80
|
+
RDF::QURI.ns[prefix.to_sym] = uri
|
81
|
+
end
|
data/lib/lightrdf.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
module RDF
|
5
|
+
VERSION = '0.1'
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'active_support'
|
10
|
+
require 'uri'
|
11
|
+
require 'open3'
|
12
|
+
require 'open-uri'
|
13
|
+
|
14
|
+
require "lightrdf/quri"
|
15
|
+
require "lightrdf/parser"
|
16
|
+
require "lightrdf/graph"
|
17
|
+
require "lightrdf/node"
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class TestLightRDF < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
Namespace :ex, 'http://www.example.com/ontology#'
|
7
|
+
Namespace :foaf, 'http://xmlns.com/foaf/0.1/'
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_equality
|
11
|
+
assert Node('_:something') == Node(Node('_:something'))
|
12
|
+
assert ID('_:2') == ID('_:2')
|
13
|
+
assert ID('http://www.w3.org/1999/02/22-rdf-syntax-ns#type') == ID('rdf:type')
|
14
|
+
assert Node('_:2') == Node('_:2')
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_type
|
18
|
+
assert ID('rdf:type').is_a?(RDF::ID)
|
19
|
+
assert ID('_:bnode').is_a?(RDF::ID)
|
20
|
+
assert ID('http://www.google.com').is_a?(RDF::ID)
|
21
|
+
assert URI.parse('http://www.google.com').is_a?(RDF::ID)
|
22
|
+
assert ID('*').is_a?(RDF::ID)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_random_node
|
26
|
+
assert Node(nil).is_a?(RDF::Node)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_graph
|
30
|
+
graph = RDF::Graph.new
|
31
|
+
assert_equal graph['rdf:type'], graph[Node('rdf:type')]
|
32
|
+
assert_equal graph['rdf:type'], graph['rdf:type']
|
33
|
+
assert_not_equal graph['foaf:name'], graph[Node('foaf::nick')]
|
34
|
+
assert_not_equal graph['rdf:type'], graph[Node('rdf:class')]
|
35
|
+
assert !graph['rdf:type'].eql?(graph[Node('rdf:class')])
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_namespaces
|
39
|
+
assert_equal Node('http://www.example.com/ontology#test'), Node('ex:test')
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_attributes
|
43
|
+
a = Node('ex:bob')
|
44
|
+
a.foaf::name = "Bob"
|
45
|
+
assert_equal "Bob", a.foaf::name.first
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_query
|
49
|
+
a = Node('ex:bob')
|
50
|
+
a.foaf::name = "Bob"
|
51
|
+
a.foaf::age = "24"
|
52
|
+
|
53
|
+
b = Node('ex:alice')
|
54
|
+
b.foaf::name = "Alice"
|
55
|
+
b.foaf::age = "22"
|
56
|
+
|
57
|
+
g = RDF::Graph.new
|
58
|
+
g << a
|
59
|
+
g << b
|
60
|
+
|
61
|
+
assert_equal 1, g.find(nil, Node('foaf:age'), "22").size
|
62
|
+
assert_equal 2, g.find(Node('ex:bob'), nil, []).size
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_addition
|
66
|
+
a = Node('ex:bob')
|
67
|
+
a.foaf::weblog = Node('http://www.awesomeweblogfordummies.com')
|
68
|
+
a.foaf::weblog << Node('http://www.anotherawesomeweblogfordummies.com')
|
69
|
+
assert_equal 2, a.foaf::weblog.size
|
70
|
+
assert a.foaf::weblog?(Node('http://www.awesomeweblogfordummies.com'))
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_graph_merge
|
74
|
+
a = Node('ex:bob')
|
75
|
+
a.foaf::name = "Bob"
|
76
|
+
|
77
|
+
b = Node('ex:alice')
|
78
|
+
b.foaf::name = "Alice"
|
79
|
+
|
80
|
+
g1 = RDF::Graph.new
|
81
|
+
g2 = RDF::Graph.new
|
82
|
+
g1 << a
|
83
|
+
g2 << b
|
84
|
+
|
85
|
+
g3 = g1.merge(g2)
|
86
|
+
|
87
|
+
assert ["Alice"], g3[Node('ex:Alice')].foaf::name
|
88
|
+
assert ["Bob"], g3[Node('ex:bob')].foaf::name
|
89
|
+
assert_equal 2, g3.triples.size
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_parsing
|
93
|
+
text = """
|
94
|
+
dc: http://purl.org/dc/elements/1.1/
|
95
|
+
rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
|
96
|
+
ex: http://www.example.com/ontology#
|
97
|
+
foaf: http://xmlns.com/foaf/0.1/
|
98
|
+
*:
|
99
|
+
rdf:type: rdf:Resource
|
100
|
+
foaf:name: \"Bob\"
|
101
|
+
foaf:weblog:
|
102
|
+
\"http://www.awesomeweblogfordummies.com\"
|
103
|
+
http://www.anotherawesomeweblogfordummies.com:
|
104
|
+
dc:title: \"Another awesome blog\"
|
105
|
+
"""
|
106
|
+
|
107
|
+
assert_equal 5, RDF::Parser.parse(:yarf, text).triples.size
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_parsing_raptor
|
111
|
+
# Very naive testing -- it only helps to check that rapper is being executed
|
112
|
+
assert RDF::Parser.parse(:rdf, open('http://planetrdf.com/guide/rss.rdf').read).serialize(:ntriples).split("\n").size > 10
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_serialization
|
116
|
+
a = Node('ex:bob')
|
117
|
+
a.foaf::name = "Bob"
|
118
|
+
a.foaf::age = "23"
|
119
|
+
|
120
|
+
g = RDF::Graph.new
|
121
|
+
g << a
|
122
|
+
|
123
|
+
assert 2, g.to_ntriples.split("\n").size
|
124
|
+
end
|
125
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lightrdf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- "Jos\xC3\xA9 Ignacio"
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-10-06 00:00:00 +02:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: activesupport
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
segments:
|
27
|
+
- 2
|
28
|
+
- 0
|
29
|
+
- 2
|
30
|
+
version: 2.0.2
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rubyforge
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 2
|
42
|
+
- 0
|
43
|
+
- 4
|
44
|
+
version: 2.0.4
|
45
|
+
type: :development
|
46
|
+
version_requirements: *id002
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: hoe
|
49
|
+
prerelease: false
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 2
|
56
|
+
- 6
|
57
|
+
- 0
|
58
|
+
version: 2.6.0
|
59
|
+
type: :development
|
60
|
+
version_requirements: *id003
|
61
|
+
description: LightRDF is a gem that makes managing RDF data and graphs easily with a Ruby interface.
|
62
|
+
email:
|
63
|
+
- joseignacio.fernandez@gmail.com
|
64
|
+
executables:
|
65
|
+
- yarfp
|
66
|
+
extensions: []
|
67
|
+
|
68
|
+
extra_rdoc_files:
|
69
|
+
- History.txt
|
70
|
+
- Manifest.txt
|
71
|
+
files:
|
72
|
+
- History.txt
|
73
|
+
- Manifest.txt
|
74
|
+
- README.rdoc
|
75
|
+
- Rakefile
|
76
|
+
- bin/yarfp
|
77
|
+
- lib/lightrdf.rb
|
78
|
+
- lib/lightrdf/graph.rb
|
79
|
+
- lib/lightrdf/node.rb
|
80
|
+
- lib/lightrdf/parser.rb
|
81
|
+
- lib/lightrdf/quri.rb
|
82
|
+
- test/test_helper.rb
|
83
|
+
- test/test_lightrdf.rb
|
84
|
+
has_rdoc: true
|
85
|
+
homepage: http://github.com/josei/lightrdf
|
86
|
+
licenses: []
|
87
|
+
|
88
|
+
post_install_message: "**Remember to install raptor RDF tools and (optionally for RDF PNG output) Graphviz**"
|
89
|
+
rdoc_options:
|
90
|
+
- --main
|
91
|
+
- README.rdoc
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
requirements: []
|
109
|
+
|
110
|
+
rubyforge_project: lightrdf
|
111
|
+
rubygems_version: 1.3.6
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: Light and easy library for managing RDF data and graphs
|
115
|
+
test_files:
|
116
|
+
- test/test_lightrdf.rb
|
117
|
+
- test/test_helper.rb
|