Skip to content

Commit

Permalink
Finish 3.1.6
Browse files Browse the repository at this point in the history
  • Loading branch information
gkellogg committed Dec 24, 2020
2 parents cdece75 + 4658876 commit 4619258
Show file tree
Hide file tree
Showing 20 changed files with 837 additions and 33 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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') }}
strategy:
fail-fast: false
matrix:
ruby:
- 2.4
- 2.5
- 2.6
- 2.7
# - ruby-head # net-http-persistent
- 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

62 changes: 59 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)](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

Expand All @@ -14,6 +15,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*][JSON-LD*].

[Implementation Report](file.earl.html)

Expand All @@ -35,6 +37,59 @@ 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*][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`.

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
}

**Note: This feature is subject to change or elimination as the standards process progresses.**

#### 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 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: true) {|reader| graph << reader}
end
graph.count #=> 1

## Examples

```ruby
Expand Down Expand Up @@ -568,6 +623,7 @@ see <https://unlicense.org/> 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
[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"
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.1.5
3.1.6
2 changes: 1 addition & 1 deletion etc/doap.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion etc/doap.nt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#implements> <https://www.w3.org/TR/json-ld11-api/> .
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#implements> <https://www.w3.org/TR/json-ld11-framing/> .
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#implements> <https://www.w3.org/TR/json-ld11/> .
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#license> <https://unlicense.org/> .
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#license> <https://unlicense.org/1.0/> .
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#maintainer> <https://greggkellogg.net/foaf#me> .
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#name> "JSON::LD" .
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#programming-language> "Ruby" .
Expand Down
2 changes: 1 addition & 1 deletion etc/doap.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
doap:implements <https://www.w3.org/TR/json-ld11/>,
<https://www.w3.org/TR/json-ld11-api/>,
<https://www.w3.org/TR/json-ld11-framing/>;
doap:license <https://unlicense.org/>;
doap:license <https://unlicense.org/1.0/>;
doap:maintainer <https://greggkellogg.net/foaf#me>;
doap:name "JSON::LD"^^xsd:string;
doap:programming-language "Ruby";
Expand Down
2 changes: 1 addition & 1 deletion etc/earl-stream.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
doap:implements <https://www.w3.org/TR/json-ld11/>,
<https://www.w3.org/TR/json-ld11-api/>,
<https://www.w3.org/TR/json-ld11-framing/>;
doap:license <https://unlicense.org/>;
doap:license <https://unlicense.org/1.0/>;
doap:maintainer <https://greggkellogg.net/foaf#me>;
doap:name "JSON::LD"^^xsd:string;
doap:programming-language "Ruby"^^xsd:string;
Expand Down
2 changes: 1 addition & 1 deletion etc/earl.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
doap:implements <https://www.w3.org/TR/json-ld11/>,
<https://www.w3.org/TR/json-ld11-api/>,
<https://www.w3.org/TR/json-ld11-framing/>;
doap:license <https://unlicense.org/>;
doap:license <https://unlicense.org/1.0/>;
doap:maintainer <https://greggkellogg.net/foaf#me>;
doap:name "JSON::LD"^^xsd:string;
doap:programming-language "Ruby"^^xsd:string;
Expand Down
12 changes: 12 additions & 0 deletions example-files/bob-star.jsonld
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions lib/json/ld.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions lib/json/ld/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +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 [Boolean] rdfstar (false)
# support parsing JSON-LD* statement resources.
# @option options [Boolean] :rename_bnodes (true)
# Rename bnodes as part of expansion, or keep them the same.
# @option options [Boolean] :unique_bnodes (false)
Expand Down
23 changes: 17 additions & 6 deletions lib/json/ld/expand.rb
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,23 @@ 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)
statements = to_enum(:item_to_rdf, rei_node)
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}"
Expand Down
11 changes: 10 additions & 1 deletion lib/json/ld/format.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
48 changes: 36 additions & 12 deletions lib/json/ld/from_rdf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -33,38 +32,37 @@ 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] ||= {}
compound_literal_subjects[name] ||= {}

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)

Expand Down Expand Up @@ -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
14 changes: 9 additions & 5 deletions lib/json/ld/to_rdf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -76,11 +78,13 @@ 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)
to_enum(:item_to_rdf, item['@id']).to_a.first
end

#log_debug("item_to_rdf") {"subject: #{subject.to_ntriples rescue 'malformed rdf'}"}
Expand Down
2 changes: 2 additions & 0 deletions script/parse
Original file line number Diff line number Diff line change
Expand Up @@ -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::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"],
Expand Down Expand Up @@ -156,6 +157,7 @@ opts.each do |opt, arg|
when '--quiet'
options[:quiet] = true
logger.level = Logger::FATAL
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
Expand Down

0 comments on commit 4619258

Please sign in to comment.