json-ld 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
@@ -21,11 +21,61 @@ module JSON
21
21
  # @author [Gregg Kellogg](http://greggkellogg.net/)
22
22
  module LD
23
23
  require 'json'
24
+ require 'json/ld/extensions'
24
25
  require 'json/ld/format'
25
26
  autoload :Reader, 'json/ld/reader'
26
27
  autoload :VERSION, 'json/ld/version'
27
28
  autoload :Writer, 'json/ld/writer'
28
29
 
30
+ # Keywords
31
+ BASE = '@base'.freeze
32
+ COERCE = '@coerce'.freeze
33
+ CONTEXT = '@context'.freeze
34
+ DATATYPE = '@datatype'.freeze
35
+ IRI = '@iri'.freeze
36
+ LANGUAGE = '@language'.freeze
37
+ LITERAL = '@literal'.freeze
38
+ SUBJECT = '@subject'.freeze
39
+ TYPE = '@type'.freeze
40
+ VOCAB = '@vocab'.freeze
41
+
42
+ # Default context
43
+ # @see http://json-ld.org/spec/ED/20110507/#the-default-context
44
+ DEFAULT_CONTEXT = {
45
+ 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
46
+ 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#',
47
+ 'owl' => 'http://www.w3.org/2002/07/owl#',
48
+ 'xsd' => 'http://www.w3.org/2001/XMLSchema#',
49
+ 'dcterms' => 'http://purl.org/dc/terms/',
50
+ 'foaf' => 'http://xmlns.com/foaf/0.1/',
51
+ 'cal' => 'http://www.w3.org/2002/12/cal/ical#',
52
+ 'vcard' => 'http://www.w3.org/2006/vcard/ns# ',
53
+ 'geo' => 'http://www.w3.org/2003/01/geo/wgs84_pos#',
54
+ 'cc' => 'http://creativecommons.org/ns#',
55
+ 'sioc' => 'http://rdfs.org/sioc/ns#',
56
+ 'doap' => 'http://usefulinc.com/ns/doap#',
57
+ 'com' => 'http://purl.org/commerce#',
58
+ 'ps' => 'http://purl.org/payswarm#',
59
+ 'gr' => 'http://purl.org/goodrelations/v1#',
60
+ 'sig' => 'http://purl.org/signature#',
61
+ 'ccard' => 'http://purl.org/commerce/creditcard#',
62
+ '@coerce' => {
63
+ # Note: rdf:type is not in the document, but necessary for this implementation
64
+ 'xsd:anyURI' => ['rdf:type', 'rdf:rest', 'foaf:homepage', 'foaf:member'],
65
+ 'xsd:integer' => 'foaf:age',
66
+ }
67
+ }.freeze
68
+
69
+ # Default type coercion, in property => datatype order
70
+ DEFAULT_COERCE = {
71
+ RDF.type => RDF::XSD.anyURI,
72
+ RDF.first => false, # Make sure @coerce isn't generated for this
73
+ RDF.rest => RDF::XSD.anyURI,
74
+ RDF::FOAF.homepage => RDF::XSD.anyURI,
75
+ RDF::FOAF.member => RDF::XSD.anyURI,
76
+ RDF::FOAF.age => RDF::XSD.integer,
77
+ }.freeze
78
+
29
79
  def self.debug?; @debug; end
30
80
  def self.debug=(value); @debug = value; end
31
81
  end
@@ -0,0 +1,34 @@
1
+ module RDF
2
+ class Graph
3
+ # Resource properties
4
+ #
5
+ # Properties arranged as a hash with the predicate Term as index to an array of resources or literals
6
+ #
7
+ # Example:
8
+ # graph.load(':foo a :bar; rdfs:label "An example" .', "http://example.com/")
9
+ # graph.resources(URI.new("http://example.com/subject")) =>
10
+ # {
11
+ # "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" => [<http://example.com/#bar>],
12
+ # "http://example.com/#label" => ["An example"]
13
+ # }
14
+ def properties(subject, recalc = false)
15
+ @properties ||= {}
16
+ @properties.delete(subject.to_s) if recalc
17
+ @properties[subject.to_s] ||= begin
18
+ hash = Hash.new
19
+ self.query(:subject => subject) do |statement|
20
+ pred = statement.predicate.to_s
21
+
22
+ hash[pred] ||= []
23
+ hash[pred] << statement.object
24
+ end
25
+ hash
26
+ end
27
+ end
28
+
29
+ # Get type(s) of subject, returns a list of symbols
30
+ def type_of(subject)
31
+ query(:subject => subject, :predicate => RDF.type).map {|st| st.object}
32
+ end
33
+ end
34
+ end
@@ -28,4 +28,20 @@ module JSON::LD
28
28
  reader { JSON::LD::Reader }
29
29
  writer { JSON::LD::Writer }
30
30
  end
31
+
32
+ # Alias for JSON-LD format
33
+ #
34
+ # This allows the following:
35
+ #
36
+ # @example Obtaining an Notation3 format class
37
+ # RDF::Format.for(:jsonld) #=> JSON::LD::JSONLD
38
+ # RDF::Format.for(:jsonld).reader #=> JSON::LD::Reader
39
+ # RDF::Format.for(:jsonld).writer #=> JSON::LD::Writer
40
+ class JSONLD < RDF::Format
41
+ content_type 'application/json', :extension => :jsonld
42
+ content_encoding 'utf-8'
43
+
44
+ reader { JSON::LD::Reader }
45
+ writer { JSON::LD::Writer }
46
+ end
31
47
  end
@@ -7,33 +7,6 @@ module JSON::LD
7
7
  class Reader < RDF::Reader
8
8
  format Format
9
9
 
10
- # Default context
11
- # @see http://json-ld.org/spec/ED/20110507/#the-default-context
12
- DEFAULT_CONTEXT = {
13
- "rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
14
- "rdfs" => "http://www.w3.org/2000/01/rdf-schema#",
15
- "owl" => "http://www.w3.org/2002/07/owl#",
16
- "xsd" => "http://www.w3.org/2001/XMLSchema#",
17
- "dcterms" => "http://purl.org/dc/terms/",
18
- "foaf" => "http://xmlns.com/foaf/0.1/",
19
- "cal" => "http://www.w3.org/2002/12/cal/ical#",
20
- "vcard" => "http://www.w3.org/2006/vcard/ns# ",
21
- "geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
22
- "cc" => "http://creativecommons.org/ns#",
23
- "sioc" => "http://rdfs.org/sioc/ns#",
24
- "doap" => "http://usefulinc.com/ns/doap#",
25
- "com" => "http://purl.org/commerce#",
26
- "ps" => "http://purl.org/payswarm#",
27
- "gr" => "http://purl.org/goodrelations/v1#",
28
- "sig" => "http://purl.org/signature#",
29
- "ccard" => "http://purl.org/commerce/creditcard#",
30
- "@coerce" => {
31
- # Note: rdf:type is not in the document, but necessary for this implementation
32
- "xsd:anyURI" => ["rdf:type", "foaf:homepage", "foaf:member", "rdf:type"],
33
- "xsd:integer" => "foaf:age",
34
- }
35
- }.freeze
36
-
37
10
  ##
38
11
  # The graph constructed when parsing.
39
12
  #
@@ -181,44 +154,49 @@ module JSON::LD
181
154
 
182
155
  # 2.1) If a @context keyword is found, the processor merges each key-value pair in
183
156
  # the local context into the active context ...
184
- if element["@context"]
157
+ if element[CONTEXT]
185
158
  # Merge context
186
- ec = parse_context(ec.dup, element["@context"])
159
+ ec = parse_context(ec.dup, element[CONTEXT])
187
160
  prefixes.merge!(ec.mappings) # Update parsed prefixes
188
161
  end
189
162
 
190
163
  # Other shortcuts to allow use of this method for terminal associative arrays
191
- if element["@iri"].is_a?(String)
164
+ if element[IRI].is_a?(String)
192
165
  # Return the IRI found from the value
193
- object = expand_term(element["@iri"], ec.base, ec)
166
+ object = expand_term(element[IRI], ec.base, ec)
194
167
  add_triple(path, subject, property, object) if subject && property
195
- elsif element["@literal"]
168
+ return
169
+ elsif element[LITERAL]
196
170
  literal_opts = {}
197
- literal_opts[:datatype] = expand_term(element["@datatype"], ec.vocab.to_s, ec) if element["@datatype"]
198
- literal_opts[:language] = element["@language"].to_sym if element["@language"]
199
- object = RDF::Literal.new(element["@literal"], literal_opts)
171
+ literal_opts[:datatype] = expand_term(element[DATATYPE], ec.vocab.to_s, ec) if element[DATATYPE]
172
+ literal_opts[:language] = element[LANGUAGE].to_sym if element[LANGUAGE]
173
+ object = RDF::Literal.new(element[LITERAL], literal_opts)
200
174
  add_triple(path, subject, property, object) if subject && property
175
+ return
201
176
  end
202
177
 
203
178
  # 2.2) ... Otherwise, if the local context is known perform the following steps:
204
179
  # 2.2.1) If a @ key is found, the processor sets the active subject to the
205
180
  # value after Object Processing has been performed.
206
- if element["@"].is_a?(String)
207
- active_subject = expand_term(element["@"], ec.base, ec)
208
-
209
- # 2.2.1.1) If the inherited subject and inherited property values are
210
- # specified, generate a triple using the inherited subject for the
211
- # subject, the inherited property for the property, and the active
212
- # subject for the object.
213
- add_triple(path, subject, property, active_subject) if subject && property
214
-
215
- subject = active_subject
181
+ if element[SUBJECT].is_a?(String)
182
+ active_subject = expand_term(element[SUBJECT], ec.base, ec)
183
+ elsif element[SUBJECT]
184
+ # Recursively process hash or Array values
185
+ traverse("#{path}[@]", element[SUBJECT], subject, property, ec)
216
186
  else
217
187
  # 2.2.7) If the end of the associative array is detected, and a active subject
218
188
  # was not discovered, then:
219
189
  # 2.2.7.1) Generate a blank node identifier and set it as the active subject.
220
- subject = RDF::Node.new
190
+ active_subject = RDF::Node.new
221
191
  end
192
+
193
+ # 2.2.1.1) If the inherited subject and inherited property values are
194
+ # specified, generate a triple using the inherited subject for the
195
+ # subject, the inherited property for the property, and the active
196
+ # subject for the object.
197
+ # 2.2.7.2) Complete any previously incomplete triples by running all substeps of Step 2.2.1.
198
+ add_triple(path, subject, property, active_subject) if subject && property
199
+ subject = active_subject
222
200
 
223
201
  element.each do |key, value|
224
202
  # 2.2.3) If a key that is not @context, @, or a, set the active property by
@@ -319,23 +297,12 @@ module JSON::LD
319
297
  # @raise [RDF::ReaderError] on a syntax error, or a reference to a term which is not defined.
320
298
  def parse_context(ec, context)
321
299
  context.each do |key, value|
322
- #add_debug("parse_context(#{key})", value.inspect)
300
+ add_debug("parse_context(#{key})", value.inspect)
323
301
  case key
324
- when '@vocab' then ec.vocab = value
325
- when '@base' then ec.base = uri(value)
326
- when '@coerce'
327
- # Spec confusion: doc says to merge each key-value mapping to the local context's @coerce mapping,
328
- # overwriting duplicate values. In the case where a mapping is indicated to a list of properties
329
- # (e.g., { "xsd:anyURI": ["foaf:homepage", "foaf:member"] }, does this overwrite a previous mapping
330
- # of { "xsd:anyURI": "foaf:knows" }, or add to it.
331
- add_error RDF::ReaderError, "Expected @coerce to reference an associative array" unless value.is_a?(Hash)
332
- value.each do |type, property|
333
- type_uri = expand_term(type, ec.vocab, ec)
334
- [property].flatten.compact.each do |prop|
335
- p = expand_term(prop, ec.vocab, ec)
336
- ec.coerce[p] = type_uri
337
- end
338
- end
302
+ when VOCAB then ec.vocab = value
303
+ when BASE then ec.base = uri(value)
304
+ when COERCE
305
+ # Process after prefix mapping
339
306
  else
340
307
  # Spec confusion: The text indicates to merge each key-value pair into the active context. Is any
341
308
  # processing performed on the values. For instance, could a value be a CURIE, or {"@iri": <value>}?
@@ -345,6 +312,21 @@ module JSON::LD
345
312
  end
346
313
  end
347
314
 
315
+ if context[COERCE]
316
+ # Spec confusion: doc says to merge each key-value mapping to the local context's @coerce mapping,
317
+ # overwriting duplicate values. In the case where a mapping is indicated to a list of properties
318
+ # (e.g., { "xsd:anyURI": ["foaf:homepage", "foaf:member"] }, does this overwrite a previous mapping
319
+ # of { "xsd:anyURI": "foaf:knows" }, or add to it.
320
+ add_error RDF::ReaderError, "Expected @coerce to reference an associative array" unless context[COERCE].is_a?(Hash)
321
+ context[COERCE].each do |type, property|
322
+ type_uri = expand_term(type, ec.vocab, ec)
323
+ [property].flatten.compact.each do |prop|
324
+ p = expand_term(prop, ec.vocab, ec)
325
+ ec.coerce[p] = type_uri
326
+ end
327
+ end
328
+ end
329
+
348
330
  ec
349
331
  end
350
332
 
@@ -2,10 +2,662 @@ module JSON::LD
2
2
  ##
3
3
  # A JSON-LD parser in Ruby.
4
4
  #
5
+ # Note that the natural interface is to write a whole graph at a time.
6
+ # Writing statements or Triples will create a graph to add them to
7
+ # and then serialize the graph.
8
+ #
9
+ # @example Obtaining a JSON-LD writer class
10
+ # RDF::Writer.for(:jsonld) #=> RDF::N3::Writer
11
+ # RDF::Writer.for("etc/test.json")
12
+ # RDF::Writer.for(:file_name => "etc/test.json")
13
+ # RDF::Writer.for(:file_extension => "json")
14
+ # RDF::Writer.for(:content_type => "application/turtle")
15
+ #
16
+ # @example Serializing RDF graph into an JSON-LD file
17
+ # JSON::LD::Writer.open("etc/test.json") do |writer|
18
+ # writer << graph
19
+ # end
20
+ #
21
+ # @example Serializing RDF statements into an JSON-LD file
22
+ # JSON::LD::Writer.open("etc/test.json") do |writer|
23
+ # graph.each_statement do |statement|
24
+ # writer << statement
25
+ # end
26
+ # end
27
+ #
28
+ # @example Serializing RDF statements into an JSON-LD string
29
+ # JSON::LD::Writer.buffer do |writer|
30
+ # graph.each_statement do |statement|
31
+ # writer << statement
32
+ # end
33
+ # end
34
+ #
35
+ # The writer will add prefix definitions, and use them for creating @context definitions, and minting CURIEs
36
+ #
37
+ # @example Creating @base, @vocab and @context prefix definitions in output
38
+ # JSON::LD::Writer.buffer(
39
+ # :base_uri => "http://example.com/",
40
+ # :vocab => "http://example.net/"
41
+ # :prefixes => {
42
+ # nil => "http://example.com/ns#",
43
+ # :foaf => "http://xmlns.com/foaf/0.1/"}
44
+ # ) do |writer|
45
+ # graph.each_statement do |statement|
46
+ # writer << statement
47
+ # end
48
+ # end
49
+ #
50
+ # Select the :canonicalize option to output JSON-LD in canonical form
51
+ #
5
52
  # @see http://json-ld.org/spec/ED/20110507/
53
+ # @see http://json-ld.org/spec/ED/20110507/#the-normalization-algorithm
6
54
  # @author [Gregg Kellogg](http://greggkellogg.net/)
7
55
  class Writer < RDF::Writer
8
56
  format Format
57
+
58
+ # @attr [Graph] Graph of statements serialized
59
+ attr :graph
60
+ # @attr [URI] Base IRI used for relativizing IRIs
61
+ attr :base_uri
62
+ # @attr [String] Vocabulary prefix used for relativizing IRIs
63
+ attr :vocab
64
+
65
+ # Type coersion to use for serialization. Defaults to DEFAULT_COERCION
66
+ #
67
+ # Maintained as a reverse mapping of `property` => `type`.
68
+ #
69
+ # @attr [Hash{RDF::URI => RDF::URI}]
70
+ attr :coerce, true
71
+
72
+ ##
73
+ # Return the pre-serialized Hash before turning into JSON
74
+ #
75
+ # @return [Hash]
76
+ def self.hash(*args, &block)
77
+ hash = {}
78
+ self.new(hash, *args, &block)
79
+ hash
80
+ end
81
+
82
+ ##
83
+ # Initializes the RDF-LD writer instance.
84
+ #
85
+ # @param [IO, File] output
86
+ # the output stream
87
+ # @param [Hash{Symbol => Object}] options
88
+ # any additional options
89
+ # @option options [Encoding] :encoding (Encoding::UTF_8)
90
+ # the encoding to use on the output stream (Ruby 1.9+)
91
+ # @option options [Boolean] :canonicalize (false)
92
+ # whether to canonicalize literals when serializing
93
+ # @option options [Hash] :prefixes (Hash.new)
94
+ # the prefix mappings to use (not supported by all writers)
95
+ # @option options [#to_s] :base_uri (nil)
96
+ # Base IRI used for relativizing IRIs
97
+ # @option options [#to_s] :vocab (nil)
98
+ # Vocabulary prefix used for relativizing IRIs
99
+ # @option options [Boolean] :standard_prefixes (false)
100
+ # Add standard prefixes to @prefixes, if necessary.
101
+ # @yield [writer] `self`
102
+ # @yieldparam [RDF::Writer] writer
103
+ # @yieldreturn [void]
104
+ # @yield [writer]
105
+ # @yieldparam [RDF::Writer] writer
106
+ def initialize(output = $stdout, options = {}, &block)
107
+ super do
108
+ @graph = RDF::Graph.new
109
+ @iri_to_prefix = DEFAULT_CONTEXT.dup.delete_if {|k,v| k == COERCE}.invert
110
+ @coerce = DEFAULT_COERCE.merge(options[:coerce] || {})
111
+ if block_given?
112
+ case block.arity
113
+ when 0 then instance_eval(&block)
114
+ else block.call(self)
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ ##
121
+ # Write whole graph
122
+ #
123
+ # @param [Graph] graph
124
+ # @return [void]
125
+ def write_graph(graph)
126
+ add_debug "Add graph #{graph.inspect}"
127
+ @graph = graph
128
+ end
129
+
130
+ ##
131
+ # Addes a statement to be serialized
132
+ # @param [RDF::Statement] statement
133
+ # @return [void]
134
+ def write_statement(statement)
135
+ @graph.insert(statement)
136
+ end
137
+
138
+ ##
139
+ # Addes a triple to be serialized
140
+ # @param [RDF::Resource] subject
141
+ # @param [RDF::URI] predicate
142
+ # @param [RDF::Value] object
143
+ # @return [void]
144
+ # @raise [NotImplementedError] unless implemented in subclass
145
+ # @abstract
146
+ def write_triple(subject, predicate, object)
147
+ @graph.insert(Statement.new(subject, predicate, object))
148
+ end
149
+
150
+ ##
151
+ # Outputs the Serialized JSON-LD representation of all stored triples.
152
+ #
153
+ # @return [void]
154
+ # @see #write_triple
155
+ def write_epilogue
156
+ @base_uri = RDF::URI(@options[:base_uri]) if @options[:base_uri] && !@options[:canonicalize]
157
+ @vocab = @options[:vocab] unless @options[:canonicalize]
158
+ @debug = @options[:debug]
159
+
160
+ reset
161
+
162
+ add_debug "\nserialize: graph: #{@graph.size}"
163
+
164
+ preprocess
165
+
166
+ # Don't generate context for canonical output
167
+ json_hash = @options[:canonicalize] ? {} : start_document
168
+
169
+ elements = []
170
+ order_subjects.each do |subject|
171
+ unless is_done?(subject)
172
+ elements << subject(subject, json_hash)
173
+ end
174
+ end
175
+
176
+ return if elements.empty?
177
+
178
+ if elements.length == 1 && elements.first.is_a?(Hash)
179
+ json_hash.merge!(elements.first)
180
+ else
181
+ json_hash[SUBJECT] = elements
182
+ end
183
+
184
+ if @output.is_a?(Hash)
185
+ @output.merge!(json_hash)
186
+ else
187
+ json_state = if @options[:canonicalize]
188
+ JSON::State.new(
189
+ :indent => "",
190
+ :space => "",
191
+ :space_before => "",
192
+ :object_nl => "",
193
+ :array_nl => ""
194
+ )
195
+ else
196
+ JSON::State.new(
197
+ :indent => " ",
198
+ :space => " ",
199
+ :space_before => "",
200
+ :object_nl => "\n",
201
+ :array_nl => "\n"
202
+ )
203
+ end
204
+ @output.write(json_hash.to_json(json_state))
205
+ end
206
+ end
207
+
208
+ ##
209
+ # Returns the representation of a IRI reference.
210
+ #
211
+ # Spec confusion: should a subject URI be normalized?
212
+ #
213
+ # @param [RDF::URI] value
214
+ # @param [Hash{Symbol => Object}] options
215
+ # @option options [:subject, :predicate, :object] position
216
+ # Useful when determining how to serialize.
217
+ # @option options [RDF::URI] property
218
+ # Property for object reference, which can be used to return
219
+ # bare strings, rather than {"iri":}
220
+ # @return [Object]
221
+ def format_uri(value, options = {})
222
+ result = case options[:position]
223
+ when :subject
224
+ # attempt base_uri replacement
225
+ short = value.to_s.sub(base_uri.to_s, "")
226
+ short == value.to_s ? (get_curie(value) || value.to_s) : short
227
+ when :predicate
228
+ # attempt vocab replacement
229
+ short = TYPE if value == RDF.type
230
+ short ||= value.to_s.sub(@vocab.to_s, "")
231
+ short == value.to_s ? (get_curie(value) || value.to_s) : short
232
+ else
233
+ # Encode like a subject
234
+ iri_range?(options[:property]) ?
235
+ format_uri(value, :position => :subject) :
236
+ {:iri => format_uri(value, :position => :subject)}
237
+ end
238
+
239
+ add_debug("format_uri(#{options.inspect}, #{value.inspect}) => #{result.inspect}")
240
+ result
241
+ end
242
+
243
+ ##
244
+ # @param [RDF::Node] value
245
+ # @param [Hash{Symbol => Object}] options
246
+ # @return [String]
247
+ # @raise [NotImplementedError] unless implemented in subclass
248
+ # @abstract
249
+ def format_node(value, options = {})
250
+ format_uri(value, options)
251
+ end
252
+
253
+ ##
254
+ # Returns the representation of a literal.
255
+ #
256
+ # @param [RDF::Literal, String, #to_s] literal
257
+ # @param [Hash{Symbol => Object}] options
258
+ # @option options [RDF::URI] property
259
+ # Property referencing literal for type coercion
260
+ # @return [Object]
261
+ def format_literal(literal, options = {})
262
+ if options[:canonical] || @options[:canonicalize]
263
+ return {
264
+ :literal => literal.value,
265
+ :datatype => (format_uri(literal.datatype, :position => :subject) if literal.has_datatype?),
266
+ :language => (literal.language.to_s if literal.has_language?)
267
+ }.delete_if {|k,v| v.nil?}
268
+ end
269
+
270
+ case literal
271
+ when RDF::Literal::Integer, RDF::Literal::Boolean
272
+ literal.object
273
+ when RDF::Literal
274
+ if datatype_range?(options[:property]) || !(literal.has_datatype? || literal.has_language?)
275
+ # Datatype coercion where literal has the same datatype
276
+ literal.value
277
+ else
278
+ format_literal(literal, :canonical => true)
279
+ end
280
+ end
281
+ end
282
+
283
+ ##
284
+ # Serialize an RDF list
285
+ # @param [RDF::URI] object
286
+ # @param [Hash{Symbol => Object}] options
287
+ # @option options [RDF::URI] property
288
+ # Property referencing literal for type coercion
289
+ # @return [Array<Array<Object>>]
290
+ def format_list(object, options = {})
291
+ predicate = options[:property]
292
+ list = []
293
+
294
+ add_debug "format_list(#{object}, #{predicate})"
295
+
296
+ @depth += 1
297
+ while object do
298
+ subject_done(object)
299
+ p = @graph.properties(object)
300
+ item = p.fetch(RDF.first.to_s, []).first
301
+ if item
302
+ add_debug "format_list serialize #{item.inspect}"
303
+ list << if predicate || item.literal?
304
+ property(predicate, item)
305
+ else
306
+ subject(item)
307
+ end
308
+ end
309
+ object = p.fetch(RDF.rest.to_s, []).first
310
+ end
311
+ @depth -= 1
312
+
313
+ # Returns
314
+ add_debug "format_list => #{[list].inspect}"
315
+ [list]
316
+ end
317
+
318
+ private
319
+ ##
320
+ # Generate @context
321
+ # @return [Hash]
322
+ def start_document
323
+ ctx = {}
324
+ ctx[BASE] = base_uri.to_s if base_uri
325
+ ctx[VOCAB] = vocab.to_s if vocab
326
+
327
+ # Prefixes
328
+ prefixes.keys.sort {|a,b| a.to_s <=> b.to_s}.each do |k|
329
+ next if DEFAULT_CONTEXT.has_key?(k.to_s)
330
+ add_debug "prefix[#{k}] => #{prefixes[k]}"
331
+ ctx[k.to_s] = prefixes[k].to_s
332
+ end
333
+
334
+ # Coerce
335
+ add_debug "start_doc: coerce= #{coerce.inspect}"
336
+ unless coerce == DEFAULT_COERCE
337
+ c_h = {}
338
+ coerce.keys.sort.each do |k|
339
+ next if coerce[k] == DEFAULT_COERCE[k] ||
340
+ coerce[k] == false ||
341
+ coerce[k] == RDF::XSD.integer ||
342
+ coerce[k] == RDF::XSD.boolean
343
+ k_iri = format_uri(k, :position => :predicate)
344
+ d_iri = format_uri(coerce[k], :position => :subject)
345
+ add_debug "coerce[#{k_iri}] => #{d_iri}"
346
+ case c_h[d_iri]
347
+ when nil
348
+ c_h[d_iri] = k_iri
349
+ when Array
350
+ c_h[d_iri] << k_iri
351
+ else
352
+ c_h[d_iri] = [c_h[d_iri], k_iri]
353
+ end
354
+ end
355
+
356
+ ctx[COERCE] = c_h unless c_h.empty?
357
+ end
358
+
359
+ add_debug "start_doc: context=#{ctx.inspect}"
360
+ # Return hash with @context, or empty
361
+ ctx.empty? ? {} : {CONTEXT => ctx}
362
+ end
363
+
364
+ # Perform any preprocessing of statements required
365
+ def preprocess
366
+ # Load defined prefixes
367
+ (@options[:prefixes] || {}).each_pair do |k, v|
368
+ @iri_to_prefix[v.to_s] = k
369
+ end
370
+ @options[:prefixes] = {} # Will define actual used when matched
371
+
372
+ @graph.each {|statement| preprocess_statement(statement)}
373
+ end
374
+
375
+ # Perform any statement preprocessing required. This is used to perform reference counts and determine required
376
+ # prefixes.
377
+ # @param [Statement] statement
378
+ def preprocess_statement(statement)
379
+ add_debug "preprocess: #{statement.inspect}"
380
+ references = ref_count(statement.object) + 1
381
+ @references[statement.object] = references
382
+ @subjects[statement.subject] = true
383
+
384
+ # Pre-fetch qnames, to fill prefixes
385
+ get_curie(statement.subject)
386
+ get_curie(statement.predicate)
387
+ if statement.object.literal?
388
+ datatype_range?(statement.predicate) # To figure out coercion requirements
389
+ else
390
+ iri_range?(statement.predicate)
391
+ get_curie(statement.object)
392
+ end
393
+
394
+ @references[statement.predicate] = ref_count(statement.predicate) + 1
395
+ end
396
+
397
+ # Serialize a subject
398
+ # Option contains referencing property, if this is recursive
399
+ # @return [Hash]
400
+ def subject(subject, options = {})
401
+ defn = {}
402
+
403
+ raise RDF::WriterError, "Illegal use of subject #{subject.inspect}, not supported" unless subject.resource?
404
+
405
+ subject_done(subject)
406
+ properties = @graph.properties(subject)
407
+ add_debug "subject: #{subject.inspect}, props: #{properties.inspect}"
408
+
409
+ @graph.query(:subject => subject).each do |st|
410
+ raise RDF::WriterError, "Illegal use of predicate #{st.predicate.inspect}, not supported in RDF/XML" unless st.predicate.uri?
411
+ end
412
+
413
+ if subject.node? && ref_count(subject) > (options[:property] ? 1 : 0) && options[:canonicalize]
414
+ raise RDF::WriterError, "Can't serialize named node when normalizing"
415
+ end
416
+
417
+ # Subject may be a list
418
+ if is_valid_list?(subject)
419
+ add_debug "subject is a list"
420
+ defn[SUBJECT] = format_list(subject)
421
+ properties.delete(RDF.first.to_s)
422
+ properties.delete(RDF.rest.to_s)
423
+
424
+ # Special case, if there are no properties, then we can just serialize the list itself
425
+ return defn[SUBJECT].first if properties.empty?
426
+ elsif subject.uri? || ref_count(subject) > 1
427
+ add_debug "subject is a uri"
428
+ # Don't need to set subject if it's a Node without references
429
+ defn[SUBJECT] = format_uri(subject, :position => :subject)
430
+ else
431
+ add_debug "subject is an unreferenced BNode"
432
+ end
433
+
434
+ prop_list = order_properties(properties)
435
+ #add_debug "=> property order: #{prop_list.to_sentence}"
436
+
437
+ prop_list.each do |prop|
438
+ predicate = RDF::URI.intern(prop)
439
+
440
+ p_iri = format_uri(predicate, :position => :predicate)
441
+ @depth += 1
442
+ defn[p_iri] = property(predicate, properties[prop])
443
+ add_debug "prop(#{p_iri}) => #{properties[prop]} => #{defn[p_iri].inspect}"
444
+ @depth -= 1
445
+ end
446
+
447
+ add_debug "subject: #{subject} has defn: #{defn.inspect}"
448
+ defn
449
+ end
450
+
451
+ ##
452
+ # Serialize objects for a property
453
+ #
454
+ # Spec confusion: sorting of multi-valued properties not adequately specified.
455
+ #
456
+ # @param [RDF::URI] predicate
457
+ # @param [Array<RDF::URI>, RDF::URI] objects
458
+ # @param [Hash{Symbol => Object}] options
459
+ # @return [Array, Hash, Object]
460
+ def property(predicate, objects, options = {})
461
+ objects = objects.first if objects.is_a?(Array) && objects.length == 1
462
+ case objects
463
+ when Array
464
+ objects.sort_by(&:to_s).map {|o| property(predicate, o, options)}
465
+ when RDF::Literal
466
+ format_literal(objects, options.merge(:property => predicate))
467
+ else
468
+ if is_valid_list?(objects)
469
+ format_list(objects, :property => predicate)
470
+ elsif is_done?(objects) || !@subjects.include?(objects)
471
+ format_uri(objects, :position => :object, :property => predicate)
472
+ else
473
+ subject(objects, :property => predicate)
474
+ end
475
+ end
476
+ end
477
+
478
+ ##
479
+ # Return a CURIE for the IRI, or nil. Adds namespace of CURIE to defined prefixes
480
+ # @param [RDF::Resource] resource
481
+ # @return [String, nil] value to use to identify IRI
482
+ def get_curie(resource)
483
+ add_debug "get_curie(#{resource.inspect})"
484
+ case resource
485
+ when RDF::Node
486
+ return resource.to_s
487
+ when RDF::URI
488
+ iri = resource.to_s
489
+ return iri if options[:canonicalize]
490
+ else
491
+ return nil
492
+ end
493
+
494
+ curie = case
495
+ when @iri_to_curie.has_key?(iri)
496
+ return @iri_to_curie[iri]
497
+ when u = @iri_to_prefix.keys.detect {|u| iri.index(u.to_s) == 0}
498
+ # Use a defined prefix
499
+ prefix = @iri_to_prefix[u]
500
+ prefix(prefix, u) # Define for output
501
+ iri.sub(u.to_s, "#{prefix}:")
502
+ when @options[:standard_prefixes] && vocab = RDF::Vocabulary.detect {|v| iri.index(v.to_uri.to_s) == 0}
503
+ prefix = vocab.__name__.to_s.split('::').last.downcase
504
+ @iri_to_prefix[vocab.to_uri.to_s] = prefix
505
+ prefix(prefix, vocab.to_uri) # Define for output
506
+ iri.sub(vocab.to_uri.to_s, "#{prefix}:")
507
+ else
508
+ nil
509
+ end
510
+
511
+ @iri_to_curie[iri] = curie
512
+ rescue Addressable::URI::InvalidURIError => e
513
+ raise RDF::WriterError, "Invalid IRI #{resource.inspect}: #{e.message}"
514
+ end
515
+
516
+ ##
517
+ # Take a hash from predicate IRIs to lists of values.
518
+ # Sort the lists of values. Return a sorted list of properties.
519
+ # @param [Hash{String => Array<Resource>}] properties A hash of Property to Resource mappings
520
+ # @return [Array<String>}] Ordered list of properties.
521
+ def order_properties(properties)
522
+ # Make sorted list of properties
523
+ prop_list = []
524
+
525
+ properties.keys.sort do |a,b|
526
+ format_uri(a, :position => :predicate) <=> format_uri(b, :position => :predicate)
527
+ end.each do |prop|
528
+ prop_list << prop.to_s
529
+ end
530
+
531
+ prop_list
532
+ end
533
+
534
+ # Order subjects for output. Override this to output subjects in another order.
535
+ #
536
+ # Uses #base_uri.
537
+ # @return [Array<Resource>] Ordered list of subjects
538
+ def order_subjects
539
+ seen = {}
540
+ subjects = []
541
+
542
+ return @subjects.keys.sort do |a,b|
543
+ format_iri(a, :position => :subject) <=> format_iri(b, :position => :subject)
544
+ end if @options[:canonicalize]
545
+
546
+ # Start with base_uri
547
+ if base_uri && @subjects.keys.include?(base_uri)
548
+ subjects << base_uri
549
+ seen[base_uri] = true
550
+ end
551
+
552
+ # Sort subjects by resources over bnodes, ref_counts and the subject URI itself
553
+ recursable = @subjects.keys.
554
+ select {|s| !seen.include?(s)}.
555
+ map {|r| [r.is_a?(RDF::Node) ? 1 : 0, ref_count(r), r]}.
556
+ sort
557
+
558
+ subjects += recursable.map{|r| r.last}
559
+ end
560
+
561
+ # Return the number of times this node has been referenced in the object position
562
+ # @return [Integer]
563
+ def ref_count(node)
564
+ @references.fetch(node, 0)
565
+ end
566
+
567
+ ##
568
+ # Does predicate have a range of IRI?
569
+ # @param [RDF::URI] predicate
570
+ # @return [Boolean]
571
+ def iri_range?(predicate)
572
+ return false if predicate.nil? || @options[:canonicalize]
573
+
574
+ unless coerce.has_key?(predicate)
575
+ # objects of all statements with the predicate may not be literal
576
+ coerce[predicate] = @graph.query(:predicate => predicate).to_a.any? {|st| st.object.literal?} ?
577
+ false : RDF::XSD.anyURI
578
+ end
579
+
580
+ add_debug "iri_range(#{predicate}) = #{coerce[predicate].inspect}"
581
+ coerce[predicate] == RDF::XSD.anyURI
582
+ end
583
+
584
+ ##
585
+ # Does predicate have a range of specific typed literal?
586
+ # @param [RDF::URI] predicate
587
+ # @return [Boolean]
588
+ def datatype_range?(predicate)
589
+ unless coerce.has_key?(predicate)
590
+ # objects of all statements with the predicate must be literal
591
+ # and have the same non-nil datatype
592
+ dt = nil
593
+ @graph.query(:predicate => predicate) do |st|
594
+ if st.object.literal? && st.object.has_datatype?
595
+ dt = st.object.datatype if dt.nil?
596
+ dt = false unless dt == st.object.datatype
597
+ else
598
+ dt = false
599
+ end
600
+ end
601
+ add_debug "range(#{predicate}) = #{dt.inspect}"
602
+ coerce[predicate] = dt
603
+ end
604
+
605
+ coerce[predicate]
606
+ end
607
+
608
+ # Reset internal helper instance variables
609
+ def reset
610
+ @depth = 0
611
+ @references = {}
612
+ @serialized = {}
613
+ @subjects = {}
614
+ @iri_to_curie = {}
615
+ end
616
+
617
+ # Add debug event to debug array, if specified
618
+ #
619
+ # @param [String] message::
620
+ def add_debug(message)
621
+ msg = "#{" " * @depth * 2}#{message}"
622
+ STDERR.puts msg if ::JSON::LD::debug?
623
+ @debug << msg if @debug.is_a?(Array)
624
+ end
625
+
626
+ # Checks if l is a valid RDF list, i.e. no nodes have other properties.
627
+ def is_valid_list?(l)
628
+ props = @graph.properties(l)
629
+ unless l.node? && props.has_key?(RDF.first.to_s) || l == RDF.nil
630
+ add_debug "is_valid_list: false, #{l.inspect}: #{props.inspect}"
631
+ return false
632
+ end
633
+
634
+ while l && l != RDF.nil do
635
+ #add_debug "is_valid_list(length): #{props.length}"
636
+ return false unless props.has_key?(RDF.first.to_s) && props.has_key?(RDF.rest.to_s)
637
+ n = props[RDF.rest.to_s]
638
+ unless n.is_a?(Array) && n.length == 1
639
+ add_debug "is_valid_list: false, #{n.inspect}"
640
+ return false
641
+ end
642
+ l = n.first
643
+ unless l.node? || l == RDF.nil
644
+ add_debug "is_valid_list: false, #{l.inspect}"
645
+ return false
646
+ end
647
+ props = @graph.properties(l)
648
+ end
649
+ add_debug "is_valid_list: valid"
650
+ true
651
+ end
652
+
653
+ def is_done?(subject)
654
+ @serialized.include?(subject)
655
+ end
656
+
657
+ # Mark a subject as done.
658
+ def subject_done(subject)
659
+ @serialized[subject] = true
660
+ end
9
661
  end
10
662
  end
11
663
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: json-ld
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.3
5
+ version: 0.0.4
6
6
  platform: ruby
7
7
  authors:
8
8
  - Gregg Kellogg
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-05-10 00:00:00 -07:00
13
+ date: 2011-06-29 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -19,7 +19,7 @@ dependencies:
19
19
  requirement: &id001 !ruby/object:Gem::Requirement
20
20
  none: false
21
21
  requirements:
22
- - - ~>
22
+ - - ">="
23
23
  - !ruby/object:Gem::Version
24
24
  version: 0.3.3
25
25
  type: :runtime
@@ -63,11 +63,66 @@ dependencies:
63
63
  requirement: &id005 !ruby/object:Gem::Requirement
64
64
  none: false
65
65
  requirements:
66
- - - ~>
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.3.2
69
69
  type: :development
70
70
  version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: rdf-n3
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 0.3.3
80
+ type: :development
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: rdf-isomorphic
84
+ prerelease: false
85
+ requirement: &id007 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 0.3.4
91
+ type: :development
92
+ version_requirements: *id007
93
+ - !ruby/object:Gem::Dependency
94
+ name: sxp
95
+ prerelease: false
96
+ requirement: &id008 !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: "0"
102
+ type: :development
103
+ version_requirements: *id008
104
+ - !ruby/object:Gem::Dependency
105
+ name: sparql-algebra
106
+ prerelease: false
107
+ requirement: &id009 !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: "0"
113
+ type: :development
114
+ version_requirements: *id009
115
+ - !ruby/object:Gem::Dependency
116
+ name: sparql-grammar
117
+ prerelease: false
118
+ requirement: &id010 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: "0"
124
+ type: :development
125
+ version_requirements: *id010
71
126
  description: JSON-LD reader/writer for Ruby.
72
127
  email: public-rdf-ruby@w3.org
73
128
  executables: []
@@ -81,6 +136,7 @@ files:
81
136
  - README
82
137
  - UNLICENSE
83
138
  - VERSION
139
+ - lib/json/ld/extensions.rb
84
140
  - lib/json/ld/format.rb
85
141
  - lib/json/ld/reader.rb
86
142
  - lib/json/ld/version.rb