From ed6e351fa8df93c249731f4d15fd8bfb5280e27b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 24 May 2020 15:07:14 -0700 Subject: [PATCH 01/11] Preliminary support for RDF* parsing using the embedded triple mode overloading `@id`. --- lib/json/ld.rb | 1 + lib/json/ld/expand.rb | 25 +++- lib/json/ld/to_rdf.rb | 21 +++- spec/expand_spec.rb | 248 ++++++++++++++++++++++++++++++++++++ spec/suite_to_rdf_spec.rb | 1 + spec/to_rdf_spec.rb | 255 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 540 insertions(+), 11 deletions(-) diff --git a/lib/json/ld.rb b/lib/json/ld.rb index efcb5bc7..5f3b2635 100644 --- a/lib/json/ld.rb +++ b/lib/json/ld.rb @@ -137,6 +137,7 @@ class InvalidLocalContext < JsonLdError; @code = "invalid local context"; end class InvalidNestValue < JsonLdError; @code = "invalid @nest value"; end class InvalidPrefixValue < JsonLdError; @code = "invalid @prefix value"; end class InvalidPropagateValue < JsonLdError; @code = "invalid @propagate value"; end + class InvalidEmbeddedNode < JsonLdError; @code = "invalid reified node"; end class InvalidRemoteContext < JsonLdError; @code = "invalid remote context"; end class InvalidReverseProperty < JsonLdError; @code = "invalid reverse property"; end class InvalidReversePropertyMap < JsonLdError; @code = "invalid reverse property map"; end diff --git a/lib/json/ld/expand.rb b/lib/json/ld/expand.rb index 87d56005..0cada4f4 100644 --- a/lib/json/ld/expand.rb +++ b/lib/json/ld/expand.rb @@ -273,12 +273,25 @@ def expand_object(input, active_property, context, output_object, context.expand_iri(v, as_string: true, base: @options[:base], documentRelative: true) end when Hash - raise JsonLdError::InvalidIdValue, - "value of @id must be a string unless framing: #{value.inspect}" unless framing - raise JsonLdError::InvalidTypeValue, - "value of @id must be a an empty object for framing: #{value.inspect}" unless - value.empty? - [{}] + if framing + raise JsonLdError::InvalidTypeValue, + "value of @id must be a an empty object for framing: #{value.inspect}" unless + value.empty? + [{}] + elsif @options[:rdfstar] + # Result must have just a single statement + rei_node = expand(value, active_property, context, log_depth: log_depth.to_i + 1) + old_star, @options[:rdfstar] = @options[:rdfstar], :SA + statements = to_enum(:item_to_rdf, rei_node) + @options[:rdfstar] = old_star # :PG would emit too many statements + raise JsonLdError::InvalidEmbeddedNode, + "Embedded node with #{statements.size} statements" unless + statements.count == 1 + rei_node + else + raise JsonLdError::InvalidIdValue, + "value of @id must be a string unless framing: #{value.inspect}" unless framing + end else raise JsonLdError::InvalidIdValue, "value of @id must be a string or hash if framing: #{value.inspect}" diff --git a/lib/json/ld/to_rdf.rb b/lib/json/ld/to_rdf.rb index 09628b7a..ec476021 100644 --- a/lib/json/ld/to_rdf.rb +++ b/lib/json/ld/to_rdf.rb @@ -16,6 +16,8 @@ module ToRDF # @return RDF::Resource the subject of this item def item_to_rdf(item, graph_name: nil, &block) # Just return value object as Term + return unless item + if value?(item) value, datatype = item.fetch('@value'), item.fetch('@type', nil) @@ -76,11 +78,20 @@ def item_to_rdf(item, graph_name: nil, &block) return parse_list(item['@list'], graph_name: graph_name, &block) end - # Skip if '@id' is nil - subject = if item.has_key?('@id') - item['@id'].nil? ? nil : as_resource(item['@id']) - else - node + subject = case item['@id'] + when nil then node + when String then as_resource(item['@id']) + when Object + # Embedded statement + # (No error checking, as this is done in expansion) + embedded_statement = to_enum(:item_to_rdf, item['@id']).to_a.first + + # If in Property Graph mode, emit this triple + # FIXME: this screws up the above assumptin; only :SA mode supported for now + yield RDF::Statement(*embedded_statement.to_a, graph_name: graph_name) if + @options[:rdfstar] == :PG + + embedded_statement end #log_debug("item_to_rdf") {"subject: #{subject.to_ntriples rescue 'malformed rdf'}"} diff --git a/spec/expand_spec.rb b/spec/expand_spec.rb index 52671c51..f6a07733 100644 --- a/spec/expand_spec.rb +++ b/spec/expand_spec.rb @@ -3376,6 +3376,254 @@ end end + context "JSON-LD*" do + { + "node with embedded subject without rdfstar option": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + exception: JSON::LD::JsonLdError::InvalidIdValue + }, + }.each do |title, params| + it(title) {run_expand params} + end + + { + "node with embedded subject having no @id": { + input: %({ + "@id": { + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + output: %([{ + "@id": { + "ex:prop": [{"@value": "value"}] + }, + "ex:prop": [{"@value": "value2"}] + }]) + }, + "node with embedded subject having IRI @id": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + output: %([{ + "@id": { + "@id": "ex:rei", + "ex:prop": [{"@value": "value"}] + }, + "ex:prop": [{"@value": "value2"}] + }]) + }, + "node with embedded subject having BNode @id": { + input: %({ + "@id": { + "@id": "_:rei", + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + output: %([{ + "@id": { + "@id": "_:rei", + "ex:prop": [{"@value": "value"}] + }, + "ex:prop": [{"@value": "value2"}] + }]) + }, + "node with embedded subject having a type": { + input: %({ + "@id": { + "@id": "ex:rei", + "@type": "ex:Type" + }, + "ex:prop": "value2" + }), + output: %([{ + "@id": { + "@id": "ex:rei", + "@type": ["ex:Type"] + }, + "ex:prop": [{"@value": "value2"}] + }]) + }, + "node with embedded subject having an IRI value": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": {"@id": "ex:value"} + }, + "ex:prop": "value2" + }), + output: %([{ + "@id": { + "@id": "ex:rei", + "ex:prop": [{"@id": "ex:value"}] + }, + "ex:prop": [{"@value": "value2"}] + }]) + }, + "node with embedded subject having an BNode value": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": {"@id": "_:value"} + }, + "ex:prop": "value2" + }), + output: %([{ + "@id": { + "@id": "ex:rei", + "ex:prop": [{"@id": "_:value"}] + }, + "ex:prop": [{"@value": "value2"}] + }]) + }, + "node with recursive embedded subject": { + input: %({ + "@id": { + "@id": { + "@id": "ex:rei", + "ex:prop": "value3" + }, + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + output: %([{ + "@id": { + "@id": { + "@id": "ex:rei", + "ex:prop": [{"@value": "value3"}] + }, + "ex:prop": [{"@value": "value"}] + }, + "ex:prop": [{"@value": "value2"}] + }]) + }, + "illegal node with subject having no property": { + input: %({ + "@id": { + "@id": "ex:rei" + }, + "ex:prop": "value3" + }), + exception: JSON::LD::JsonLdError::InvalidEmbeddedNode + }, + "illegal node with subject having multiple properties": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": ["value1", "value2"] + }, + "ex:prop": "value3" + }), + exception: JSON::LD::JsonLdError::InvalidEmbeddedNode + }, + "illegal node with subject having multiple types": { + input: %({ + "@id": { + "@id": "ex:rei", + "@type": ["ex:Type1", "ex:Type2"] + }, + "ex:prop": "value3" + }), + exception: JSON::LD::JsonLdError::InvalidEmbeddedNode + }, + "illegal node with subject having type and property": { + input: %({ + "@id": { + "@id": "ex:rei", + "@type": "ex:Type", + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + exception: JSON::LD::JsonLdError::InvalidEmbeddedNode + }, + "node with embedded object": { + input: %({ + "@id": "ex:subj", + "ex:value": { + "@id": { + "@id": "ex:rei", + "ex:prop": "value" + } + } + }), + output: %([{ + "@id": "ex:subj", + "ex:value": [{ + "@id": { + "@id": "ex:rei", + "ex:prop": [{"@value": "value"}] + } + }] + }]) + }, + "illegal node with embedded object having properties": { + input: %({ + "@id": "ex:subj", + "ex:value": { + "@id": { + "@id": "ex:rei", + "ex:prop": "value" + }, + "ex:prop": "value2" + } + }), + output: %([{ + "@id": "ex:subj", + "ex:value": [{ + "@id": { + "@id": "ex:rei", + "ex:prop": [{"@value": "value"}] + }, + "ex:prop": [{"@value": "value2"}] + }] + }]) + }, + "node with recursive embedded object": { + input: %({ + "@id": "ex:subj", + "ex:value": { + "@id": { + "@id": { + "@id": "ex:rei", + "ex:prop": "value3" + }, + "ex:prop": "value" + }, + "ex:prop": "value2" + } + }), + output: %([{ + "@id": "ex:subj", + "ex:value": [{ + "@id": { + "@id": { + "@id": "ex:rei", + "ex:prop": [{"@value": "value3"}] + }, + "ex:prop":[{"@value": "value"}] + }, + "ex:prop": [{"@value": "value2"}] + }] + }]) + }, + }.each do |title, params| + it(title) {run_expand params.merge(rdfstar: :SA)} + end + end + begin require 'nokogiri' rescue LoadError diff --git a/spec/suite_to_rdf_spec.rb b/spec/suite_to_rdf_spec.rb index e1fa5cce..293d56d9 100644 --- a/spec/suite_to_rdf_spec.rb +++ b/spec/suite_to_rdf_spec.rb @@ -9,6 +9,7 @@ m.entries.each do |t| specify "#{t.property('@id')}: #{t.name}#{' (negative test)' unless t.positiveTest?}" do pending "Generalized RDF" if t.options[:produceGeneralizedRdf] + pending "RDF*" if t.property('@id') == '#te122' if %w(#t0118).include?(t.property('@id')) expect {t.run self}.to write(/Statement .* is invalid/).to(:error) elsif %w(#te075).include?(t.property('@id')) diff --git a/spec/to_rdf_spec.rb b/spec/to_rdf_spec.rb index 9bbcbea7..34d0fba2 100644 --- a/spec/to_rdf_spec.rb +++ b/spec/to_rdf_spec.rb @@ -1175,6 +1175,261 @@ end end + context "JSON-LD*" do + { + "node with embedded subject without rdfstar option": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + exception: JSON::LD::JsonLdError::InvalidIdValue + }, + }.each do |title, params| + it(title) {run_to_rdf params} + end + + { + "node with embedded subject having no @id": { + input: %({ + "@id": { + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + sa: %( + <<_:b0 "value">> "value2" . + ), + pg: %( + <<_:b0 "value">> "value2" . + _:b0 "value" . + ), + }, + "node with embedded subject having IRI @id": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + sa: %( + << "value">> "value2" . + ), + pg: %( + << "value">> "value2" . + "value" . + ), + }, + "node with embedded subject having BNode @id": { + input: %({ + "@id": { + "@id": "_:rei", + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + sa: %( + <<_:b0 "value">> "value2" . + ), + pg: %( + <<_:b0 "value">> "value2" . + _:b0 "value" . + ), + }, + "node with embedded subject having a type": { + input: %({ + "@id": { + "@id": "ex:rei", + "@type": "ex:Type" + }, + "ex:prop": "value2" + }), + sa: %( + << >> "value2" . + ), + pg: %( + << >> "value2" . + . + ), + }, + "node with embedded subject having an IRI value": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": {"@id": "ex:value"} + }, + "ex:prop": "value2" + }), + sa: %( + << >> "value2" . + ), + pg: %( + << >> "value2" . + . + ), + }, + "node with embedded subject having an BNode value": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": {"@id": "_:value"} + }, + "ex:prop": "value2" + }), + sa: %( + << _:b0>> "value2" . + ), + pg: %( + << _:b0>> "value2" . + _:b0 . + ), + }, + "node with recursive embedded subject": { + input: %({ + "@id": { + "@id": { + "@id": "ex:rei", + "ex:prop": "value3" + }, + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + sa: %( + <<<< "value3">> "value">> "value2" . + ), + #pg: %( + # <<<< "value3">> "value">> "value2" . + # << "value3">> "value" . + # "value3" . + #), + }, + "illegal node with subject having no property": { + input: %({ + "@id": { + "@id": "ex:rei" + }, + "ex:prop": "value3" + }), + exception: JSON::LD::JsonLdError::InvalidEmbeddedNode + }, + "illegal node with subject having multiple properties": { + input: %({ + "@id": { + "@id": "ex:rei", + "ex:prop": ["value1", "value2"] + }, + "ex:prop": "value3" + }), + exception: JSON::LD::JsonLdError::InvalidEmbeddedNode + }, + "illegal node with subject having multiple types": { + input: %({ + "@id": { + "@id": "ex:rei", + "@type": ["ex:Type1", "ex:Type2"] + }, + "ex:prop": "value3" + }), + exception: JSON::LD::JsonLdError::InvalidEmbeddedNode + }, + "illegal node with subject having type and property": { + input: %({ + "@id": { + "@id": "ex:rei", + "@type": "ex:Type", + "ex:prop": "value" + }, + "ex:prop": "value2" + }), + exception: JSON::LD::JsonLdError::InvalidEmbeddedNode + }, + "node with embedded object": { + input: %({ + "@id": "ex:subj", + "ex:value": { + "@id": { + "@id": "ex:rei", + "ex:prop": "value" + } + } + }), + sa: %( + << "value">> . + ), + pg: %( + << "value">> . + "value" . + ), + }, + "node with embedded object having properties": { + input: %({ + "@id": "ex:subj", + "ex:value": { + "@id": { + "@id": "ex:rei", + "ex:prop": "value" + }, + "ex:prop": "value2" + } + }), + sa: %( + << "value">> . + << "value">> "value2" . + ), + pg: %( + << "value">> . + << "value">> "value2" . + "value" . + ), + }, + "node with recursive embedded object": { + input: %({ + "@id": "ex:subj", + "ex:value": { + "@id": { + "@id": { + "@id": "ex:rei", + "ex:prop": "value3" + }, + "ex:prop": "value" + }, + "ex:prop": "value2" + } + }), + sa: %( + <<<< "value3">> "value">> . + <<<< "value3">> "value">> "value2" . + ), + #pg: %( + # <<<< "value3">> "value">> . + # << "value3">> "value" . + # "value3" . + # <<<< "value3">> "value">> "value2" . + #), + }, + }.each do |title, params| + context(title) do + it "Separate Assertions" do + output_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(params[:sa], rdfstar: :SA)} + run_to_rdf params.merge(rdfstar: :SA, output: output_graph) + end if params[:sa] + + it "Property Graph" do + output_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(params[:pg], rdfstar: :SA)} + run_to_rdf params.merge(rdfstar: :PG, output: output_graph) + end if params[:pg] + + it "Exception" do + run_to_rdf params.merge(rdfstar: :SA) + end if params[:exception] + end + end + end + context "exceptions" do { "Invalid subject" => { From 702e80e40cb92c80949f6074a6d07146f98d28e4 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 25 May 2020 16:34:06 -0700 Subject: [PATCH 02/11] From RDF with embedded triples. --- lib/json/ld/from_rdf.rb | 48 ++++++++--- spec/from_rdf_spec.rb | 181 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 12 deletions(-) diff --git a/lib/json/ld/from_rdf.rb b/lib/json/ld/from_rdf.rb index 27a088b4..f7c09550 100644 --- a/lib/json/ld/from_rdf.rb +++ b/lib/json/ld/from_rdf.rb @@ -22,7 +22,6 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false) referenced_once = {} value = nil - ec = @context # Create an entry for compound-literal node detection compound_literal_subjects = {} @@ -33,7 +32,7 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false) dataset.each do |statement| #log_debug("statement") { statement.to_nquads.chomp} - name = statement.graph_name ? ec.expand_iri(statement.graph_name, base: @options[:base]).to_s : '@default' + name = statement.graph_name ? @context.expand_iri(statement.graph_name, base: @options[:base]).to_s : '@default' # Create a graph entry as needed node_map = graph_map[name] ||= {} @@ -41,30 +40,29 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false) default_graph[name] ||= {'@id' => name} unless name == '@default' - subject = ec.expand_iri(statement.subject, as_string: true, base: @options[:base]) - node = node_map[subject] ||= {'@id' => subject} + subject = statement.subject.to_s + node = node_map[subject] ||= resource_representation(statement.subject,useNativeTypes) # If predicate is rdf:datatype, note subject in compound literal subjects map if @options[:rdfDirection] == 'compound-literal' && statement.predicate == RDF.to_uri + 'direction' compound_literal_subjects[name][subject] ||= true end - # If object is an IRI or blank node identifier, and node map does not have an object member, create one and initialize its value to a new JSON object consisting of a single member @id whose value is set to object. - node_map[statement.object.to_s] ||= {'@id' => statement.object.to_s} unless - statement.object.literal? + # If object is an IRI, blank node identifier, or statement, and node map does not have an object member, create one and initialize its value to a new JSON object consisting of a single member @id whose value is set to object. + unless statement.object.literal? + node_map[statement.object.to_s] ||= + resource_representation(statement.object, useNativeTypes) + end # If predicate equals rdf:type, and object is an IRI or blank node identifier, append object to the value of the @type member of node. If no such member exists, create one and initialize it to an array whose only item is object. Finally, continue to the next RDF triple. + # XXX JSON-LD* does not support embedded value of @type if statement.predicate == RDF.type && statement.object.resource? && !useRdfType merge_value(node, '@type', statement.object.to_s) next end # Set value to the result of using the RDF to Object Conversion algorithm, passing object, rdfDirection, and use native types. - value = ec.expand_value(nil, - statement.object, - rdfDirection: @options[:rdfDirection], - useNativeTypes: useNativeTypes, - base: @options[:base]) + value = resource_representation(statement.object, useNativeTypes) merge_value(node, statement.predicate.to_s, value) @@ -162,5 +160,31 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false) #log_debug("fromRdf") {result.to_json(JSON_STATE) rescue 'malformed json'} result end + + private + def resource_representation(resource, useNativeTypes) + case resource + when RDF::Statement + # Note, if either subject or object are a BNode which is used elsewhere, + # this might not work will with the BNode accounting from above. + rep = {'@id' => resource_representation(resource.subject, false)} + if resource.predicate == RDF.type + rep['@id'].merge!('@type' => resource.object.to_s) + else + rep['@id'].merge!( + resource.predicate.to_s => + as_array(resource_representation(resource.object, useNativeTypes))) + end + rep + when RDF::Literal + @context.expand_value(nil, + resource, + rdfDirection: @options[:rdfDirection], + useNativeTypes: useNativeTypes, + base: @options[:base]) + else + {'@id' => resource.to_s} + end + end end end diff --git a/spec/from_rdf_spec.rb b/spec/from_rdf_spec.rb index ebea3175..87f88c51 100644 --- a/spec/from_rdf_spec.rb +++ b/spec/from_rdf_spec.rb @@ -766,6 +766,187 @@ end end + context "RDF*" do + { + "subject-iii": { + input: RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %([{ + "@id": { + "@id": "http://example/s1", + "http://example/p1": [{"@id": "http://example/o1"}] + }, + "http://example/p": [{"@id": "http://example/o"}] + }]) + }, + "subject-iib": { + input: RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Node.new('o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %([{ + "@id": { + "@id": "http://example/s1", + "http://example/p1": [{"@id": "_:o1"}] + }, + "http://example/p": [{"@id": "http://example/o"}] + }]) + }, + "subject-iil": { + input: RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Literal('o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %([{ + "@id": { + "@id": "http://example/s1", + "http://example/p1": [{"@value": "o1"}] + }, + "http://example/p": [{"@id": "http://example/o"}] + }]) + }, + "subject-bii": { + input: RDF::Statement( + RDF::Statement( + RDF::Node('s1'), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %([{ + "@id": { + "@id": "_:s1", + "http://example/p1": [{"@id": "http://example/o1"}] + }, + "http://example/p": [{"@id": "http://example/o"}] + }]) + }, + "subject-bib": { + input: RDF::Statement( + RDF::Statement( + RDF::Node('s1'), + RDF::URI('http://example/p1'), + RDF::Node.new('o1')), + RDF::URI('http://example/p'), RDF::URI('http://example/o')), + output: %([{ + "@id": { + "@id": "_:s1", + "http://example/p1": [{"@id": "_:o1"}] + }, + "http://example/p": [{"@id": "http://example/o"}] + }]) + }, + "subject-bil": { + input: RDF::Statement( + RDF::Statement( + RDF::Node('s1'), + RDF::URI('http://example/p1'), + RDF::Literal('o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %([{ + "@id": { + "@id": "_:s1", + "http://example/p1": [{"@value": "o1"}] + }, + "http://example/p": [{"@id": "http://example/o"}] + }]) + }, + "object-iii": { + input: RDF::Statement( + RDF::URI('http://example/s'), + RDF::URI('http://example/p'), + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1'))), + output: %([{ + "@id": "http://example/s", + "http://example/p": [{ + "@id": { + "@id": "http://example/s1", + "http://example/p1": [{"@id": "http://example/o1"}] + } + }] + }]) + }, + "object-iib": { + input: RDF::Statement( + RDF::URI('http://example/s'), + RDF::URI('http://example/p'), + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Node.new('o1'))), + output: %([{ + "@id": "http://example/s", + "http://example/p": [{ + "@id": { + "@id": "http://example/s1", + "http://example/p1": [{"@id": "_:o1"}] + } + }] + }]) + }, + "object-iil": { + input: RDF::Statement( + RDF::URI('http://example/s'), + RDF::URI('http://example/p'), + RDF::Statement( + RDF::URI('http://example/s1'), + RDF::URI('http://example/p1'), + RDF::Literal('o1'))), + output: %([{ + "@id": "http://example/s", + "http://example/p": [{ + "@id": { + "@id": "http://example/s1", + "http://example/p1": [{"@value": "o1"}] + } + }] + }]) + }, + "recursive-subject": { + input: RDF::Statement( + RDF::Statement( + RDF::Statement( + RDF::URI('http://example/s2'), + RDF::URI('http://example/p2'), + RDF::URI('http://example/o2')), + RDF::URI('http://example/p1'), + RDF::URI('http://example/o1')), + RDF::URI('http://example/p'), + RDF::URI('http://example/o')), + output: %([{ + "@id": { + "@id": { + "@id": "http://example/s2", + "http://example/p2": [{"@id": "http://example/o2"}] + }, + "http://example/p1": [{"@id": "http://example/o1"}] + }, + "http://example/p": [{"@id": "http://example/o"}] + }]) + }, + }.each do |name, params| + it name do + graph = RDF::Graph.new {|g| g << params[:input]} + do_fromRdf(params.merge(input: graph, prefixes: {ex: 'http://example/'})) + end + end + end + context "problems" do { "xsd:boolean as value" => { From 677c40ed8dba8527b8fb12314750260d66eb0d74 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 26 May 2020 09:41:33 -0700 Subject: [PATCH 03/11] Documentation and CLI arguments update. --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++ lib/json/ld/api.rb | 9 +++++++ lib/json/ld/format.rb | 11 +++++++- lib/json/ld/to_rdf.rb | 25 ++++++++++++------ spec/to_rdf_spec.rb | 22 ++++++++-------- 5 files changed, 107 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index e6e112f5..3f1d3a4f 100755 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ JSON::LD can now be used to create a _context_ from an RDFS/OWL definition, and * If the [jsonlint][] gem is installed, it will be used when validating an input document. * If available, uses [Nokogiri][] and/or [Nokogumbo][] for parsing HTML, falls back to REXML otherwise. +* Provisional support for [JSON-LD*][RDF*]. [Implementation Report](file.earl.html) @@ -35,6 +36,64 @@ The order of triples retrieved from the `RDF::Enumerable` dataset determines the ### MultiJson parser The [MultiJson](https://rubygems.org/gems/multi_json) gem is used for parsing JSON; this defaults to the native JSON parser, but will use a more performant parser if one is available. A specific parser can be specified by adding the `:adapter` option to any API call. See [MultiJson](https://rubygems.org/gems/multi_json) for more information. +### JSON-LD* (RDFStar) + +The {JSON::LD::API.toRdf} and {JSON::LD::API.fromRdf} API methods, along with the {JSON::LD::Reader} and {JSON::LD::Writer}, include provisional support for [JSON-LD*][RDF*]. + +Internally, an `RDF::Statement` is treated as another resource, along with `RDF::URI` and `RDF::Node`, which allows an `RDF::Statement` to have a `#subject` or `#object` which is also an `RDF::Statement`. + +In JSON-LD, with the `rdfstar` option set, the value of `@id`, in addition to an IRI or Blank Node Identifier, can be a JSON-LD node object having exactly one property with an optional `@id`, which may also be an embedded object. (It may also have `@context` and `@index` values). + + { + "@id": { + "@context": {"foaf": "http://xmlns.com/foaf/0.1/"}, + "@index": "ignored", + "@id": "bob", + "foaf:age" 23 + }, + "ex:certainty": 0.9 + } + +#### Serializing a Graph containing embedded statements + + require 'json-ld' + statement = RDF::Statement(RDF::URI('bob'), RDF::Vocab::FOAF.age, RDF::Literal(23)) + graph = RDF::Graph.new << [statement, RDF::URI("ex:certainty"), RDF::Literal(0.9)] + graph.dump(:jsonld, validate: false, standard_prefixes: true) + # => {"@id": {"@id": "bob", "foaf:age" 23}, "ex:certainty": 0.9} + +Alternatively, using the {JSON::LD::API.fromRdf} method: + + JSON::LD::API::fromRdf(graph) + # => {"@id": {"@id": "bob", "foaf:age" 23}, "ex:certainty": 0.9} + +#### Reading a Graph containing embedded statements + +By default, {JSON::LD::API.toRdf} (and {JSON::LD::Reader}) will reject a document containing a subject resource. + + jsonld = %({ + "@id": { + "@id": "bob", "foaf:age" 23 + }, + "ex:certainty": 0.9 + }) + graph = RDF::Graph.new << JSON::LD::API.toRdf(input) + # => JSON::LD::JsonLdError::InvalidIdValue + +{JSON::LD::API.toRdf} (and {JSON::LD::Reader}) support a `rdfstar` option with either `:PG` (Property Graph) or `:SA` (Separate Assertions) modes. In `:PG` mode, statements that are used in the subject or object positions are also implicitly added to the graph: + + graph = RDF::Graph.new do |graph| + JSON::LD::Reader.new(jsonld, rdfstar: :PG) {|reader| graph << reader} + end + graph.count #=> 2 + +When using the `:SA` mode, only one statement is asserted, although the reified statement is contained within the graph. + + graph = RDF::Graph.new do |graph| + JSON::LD::Reader.new(jsonld, rdfstar: :PG) {|reader| graph << reader} + end + graph.count #=> 1 + ## Examples ```ruby @@ -568,6 +627,7 @@ see or the accompanying {file:UNLICENSE} file. [YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md [PDD]: https://unlicense.org/#unlicensing-contributions [RDF.rb]: https://rubygems.org/gems/rdf +[RDF*]: https://lists.w3.org/Archives/Public/public-rdf-star/ [Rack::LinkedData]: https://rubygems.org/gems/rack-linkeddata [Backports]: https://rubygems.org/gems/backports [JSON-LD]: https://www.w3.org/TR/json-ld11/ "JSON-LD 1.1" diff --git a/lib/json/ld/api.rb b/lib/json/ld/api.rb index c7c7a252..aca23199 100644 --- a/lib/json/ld/api.rb +++ b/lib/json/ld/api.rb @@ -89,6 +89,10 @@ class API # @option options [String] :processingMode # Processing mode, json-ld-1.0 or json-ld-1.1. # If `processingMode` is not specified, a mode of `json-ld-1.0` or `json-ld-1.1` is set, the context used for `expansion` or `compaction`. + # @option options [:PG, :SA] rdfstar (nil) + # support parsing JSON-LD* statement resources. + # If `:PG`, referenced statements are also emitted (toRdf). + # If `:SA`, referenced statements are not emitted. # @option options [Boolean] :rename_bnodes (true) # Rename bnodes as part of expansion, or keep them the same. # @option options [Boolean] :unique_bnodes (false) @@ -498,6 +502,11 @@ def self.toRdf(input, expanded: false, **options, &block) end yield statement + + # If in Property Graph mode for RDF* and statement has an embedded triple, yield those embedded triples as well. + if options[:rdfstar] == :PG && statement.to_a.any?(&:statement?) + each_pg_statement(statement, &block) + end end end end diff --git a/lib/json/ld/format.rb b/lib/json/ld/format.rb index 8d73210d..b482300e 100644 --- a/lib/json/ld/format.rb +++ b/lib/json/ld/format.rb @@ -165,7 +165,16 @@ def self.cli_commands end end end - end + end, + options: [ + RDF::CLI::Option.new( + symbol: :context, + datatype: RDF::URI, + control: :url2, + use: :required, + on: ["--context CONTEXT"], + description: "Context to use when compacting.") {|arg| RDF::URI(arg)}, + ] }, frame: { description: "Frame JSON-LD or parsed RDF", diff --git a/lib/json/ld/to_rdf.rb b/lib/json/ld/to_rdf.rb index ec476021..8a5e353e 100644 --- a/lib/json/ld/to_rdf.rb +++ b/lib/json/ld/to_rdf.rb @@ -84,14 +84,7 @@ def item_to_rdf(item, graph_name: nil, &block) when Object # Embedded statement # (No error checking, as this is done in expansion) - embedded_statement = to_enum(:item_to_rdf, item['@id']).to_a.first - - # If in Property Graph mode, emit this triple - # FIXME: this screws up the above assumptin; only :SA mode supported for now - yield RDF::Statement(*embedded_statement.to_a, graph_name: graph_name) if - @options[:rdfstar] == :PG - - embedded_statement + to_enum(:item_to_rdf, item['@id']).to_a.first end #log_debug("item_to_rdf") {"subject: #{subject.to_ntriples rescue 'malformed rdf'}"} @@ -189,6 +182,22 @@ def parse_list(list, graph_name: nil, &block) result end + ## + # Recursively emit embedded statements in Property Graph mode + # + # @param [RDF::Statement] statement + def each_pg_statement(statement, &block) + if statement.subject.statement? + block.call(statement.subject) + each_pg_statement(statement.subject, &block) + end + + if statement.object.statement? + block.call(statement.object) + each_pg_statement(statement.object, &block) + end + end + ## # Create a new named node using the sequence def node diff --git a/spec/to_rdf_spec.rb b/spec/to_rdf_spec.rb index 34d0fba2..9ca1b59b 100644 --- a/spec/to_rdf_spec.rb +++ b/spec/to_rdf_spec.rb @@ -1301,11 +1301,11 @@ sa: %( <<<< "value3">> "value">> "value2" . ), - #pg: %( - # <<<< "value3">> "value">> "value2" . - # << "value3">> "value" . - # "value3" . - #), + pg: %( + <<<< "value3">> "value">> "value2" . + << "value3">> "value" . + "value3" . + ), }, "illegal node with subject having no property": { input: %({ @@ -1404,12 +1404,12 @@ <<<< "value3">> "value">> . <<<< "value3">> "value">> "value2" . ), - #pg: %( - # <<<< "value3">> "value">> . - # << "value3">> "value" . - # "value3" . - # <<<< "value3">> "value">> "value2" . - #), + pg: %( + <<<< "value3">> "value">> . + << "value3">> "value" . + "value3" . + <<<< "value3">> "value">> "value2" . + ), }, }.each do |title, params| context(title) do From c28a5d6ee39a858e1896bd0c2e2b672331aad5e6 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 28 May 2020 16:09:42 -0700 Subject: [PATCH 04/11] Add caveat on use of JSON-LD* features. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3f1d3a4f..7bbb7aad 100755 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ In JSON-LD, with the `rdfstar` option set, the value of `@id`, in addition to an "ex:certainty": 0.9 } +**Note: This feature is subject to change or elimination as the standards process progresses.** + #### Serializing a Graph containing embedded statements require 'json-ld' From eb57e42314a363cd50be196b40665cc57aee48c2 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 30 May 2020 12:29:04 -0700 Subject: [PATCH 05/11] Update doap:license (again) to https://unlicense.org/1.0/. --- etc/doap.jsonld | 2 +- etc/doap.nt | 2 +- etc/doap.ttl | 2 +- etc/earl-stream.ttl | 2 +- etc/earl.ttl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/etc/doap.jsonld b/etc/doap.jsonld index 239c807a..79c42887 100644 --- a/etc/doap.jsonld +++ b/etc/doap.jsonld @@ -26,7 +26,7 @@ "@type": "doap:Project", "doap:name": "JSON::LD", "doap:homepage": "https://github.com/ruby-rdf/json-ld/", - "doap:license": "https://unlicense.org/", + "doap:license": "https://unlicense.org/1.0/", "doap:shortdesc": "JSON-LD support for RDF.rb.", "doap:description": "RDF.rb extension for parsing/serializing JSON-LD data.", "doap:created": "2011-05-07", diff --git a/etc/doap.nt b/etc/doap.nt index deb3306d..0cfcd495 100644 --- a/etc/doap.nt +++ b/etc/doap.nt @@ -14,7 +14,7 @@ . . . - . + . . "JSON::LD" . "Ruby" . diff --git a/etc/doap.ttl b/etc/doap.ttl index 4f382f23..5f9b3c1b 100644 --- a/etc/doap.ttl +++ b/etc/doap.ttl @@ -19,7 +19,7 @@ doap:implements , , ; - doap:license ; + doap:license ; doap:maintainer ; doap:name "JSON::LD"^^xsd:string; doap:programming-language "Ruby"; diff --git a/etc/earl-stream.ttl b/etc/earl-stream.ttl index c5029885..e10ea1a0 100644 --- a/etc/earl-stream.ttl +++ b/etc/earl-stream.ttl @@ -18,7 +18,7 @@ doap:implements , , ; - doap:license ; + doap:license ; doap:maintainer ; doap:name "JSON::LD"^^xsd:string; doap:programming-language "Ruby"^^xsd:string; diff --git a/etc/earl.ttl b/etc/earl.ttl index a70b4fa6..7604951b 100644 --- a/etc/earl.ttl +++ b/etc/earl.ttl @@ -18,7 +18,7 @@ doap:implements , , ; - doap:license ; + doap:license ; doap:maintainer ; doap:name "JSON::LD"^^xsd:string; doap:programming-language "Ruby"^^xsd:string; From 0f712007167d0395f9c3e1155e6a3249db53c258 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 2 Oct 2020 12:10:13 -0700 Subject: [PATCH 06/11] Add --rdfstar option to script/parse. --- example-files/bob-star.jsonld | 12 ++++++++++++ script/parse | 4 ++++ 2 files changed, 16 insertions(+) create mode 100644 example-files/bob-star.jsonld diff --git a/example-files/bob-star.jsonld b/example-files/bob-star.jsonld new file mode 100644 index 00000000..8268b74e --- /dev/null +++ b/example-files/bob-star.jsonld @@ -0,0 +1,12 @@ +{ + "@context": { + "@base": "http://example.org/", + "ex": "http://example.org/", + "foaf": "http://xmlns.com/foaf/0.1/" + }, + "@id": { + "@id": "bob", + "foaf:age": 23 + }, + "ex:certainty": 0.8 +} diff --git a/script/parse b/script/parse index cc131686..8d7b7881 100755 --- a/script/parse +++ b/script/parse @@ -116,6 +116,7 @@ OPT_ARGS = [ ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT, "Where to store output (default STDOUT)"], ["--profile", GetoptLong::NO_ARGUMENT, "Run profiler with output to doc/profiles/"], ["--quiet", GetoptLong::NO_ARGUMENT, "Reduce output"], + ["--rdfstar", GetoptLong::REQUIRED_ARGUMENT, "RDF* mode, SA or PG"], ["--stream", GetoptLong::NO_ARGUMENT, "Streaming reader/writer"], ["--uri", GetoptLong::REQUIRED_ARGUMENT, "Run with argument value as base"], ["--validate", GetoptLong::NO_ARGUMENT, "Validate input"], @@ -156,6 +157,9 @@ opts.each do |opt, arg| when '--quiet' options[:quiet] = true logger.level = Logger::FATAL + when '--rdfstar' + parser_options[:rdfstar] = arg.to_sym + usage unless [:PG, :SA].include?(parser_options[:rdfstar]) when '--stream' then parser_options[:stream] = true when '--uri' then parser_options[:base] = arg when '--validate' then parser_options[:validate] = true From 40655ecb8f6a552e52a480c48c4096d03d7d3dfa Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 11 Dec 2020 16:56:25 -0800 Subject: [PATCH 07/11] Update link to JSON-LD* and consolidate rdfstar mode. --- README.md | 17 +++------- lib/json/ld/api.rb | 9 +---- lib/json/ld/expand.rb | 2 -- lib/json/ld/to_rdf.rb | 16 --------- script/parse | 6 ++-- spec/expand_spec.rb | 2 +- spec/to_rdf_spec.rb | 79 ++++++++----------------------------------- 7 files changed, 24 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 7bbb7aad..346f426e 100755 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ JSON::LD can now be used to create a _context_ from an RDFS/OWL definition, and * If the [jsonlint][] gem is installed, it will be used when validating an input document. * If available, uses [Nokogiri][] and/or [Nokogumbo][] for parsing HTML, falls back to REXML otherwise. -* Provisional support for [JSON-LD*][RDF*]. +* Provisional support for [JSON-LD*][JSON-LD*]. [Implementation Report](file.earl.html) @@ -38,7 +38,7 @@ The [MultiJson](https://rubygems.org/gems/multi_json) gem is used for parsing JS ### JSON-LD* (RDFStar) -The {JSON::LD::API.toRdf} and {JSON::LD::API.fromRdf} API methods, along with the {JSON::LD::Reader} and {JSON::LD::Writer}, include provisional support for [JSON-LD*][RDF*]. +The {JSON::LD::API.toRdf} and {JSON::LD::API.fromRdf} API methods, along with the {JSON::LD::Reader} and {JSON::LD::Writer}, include provisional support for [JSON-LD*][JSON-LD*]. Internally, an `RDF::Statement` is treated as another resource, along with `RDF::URI` and `RDF::Node`, which allows an `RDF::Statement` to have a `#subject` or `#object` which is also an `RDF::Statement`. @@ -82,17 +82,10 @@ By default, {JSON::LD::API.toRdf} (and {JSON::LD::Reader}) will reject a documen graph = RDF::Graph.new << JSON::LD::API.toRdf(input) # => JSON::LD::JsonLdError::InvalidIdValue -{JSON::LD::API.toRdf} (and {JSON::LD::Reader}) support a `rdfstar` option with either `:PG` (Property Graph) or `:SA` (Separate Assertions) modes. In `:PG` mode, statements that are used in the subject or object positions are also implicitly added to the graph: +{JSON::LD::API.toRdf} (and {JSON::LD::Reader}) support a boolean valued `rdfstar` option; only one statement is asserted, although the reified statement is contained within the graph. graph = RDF::Graph.new do |graph| - JSON::LD::Reader.new(jsonld, rdfstar: :PG) {|reader| graph << reader} - end - graph.count #=> 2 - -When using the `:SA` mode, only one statement is asserted, although the reified statement is contained within the graph. - - graph = RDF::Graph.new do |graph| - JSON::LD::Reader.new(jsonld, rdfstar: :PG) {|reader| graph << reader} + JSON::LD::Reader.new(jsonld, rdfstar: true) {|reader| graph << reader} end graph.count #=> 1 @@ -629,7 +622,7 @@ see or the accompanying {file:UNLICENSE} file. [YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md [PDD]: https://unlicense.org/#unlicensing-contributions [RDF.rb]: https://rubygems.org/gems/rdf -[RDF*]: https://lists.w3.org/Archives/Public/public-rdf-star/ +[JSON-LD*]: https://json-ld.github.io/json-ld-star/ [Rack::LinkedData]: https://rubygems.org/gems/rack-linkeddata [Backports]: https://rubygems.org/gems/backports [JSON-LD]: https://www.w3.org/TR/json-ld11/ "JSON-LD 1.1" diff --git a/lib/json/ld/api.rb b/lib/json/ld/api.rb index aca23199..2a7373d2 100644 --- a/lib/json/ld/api.rb +++ b/lib/json/ld/api.rb @@ -89,10 +89,8 @@ class API # @option options [String] :processingMode # Processing mode, json-ld-1.0 or json-ld-1.1. # If `processingMode` is not specified, a mode of `json-ld-1.0` or `json-ld-1.1` is set, the context used for `expansion` or `compaction`. - # @option options [:PG, :SA] rdfstar (nil) + # @option options [Boolean] rdfstar (false) # support parsing JSON-LD* statement resources. - # If `:PG`, referenced statements are also emitted (toRdf). - # If `:SA`, referenced statements are not emitted. # @option options [Boolean] :rename_bnodes (true) # Rename bnodes as part of expansion, or keep them the same. # @option options [Boolean] :unique_bnodes (false) @@ -502,11 +500,6 @@ def self.toRdf(input, expanded: false, **options, &block) end yield statement - - # If in Property Graph mode for RDF* and statement has an embedded triple, yield those embedded triples as well. - if options[:rdfstar] == :PG && statement.to_a.any?(&:statement?) - each_pg_statement(statement, &block) - end end end end diff --git a/lib/json/ld/expand.rb b/lib/json/ld/expand.rb index 0cada4f4..a8a9a97f 100644 --- a/lib/json/ld/expand.rb +++ b/lib/json/ld/expand.rb @@ -281,9 +281,7 @@ def expand_object(input, active_property, context, output_object, elsif @options[:rdfstar] # Result must have just a single statement rei_node = expand(value, active_property, context, log_depth: log_depth.to_i + 1) - old_star, @options[:rdfstar] = @options[:rdfstar], :SA statements = to_enum(:item_to_rdf, rei_node) - @options[:rdfstar] = old_star # :PG would emit too many statements raise JsonLdError::InvalidEmbeddedNode, "Embedded node with #{statements.size} statements" unless statements.count == 1 diff --git a/lib/json/ld/to_rdf.rb b/lib/json/ld/to_rdf.rb index 8a5e353e..d3840d6c 100644 --- a/lib/json/ld/to_rdf.rb +++ b/lib/json/ld/to_rdf.rb @@ -182,22 +182,6 @@ def parse_list(list, graph_name: nil, &block) result end - ## - # Recursively emit embedded statements in Property Graph mode - # - # @param [RDF::Statement] statement - def each_pg_statement(statement, &block) - if statement.subject.statement? - block.call(statement.subject) - each_pg_statement(statement.subject, &block) - end - - if statement.object.statement? - block.call(statement.object) - each_pg_statement(statement.object, &block) - end - end - ## # Create a new named node using the sequence def node diff --git a/script/parse b/script/parse index 8d7b7881..9c7bff78 100755 --- a/script/parse +++ b/script/parse @@ -116,7 +116,7 @@ OPT_ARGS = [ ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT, "Where to store output (default STDOUT)"], ["--profile", GetoptLong::NO_ARGUMENT, "Run profiler with output to doc/profiles/"], ["--quiet", GetoptLong::NO_ARGUMENT, "Reduce output"], - ["--rdfstar", GetoptLong::REQUIRED_ARGUMENT, "RDF* mode, SA or PG"], + ["--rdfstar", GetoptLong::NO_ARGUMENT, "RDF* mode"], ["--stream", GetoptLong::NO_ARGUMENT, "Streaming reader/writer"], ["--uri", GetoptLong::REQUIRED_ARGUMENT, "Run with argument value as base"], ["--validate", GetoptLong::NO_ARGUMENT, "Validate input"], @@ -157,9 +157,7 @@ opts.each do |opt, arg| when '--quiet' options[:quiet] = true logger.level = Logger::FATAL - when '--rdfstar' - parser_options[:rdfstar] = arg.to_sym - usage unless [:PG, :SA].include?(parser_options[:rdfstar]) + when '--rdfstar' then parser_options[:rdfstar] = true when '--stream' then parser_options[:stream] = true when '--uri' then parser_options[:base] = arg when '--validate' then parser_options[:validate] = true diff --git a/spec/expand_spec.rb b/spec/expand_spec.rb index f6a07733..e4e04118 100644 --- a/spec/expand_spec.rb +++ b/spec/expand_spec.rb @@ -3620,7 +3620,7 @@ }]) }, }.each do |title, params| - it(title) {run_expand params.merge(rdfstar: :SA)} + it(title) {run_expand params.merge(rdfstar: true)} end end diff --git a/spec/to_rdf_spec.rb b/spec/to_rdf_spec.rb index 9ca1b59b..bfabfda8 100644 --- a/spec/to_rdf_spec.rb +++ b/spec/to_rdf_spec.rb @@ -1199,13 +1199,9 @@ }, "ex:prop": "value2" }), - sa: %( + expected: %( <<_:b0 "value">> "value2" . ), - pg: %( - <<_:b0 "value">> "value2" . - _:b0 "value" . - ), }, "node with embedded subject having IRI @id": { input: %({ @@ -1215,12 +1211,8 @@ }, "ex:prop": "value2" }), - sa: %( - << "value">> "value2" . - ), - pg: %( + expected: %( << "value">> "value2" . - "value" . ), }, "node with embedded subject having BNode @id": { @@ -1231,12 +1223,8 @@ }, "ex:prop": "value2" }), - sa: %( - <<_:b0 "value">> "value2" . - ), - pg: %( + expected: %( <<_:b0 "value">> "value2" . - _:b0 "value" . ), }, "node with embedded subject having a type": { @@ -1247,13 +1235,9 @@ }, "ex:prop": "value2" }), - sa: %( + expected: %( << >> "value2" . ), - pg: %( - << >> "value2" . - . - ), }, "node with embedded subject having an IRI value": { input: %({ @@ -1263,12 +1247,8 @@ }, "ex:prop": "value2" }), - sa: %( - << >> "value2" . - ), - pg: %( + expected: %( << >> "value2" . - . ), }, "node with embedded subject having an BNode value": { @@ -1279,12 +1259,8 @@ }, "ex:prop": "value2" }), - sa: %( - << _:b0>> "value2" . - ), - pg: %( + expected: %( << _:b0>> "value2" . - _:b0 . ), }, "node with recursive embedded subject": { @@ -1298,14 +1274,9 @@ }, "ex:prop": "value2" }), - sa: %( + expected: %( <<<< "value3">> "value">> "value2" . ), - pg: %( - <<<< "value3">> "value">> "value2" . - << "value3">> "value" . - "value3" . - ), }, "illegal node with subject having no property": { input: %({ @@ -1357,12 +1328,8 @@ } } }), - sa: %( - << "value">> . - ), - pg: %( + expected: %( << "value">> . - "value" . ), }, "node with embedded object having properties": { @@ -1376,15 +1343,10 @@ "ex:prop": "value2" } }), - sa: %( + expected: %( << "value">> . << "value">> "value2" . ), - pg: %( - << "value">> . - << "value">> "value2" . - "value" . - ), }, "node with recursive embedded object": { input: %({ @@ -1400,31 +1362,20 @@ "ex:prop": "value2" } }), - sa: %( + expected: %( <<<< "value3">> "value">> . <<<< "value3">> "value">> "value2" . ), - pg: %( - <<<< "value3">> "value">> . - << "value3">> "value" . - "value3" . - <<<< "value3">> "value">> "value2" . - ), }, }.each do |title, params| context(title) do - it "Separate Assertions" do - output_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(params[:sa], rdfstar: :SA)} - run_to_rdf params.merge(rdfstar: :SA, output: output_graph) - end if params[:sa] - - it "Property Graph" do - output_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(params[:pg], rdfstar: :SA)} - run_to_rdf params.merge(rdfstar: :PG, output: output_graph) - end if params[:pg] + it "Generates statements" do + output_graph = RDF::Graph.new {|g| g << RDF::NTriples::Reader.new(params[:expected], rdfstar: true)} + run_to_rdf params.merge(rdfstar: true, output: output_graph) + end if params[:expected] it "Exception" do - run_to_rdf params.merge(rdfstar: :SA) + run_to_rdf params.merge(rdfstar: true) end if params[:exception] end end From 45b4c7a2655b3880448bc4549f4345d77ff6c8b7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 19 Dec 2020 17:05:00 -0800 Subject: [PATCH 08/11] Use GitHub actions for CI. --- .github/workflows/ci.yml | 47 ++++++++++++++++++++++++++++++++++++++++ README.md | 7 +++--- 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..9c47cf4e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +# This workflow runs continuous CI across different versions of ruby on all branches and pull requests to develop. + +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the develop branch + push: + branches: [ '**' ] + pull_request: + branches: [ develop ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + tests: + name: Ruby ${{ matrix.ruby }} + if: "contains(github.event.commits[0].message, '[ci skip]') == false" + runs-on: ubuntu-latest + env: + CI: true + ALLOW_FAILURES: false # ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'jruby' }} + strategy: + fail-fast: false + matrix: + ruby: + - 2.4 + - 2.5 + - 2.6 + - 2.7 + - ruby-head + - jruby + steps: + - name: Clone repository + uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + - name: Run tests + run: bundle exec rspec spec || $ALLOW_FAILURES + diff --git a/README.md b/README.md index 346f426e..289c67cf 100755 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ [JSON-LD][] reader/writer for [RDF.rb][RDF.rb] and fully conforming [JSON-LD API][] processor. Additionally this gem implements [JSON-LD Framing][]. -[![Gem Version](https://badge.fury.io/rb/json-ld.png)](https://badge.fury.io/rb/json-ld) -[![Build Status](https://secure.travis-ci.org/ruby-rdf/json-ld.png?branch=master)](https://travis-ci.org/ruby-rdf/json-ld) -[![Coverage Status](https://coveralls.io/repos/ruby-rdf/json-ld/badge.svg)](https://coveralls.io/r/ruby-rdf/json-ld) +![Gem Version](https://badge.fury.io/rb/json-ld.png)] +![Build Status](https://secure.travis-ci.org/ruby-rdf/json-ld.png?branch=master) +![Coverage Status](https://coveralls.io/repos/ruby-rdf/json-ld/badge.svg) +[![Gitter chat](https://badges.gitter.im/ruby-rdf.png)](https://gitter.im/gitterHQ/gitter) ## Features From f68625d9c1b858b5fb54a93982fc460b75495173 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 19 Dec 2020 17:20:40 -0800 Subject: [PATCH 09/11] Failures only for ruby-head (although, there continue to be intermittent failures). --- .github/workflows/ci.yml | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c47cf4e..978be66b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest env: CI: true - ALLOW_FAILURES: false # ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'jruby' }} + ALLOW_FAILURES: false ${{ endsWith(matrix.ruby, 'head') }} strategy: fail-fast: false matrix: diff --git a/README.md b/README.md index 289c67cf..cb522c7c 100755 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [JSON-LD][] reader/writer for [RDF.rb][RDF.rb] and fully conforming [JSON-LD API][] processor. Additionally this gem implements [JSON-LD Framing][]. -![Gem Version](https://badge.fury.io/rb/json-ld.png)] -![Build Status](https://secure.travis-ci.org/ruby-rdf/json-ld.png?branch=master) -![Coverage Status](https://coveralls.io/repos/ruby-rdf/json-ld/badge.svg) +[![Gem Version](https://badge.fury.io/rb/json-ld.png)](https://rubygems.org/gems/json-ld) +[![Build Status](https://secure.travis-ci.org/ruby-rdf/json-ld.png?branch=develop)](https://github.com/ruby-rdf/json-ld/actions?query=workflow%3ACI) +[![Coverage Status](https://coveralls.io/repos/ruby-rdf/json-ld/badge.svg)](https://coveralls.io/github/ruby-rdf/json-ld) [![Gitter chat](https://badges.gitter.im/ruby-rdf.png)](https://gitter.im/gitterHQ/gitter) ## Features From 38b57d0d98ffe86a580da35b13d386f15dd8b8bb Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 23 Dec 2020 16:03:57 -0800 Subject: [PATCH 10/11] Don't CI on ruby-head because of net-http-persistent. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 978be66b..f3768924 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: - 2.5 - 2.6 - 2.7 - - ruby-head + # - ruby-head # net-http-persistent - jruby steps: - name: Clone repository From 46588763a740287e1c64efafee3dad7681f5fdcc Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 23 Dec 2020 16:05:40 -0800 Subject: [PATCH 11/11] Version 3.1.6. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3ad0595a..9cec7165 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.5 +3.1.6