roadforest 0.0.1 → 0.0.2

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.
Files changed (40) hide show
  1. data/examples/file-management.rb +2 -1
  2. data/lib/roadforest/application/dispatcher.rb +12 -2
  3. data/lib/roadforest/application/services-host.rb +1 -1
  4. data/lib/roadforest/application.rb +12 -5
  5. data/lib/roadforest/blob-model.rb +7 -1
  6. data/lib/roadforest/content-handling/engine.rb +4 -1
  7. data/lib/roadforest/content-handling/type-handlers/jsonld.rb +25 -17
  8. data/lib/roadforest/http/adapters/excon.rb +1 -1
  9. data/lib/roadforest/http/graph-response.rb +18 -4
  10. data/lib/roadforest/http/graph-transfer.rb +52 -8
  11. data/lib/roadforest/http/message.rb +10 -1
  12. data/lib/roadforest/model.rb +17 -4
  13. data/lib/roadforest/rdf/context-fascade.rb +80 -5
  14. data/lib/roadforest/rdf/etagging.rb +53 -0
  15. data/lib/roadforest/rdf/focus-list.rb +47 -5
  16. data/lib/roadforest/rdf/graph-copier.rb +4 -5
  17. data/lib/roadforest/rdf/graph-focus.rb +38 -14
  18. data/lib/roadforest/rdf/graph-reading.rb +102 -47
  19. data/lib/roadforest/rdf/graph-store.rb +12 -8
  20. data/lib/roadforest/rdf/investigation.rb +0 -1
  21. data/lib/roadforest/rdf/normalization.rb +37 -4
  22. data/lib/roadforest/rdf/resource-pattern.rb +1 -0
  23. data/lib/roadforest/rdf/source-rigor/credence-annealer.rb +2 -0
  24. data/lib/roadforest/rdf/source-rigor/http-investigator.rb +12 -14
  25. data/lib/roadforest/rdf/update-focus.rb +7 -62
  26. data/lib/roadforest/remote-host.rb +21 -10
  27. data/lib/roadforest/resource/rdf/read-only.rb +4 -0
  28. data/lib/roadforest/server.rb +22 -0
  29. data/lib/roadforest/test-support/dispatcher-facade.rb +1 -1
  30. data/lib/roadforest/test-support/http-client.rb +8 -0
  31. data/spec/client.rb +20 -2
  32. data/spec/credence-annealer.rb +1 -1
  33. data/spec/excon-adapater.rb +30 -0
  34. data/spec/focus-list.rb +34 -0
  35. data/spec/form-parsing.rb +1 -0
  36. data/spec/full-integration.rb +193 -0
  37. data/spec/rdf-normalization.rb +17 -0
  38. data/spec/update-focus.rb +37 -0
  39. metadata +22 -17
  40. data/lib/roadforest/rdf/focus-wrapping.rb +0 -30
@@ -56,6 +56,7 @@ module FileManagementExample
56
56
  end
57
57
 
58
58
  def add_child(graph)
59
+ services.logger.debug(graph.source_graph.dump(:nquads))
59
60
  new_file = FileRecord.new(graph.first(:lc, "name"), false)
60
61
  services.file_records << new_file
61
62
  end
@@ -83,7 +84,7 @@ module FileManagementExample
83
84
  end
84
85
 
85
86
  def graph_update(graph)
86
- data.resolved = graph[[:lc, "resolved"]]
87
+ data.resolved = graph[:lc, "resolved"]
87
88
  new_graph
88
89
  end
89
90
 
@@ -7,8 +7,9 @@ module RoadForest
7
7
  super(method(:create_resource))
8
8
  @services = services
9
9
  @route_names = {}
10
+ @trace_by_default = false
10
11
  end
11
- attr_accessor :services
12
+ attr_accessor :services, :trace_by_default
12
13
 
13
14
  def resource_route(resource, name, path_spec, bindings)
14
15
  route = Route.new(path_spec, resource, bindings || {})
@@ -19,10 +20,19 @@ module RoadForest
19
20
  end
20
21
 
21
22
  def add_route(name, path_spec, resource_type, model_class, bindings = nil, &block)
23
+ if trace_by_default
24
+ return add_traced_route(name, path_spec, resource_type, model_class, bindings, &block)
25
+ else
26
+ return add_untraced_route(name, path_spec, resource_type, model_class, bindings, &block)
27
+ end
28
+ end
29
+ alias add add_route
30
+
31
+ def add_untraced_route(name, path_spec, resource_type, model_class, bindings = nil, &block)
22
32
  resource = bundle_typed_resource(resource_type, model_class, name)
23
33
  resource_route(resource, name, path_spec, bindings, &block)
24
34
  end
25
- alias add add_route
35
+ alias add_untraced add_untraced_route
26
36
 
27
37
  def add_traced_route(name, path_spec, resource_type, model_class, bindings = nil, &block)
28
38
  resource = bundle_traced_resource(resource_type, model_class, name)
@@ -5,6 +5,6 @@ class RoadForest::Application
5
5
  def initialize
6
6
  end
7
7
 
8
- attr_accessor :router, :canonical_host, :type_handling
8
+ attr_accessor :router, :canonical_host, :type_handling, :logger
9
9
  end
10
10
  end
@@ -9,17 +9,19 @@ require 'roadforest/application/path-provider'
9
9
  require 'roadforest/application/services-host'
10
10
  require 'roadforest/resource/rdf'
11
11
  require 'roadforest/content-handling/engine'
12
+ require 'roadforest/rdf/normalization'
12
13
 
13
14
  module RoadForest
14
15
  class Application
16
+ include RDF::Normalization
15
17
  include Resource::Handlers
16
18
 
17
- def initialize(canonical_host, services, configuration = nil, dispatcher = nil)
18
- @canonical_host = ::RDF::URI.parse(canonical_host)
19
+ def initialize(canonical_host, services = nil, configuration = nil, dispatcher = nil)
20
+ @canonical_host = normalize_resource(canonical_host)
19
21
  configuration ||= Webmachine::Configuration.default
20
22
  dispatcher ||= Dispatcher.new(services)
21
23
  super(configuration, dispatcher)
22
- self.services = services
24
+ self.services = services unless services.nil?
23
25
 
24
26
  setup
25
27
  end
@@ -27,16 +29,21 @@ module RoadForest
27
29
  def setup
28
30
  end
29
31
 
30
- attr_reader :services, :canonical_host
32
+ attr_accessor :services, :canonical_host
31
33
 
32
34
  alias router dispatcher
33
35
 
34
36
  def services=(service_host)
35
37
  router.services = service_host
36
38
  @services = service_host
37
- @services.canonical_host = @canonical_host
39
+ @services.canonical_host = canonical_host
38
40
  @services.router = PathProvider.new(@dispatcher)
39
41
  @services.type_handling ||= ContentHandling::Engine.default
42
+ @services.logger ||=
43
+ begin
44
+ require 'logger'
45
+ Logger.new("roadforest.log")
46
+ end
40
47
  end
41
48
  end
42
49
  end
@@ -44,12 +44,18 @@ module RoadForest
44
44
  File::open(path)
45
45
  end
46
46
 
47
+ def incomplete_path
48
+ [path,"incomplete"].join(".")
49
+ end
50
+
47
51
  def update(incoming)
48
- File::open(path, "w") do |file|
52
+ File::open(incomplete_path, "w") do |file|
49
53
  incoming.each do |chunk|
50
54
  file.write(chunk)
51
55
  end
52
56
  end
57
+ Pathname.new(incomplete_path).rename(path)
58
+
53
59
  return nil
54
60
  end
55
61
  end
@@ -2,6 +2,8 @@ require 'roadforest/content-handling/media-type'
2
2
 
3
3
  module RoadForest
4
4
  module ContentHandling
5
+ class UnrecognizedType < ::StandardError; end
6
+
5
7
  class Engine
6
8
  class TypeHandlerList
7
9
  def initialize(prefix)
@@ -39,7 +41,7 @@ module RoadForest
39
41
  type = MediaType.parse(type)
40
42
  @handlers.fetch(type)
41
43
  rescue KeyError
42
- raise "No Content-Type handler for #{content_type}"
44
+ raise UnrecognizedType, "No Content-Type handler for #{type}"
43
45
  end
44
46
  end
45
47
 
@@ -105,6 +107,7 @@ module RoadForest
105
107
  # Given the 'Accept' header and provided types, chooses an
106
108
  # appropriate media type.
107
109
  def choose_media_type(provided, header)
110
+ return "*/*" if header.nil?
108
111
  requested = MediaTypeList.build(header.split(/\s*,\s*/))
109
112
  requested.best_match_from(provided)
110
113
  end
@@ -1,6 +1,7 @@
1
1
  #@require 'rdf/rdfa' #XXX Otherwise json-ld grabs RDFa documents. Awaiting fix
2
2
  #upstream
3
3
  require 'json/ld'
4
+ require 'roadforest/rdf/normalization'
4
5
 
5
6
  module RoadForest
6
7
  module MediaType
@@ -13,13 +14,13 @@ module RoadForest
13
14
  end
14
15
  attr_reader :type, :handler
15
16
 
16
- def local_to_network(network)
17
- @handler.local_to_network(network)
17
+ def local_to_network(base_uri, network)
18
+ @handler.local_to_network(base_uri, network)
18
19
  end
19
20
  alias from_graph local_to_network
20
21
 
21
- def network_to_local(source)
22
- @handler.network_to_local(source)
22
+ def network_to_local(base_uri, source)
23
+ @handler.network_to_local(base_uri, source)
23
24
  end
24
25
  alias to_graph network_to_local
25
26
  end
@@ -46,23 +47,23 @@ module RoadForest
46
47
  end
47
48
 
48
49
  class Handler
49
- def network_to_local(network)
50
+ def network_to_local(base_uri, network)
50
51
  return network
51
52
  end
52
53
 
53
- def local_to_network(local)
54
+ def local_to_network(base_uri, local)
54
55
  return local
55
56
  end
56
57
 
57
58
  def parse_for(resource)
58
59
  source = resource.request_body
59
- input_data = network_to_local(source)
60
60
  model = resource.model
61
+ input_data = network_to_local(model.my_url, source)
61
62
 
62
63
  update_model(model, input_data)
63
64
 
64
65
  renderer = model.type_handling.choose_renderer(resource.request_accept_header)
65
- body = renderer.local_to_network(model.response_data)
66
+ body = renderer.local_to_network(model.my_url, model.response_data)
66
67
 
67
68
  build_response(resource)
68
69
  end
@@ -70,13 +71,13 @@ module RoadForest
70
71
  def render_for(resource)
71
72
  model = resource.model
72
73
  output_data = get_output(model)
73
- local_to_network(output_data)
74
+ local_to_network(model.my_url, output_data)
74
75
  end
75
76
 
76
77
  def add_child_to(resource)
77
78
  model = resource.model
78
79
  source = resource.request_body
79
- input_data = network_to_local(source)
80
+ input_data = network_to_local(model.my_url, source)
80
81
 
81
82
  child_for_model(resource.model, input_data)
82
83
 
@@ -87,7 +88,7 @@ module RoadForest
87
88
  model = resource.model
88
89
 
89
90
  renderer = model.type_handling.choose_renderer(resource.request_accept_header)
90
- body = renderer.local_to_network(model.response_data)
91
+ body = renderer.local_to_network(model.my_url, model.response_data)
91
92
 
92
93
  resource.response_content_type = renderer.content_type_header
93
94
  resource.response_body = body
@@ -115,8 +116,6 @@ module RoadForest
115
116
  result
116
117
  end
117
118
  end
118
-
119
- require 'roadforest/rdf/normalization'
120
119
  class RDFHandler < Handler
121
120
  include RDF::Normalization
122
121
 
@@ -150,17 +149,26 @@ require 'roadforest/rdf/normalization'
150
149
 
151
150
  #application/ld+json
152
151
  class JSONLD < RDFHandler
153
- def local_to_network(rdf)
154
- JSON::LD::Writer.buffer do |writer|
152
+ include RDF::Normalization
153
+
154
+ def local_to_network(base_uri, rdf)
155
+ raise "Invalid base uri: #{base_uri}" if base_uri.nil?
156
+ prefixes = relevant_prefixes_for_graph(rdf)
157
+ prefixes.keys.each do |prefix|
158
+ prefixes[prefix.to_sym] = prefixes[prefix]
159
+ end
160
+ JSON::LD::Writer.buffer(:base_uri => base_uri.to_s,
161
+ :prefixes => prefixes) do |writer|
155
162
  rdf.each_statement do |statement|
156
163
  writer << statement
157
164
  end
158
165
  end
159
166
  end
160
167
 
161
- def network_to_local(source)
168
+ def network_to_local(base_uri, source)
169
+ raise "Invalid base uri: #{base_uri.inspect}" if base_uri.nil?
162
170
  graph = ::RDF::Graph.new
163
- reader = JSON::LD::Reader.new(source.to_s)
171
+ reader = JSON::LD::Reader.new(source.to_s, :base_uri => base_uri.to_s)
164
172
  reader.each_statement do |statement|
165
173
  graph.insert(statement)
166
174
  end
@@ -27,7 +27,7 @@ module RoadForest
27
27
  excon_response = connection.request(
28
28
  :method => request.method,
29
29
  :path => uri.path,
30
- :headers => request.headers,
30
+ :headers => {"Host" => uri.host}.merge(request.headers),
31
31
  :body => request.body
32
32
  )
33
33
 
@@ -1,20 +1,34 @@
1
1
  module RoadForest
2
2
  module HTTP
3
- class GraphResponse
4
- attr_accessor :graph
3
+ class BaseResponse
5
4
  attr_reader :request, :response
6
5
 
7
- def initialize(request, response, graph)
8
- @request, @response, @graph = request, response, graph
6
+ def initialize(request, response)
7
+ @request, @response = request, response
9
8
  end
10
9
 
11
10
  def url
12
11
  request.url
13
12
  end
14
13
 
14
+ def etag
15
+ response.etag
16
+ end
17
+
15
18
  def status
16
19
  response.status
17
20
  end
21
+
22
+ def raw_body
23
+ response.body
24
+ end
25
+ end
26
+
27
+ class UnparseableResponse < BaseResponse
28
+ end
29
+
30
+ class GraphResponse < BaseResponse
31
+ attr_accessor :graph
18
32
  end
19
33
  end
20
34
  end
@@ -5,11 +5,18 @@ require 'roadforest/content-handling/engine'
5
5
  module RoadForest
6
6
  module HTTP
7
7
  class GraphTransfer
8
- attr_accessor :http_client
8
+ class Retryable < StandardError; end
9
+
10
+ attr_accessor :http_client, :trace
9
11
  attr_writer :type_handling
12
+ attr_reader :graph_cache
10
13
 
11
14
  def initialize
15
+ @trace = nil
12
16
  @type_preferences = Hash.new{|h,k| k.nil? ? "*/*" : h[nil]}
17
+ @graph_cache = Hash.new do |cache, url|
18
+ cache[url] = {}
19
+ end
13
20
  end
14
21
 
15
22
  def type_handling
@@ -37,7 +44,20 @@ module RoadForest
37
44
 
38
45
  response = send_request(request, graph)
39
46
 
40
- return build_response(request, response)
47
+ case response
48
+ when HTTP::Response
49
+ response = build_response(request, response)
50
+ cache_response(response)
51
+ return response
52
+ when GraphResponse
53
+ return response
54
+ end
55
+ end
56
+
57
+ def cache_response(response)
58
+ return if response.etag.nil?
59
+ return if response.etag.empty?
60
+ graph_cache[response.url][response.etag] = response
41
61
  end
42
62
 
43
63
  def validate(method, url, graph)
@@ -59,9 +79,18 @@ module RoadForest
59
79
  def setup_request(method, url)
60
80
  request = Request.new(method, url)
61
81
  request.headers["Accept"] = type_handling.parsers.types.accept_header
82
+ add_cache_headers(request)
62
83
  request
63
84
  end
64
85
 
86
+ def add_cache_headers(request)
87
+ return unless request.method == "GET"
88
+ return unless graph_cache.has_key?(request.url)
89
+ cached = graph_cache[request.url]
90
+ return if cached.empty?
91
+ request.headers["If-None-Match"] = cached.keys.join(", ")
92
+ end
93
+
65
94
  def select_renderer(url)
66
95
  end
67
96
 
@@ -77,17 +106,28 @@ module RoadForest
77
106
  content_type = best_type_for(request.url)
78
107
  renderer = type_handling.choose_renderer(content_type)
79
108
  request.headers["Content-Type"] = renderer.content_type_header
80
- request.body_string = renderer.from_graph(graph)
109
+ request.body_string = renderer.from_graph(request.url, graph)
81
110
  end
82
111
 
83
- class Retryable < StandardError; end
112
+ def trace_message(message)
113
+ return unless @trace
114
+ @trace = $stdout unless IO === @trace
115
+ @trace.puts message.inspect
116
+ end
84
117
 
85
118
  def send_request(request, graph)
86
119
  retry_limit ||= 5
120
+ #Check expires headers on received
87
121
  render_graph(graph, request)
88
122
 
123
+ trace_message(request)
89
124
  response = http_client.do_request(request)
125
+ trace_message(response)
90
126
  case response.status
127
+ when 304 #Not Modified
128
+ response = graph_cache.fetch(request.url).fetch(response.etag)
129
+ trace_message(response)
130
+ return response
91
131
  when 415 #Type not accepted
92
132
  record_accept_header(request.url, response.headers["Accept"])
93
133
  raise Retryable
@@ -98,14 +138,18 @@ module RoadForest
98
138
  retry
99
139
  end
100
140
 
101
- def parse_response(response)
141
+ def parse_response(base_uri, response)
102
142
  parser = type_handling.choose_parser(response.headers["Content-Type"])
103
- parser.to_graph(response.body_string)
143
+ parser.to_graph(base_uri, response.body_string)
104
144
  end
105
145
 
106
146
  def build_response(request, response)
107
- graph = parse_response(response)
108
- return GraphResponse.new(request, response, graph)
147
+ graph = parse_response(request.url, response)
148
+ response = GraphResponse.new(request, response)
149
+ response.graph = graph
150
+ return response
151
+ rescue ContentHandling::UnrecognizedType
152
+ return UnparseableResponse.new(request, response)
109
153
  end
110
154
  end
111
155
  end
@@ -69,6 +69,11 @@ module RoadForest
69
69
  def initialize(method, url)
70
70
  super()
71
71
  @method, @url = method, url
72
+ headers["Host"] = Addressable::URI.parse(url).host
73
+ end
74
+
75
+ def inspect
76
+ "\n" + super
72
77
  end
73
78
 
74
79
  def needs_body?
@@ -76,13 +81,17 @@ module RoadForest
76
81
  end
77
82
 
78
83
  def inspection_payload
79
- ["#{method} #{url}"] + super
84
+ [url, "#{method} #{url.path}"] + super
80
85
  end
81
86
  end
82
87
 
83
88
  class Response < Message
84
89
  attr_accessor :status
85
90
 
91
+ def etag
92
+ headers["ETag"]
93
+ end
94
+
86
95
  def inspection_payload
87
96
  [status] + super
88
97
  end
@@ -12,6 +12,7 @@ module RoadForest
12
12
  @response_values = {}
13
13
  end
14
14
  attr_reader :route_name, :params, :services, :data
15
+ attr_reader :response_values
15
16
 
16
17
  def path_for(route_name = nil, params = nil)
17
18
  services.router.path_for(route_name, (params || self.params).to_hash)
@@ -103,7 +104,10 @@ module RoadForest
103
104
 
104
105
  end
105
106
 
107
+ require 'roadforest/rdf/etagging'
106
108
  class RDFModel < Model
109
+ include RDF::Etagging
110
+
107
111
  def update(graph)
108
112
  graph_update(start_focus(graph))
109
113
  end
@@ -137,13 +141,22 @@ module RoadForest
137
141
  return focus
138
142
  end
139
143
 
140
- def start_graph(resource_url=nil, &block)
141
- graph = ::RDF::Graph.new
142
- start_focus(graph, resource_url, &block)
144
+ def etag
145
+ @etag ||= etag_from(etag_graph)
146
+ end
147
+
148
+ def etag_graph
149
+ current_graph
150
+ end
151
+
152
+ def current_graph
153
+ return response_data if response_values.has_key?(:data)
154
+ new_graph
143
155
  end
144
156
 
145
157
  def new_graph
146
- focus = start_graph(my_url)
158
+ graph = ::RDF::Graph.new
159
+ focus = start_focus(graph, my_url)
147
160
  fill_graph(focus)
148
161
  self.response_data = focus.graph
149
162
  end
@@ -1,25 +1,100 @@
1
1
  require 'rdf'
2
+ require 'roadforest/rdf/resource-query'
3
+ require 'roadforest/rdf/resource-pattern'
4
+ require 'roadforest/rdf/normalization'
2
5
 
3
6
  module RoadForest::RDF
4
7
  class ContextFascade
5
8
  include ::RDF::Countable
6
9
  include ::RDF::Enumerable
7
10
  include ::RDF::Queryable
11
+ include Normalization
8
12
 
9
- def initialize(store, resource, rigor)
10
- @store, @resource, @rigor = store, resource, rigor
13
+ attr_accessor :resource, :rigor, :source_graph, :target_graph, :copied_contexts
14
+
15
+ def initialize
16
+ @copied_contexts = {}
17
+ end
18
+
19
+ def resource=(resource)
20
+ @resource = normalize_context(resource)
21
+ end
22
+
23
+ def dup
24
+ other = self.class.allocate
25
+ other.resource = self.resource
26
+ other.rigor = self.rigor
27
+
28
+ other.copied_contexts = self.copied_contexts
29
+ other.source_graph = self.source_graph
30
+ other.target_graph = self.target_graph
31
+ return other
32
+ end
33
+
34
+ def parceller
35
+ @parceller ||=
36
+ begin
37
+ parceller = Parcel.new
38
+ parceller.graph = source_graph
39
+ parceller
40
+ end
41
+ end
42
+
43
+ def copy_context
44
+ return if copied_contexts[resource]
45
+ return if target_graph.nil? or source_graph == target_graph
46
+ parceller.graph_for(resource).each_statement do |statement|
47
+ statement.context = resource
48
+ target_graph << statement
49
+ end
50
+ copied_contexts[resource] = true
51
+ end
52
+
53
+ #superfluous?
54
+ def build_query
55
+ ResourceQuery.new([], {}) do |query|
56
+ query.subject_context = resource
57
+ query.source_rigor = rigor
58
+ yield query
59
+ end
11
60
  end
12
61
 
13
62
  def query_execute(query, &block)
14
- ResourceQuery.from(query, @resource, @rigor).execute(@store, &block)
63
+ query = ResourceQuery.from(query, resource, rigor)
64
+ execute_search(query, &block)
15
65
  end
16
66
 
17
67
  def query_pattern(pattern, &block)
18
- ResourcePattern.from(pattern, {:context_roles => {:subject => @resource}, :source_rigor => @rigor}).execute(@store, &block)
68
+ pattern = ResourcePattern.from(pattern, {:context_roles => {:subject => resource}, :source_rigor => rigor})
69
+ execute_search(pattern, &block)
70
+ end
71
+
72
+ def execute_search(search, &block)
73
+ if target_graph != source_graph and not target_graph.nil?
74
+ enum = search.execute(target_graph)
75
+ if enum.any?{ true }
76
+ enum.each(&block)
77
+ return enum
78
+ end
79
+ end
80
+ search.execute(source_graph, &block)
19
81
  end
20
82
 
21
83
  def each(&block)
22
- @store.each(&block)
84
+ source_graph.each(&block)
85
+ end
86
+
87
+ def insert(statement)
88
+ copy_context
89
+ statement[3] = resource
90
+ target_graph.insert(statement)
91
+ end
92
+
93
+ def delete(statement)
94
+ statement = RDF::Query::Pattern.from(statement)
95
+ statement.context = resource
96
+ copy_context
97
+ target_graph.delete(statement)
23
98
  end
24
99
  end
25
100
  end
@@ -0,0 +1,53 @@
1
+ require 'roadforest/rdf'
2
+
3
+ module RoadForest::RDF
4
+ module Etagging
5
+ def etag_from(graph)
6
+ require 'openssl'
7
+ quads = sorted_quads(graph)
8
+ mapped = blank_mapped(quads)
9
+ strings = mapped.map(&:inspect)
10
+
11
+ ripe = OpenSSL::Digest::RIPEMD160.new
12
+ mapped.each do |quad|
13
+ ripe << quad.inspect
14
+ end
15
+ "W/\"#{ripe.base64digest}\""
16
+ end
17
+
18
+ def blank_mapped(quads)
19
+ sequence = 0
20
+ mapping = Hash.new do |h,k|
21
+ h[k] = RDF::Node.new(sequence+=1)
22
+ end
23
+
24
+ quads.map do |quad|
25
+ quad.map do |term|
26
+ case term
27
+ when RDF::Node
28
+ mapping[term].to_s
29
+ when nil
30
+ nil
31
+ else
32
+ term.to_s
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def sorted_quads(graph)
39
+ graph.statements.map do |statement|
40
+ [statement.subject, statement.predicate, statement.object, statement.context]
41
+ end.sort_by do |quad|
42
+ quad.map do |term|
43
+ case term
44
+ when RDF::Node
45
+ nil
46
+ else
47
+ term
48
+ end
49
+ end.join("/")
50
+ end
51
+ end
52
+ end
53
+ end