redlander 0.2.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/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