rdf-turtle 3.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/rdf/turtle/writer.rb +109 -73
  4. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07b298640314a7317b9061aafd7c38941bbff437c19920992c82a275ef458d31
4
- data.tar.gz: 97e9132f36a0aaa818fd7749f96e78858e1b953be20c26a463cbe1262f3aaccd
3
+ metadata.gz: 1a8fb1c87f5b5149cc3430a1cdac50fdd7bfbaeacfeaa1a6770e54e83a1aff09
4
+ data.tar.gz: 7bf9532ca0c78ce4e24e51ef3a4076a8563e2f0fa2e9f9853616f47776f0947f
5
5
  SHA512:
6
- metadata.gz: 15da972d14df0a76b5e82095bfb4a7c2a8d9f2f019d20bb35c53de0a4ec2638586d583039ea0d426ffeed724026366537829b789201c2fdd37c47b56881de637
7
- data.tar.gz: 7c3a9f79b5b3ee5fdd8fb712c055846f4af1c5a94feac8d51865015f786d21e72d03820a72f64a3a327535d6e3ec20b4d28d178ec21de8685a17e5a08c9a5602
6
+ metadata.gz: 55ec7f72b8b020506c134805828ff43341ce455b469edfb396332c61b67ae3cc7bdf2a0dcfcb3808c47201c77564e046a84d0aaaba2a74553fd2d1c30c93a184
7
+ data.tar.gz: 308390211f1da6158fe12d322d8dec7e24ce94931ccd994fb202bf9ac5f38665ac3f1f2e7c02ae9a20e8f3b775462e8831b1357a96be045f8f427d5d241dbf84
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.1
1
+ 3.0.2
@@ -183,8 +183,15 @@ module RDF::Turtle
183
183
  log_debug("\nserialize") {"graph: #{@graph.size}"}
184
184
 
185
185
  preprocess
186
+
186
187
  start_document
187
188
 
189
+ # Remove lists that are referenced and have non-list properties;
190
+ # these are legal, but can't be serialized as lists
191
+ @lists.reject! do |node, list|
192
+ ref_count(node) > 0 && non_list_prop_count(node) > 0
193
+ end
194
+
188
195
  order_subjects.each do |subject|
189
196
  unless is_done?(subject)
190
197
  statement(subject)
@@ -193,7 +200,7 @@ module RDF::Turtle
193
200
  end
194
201
  super
195
202
  end
196
-
203
+
197
204
  # Return a QName for the URI, or nil. Adds namespace of QName to defined prefixes
198
205
  # @param [RDF::Resource] resource
199
206
  # @return [String, nil] value to use to identify URI
@@ -210,7 +217,7 @@ module RDF::Turtle
210
217
  pname = case
211
218
  when @uri_to_pname.has_key?(uri)
212
219
  return @uri_to_pname[uri]
213
- when u = @uri_to_prefix.keys.sort_by {|u| u.length}.reverse.detect {|u| uri.index(u.to_s) == 0}
220
+ when u = @uri_to_prefix.keys.sort_by {|uu| uu.length}.reverse.detect {|uu| uri.index(uu.to_s) == 0}
214
221
  # Use a defined prefix
215
222
  prefix = @uri_to_prefix[u]
216
223
  unless u.to_s.empty?
@@ -227,7 +234,7 @@ module RDF::Turtle
227
234
  else
228
235
  nil
229
236
  end
230
-
237
+
231
238
  # Make sure pname is a valid pname
232
239
  if pname
233
240
  md = Terminals::PNAME_LN.match(pname) || Terminals::PNAME_NS.match(pname)
@@ -236,7 +243,7 @@ module RDF::Turtle
236
243
 
237
244
  @uri_to_pname[uri] = pname
238
245
  end
239
-
246
+
240
247
  # Take a hash from predicate uris to lists of values.
241
248
  # Sort the lists of values. Return a sorted list of properties.
242
249
  # @param [Hash{String => Array<Resource>}] properties A hash of Property to Resource mappings
@@ -244,17 +251,17 @@ module RDF::Turtle
244
251
  def sort_properties(properties)
245
252
  # Make sorted list of properties
246
253
  prop_list = []
247
-
254
+
248
255
  predicate_order.each do |prop|
249
256
  next unless properties[prop.to_s]
250
257
  prop_list << prop.to_s
251
258
  end
252
-
259
+
253
260
  properties.keys.sort.each do |prop|
254
261
  next if prop_list.include?(prop.to_s)
255
262
  prop_list << prop.to_s
256
263
  end
257
-
264
+
258
265
  log_debug("sort_properties") {prop_list.join(', ')}
259
266
  prop_list
260
267
  end
@@ -283,7 +290,7 @@ module RDF::Turtle
283
290
  quoted(literal.to_s)
284
291
  end
285
292
  end
286
-
293
+
287
294
  ##
288
295
  # Returns the Turtle representation of a URI reference.
289
296
  #
@@ -295,7 +302,7 @@ module RDF::Turtle
295
302
  log_debug("relativize") {"#{uri.to_ntriples} => #{md.inspect}"} if md != uri.to_s
296
303
  md != uri.to_s ? "<#{md}>" : (get_pname(uri) || "<#{uri}>")
297
304
  end
298
-
305
+
299
306
  ##
300
307
  # Returns the Turtle representation of a blank node.
301
308
  #
@@ -305,12 +312,12 @@ module RDF::Turtle
305
312
  def format_node(node, options = {})
306
313
  options[:unique_bnodes] ? node.to_unique_base : node.to_base
307
314
  end
308
-
315
+
309
316
  protected
310
317
  # Output @base and @prefix definitions
311
318
  def start_document
312
319
  @output.write("#{indent}@base <#{base_uri}> .\n") unless base_uri.to_s.empty?
313
-
320
+
314
321
  log_debug("start_document") {prefixes.inspect}
315
322
  prefixes.keys.sort_by(&:to_s).each do |prefix|
316
323
  @output.write("#{indent}@prefix #{prefix}: <#{prefixes[prefix]}> .\n")
@@ -325,7 +332,7 @@ module RDF::Turtle
325
332
  # `\[rdf:type, rdfs:label, dc:title\]`
326
333
  # @return [Array<URI>]
327
334
  def predicate_order; [RDF.type, RDF::RDFS.label, RDF::URI("http://purl.org/dc/terms/title")]; end
328
-
335
+
329
336
  # Order subjects for output. Override this to output subjects in another order.
330
337
  #
331
338
  # Uses #top_classes and #base_uri.
@@ -333,31 +340,44 @@ module RDF::Turtle
333
340
  def order_subjects
334
341
  seen = {}
335
342
  subjects = []
336
-
343
+
337
344
  # Start with base_uri
338
345
  if base_uri && @subjects.keys.include?(base_uri)
339
346
  subjects << RDF::URI(base_uri)
340
347
  seen[RDF::URI(base_uri)] = true
341
348
  end
342
-
349
+
343
350
  # Add distinguished classes
344
351
  top_classes.each do |class_uri|
345
- graph.query(predicate: RDF.type, object: class_uri).map {|st| st.subject}.sort.uniq.each do |subject|
352
+ graph.query(predicate: RDF.type, object: class_uri).
353
+ map {|st| st.subject}.
354
+ sort.
355
+ uniq.
356
+ each do |subject|
346
357
  log_debug("order_subjects") {subject.to_ntriples}
347
358
  subjects << subject
348
359
  seen[subject] = true
349
360
  end
350
361
  end
351
-
362
+
363
+ # Mark as seen lists that are part of another list
364
+ @lists.values.map(&:statements).
365
+ flatten.each do |st|
366
+ seen[st.object] if @lists.has_key?(st.object)
367
+ end
368
+
369
+ # List elements should not be targets for top-level serialization
370
+ list_elements = @lists.values.map(&:to_a).flatten.compact
371
+
352
372
  # Sort subjects by resources over bnodes, ref_counts and the subject URI itself
353
- recursable = @subjects.keys.
373
+ recursable = (@subjects.keys - list_elements).
354
374
  select {|s| !seen.include?(s)}.
355
375
  map {|r| [r.node? ? 1 : 0, ref_count(r), r]}.
356
376
  sort
357
-
358
- subjects += recursable.map{|r| r.last}
377
+
378
+ subjects + recursable.map{|r| r.last}
359
379
  end
360
-
380
+
361
381
  # Perform any preprocessing of statements required
362
382
  def preprocess
363
383
  # Load defined prefixes
@@ -375,14 +395,27 @@ module RDF::Turtle
375
395
  @graph.each {|statement| preprocess_statement(statement)}
376
396
  end
377
397
  end
378
-
398
+
379
399
  # Perform any statement preprocessing required. This is used to perform reference counts and determine required
380
400
  # prefixes.
381
401
  # @param [Statement] statement
382
402
  def preprocess_statement(statement)
383
403
  #log_debug("preprocess") {statement.to_ntriples}
384
404
  bump_reference(statement.object)
385
- @subjects[statement.subject] = true
405
+ # Count properties of this subject
406
+ (@subjects[statement.subject] ||= {})[statement.predicate] ||= 0
407
+ @subjects[statement.subject][statement.predicate] += 1
408
+
409
+ # Collect lists
410
+ if statement.predicate == RDF.first
411
+ l = RDF::List.new(subject: statement.subject, graph: graph)
412
+ @lists[statement.subject] = l if l.valid?
413
+ end
414
+
415
+ if statement.object == RDF.nil || statement.subject == RDF.nil
416
+ # Add an entry for the list tail
417
+ @lists[RDF.nil] ||= RDF::List[]
418
+ end
386
419
 
387
420
  # Pre-fetch pnames, to fill prefixes
388
421
  get_pname(statement.subject)
@@ -401,6 +434,7 @@ module RDF::Turtle
401
434
  # Reset internal helper instance variables
402
435
  def reset
403
436
  @lists = {}
437
+
404
438
  @references = {}
405
439
  @serialized = {}
406
440
  @subjects = {}
@@ -425,13 +459,13 @@ module RDF::Turtle
425
459
  # Checks if l is a valid RDF list, i.e. no nodes have other properties.
426
460
  def is_valid_list?(l)
427
461
  #log_debug("is_valid_list?") {l.inspect}
428
- return RDF::List.new(subject: l, graph: @graph).valid?
462
+ return @lists[l] && @lists[l].valid?
429
463
  end
430
-
431
- def do_list(l)
432
- list = RDF::List.new(subject: l, graph: @graph)
464
+
465
+ def do_list(l, position)
466
+ list = @lists[l]
433
467
  log_debug("do_list") {list.inspect}
434
- position = :subject
468
+ subject_done(RDF.nil)
435
469
  list.each_statement do |st|
436
470
  next unless st.predicate == RDF.first
437
471
  log_debug {" list this: #{st.subject} first: #{st.object}[#{position}]"}
@@ -443,38 +477,38 @@ module RDF::Turtle
443
477
 
444
478
  def collection(node, position)
445
479
  return false if !is_valid_list?(node)
480
+ return false if position == :subject && ref_count(node) > 0
481
+ return false if position == :object && non_list_prop_count(node) > 0
446
482
  #log_debug("collection") {"#{node.to_ntriples}, #{position}"}
447
483
 
448
484
  @output.write(position == :subject ? "(" : " (")
449
- log_depth {do_list(node)}
485
+ log_depth {do_list(node, position)}
450
486
  @output.write(')')
451
487
  end
452
488
 
453
- # Can object be represented using a blankNodePropertyList?
454
- def p_squared?(resource, position)
489
+ # Can subject be represented as a blankNodePropertyList?
490
+ def blankNodePropertyList?(resource, position)
455
491
  resource.node? &&
456
- !@serialized.has_key?(resource) &&
457
- ref_count(resource) <= 1
492
+ !is_valid_list?(resource) &&
493
+ (!is_done?(resource) || position == :subject) &&
494
+ ref_count(resource) == (position == :object ? 1 : 0)
458
495
  end
459
496
 
460
- # Represent an object as a blankNodePropertyList
461
- def p_squared(resource, position)
462
- return false unless p_squared?(resource, position)
497
+ # Represent resource as a blankNodePropertyList
498
+ def blankNodePropertyList(resource, position)
499
+ return false unless blankNodePropertyList?(resource, position)
463
500
 
464
- #log_debug("p_squared") {"#{resource.to_ntriples}, #{position}"}
501
+ log_debug("blankNodePropertyList") {resource.to_ntriples}
465
502
  subject_done(resource)
466
- @output.write(position == :subject ? '[' : ' [')
467
- log_depth do
468
- num_props = predicateObjectList(resource, true)
469
- @output.write(num_props > 1 ? "\n#{indent} ]" : "]")
470
- end
471
-
503
+ @output.write(position == :subject ? "\n#{indent} [" : ' [')
504
+ num_props = log_depth {predicateObjectList(resource, true)}
505
+ @output.write((num_props > 1 ? "\n#{indent}" : "") + (position == :object ? ']' : '] .'))
472
506
  true
473
507
  end
474
508
 
475
509
  # Default singular resource representation.
476
- def p_default(resource, position)
477
- #log_debug("p_default") {"#{resource.to_ntriples}, #{position}"}
510
+ def p_term(resource, position)
511
+ #log_debug("p_term") {"#{resource.to_ntriples}, #{position}"}
478
512
  l = (position == :subject ? "" : " ") + format_term(resource, options)
479
513
  @output.write(l)
480
514
  end
@@ -486,12 +520,15 @@ module RDF::Turtle
486
520
  "#{resource.to_ntriples}, " +
487
521
  "pos: #{position}, " +
488
522
  "()?: #{is_valid_list?(resource)}, " +
489
- "[]?: #{p_squared?(resource, position)}, " +
523
+ "[]?: #{blankNodePropertyList?(resource, position)}, " +
490
524
  "rc: #{ref_count(resource)}"
491
525
  end
492
- raise RDF::WriterError, "Cannot serialize resource '#{resource}'" unless collection(resource, position) || p_squared(resource, position) || p_default(resource, position)
526
+ raise RDF::WriterError, "Cannot serialize resource '#{resource}'" unless
527
+ collection(resource, position) ||
528
+ blankNodePropertyList(resource, position) ||
529
+ p_term(resource, position)
493
530
  end
494
-
531
+
495
532
  def predicate(resource)
496
533
  log_debug("predicate") {resource.to_ntriples}
497
534
  if resource == RDF.type
@@ -507,7 +544,7 @@ module RDF::Turtle
507
544
  return if objects.empty?
508
545
 
509
546
  objects.each_with_index do |obj, i|
510
- if i > 0 && p_squared?(obj, :object)
547
+ if i > 0 && blankNodePropertyList?(obj, :object)
511
548
  @output.write ", "
512
549
  elsif i > 0
513
550
  @output.write ",\n#{indent(4)}"
@@ -524,7 +561,8 @@ module RDF::Turtle
524
561
  (properties[st.predicate.to_s] ||= []) << st.object
525
562
  end
526
563
 
527
- prop_list = sort_properties(properties) - [RDF.first.to_s, RDF.rest.to_s]
564
+ prop_list = sort_properties(properties)
565
+ prop_list -= [RDF.first.to_s, RDF.rest.to_s] if @lists.include?(subject)
528
566
  log_debug("predicateObjectList") {prop_list.inspect}
529
567
  return 0 if prop_list.empty?
530
568
 
@@ -539,22 +577,6 @@ module RDF::Turtle
539
577
  properties.keys.length
540
578
  end
541
579
 
542
- # Can subject be represented as a blankNodePropertyList?
543
- def blankNodePropertyList?(subject)
544
- ref_count(subject) == 0 && subject.node? && !is_valid_list?(subject)
545
- end
546
-
547
- # Represent subject as a blankNodePropertyList?
548
- def blankNodePropertyList(subject)
549
- return false unless blankNodePropertyList?(subject)
550
-
551
- log_debug("blankNodePropertyList") {subject.to_ntriples}
552
- @output.write("\n#{indent} [")
553
- num_props = log_depth {predicateObjectList(subject, true)}
554
- @output.write(num_props > 1 ? "\n#{indent} ] ." : "] .")
555
- true
556
- end
557
-
558
580
  # Render triples having the same subject using an explicit subject
559
581
  def triples(subject)
560
582
  @output.write("\n#{indent}")
@@ -563,18 +585,28 @@ module RDF::Turtle
563
585
  @output.write(" .")
564
586
  true
565
587
  end
566
-
588
+
567
589
  def statement(subject)
568
- log_debug("statement") {"#{subject.to_ntriples}, bnodePL?: #{blankNodePropertyList?(subject)}"}
590
+ log_debug("statement") {"#{subject.to_ntriples}, bnodePL?: #{blankNodePropertyList?(subject, :subject)}"}
569
591
  subject_done(subject)
570
- blankNodePropertyList(subject) || triples(subject)
592
+ blankNodePropertyList(subject, :subject) || triples(subject)
571
593
  @output.puts
572
594
  end
573
-
574
- def is_done?(subject)
575
- @serialized.include?(subject)
595
+
596
+ # Return the number of statements having this resource as a subject
597
+ # @return [Integer]
598
+ def prop_count(subject)
599
+ @subjects.fetch(subject, {}).values.reduce(:+) || 0
600
+ end
601
+
602
+ # Return the number of statements having this resource as a subject other than for list properties
603
+ # @return [Integer]
604
+ def non_list_prop_count(subject)
605
+ @subjects.fetch(subject, {}).
606
+ reject {|k, v| [RDF.type, RDF.first, RDF.rest].include?(k)}.
607
+ values.reduce(:+) || 0
576
608
  end
577
-
609
+
578
610
  # Return the number of times this node has been referenced in the object position
579
611
  # @return [Integer]
580
612
  def ref_count(resource)
@@ -587,7 +619,11 @@ module RDF::Turtle
587
619
  def bump_reference(resource)
588
620
  @references[resource] = ref_count(resource) + 1
589
621
  end
590
-
622
+
623
+ def is_done?(subject)
624
+ @serialized.include?(subject)
625
+ end
626
+
591
627
  # Mark a subject as done.
592
628
  def subject_done(subject)
593
629
  @serialized[subject] = true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-turtle
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 3.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregg Kellogg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-14 00:00:00.000000000 Z
11
+ date: 2018-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdf
@@ -195,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
195
  version: '0'
196
196
  requirements: []
197
197
  rubyforge_project:
198
- rubygems_version: 2.7.3
198
+ rubygems_version: 2.7.6
199
199
  signing_key:
200
200
  specification_version: 4
201
201
  summary: Turtle reader/writer for Ruby.