redlander 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,86 +1,69 @@
1
1
  module Redlander
2
+ # RDF node (usually, a part of an RDF statement)
2
3
  class Node
4
+ # @api private
3
5
  attr_reader :rdf_node
4
6
 
7
+ # Datatype URI for the literal node, or nil
8
+ attr_reader :datatype
9
+
5
10
  # Create a RDF node.
6
- # Argument can be:
11
+ #
12
+ # @param [Any] arg
7
13
  # - an instance of URI - to create a RDF "resource",
8
- # - an instance of Node - to create a copy of the node,
9
- # - nil (or absent) - to create a "blank" node,
10
- # - an instance of Statement ("role" must be supplied then) -
11
- # to create a node from subject, predicate or object
12
- # (determined by "role" parameter) of the statement.
14
+ # Note that you cannot create a resource node from an URI string,
15
+ # it must be an instance of URI. Otherwise it is treated as a string literal.
16
+ # - nil (or absent) - to create a blank node,
13
17
  # - any other Ruby object, which can be coerced into a literal.
14
- # If nothing else, a RedlandError is thrown.
15
- #
16
- # Note that you cannot create a resource node from an URI string,
17
- # it must be an instance of URI. Otherwise it is treated as a string literal.
18
- def initialize(arg = nil, role = :subject)
19
- @bound = false
18
+ # @param [Hash] options
19
+ # @option options [String] :blank_id optional ID to use for a blank node.
20
+ # @raise [RedlandError] if it fails to create a node from the given args.
21
+ def initialize(arg = nil, options = {})
20
22
  @rdf_node = case arg
23
+ when FFI::Pointer
24
+ unless Redland.librdf_node_is_literal(arg).zero?
25
+ rdf_uri = Redland.librdf_node_get_literal_value_datatype_uri(arg)
26
+ @datatype = rdf_uri.null? ? XmlSchema.datatype_of("") : URI(Redland.librdf_uri_to_string(rdf_uri))
27
+ end
28
+ wrap(arg)
21
29
  when NilClass
22
- Redland.librdf_new_node_from_blank_identifier(Redlander.rdf_world, nil)
30
+ Redland.librdf_new_node_from_blank_identifier(Redlander.rdf_world, options[:blank_id])
23
31
  when URI
24
32
  Redland.librdf_new_node_from_uri_string(Redlander.rdf_world, arg.to_s)
25
- when Node
26
- Redland.librdf_new_node_from_node(arg.rdf_node)
27
- when Statement
28
- # TODO: Bound nodes should better be produced
29
- # using an explicit "factory" like Node.from_statement
30
- # to keep the clutter in the constructor at minimum.
31
- # (Esp. handling the "bound" stuff)
32
- @bound = true
33
- case role
34
- when :subject
35
- copy_rdf_node_on_initialize(Redland.librdf_statement_get_subject(arg.rdf_statement))
36
- when :object
37
- copy_rdf_node_on_initialize(Redland.librdf_statement_get_object(arg.rdf_statement))
38
- when :predicate
39
- copy_rdf_node_on_initialize(Redland.librdf_statement_get_predicate(arg.rdf_statement))
40
- else
41
- raise RedlandError.new("Invalid role specified")
42
- end
43
33
  else
44
34
  value = arg.respond_to?(:xmlschema) ? arg.xmlschema : arg.to_s
45
- datatype = Uri.new(XmlSchema.datatype_of(arg))
46
- Redland.librdf_new_node_from_typed_literal(Redlander.rdf_world, value, nil, datatype.rdf_uri)
35
+ @datatype = XmlSchema.datatype_of(arg)
36
+ Redland.librdf_new_node_from_typed_literal(Redlander.rdf_world, value, nil, Uri.new(@datatype).rdf_uri)
47
37
  end
48
- if @rdf_node.null?
49
- raise RedlandError.new("Failed to create a new node") unless @bound
50
- else
51
- ObjectSpace.define_finalizer(self, proc { Redland.librdf_free_node(@rdf_node) })
52
- # bound nodes cannot be added to (other) statements
53
- freeze if @bound
54
- end
55
- end
56
-
57
- # Bound nodes are those belonging to a statement
58
- # (bound nodes cannot be modified or added to other statements).
59
- def bound?
60
- @bound
38
+ raise RedlandError, "Failed to create a new node" if @rdf_node.null?
39
+ ObjectSpace.define_finalizer(self, proc { Redland.librdf_free_node(@rdf_node) })
61
40
  end
62
41
 
42
+ # Check whether the node is a resource (identified by a URI)
43
+ #
44
+ # @return [Boolean]
63
45
  def resource?
64
46
  Redland.librdf_node_is_resource(@rdf_node) != 0
65
47
  end
66
48
 
67
49
  # Return true if node is a literal.
50
+ #
51
+ # @return [Boolean]
68
52
  def literal?
69
53
  Redland.librdf_node_is_literal(@rdf_node) != 0
70
54
  end
71
55
 
72
56
  # Return true if node is a blank node.
57
+ #
58
+ # @return [Boolean]
73
59
  def blank?
74
60
  Redland.librdf_node_is_blank(@rdf_node) != 0
75
61
  end
76
62
 
77
- # Return the datatype URI of the node.
78
- # Returns nil if the node is not a literal, or has no datatype URI.
79
- def datatype
80
- Uri.new(self).to_s if literal?
81
- end
82
-
83
63
  # Equivalency. Only works for comparing two Nodes.
64
+ #
65
+ # @param [Node] other_node Node to be compared with.
66
+ # @return [Boolean]
84
67
  def eql?(other_node)
85
68
  Redland.librdf_node_equals(@rdf_node, other_node.rdf_node) != 0
86
69
  end
@@ -91,14 +74,37 @@ module Redlander
91
74
  end
92
75
 
93
76
  # Convert this node to a string (with a datatype suffix).
77
+ #
78
+ # @return [String]
94
79
  def to_s
95
80
  Redland.librdf_node_to_string(@rdf_node)
96
81
  end
97
82
 
83
+ # Internal URI of the Node.
84
+ #
85
+ # Returns the datatype URI for literal nodes,
86
+ # nil for blank nodes.
87
+ #
88
+ # @return [URI, nil]
89
+ def uri
90
+ if resource?
91
+ URI(to_s[1..-2])
92
+ elsif literal?
93
+ datatype
94
+ else
95
+ nil
96
+ end
97
+ end
98
+
98
99
  # Value of the literal node as a Ruby object instance.
100
+ #
101
+ # Returns an instance of URI for resource nodes,
102
+ # "blank identifier" for blank nodes.
103
+ #
104
+ # @return [URI, Any]
99
105
  def value
100
106
  if resource?
101
- URI.parse(Uri.new(self).to_s)
107
+ uri
102
108
  else
103
109
  XmlSchema.instantiate(to_s)
104
110
  end
@@ -107,13 +113,10 @@ module Redlander
107
113
 
108
114
  private
109
115
 
110
- def copy_rdf_node_on_initialize(n)
116
+ # @api private
117
+ def wrap(n)
111
118
  if n.null?
112
- if bound?
113
- n
114
- else
115
- raise RedlandError.new("Failed to create a new node")
116
- end
119
+ raise RedlandError, "Failed to create a new node"
117
120
  else
118
121
  Redland.librdf_new_node_from_node(n)
119
122
  end
@@ -0,0 +1,123 @@
1
+ module Redlander
2
+ # Syntax parsing methods.
3
+ # "self" is assumed to be an instance of Redlander::Model
4
+ module Parsing
5
+ # Core parsing method for non-streams
6
+ #
7
+ # @note
8
+ # If a block is given, the extracted statements will be yielded into
9
+ # the block and inserted into the model depending on the output
10
+ # of the block (if true, the statement will be added,
11
+ # if false, the statement will not be added).
12
+ #
13
+ # @param [String, URI] content
14
+ # - Can be a String,
15
+ # causing the statements to be extracted
16
+ # directly from it, or
17
+ # - URI
18
+ # causing the content to be first pulled
19
+ # from the specified URI (or a local file,
20
+ # if URI schema == "file:")
21
+ # @param [Hash] options
22
+ # @option options [String] :format name of the parser to use,
23
+ # @option options [String] :mime_type MIME type of the syntax, if applicable,
24
+ # @option options [String, URI] :type_uri URI of syntax, if applicable,
25
+ # @option options [String, URI] :base_uri base URI,
26
+ # to be applied to the nodes with relative URIs.
27
+ # @yieldparam [Statement]
28
+ # @raise [RedlandError] if it fails to create a parser or stream
29
+ # @return [Model]
30
+ def from(content, options = {})
31
+ format = options[:format].to_s
32
+ mime_type = options[:mime_type] && options[:mime_type].to_s
33
+ type_uri = options[:type_uri] && options[:type_uri].to_s
34
+ base_uri = options[:base_uri] && options[:base_uri].to_s
35
+ content = Uri.new(content) if content.is_a?(URI)
36
+
37
+ # FIXME: to be fixed in librdf:
38
+ # ntriples parser absolutely needs "\n" at the end of the input
39
+ if format == "ntriples" && !content.is_a?(Uri) && !content.end_with?("\n")
40
+ content << "\n"
41
+ end
42
+
43
+ rdf_parser = Redland.librdf_new_parser(Redlander.rdf_world, format, mime_type, type_uri)
44
+ raise RedlandError, "Failed to create a new '#{format}' parser" if rdf_parser.null?
45
+
46
+ begin
47
+ if block_given?
48
+ rdf_stream =
49
+ if content.is_a?(Uri)
50
+ Redland.librdf_parser_parse_as_stream(rdf_parser, content.rdf_uri, base_uri)
51
+ else
52
+ Redland.librdf_parser_parse_string_as_stream(rdf_parser, content, base_uri)
53
+ end
54
+ raise RedlandError, "Failed to create a new stream" if rdf_stream.null?
55
+
56
+ begin
57
+ while Redland.librdf_stream_end(rdf_stream).zero?
58
+ statement = Statement.new(Redland.librdf_stream_get_object(rdf_stream))
59
+ statements.add(statement) if yield statement
60
+ Redland.librdf_stream_next(rdf_stream)
61
+ end
62
+ ensure
63
+ Redland.librdf_free_stream(rdf_stream)
64
+ end
65
+ else
66
+ if content.is_a?(Uri)
67
+ Redland.librdf_parser_parse_into_model(rdf_parser, content.rdf_uri, base_uri, @rdf_model).zero?
68
+ else
69
+ Redland.librdf_parser_parse_string_into_model(rdf_parser, content, base_uri, @rdf_model).zero?
70
+ end
71
+ end
72
+ ensure
73
+ Redland.librdf_free_parser(rdf_parser)
74
+ end
75
+ self
76
+ end
77
+
78
+ # Parse input in RDF/XML format.
79
+ # Shortcut for {#from}(content, :format => "rdfxml").
80
+ #
81
+ # @param (see #from)
82
+ # @yieldparam [Statement]
83
+ # @return [void]
84
+ def from_rdfxml(content, options = {}, &block)
85
+ from(content, options.merge(:format => "rdfxml"), &block)
86
+ end
87
+
88
+ # Parse input in NTriples format.
89
+ # Shortcut for {#from}(content, :format => "ntriples").
90
+ #
91
+ # @param (see #from)
92
+ # @yieldparam [Statement]
93
+ # @return [void]
94
+ def from_ntriples(content, options = {}, &block)
95
+ from(content, options.merge(:format => "ntriples"), &block)
96
+ end
97
+
98
+ # Parse input in Turtls format.
99
+ # Shortcut for {#from}(content, :format => "turtle").
100
+ #
101
+ # @param (see #from)
102
+ # @yieldparam [Statement]
103
+ # @return [void]
104
+ def from_turtle(content, options = {}, &block)
105
+ from(content, options.merge(:format => "turtle"), &block)
106
+ end
107
+
108
+ # Parse input as stream from URI (or File)
109
+ #
110
+ # @param [URI, String] uri URI of the endpoint or file path
111
+ # @param [Hash] options (see {#from})
112
+ # @yieldparam [Statement]
113
+ # @return [void]
114
+ def from_uri(uri, options = {}, &block)
115
+ if uri.is_a?(String)
116
+ uri = URI.parse(uri)
117
+ uri = URI.parse("file://#{File.expand_path(uri.to_s)}") if uri.scheme.nil?
118
+ end
119
+ from(uri, options, &block)
120
+ end
121
+ alias_method :from_file, :from_uri
122
+ end
123
+ end
@@ -0,0 +1,89 @@
1
+ module Redlander
2
+ # Syntax parsing methods.
3
+ # "self" is assumed to be an instance of Redlander::Model
4
+ module Serializing
5
+ # Serialize model into a string
6
+ #
7
+ # @param [Hash] options
8
+ # @option options [String] :format name of the serializer to use,
9
+ # @option options [String] :mime_type MIME type of the syntax, if applicable,
10
+ # @option options [String, URI] :type_uri URI of syntax, if applicable,
11
+ # @option options [String, URI] :base_uri base URI,
12
+ # to be applied to the nodes with relative URIs.
13
+ # @raise [RedlandError] if it fails to create a serializer
14
+ def to(options = {})
15
+ format = options[:format].to_s
16
+ mime_type = options[:mime_type] && options[:mime_type].to_s
17
+ type_uri = options[:type_uri] && options[:type_uri].to_s
18
+ base_uri = options[:base_uri] && options[:base_uri].to_s
19
+
20
+ rdf_serializer = Redland.librdf_new_serializer(Redlander.rdf_world, format, mime_type, type_uri)
21
+ raise RedlandError, "Failed to create a new serializer" if rdf_serializer.null?
22
+
23
+ begin
24
+ if options[:file]
25
+ Redland.librdf_serializer_serialize_model_to_file(rdf_serializer, options[:file], base_uri, @rdf_model).zero?
26
+ else
27
+ Redland.librdf_serializer_serialize_model_to_string(rdf_serializer, base_uri, @rdf_model)
28
+ end
29
+ ensure
30
+ Redland.librdf_free_serializer(rdf_serializer)
31
+ end
32
+ end
33
+
34
+ # Serialize the model in RDF/XML format.
35
+ # Shortcut for {#to}(:format => "rdfxml").
36
+ #
37
+ # @param (see #to)
38
+ # @return [String]
39
+ def to_rdfxml(options = {})
40
+ to(options.merge(:format => "rdfxml"))
41
+ end
42
+
43
+ # Serialize the model in NTriples format.
44
+ # Shortcut for {#to}(:format => "ntriples").
45
+ #
46
+ # @param (see #to)
47
+ # @return [String]
48
+ def to_ntriples(options = {})
49
+ to(options.merge(:format => "ntriples"))
50
+ end
51
+
52
+ # Serialize the model in Turtle format.
53
+ # Shortcut for {#to}(:format => "turtle").
54
+ #
55
+ # @param (see #to)
56
+ # @return [String]
57
+ def to_turtle(options = {})
58
+ to(options.merge(:format => "turtle"))
59
+ end
60
+
61
+ # Serialize the model in JSON format.
62
+ # Shortcut for {#to}(:format => "json").
63
+ #
64
+ # @param (see #to)
65
+ # @return [String]
66
+ def to_json(options = {})
67
+ to(options.merge(:format => "json"))
68
+ end
69
+
70
+ # Serialize the model in Dot format.
71
+ # Shortcut for {#to}(:format => "dot").
72
+ #
73
+ # @param (see #to)
74
+ # @return [String]
75
+ def to_dot(options = {})
76
+ to(options.merge(:format => "dot"))
77
+ end
78
+
79
+ # Serialize the model to a file.
80
+ # Shortcut for {#to}(:format => "rdfxml").
81
+ #
82
+ # @param [String] filename path to the output file
83
+ # @param [Hash] options (see {#to} options)
84
+ # @return [void]
85
+ def to_file(filename, options = {})
86
+ to(options.merge(:file => filename))
87
+ end
88
+ end
89
+ end
@@ -1,72 +1,80 @@
1
- require 'redlander/stream'
2
- require "redlander/error_container"
3
-
4
1
  module Redlander
2
+ # RDF statement
5
3
  class Statement
6
- include ErrorContainer
7
-
4
+ # @api private
8
5
  attr_reader :rdf_statement
9
6
 
10
7
  # Create an RDF statement.
11
- # Source can be:
12
- # Hash, where
13
- # :subject
14
- # :predicate
15
- # :object
16
- # Stream, so that a statement is extracted from its current position
8
+ #
9
+ # @param [Hash] source
10
+ # @option source [Node, String, URI, nil] :subject
11
+ # @option source [Node, String, URI, nil] :predicate
12
+ # @option source [Node, String, URI, nil] :object
13
+ # @raise [NotImplementedError] if cannot create a Statement from the given source.
14
+ # @raise [RedlandError] if it fails to create a Statement.
17
15
  def initialize(source = {})
18
16
  @rdf_statement = case source
19
- when Stream
20
- # Pull a (current) statement from the stream
21
- copy_rdf_statement_on_initialize(Redland.librdf_stream_get_object(source.rdf_stream))
17
+ when FFI::Pointer
18
+ wrap(source)
22
19
  when Hash
23
20
  # Create a new statement from nodes
24
- s = source[:subject] && Node.new(source[:subject]).rdf_node
25
- p = source[:predicate] && Node.new(source[:predicate]).rdf_node
26
- o = source[:object] && Node.new(source[:object]).rdf_node
21
+ s = rdf_node_from(source[:subject])
22
+ p = rdf_node_from(source[:predicate])
23
+ o = rdf_node_from(source[:object])
27
24
  Redland.librdf_new_statement_from_nodes(Redlander.rdf_world, s, p, o)
28
25
  else
29
- # TODO
30
- raise NotImplementedError.new
26
+ raise NotImplementedError, "Cannot create Statement from '#{source.inspect}'"
31
27
  end
32
- raise RedlandError.new("Failed to create a new statement") if @rdf_statement.null?
28
+ raise RedlandError, "Failed to create a new statement" if @rdf_statement.null?
33
29
  ObjectSpace.define_finalizer(self, proc { Redland.librdf_free_statement(@rdf_statement) })
34
30
  end
35
31
 
32
+ # Subject of the statment.
33
+ #
34
+ # @return [Node, nil]
36
35
  def subject
37
- node = Node.new(self, :subject)
38
- node.rdf_node.null? ? nil : node
36
+ rdf_node = Redland.librdf_statement_get_subject(@rdf_statement)
37
+ rdf_node.null? ? nil : Node.new(rdf_node)
39
38
  end
40
39
 
40
+ # Predicate of the statement.
41
+ #
42
+ # @return [Node, nil]
41
43
  def predicate
42
- node = Node.new(self, :predicate)
43
- node.rdf_node.null? ? nil : node
44
+ rdf_node = Redland.librdf_statement_get_predicate(@rdf_statement)
45
+ rdf_node.null? ? nil : Node.new(rdf_node)
44
46
  end
45
47
 
48
+ # Object of the statement.
49
+ #
50
+ # @return [Node, nil]
46
51
  def object
47
- node = Node.new(self, :object)
48
- node.rdf_node.null? ? nil : node
52
+ rdf_node = Redland.librdf_statement_get_object(@rdf_statement)
53
+ rdf_node.null? ? nil : Node.new(rdf_node)
49
54
  end
50
55
 
51
- # set the subject of the statement
56
+ # Set the subject of the statement
57
+ #
58
+ # @param [Node, nil] node
59
+ # @return [void]
52
60
  def subject=(node)
53
- binding_to_statement(node) {
54
- Redland.librdf_statement_set_subject(@rdf_statement, node.rdf_node)
55
- }
61
+ Redland.librdf_statement_set_subject(@rdf_statement, rdf_node_from(node))
56
62
  end
57
63
 
58
- # set the predicate of the statement
64
+ # Set the predicate of the statement
65
+ #
66
+ # @param [Node, nil] node
67
+ # @return [void]
59
68
  def predicate=(node)
60
- binding_to_statement(node) {
61
- Redland.librdf_statement_set_predicate(@rdf_statement, node.rdf_node)
62
- }
69
+ Redland.librdf_statement_set_predicate(@rdf_statement, rdf_node_from(node))
63
70
  end
64
71
 
65
- # set the object of the statement
72
+ # Set the object of the statement
73
+ #
74
+ # @param [Node, nil] node
75
+ # @return [void]
66
76
  def object=(node)
67
- binding_to_statement(node) {
68
- Redland.librdf_statement_set_object(@rdf_statement, node.rdf_node)
69
- }
77
+ Redland.librdf_statement_set_object(@rdf_statement, rdf_node_from(node))
70
78
  end
71
79
 
72
80
  def eql?(other_statement)
@@ -84,36 +92,29 @@ module Redlander
84
92
  Redland.librdf_statement_to_string(@rdf_statement)
85
93
  end
86
94
 
87
- # A valid statement satisfies the following:
88
- # URI or blank subject, URI predicate and URI or blank or literal object (i.e. anything).
89
- def valid?
90
- attributes_satisfy? ? errors.clear : errors.add("is invalid")
91
- errors.empty?
92
- end
93
-
94
95
 
95
96
  private
96
97
 
97
- def attributes_satisfy?
98
- !subject.nil? && (subject.resource? || subject.blank?) &&
99
- !predicate.nil? && predicate.resource? &&
100
- !object.nil?
101
- end
102
-
103
- def binding_to_statement(node)
104
- if node.frozen?
105
- raise RedlandError.new("Cannot assign a bound node")
98
+ # @api private
99
+ def wrap(s)
100
+ if s.null?
101
+ raise RedlandError, "Failed to create a new statement"
106
102
  else
107
- node.freeze
108
- yield
103
+ Redland.librdf_new_statement_from_statement(s)
109
104
  end
110
105
  end
111
106
 
112
- def copy_rdf_statement_on_initialize(s)
113
- if s.null?
114
- raise RedlandError.new("Failed to create a new statement")
107
+ # Create a Node from the source
108
+ # and get its rdf_node, or return nil
109
+ # @api private
110
+ def rdf_node_from(source)
111
+ case source
112
+ when NilClass
113
+ nil
114
+ when Node
115
+ source.rdf_node
115
116
  else
116
- Redland.librdf_new_statement_from_statement(s)
117
+ Node.new(source).rdf_node
117
118
  end
118
119
  end
119
120
  end