redlander 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/redlander.rb ADDED
@@ -0,0 +1,61 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'uri'
5
+ require 'xml_schema'
6
+
7
+ module Redlander
8
+ require 'redland'
9
+ require 'redlander/version'
10
+
11
+ class RedlandError < RuntimeError; end
12
+
13
+ class << self
14
+
15
+ def rdf_world
16
+ unless @rdf_world
17
+ @rdf_world = Redland.librdf_new_world
18
+ raise RedlandError.new("Could not create a new RDF world") unless @rdf_world
19
+ ObjectSpace.define_finalizer(@rdf_world, proc { Redland.librdf_free_world(@rdf_world) })
20
+ Redland.librdf_world_open(@rdf_world)
21
+ end
22
+ @rdf_world
23
+ end
24
+
25
+ # Convert options hash into a string for librdf.
26
+ # What it does:
27
+ # 1) Convert boolean values into 'yes/no' values
28
+ # 2) Change underscores in key names into dashes ('dhar_ma' => 'dhar-ma')
29
+ # 3) Join all options as "key='value'" pairs in a comma-separated string
30
+ def to_rdf_options(options = {})
31
+ options.inject([]){|opts, option_pair|
32
+ key = option_pair[0].to_s.gsub(/_/, '-')
33
+ value = if [TrueClass, FalseClass].include?(option_pair[1].class)
34
+ option_pair[1] ? 'yes' : 'no'
35
+ else
36
+ option_pair[1]
37
+ end
38
+ opts << "#{key}='#{value}'"
39
+ }.join(',')
40
+ end
41
+
42
+ # Helper method to create an instance of rdfuri.
43
+ # For internal use only!
44
+ def to_rdf_uri(uri)
45
+ return nil if uri.nil?
46
+ uri = uri.is_a?(URI) ? uri.to_s : uri
47
+ rdf_uri = Redland.librdf_new_uri(rdf_world, uri)
48
+ raise RedlandError.new("Failed to create URI from '#{uri}'") unless rdf_uri
49
+ ObjectSpace.define_finalizer(rdf_uri, proc { Redland.librdf_free_uri(rdf_uri) })
50
+ rdf_uri
51
+ end
52
+
53
+ end
54
+
55
+ require 'redlander/error_container'
56
+ require 'redlander/statement_iterator'
57
+ require 'redlander/parser'
58
+ require 'redlander/serializer'
59
+ require 'redlander/model'
60
+ require 'redlander/node'
61
+ end
@@ -0,0 +1,41 @@
1
+ module ErrorContainer
2
+
3
+ class Errors
4
+ include Enumerable
5
+
6
+ def initialize
7
+ @container = []
8
+ end
9
+
10
+ def add(error_message)
11
+ if @container.include?(error_message)
12
+ @container
13
+ else
14
+ @container << error_message
15
+ end
16
+ end
17
+
18
+ def each
19
+ @container.each do |err|
20
+ yield err
21
+ end
22
+ end
23
+
24
+ def empty?
25
+ @container.empty?
26
+ end
27
+
28
+ def clear
29
+ @container.clear
30
+ end
31
+
32
+ def size
33
+ @container.size
34
+ end
35
+ end
36
+
37
+ def errors
38
+ @errors ||= Errors.new
39
+ end
40
+
41
+ end
@@ -0,0 +1,33 @@
1
+ require 'redlander/storage'
2
+ require 'redlander/model_proxy'
3
+
4
+ module Redlander
5
+
6
+ class Model
7
+
8
+ include Redlander::ParsingInstanceMethods
9
+ include Redlander::SerializingInstanceMethods
10
+
11
+ attr_reader :rdf_model
12
+
13
+ # Create a new RDF model.
14
+ # For explanation of options, read Storage.initialize_storage
15
+ def initialize(options = {})
16
+ @rdf_storage = Storage.initialize_storage(options)
17
+
18
+ @rdf_model = Redland.librdf_new_model(Redlander.rdf_world, @rdf_storage, "")
19
+ raise RedlandError.new("Failed to create a new model") unless @rdf_model
20
+ ObjectSpace.define_finalizer(@rdf_model, proc { Redland.librdf_free_model(@rdf_model) })
21
+ end
22
+
23
+ # Statements contained in the model.
24
+ #
25
+ # Similar to Ruby on Rails, a proxy object is actually returned,
26
+ # which delegates methods to Statement class.
27
+ def statements
28
+ ModelProxy.new(self)
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,85 @@
1
+ require 'redlander/statement'
2
+
3
+ module Redlander
4
+
5
+ class ModelProxy
6
+
7
+ include StatementIterator
8
+
9
+ def initialize(model, rdf_stream = nil)
10
+ @model = model
11
+ @rdf_stream = if rdf_stream
12
+ rdf_stream
13
+ else
14
+ Redland.librdf_model_as_stream(@model.rdf_model)
15
+ end
16
+ raise RedlandError.new("Failed to create a new stream") unless @rdf_stream
17
+ ObjectSpace.define_finalizer(@rdf_stream, proc { Redland.librdf_free_stream(@rdf_stream) })
18
+ end
19
+
20
+ # Add a statement to the model.
21
+ # It must be a complete statement - all of subject, predicate, object parts must be present.
22
+ # Only statements that are legal RDF can be added.
23
+ # If the statement already exists in the model, it is not added.
24
+ #
25
+ # Returns true on success or false on failure.
26
+ def add(statement)
27
+ statement.model = @model
28
+ end
29
+
30
+ # Create a statement and add it to the model.
31
+ #
32
+ # Options are:
33
+ # :subject, :predicate, :object,
34
+ # (see Statement.new for option explanations).
35
+ #
36
+ # Returns an instance of Statement on success,
37
+ # or nil if the statement could not be added.
38
+ def create(options = {})
39
+ statement = Statement.new(options)
40
+ add(statement) && statement
41
+ end
42
+
43
+ def empty?
44
+ size.zero?
45
+ end
46
+
47
+ def size
48
+ s = Redland.librdf_model_size(@model.rdf_model)
49
+ if s < 0
50
+ raise RedlandError.new("Attempt to get size when using non-countable storage")
51
+ else
52
+ s
53
+ end
54
+ end
55
+
56
+ # Find statements satisfying the given criteria.
57
+ # Scope can be:
58
+ # :all
59
+ # :first
60
+ def find(scope, options = {})
61
+ statement = Statement.new(options)
62
+ rdf_stream = Redland.librdf_model_find_statements(@model.rdf_model, statement.rdf_statement)
63
+ proxy = self.class.new(@model, rdf_stream)
64
+
65
+ case scope
66
+ when :first
67
+ proxy.first
68
+ when :all
69
+ if block_given?
70
+ proxy.each
71
+ else
72
+ proxy
73
+ end
74
+ else
75
+ raise RedlandError.new("Invalid search scope '#{scope}' specified.")
76
+ end
77
+ end
78
+
79
+ def all(options = {})
80
+ find(:all, options)
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,90 @@
1
+ module Redlander
2
+
3
+ class Node
4
+
5
+ attr_reader :rdf_node
6
+
7
+ # Create a RDF node.
8
+ # Argument can be:
9
+ # - an instance of URI - to create a RDF "resource",
10
+ # - an instance of Node - to create a copy of the node,
11
+ # - nil (or absent) - to create a "blank" node,
12
+ # - any other Ruby object, which can be coerced into a literal.
13
+ # If nothing else, a RedlandError is thrown.
14
+ #
15
+ # Note, that you cannot create a resource node from an URI string,
16
+ # it must be an instance of URI. Otherwise it is treated as a string literal.
17
+ def initialize(arg = nil)
18
+ @rdf_node = if arg.nil?
19
+ Redland.librdf_new_node_from_blank_identifier(Redlander.rdf_world, nil)
20
+ elsif arg.is_a?(URI)
21
+ Redland.librdf_new_node_from_uri_string(Redlander.rdf_world, arg.to_s)
22
+ elsif arg.is_a?(SWIG::TYPE_p_librdf_node_s)
23
+ # A special case, where you can pass an instance of SWIG::TYPE_p_librdf_node_s
24
+ # in order to create a Node from an internal RDF node representation.
25
+ arg
26
+ elsif arg.is_a?(Node)
27
+ Redland.librdf_new_node_from_node(arg.rdf_node)
28
+ else
29
+ value = arg.respond_to?(:xmlschema) ? arg.xmlschema : arg.to_s
30
+ datatype = Redlander.to_rdf_uri(XmlSchema.datatype_of(arg))
31
+ Redland.librdf_new_node_from_typed_literal(Redlander.rdf_world, value, nil, datatype)
32
+ end
33
+
34
+ raise RedlandError.new("Failed to create a new node") unless @rdf_node
35
+ ObjectSpace.define_finalizer(@rdf_node, proc { Redland.librdf_free_node(@rdf_node) })
36
+ end
37
+
38
+ def resource?
39
+ !Redland.librdf_node_is_resource(@rdf_node).zero?
40
+ end
41
+
42
+ # Return true if node is a literal.
43
+ def literal?
44
+ !Redland.librdf_node_is_literal(@rdf_node).zero?
45
+ end
46
+
47
+ # Return true if node is a blank node.
48
+ def blank?
49
+ !Redland.librdf_node_is_blank(@rdf_node).zero?
50
+ end
51
+
52
+ # Return the datatype URI of the node.
53
+ # Returns nil if the node is not a literal, or has no datatype URI.
54
+ def datatype
55
+ if rdf_uri = Redland.librdf_node_get_literal_value_datatype_uri(@rdf_node)
56
+ ObjectSpace.define_finalizer(rdf_uri, proc { Redland.librdf_free_uri(rdf_uri) })
57
+ Redland.librdf_uri_to_string(rdf_uri)
58
+ end
59
+ end
60
+
61
+ # Equivalency. Only works for comparing two Nodes.
62
+ def eql?(other_node)
63
+ !Redland.librdf_node_equals(@rdf_node, other_node.rdf_node).zero?
64
+ end
65
+ alias_method :==, :eql?
66
+
67
+ def hash
68
+ self.class.hash + to_s.hash
69
+ end
70
+
71
+ # Convert this node to a string (with a datatype suffix).
72
+ def to_s
73
+ Redland.librdf_node_to_string(@rdf_node)
74
+ end
75
+
76
+ # Value of the literal node as a Ruby object instance.
77
+ def value
78
+ if resource?
79
+ if rdf_uri = Redland.librdf_node_get_uri(@rdf_node)
80
+ ObjectSpace.define_finalizer(rdf_uri, proc { Redland.librdf_free_uri(rdf_uri) })
81
+ URI.parse(Redland.librdf_uri_to_string(rdf_uri))
82
+ end
83
+ else
84
+ XmlSchema.instantiate(to_s)
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,92 @@
1
+ require 'redlander/parser_proxy'
2
+
3
+ module Redlander
4
+
5
+ class Parser
6
+
7
+ attr_reader :rdf_parser
8
+
9
+ # Create a new parser.
10
+ # Name can be either of [:rdfxml, :ntriples, :turtle],
11
+ # or nil, which defaults to :rdfxml.
12
+ #
13
+ # TODO: Only a small subset of parsers is implemented,
14
+ # because the rest seem to be very buggy.
15
+ def initialize(name = :rdfxml)
16
+ # name, mime-type and syntax uri can all be nil, which defaults to :rdfxml parser
17
+ @rdf_parser = Redland.librdf_new_parser(Redlander.rdf_world, name.to_s, nil, nil)
18
+ raise RedlandError.new("Failed to create a new parser") unless @rdf_parser
19
+ ObjectSpace.define_finalizer(@rdf_parser, proc { Redland.librdf_free_parser(@rdf_parser) })
20
+ end
21
+
22
+ # Parse the content (String) into the model.
23
+ #
24
+ # Options are:
25
+ # :base_uri base URI (String or URI)
26
+ #
27
+ # Returns true on success, or false.
28
+ def from_string(model, content, options = {})
29
+ # FIXME: A bug (?) in Redland breaks NTriples parser if its input is not terminated with "\n"
30
+ content.concat("\n") unless content.end_with?("\n")
31
+ Redland.librdf_parser_parse_string_into_model(@rdf_parser, content, Redlander.to_rdf_uri(options[:base_uri]), model.rdf_model).zero?
32
+ end
33
+
34
+ # Parse the content from URI into the model.
35
+ # (It is possible to use "file:" schema for local files).
36
+ #
37
+ # Options are:
38
+ # :base_uri base URI (String or URI)
39
+ #
40
+ # Returns true on success, or false.
41
+ def from_uri(model, uri, options = {})
42
+ uri = URI.parse(uri)
43
+ uri = URI.parse("file://#{File.expand_path(uri.to_s)}") if uri.scheme.nil?
44
+ Redland.librdf_parser_parse_into_model(@rdf_parser, Redlander.to_rdf_uri(uri), Redlander.to_rdf_uri(options[:base_uri]), model.rdf_model).zero?
45
+ end
46
+ alias_method :from_file, :from_uri
47
+
48
+ def statements(content, options = {})
49
+ # BUG: Parser accumulates data from consequent runs??? WTF, Redland?!
50
+ # When parsing a series of files, parser reported a duplicate entry,
51
+ # then seemed to have stopped yielding statements at all.
52
+
53
+ # FIXME: A bug (?) in Redland breaks NTriples parser if its input is not terminated with "\n"
54
+ content.concat("\n") unless content.end_with?("\n")
55
+ ParserProxy.new(self, content, options)
56
+ end
57
+
58
+ end
59
+
60
+
61
+ module ParsingInstanceMethods
62
+
63
+ def from_rdfxml(content, options = {})
64
+ parser = Parser.new(:rdfxml)
65
+ parser.from_string(self, content, options)
66
+ end
67
+
68
+ def from_ntriples(content, options = {})
69
+ parser = Parser.new(:ntriples)
70
+ parser.from_string(self, content, options)
71
+ end
72
+
73
+ def from_turtle(content, options = {})
74
+ parser = Parser.new(:turtle)
75
+ parser.from_string(self, content, options)
76
+ end
77
+
78
+ # Load the model from an URI content.
79
+ #
80
+ # Options are:
81
+ # :format - content format [:rdfxml (default), :ntriples, :turtle]
82
+ # :base_uri - base URI
83
+ def from_uri(uri, options = {})
84
+ parser_options = options.dup
85
+ parser = Parser.new(parser_options.delete(:format) || :rdfxml)
86
+ parser.from_uri(self, uri, parser_options)
87
+ end
88
+ alias_method :from_file, :from_uri
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,18 @@
1
+ require 'redlander/statement'
2
+
3
+ module Redlander
4
+
5
+ class ParserProxy
6
+
7
+ include StatementIterator
8
+
9
+ def initialize(parser, content, options = {})
10
+ @model = nil # the yielded statements will not be bound to a model
11
+ @rdf_stream = Redland.librdf_parser_parse_string_as_stream(parser.rdf_parser, content, Redlander.to_rdf_uri(options[:base_uri]))
12
+ raise RedlandError.new("Failed to create a new stream") unless @rdf_stream
13
+ ObjectSpace.define_finalizer(@rdf_stream, proc { Redland.librdf_free_stream(@rdf_stream) })
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,80 @@
1
+ module Redlander
2
+
3
+ class Serializer
4
+
5
+ # Create a new serializer.
6
+ # Name can be either of [:rdfxml, :ntriples, :turtle, :json, :dot],
7
+ # or nil, which defaults to :rdfxml.
8
+ #
9
+ # TODO: Only a small subset of parsers is implemented,
10
+ # because the rest seem to be very buggy.
11
+ def initialize(name = :rdfxml)
12
+ @rdf_serializer = Redland.librdf_new_serializer(Redlander.rdf_world, name.to_s, nil, nil)
13
+ raise RedlandError.new("Failed to create a new serializer") unless @rdf_serializer
14
+ ObjectSpace.define_finalizer(@rdf_serializer, proc { Redland.librdf_free_serializer(@rdf_serializer) })
15
+ end
16
+
17
+ # Serialize a model into a string.
18
+ #
19
+ # Options are:
20
+ # :base_uri base URI (String or URI)
21
+ def to_string(model, options = {})
22
+ Redland.librdf_serializer_serialize_model_to_string(@rdf_serializer, Redlander.to_rdf_uri(options[:base_uri]), model.rdf_model)
23
+ end
24
+
25
+ # Serializes a model and stores it in a file
26
+ # filename - the name of the file to serialize to
27
+ # model - model instance
28
+ #
29
+ # Options are:
30
+ # :base_uri base URI (String or URI)
31
+ #
32
+ # Returns true on success, or false.
33
+ def to_file(model, filename, options = {})
34
+ Redland.librdf_serializer_serialize_model_to_file(@rdf_serializer, filename, Redlander.to_rdf_uri(options[:base_uri]), model.rdf_model).zero?
35
+ end
36
+
37
+ end
38
+
39
+
40
+ module SerializingInstanceMethods
41
+
42
+ def to_rdfxml(options = {})
43
+ serializer = Serializer.new(:rdfxml)
44
+ serializer.to_string(self, options)
45
+ end
46
+
47
+ def to_ntriples(options = {})
48
+ serializer = Serializer.new(:ntriples)
49
+ serializer.to_string(self, options)
50
+ end
51
+
52
+ def to_turtle(options = {})
53
+ serializer = Serializer.new(:turtle)
54
+ serializer.to_string(self, options)
55
+ end
56
+
57
+ def to_json(options = {})
58
+ serializer = Serializer.new(:json)
59
+ serializer.to_string(self, options)
60
+ end
61
+
62
+ def to_dot(options = {})
63
+ serializer = Serializer.new(:dot)
64
+ serializer.to_string(self, options)
65
+ end
66
+
67
+ # Serialize the model into a file.
68
+ #
69
+ # Options are:
70
+ # :format - output format [:rdfxml (default), :ntriples, :turtle, :json, :dot]
71
+ # :base_uri - base URI
72
+ def to_file(filename, options = {})
73
+ serializer_options = options.dup
74
+ serializer = Serializer.new(serializer_options.delete(:format) || :rdfxml)
75
+ serializer.to_file(self, filename, serializer_options)
76
+ end
77
+
78
+ end
79
+
80
+ end