From f6621862bdfef7d2ce0794a7fa0cf76e11c9a916 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 7 Aug 2018 16:57:16 -0700 Subject: [PATCH 1/5] Remove gemspec deprecations. --- rdf-n3.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index 7f5666b..3fb657e 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -17,7 +17,6 @@ Gem::Specification.new do |gem| gem.platform = Gem::Platform::RUBY gem.files = %w(README.md History.markdown AUTHORS VERSION UNLICENSE) + Dir.glob('lib/**/*.rb') gem.require_paths = %w(lib) - gem.has_rdoc = false gem.required_ruby_version = '>= 2.2.2' gem.requirements = [] From 5ccc284d9af994f98b300807c7ba7d990011b024 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 11 Sep 2018 17:04:18 -0700 Subject: [PATCH 2/5] Fix list serialization problems. Fixes #18. --- lib/rdf/n3/writer.rb | 54 +++++++++++++++++++++++++++++---------- spec/matchers.rb | 60 +++++++------------------------------------- spec/writer_spec.rb | 29 ++++++++++++++++++--- 3 files changed, 75 insertions(+), 68 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 4517841..3a5de19 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -1,3 +1,5 @@ +require 'tsort' + # coding: utf-8 module RDF::N3 ## @@ -273,8 +275,6 @@ def format_node(node, options = {}) protected # Output @base and @prefix definitions def start_document - @started = true - @output.write("#{indent}@base <#{base_uri}> .\n") unless base_uri.to_s.empty? log_debug {"start_document: #{prefixes.inspect}"} @@ -317,8 +317,12 @@ def order_subjects # Add distinguished classes top_classes.each do |class_uri| - graph.query(predicate: RDF.type, object: class_uri).map {|st| st.subject}.sort.uniq.each do |subject| - log_debug {"order_subjects: #{subject.inspect}"} + graph.query(predicate: RDF.type, object: class_uri). + map {|st| st.subject}. + sort. + uniq. + each do |subject| + log_debug("order_subjects") {subject.to_ntriples} subjects << subject seen[subject] = true end @@ -326,7 +330,7 @@ def order_subjects log_debug {"subjects2: #{subjects.inspect}"} # Sort subjects by resources over bnodes, ref_counts and the subject URI itself - recursable = @subjects.keys. + recursable = (@subjects.keys - @lists.keys + @lists.tsort.reverse). select {|s| !seen.include?(s)}. map {|r| [r.node? ? 1 : 0, ref_count(r), r]}. sort @@ -358,7 +362,17 @@ def preprocess_statement(statement) references = ref_count(statement.object) + 1 @references[statement.object] = references @subjects[statement.subject] = true - + + # Collect lists + if statement.predicate == RDF.first + @lists[statement.subject] = RDF::List.new(subject: statement.subject, graph: graph) + end + + if statement.object == RDF.nil || statement.subject == RDF.nil + # Add an entry for the list tail + @lists[RDF.nil] ||= RDF::List[] + end + # Pre-fetch qnames, to fill prefixes get_qname(statement.subject) get_qname(statement.predicate) @@ -384,13 +398,11 @@ def indent(modifier = 0) # Reset internal helper instance variables def reset @depth = 0 - @lists = {} - @namespaces = {} + @lists = ListMap.new + @references = {} @serialized = {} @subjects = {} - @shortNames = {} - @started = false end ## @@ -412,11 +424,11 @@ def quoted(string) # Checks if l is a valid RDF list, i.e. no nodes have other properties. def is_valid_list(l) #log_debug {"is_valid_list: #{l.inspect}"} - return (l.node? && RDF::List.new(subject: l, graph: @graph).valid?) || l == RDF.nil + return @lists[l] && @lists[l].valid? end def do_list(l) - list = RDF::List.new(subject: l, graph: @graph) + list = @lists[l] log_debug {"do_list: #{list.inspect}"} position = :subject list.each_statement do |st| @@ -502,7 +514,8 @@ def predicate_list(subject) properties[st.predicate.to_s] << st.object end - prop_list = sort_properties(properties) - [RDF.first.to_s, RDF.rest.to_s] + prop_list = sort_properties(properties) + prop_list -= [RDF.first.to_s, RDF.rest.to_s] if subject.node? log_debug {"predicate_list: #{prop_list.inspect}"} return if prop_list.empty? @@ -556,5 +569,20 @@ def is_done?(subject) def subject_done(subject) @serialized[subject] = true end + + # Keep lists so they can be topologically sorted using `tsort` + class ListMap < Hash + include TSort + alias tsort_each_node each_key + def tsort_each_child(node, &block) + list = fetch(node) + + # All list entries that are lists + list.each {|item| block.call(item) if has_key?(item)} + + # All list subjects except ourselves + list.each_subject {|item| block.call(item) unless item == node} + end + end end end diff --git a/spec/matchers.rb b/spec/matchers.rb index 9e76b71..a310ad4 100644 --- a/spec/matchers.rb +++ b/spec/matchers.rb @@ -1,57 +1,15 @@ -require 'rdf/isomorphic' - -# Don't use be_equivalent_graph from rdf/spec because of odd N3 semantics -Info = Struct.new(:about, :logger, :inputDocument, :outputDocument, :format) - -RSpec::Matchers.define :be_equivalent_graph do |expected, info| +# coding: utf-8 +RSpec::Matchers.define :match_re do |expected, info| match do |actual| - def normalize(graph) - case graph - when RDF::Enumerable then graph - when IO, StringIO - RDF::Repository.new.load(graph, base_uri: @info.about) - else - # Figure out which parser to use - g = RDF::Repository.new - reader_class = RDF::Reader.for(detect_format(graph)) - reader_class.new(graph, base_uri: @info.about).each {|s| g << s} - g - end - end - - @info = if info.respond_to?(:about) - info - elsif info.is_a?(Logger) - Info.new("", info) - elsif info.is_a?(Hash) - Info.new(info[:about], info[:logger]) - else - Info.new(expected.is_a?(RDF::Graph) ? expected.graph_name : info, info.to_s) - end - @info.format ||= :n3 - @expected = normalize(expected) - @actual = normalize(actual) - @actual.isomorphic_with?(@expected) rescue false + actual.to_s.match(expected) end failure_message do |actual| - trace = case @info.logger - when Logger then @info.logger.to_s - when Array then @info.logger.join("\n") - end - info = @info.respond_to?(:about) ? @info.about : @info.inspect - if @expected.is_a?(RDF::Enumerable) && @actual.size != @expected.size - "Graph entry count differs:\nexpected: #{@expected.size}\nactual: #{@actual.size}" - elsif @expected.is_a?(Array) && @actual.size != @expected.length - "Graph entry count differs:\nexpected: #{@expected.length}\nactual: #{@actual.size}" - else - "Graph differs" - end + - "\n#{info + "\n" unless info.to_s.empty?}" + - (@info.inputDocument ? "Input file: #{@info.inputDocument}\n" : "") + - (@info.outputDocument ? "Output file: #{@info.outputDocument}\n" : "") + - "Expected:\n#{@expected.dump(@info.format, standard_prefixes: true)}" + - "Results:\n#{@actual.dump(@info.format, standard_prefixes: true)}" + - (trace ? "\nDebug:\n#{trace}" : "") + "Match failed\n" + + "#{info[:about]}\n" + + "Input file:\n#{info[:input]}\n" + + "Result:\n#{actual}\n" + + "Expression: #{expected}\n" + + "Debug:\n#{info[:trace]}" end end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index c1135fa..302297a 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -6,8 +6,6 @@ describe RDF::N3::Writer do let(:logger) {RDF::Spec.logger} - after(:each) {|example| puts logger.to_s if example.exception} - it_behaves_like 'an RDF::Writer' do let(:writer) {RDF::N3::Writer.new(StringIO.new)} end @@ -265,6 +263,27 @@ ) #$verbose = false end + + it "should generate list with first subject a URI" do + input = %( + "1"^^ . + _:g47006741228480 . + _:g47006741228480 "2"^^ . + _:g47006741228480 _:g47006737917560 . + _:g47006737917560 "3"^^ . + _:g47006737917560 . + ) + #$verbose = true + serialize(input, nil, + [ + %r(@prefix rdf: \.), + %r( rdf:first 1;), + %r(rdf:rest \(2 3\) \.), + ], + standard_prefixes: true + ) + #$verbose = false + end end describe "literals" do @@ -415,7 +434,7 @@ def parse(input, options = {}) # Serialize ntstr to a string and compare against regexps def serialize(ntstr, base = nil, regexps = [], options = {}) prefixes = options[:prefixes] || {} - g = parse(ntstr, base_uri: base, prefixes: prefixes) + g = ntstr.is_a?(RDF::Enumerable) ? ntstr : parse(ntstr, base_uri: base, prefixes: prefixes, validate: false, logger: []) result = RDF::N3::Writer.buffer(options.merge(logger: logger, base_uri: base, prefixes: prefixes)) do |writer| writer << g end @@ -424,8 +443,10 @@ def serialize(ntstr, base = nil, regexps = [], options = {}) #puts CGI.escapeHTML(result) end + logger.info "result: #{result}" regexps.each do |re| - expect(result).to match re + logger.info "match: #{re.inspect}" + expect(result).to match_re(re, about: base, logger: logger, input: ntstr), logger.to_s end result From ee3326ca0700523301c3e75821bbe78ba01f080d Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 11 Sep 2018 19:08:58 -0700 Subject: [PATCH 3/5] Update to writing lists. Fixes #18. --- lib/rdf/n3/writer.rb | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 3a5de19..b90c694 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -1,5 +1,3 @@ -require 'tsort' - # coding: utf-8 module RDF::N3 ## @@ -328,9 +326,15 @@ def order_subjects end end log_debug {"subjects2: #{subjects.inspect}"} - + + # Mark as seen lists that are part of another list + @lists.values.map(&:statements). + flatten.each do |st| + seen[st.object] if @lists.has_key?(st.object) + end + # Sort subjects by resources over bnodes, ref_counts and the subject URI itself - recursable = (@subjects.keys - @lists.keys + @lists.tsort.reverse). + recursable = @subjects.keys. select {|s| !seen.include?(s)}. map {|r| [r.node? ? 1 : 0, ref_count(r), r]}. sort @@ -398,7 +402,7 @@ def indent(modifier = 0) # Reset internal helper instance variables def reset @depth = 0 - @lists = ListMap.new + @lists = {} @references = {} @serialized = {} @@ -569,20 +573,5 @@ def is_done?(subject) def subject_done(subject) @serialized[subject] = true end - - # Keep lists so they can be topologically sorted using `tsort` - class ListMap < Hash - include TSort - alias tsort_each_node each_key - def tsort_each_child(node, &block) - list = fetch(node) - - # All list entries that are lists - list.each {|item| block.call(item) if has_key?(item)} - - # All list subjects except ourselves - list.each_subject {|item| block.call(item) unless item == node} - end - end end end From bdb080bc80add7781b65751d080c57d032596fbd Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 11 Sep 2018 19:09:13 -0700 Subject: [PATCH 4/5] Fix emergent reader spec issues. --- spec/reader_spec.rb | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index c7176e1..2c3d3dc 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -263,7 +263,8 @@ }.each_pair do |name, statement| specify "test #{name}" do graph = parse([statement].flatten.first) - expect(graph).to be_equivalent_graph([statement].flatten.last, about: "http://a/b", logger: logger) + g2 = RDF::NTriples::Reader.new([statement].flatten.last) + expect(graph).to be_equivalent_graph(g2, about: "http://a/b", logger: logger) end end @@ -438,7 +439,8 @@ %(:a :b 1.0E1) => %( "1.0E1"^^ .), }.each_pair do |n3, nt| it "should create typed literal for '#{n3}'" do - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::NTriples::Reader.new(nt) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end end @@ -635,7 +637,8 @@ %(@keywords has. :a has :b :c.) => %( .), } .each_pair do |n3, nt| it "should use keyword for '#{n3}'" do - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::NTriples::Reader.new(nt) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end end @@ -708,9 +711,9 @@ it "should create BNode for [] as predicate" do n3 = %(@prefix a: . a:s [] a:o .) - nt = %( _:bnode0 .) g = parse(n3, base_uri: "http://a/b") - expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + st = g.statements.first + expect(st.predicate).to be_a_node end it "should create BNode for [] as object" do @@ -757,7 +760,8 @@ _:bnode0 "2" . _:a :pred _:bnode0 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end it "should create nested BNodes" do @@ -780,14 +784,16 @@ describe "from paths" do it "should create bnode for path x!p" do n3 = %(:x2!:y2 :p2 "3" .) - nt = %(:x2 :y2 _:bnode0 . _:bnode0 :p2 "3" .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + nt = %(:x2 :y2 _:bnode0 ._:bnode0 :p2 "3" .) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end it "should create bnode for path x^p" do n3 = %(:x2^:y2 :p2 "3" .) nt = %(_:bnode0 :y2 :x2 . _:bnode0 :p2 "3" .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end it "should decode :joe!fam:mother!loc:office!loc:zip as Joe's mother's office's zipcode" do @@ -802,7 +808,8 @@ _:bnode0 _:bnode1 . _:bnode1 _:bnode2 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end it "should decode :joe!fam:mother^fam:mother Anyone whose mother is Joe's mother." do @@ -816,7 +823,8 @@ :joe _:bnode0 . _:bnode1 _:bnode0 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end it "should decode path with property list." do @@ -831,7 +839,8 @@ _:bnode1 :q2 "4" . _:bnode1 :q2 "5" . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end it "should decode path as object(1)" do @@ -840,7 +849,8 @@ :a :b _:bnode . _:bnode :c "lit" . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end it "should decode path as object(2)" do @@ -850,7 +860,8 @@ _:bnode0 _:bnode1 . :r :p _:bnode1 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end end end @@ -920,7 +931,8 @@ it "should create 2 statements for simple list" do n3 = %(:a :b :c, :d) nt = %( . .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end end @@ -1167,7 +1179,8 @@ it "returns object #{result} given #{input}" do n3 = %(@prefix xsd: . :a :b #{input} .) nt = %( #{result} .) - expect(parse(n3, base_uri: "http://a/b", canonicalize: true)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b", canonicalize: true)).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end end end From 8cba4a4679f76dfa7334beaa811c38563b766e12 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 11 Sep 2018 19:11:14 -0700 Subject: [PATCH 5/5] Version 3.0.1. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 4a36342..cb2b00e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.0 +3.0.1