Skip to content

Commit 4619258

Browse files
committed
Finish 3.1.6
2 parents cdece75 + 4658876 commit 4619258

20 files changed

+837
-33
lines changed

Diff for: .github/workflows/ci.yml

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# This workflow runs continuous CI across different versions of ruby on all branches and pull requests to develop.
2+
3+
name: CI
4+
5+
# Controls when the action will run.
6+
on:
7+
# Triggers the workflow on push or pull request events but only for the develop branch
8+
push:
9+
branches: [ '**' ]
10+
pull_request:
11+
branches: [ develop ]
12+
13+
# Allows you to run this workflow manually from the Actions tab
14+
workflow_dispatch:
15+
16+
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
17+
jobs:
18+
# This workflow contains a single job called "build"
19+
tests:
20+
name: Ruby ${{ matrix.ruby }}
21+
if: "contains(github.event.commits[0].message, '[ci skip]') == false"
22+
runs-on: ubuntu-latest
23+
env:
24+
CI: true
25+
ALLOW_FAILURES: false ${{ endsWith(matrix.ruby, 'head') }}
26+
strategy:
27+
fail-fast: false
28+
matrix:
29+
ruby:
30+
- 2.4
31+
- 2.5
32+
- 2.6
33+
- 2.7
34+
# - ruby-head # net-http-persistent
35+
- jruby
36+
steps:
37+
- name: Clone repository
38+
uses: actions/checkout@v2
39+
- name: Set up Ruby
40+
uses: ruby/setup-ruby@v1
41+
with:
42+
ruby-version: ${{ matrix.ruby }}
43+
- name: Install dependencies
44+
run: bundle install --jobs 4 --retry 3
45+
- name: Run tests
46+
run: bundle exec rspec spec || $ALLOW_FAILURES
47+

Diff for: README.md

+59-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
[JSON-LD][] reader/writer for [RDF.rb][RDF.rb] and fully conforming [JSON-LD API][] processor. Additionally this gem implements [JSON-LD Framing][].
44

5-
[![Gem Version](https://badge.fury.io/rb/json-ld.png)](https://badge.fury.io/rb/json-ld)
6-
[![Build Status](https://secure.travis-ci.org/ruby-rdf/json-ld.png?branch=master)](https://travis-ci.org/ruby-rdf/json-ld)
7-
[![Coverage Status](https://coveralls.io/repos/ruby-rdf/json-ld/badge.svg)](https://coveralls.io/r/ruby-rdf/json-ld)
5+
[![Gem Version](https://badge.fury.io/rb/json-ld.png)](https://rubygems.org/gems/json-ld)
6+
[![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)
7+
[![Coverage Status](https://coveralls.io/repos/ruby-rdf/json-ld/badge.svg)](https://coveralls.io/github/ruby-rdf/json-ld)
8+
[![Gitter chat](https://badges.gitter.im/ruby-rdf.png)](https://gitter.im/gitterHQ/gitter)
89

910
## Features
1011

@@ -14,6 +15,7 @@ JSON::LD can now be used to create a _context_ from an RDFS/OWL definition, and
1415

1516
* If the [jsonlint][] gem is installed, it will be used when validating an input document.
1617
* If available, uses [Nokogiri][] and/or [Nokogumbo][] for parsing HTML, falls back to REXML otherwise.
18+
* Provisional support for [JSON-LD*][JSON-LD*].
1719

1820
[Implementation Report](file.earl.html)
1921

@@ -35,6 +37,59 @@ The order of triples retrieved from the `RDF::Enumerable` dataset determines the
3537
### MultiJson parser
3638
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.
3739

40+
### JSON-LD* (RDFStar)
41+
42+
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*].
43+
44+
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`.
45+
46+
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).
47+
48+
{
49+
"@id": {
50+
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
51+
"@index": "ignored",
52+
"@id": "bob",
53+
"foaf:age" 23
54+
},
55+
"ex:certainty": 0.9
56+
}
57+
58+
**Note: This feature is subject to change or elimination as the standards process progresses.**
59+
60+
#### Serializing a Graph containing embedded statements
61+
62+
require 'json-ld'
63+
statement = RDF::Statement(RDF::URI('bob'), RDF::Vocab::FOAF.age, RDF::Literal(23))
64+
graph = RDF::Graph.new << [statement, RDF::URI("ex:certainty"), RDF::Literal(0.9)]
65+
graph.dump(:jsonld, validate: false, standard_prefixes: true)
66+
# => {"@id": {"@id": "bob", "foaf:age" 23}, "ex:certainty": 0.9}
67+
68+
Alternatively, using the {JSON::LD::API.fromRdf} method:
69+
70+
JSON::LD::API::fromRdf(graph)
71+
# => {"@id": {"@id": "bob", "foaf:age" 23}, "ex:certainty": 0.9}
72+
73+
#### Reading a Graph containing embedded statements
74+
75+
By default, {JSON::LD::API.toRdf} (and {JSON::LD::Reader}) will reject a document containing a subject resource.
76+
77+
jsonld = %({
78+
"@id": {
79+
"@id": "bob", "foaf:age" 23
80+
},
81+
"ex:certainty": 0.9
82+
})
83+
graph = RDF::Graph.new << JSON::LD::API.toRdf(input)
84+
# => JSON::LD::JsonLdError::InvalidIdValue
85+
86+
{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.
87+
88+
graph = RDF::Graph.new do |graph|
89+
JSON::LD::Reader.new(jsonld, rdfstar: true) {|reader| graph << reader}
90+
end
91+
graph.count #=> 1
92+
3893
## Examples
3994

4095
```ruby
@@ -568,6 +623,7 @@ see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
568623
[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md
569624
[PDD]: https://unlicense.org/#unlicensing-contributions
570625
[RDF.rb]: https://rubygems.org/gems/rdf
626+
[JSON-LD*]: https://json-ld.github.io/json-ld-star/
571627
[Rack::LinkedData]: https://rubygems.org/gems/rack-linkeddata
572628
[Backports]: https://rubygems.org/gems/backports
573629
[JSON-LD]: https://www.w3.org/TR/json-ld11/ "JSON-LD 1.1"

Diff for: VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.1.5
1+
3.1.6

Diff for: etc/doap.jsonld

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"@type": "doap:Project",
2727
"doap:name": "JSON::LD",
2828
"doap:homepage": "https://github.com/ruby-rdf/json-ld/",
29-
"doap:license": "https://unlicense.org/",
29+
"doap:license": "https://unlicense.org/1.0/",
3030
"doap:shortdesc": "JSON-LD support for RDF.rb.",
3131
"doap:description": "RDF.rb extension for parsing/serializing JSON-LD data.",
3232
"doap:created": "2011-05-07",

Diff for: etc/doap.nt

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#implements> <https://www.w3.org/TR/json-ld11-api/> .
1515
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#implements> <https://www.w3.org/TR/json-ld11-framing/> .
1616
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#implements> <https://www.w3.org/TR/json-ld11/> .
17-
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#license> <https://unlicense.org/> .
17+
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#license> <https://unlicense.org/1.0/> .
1818
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#maintainer> <https://greggkellogg.net/foaf#me> .
1919
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#name> "JSON::LD" .
2020
<https://rubygems.org/gems/json-ld> <http://usefulinc.com/ns/doap#programming-language> "Ruby" .

Diff for: etc/doap.ttl

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
doap:implements <https://www.w3.org/TR/json-ld11/>,
2020
<https://www.w3.org/TR/json-ld11-api/>,
2121
<https://www.w3.org/TR/json-ld11-framing/>;
22-
doap:license <https://unlicense.org/>;
22+
doap:license <https://unlicense.org/1.0/>;
2323
doap:maintainer <https://greggkellogg.net/foaf#me>;
2424
doap:name "JSON::LD"^^xsd:string;
2525
doap:programming-language "Ruby";

Diff for: etc/earl-stream.ttl

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
doap:implements <https://www.w3.org/TR/json-ld11/>,
1919
<https://www.w3.org/TR/json-ld11-api/>,
2020
<https://www.w3.org/TR/json-ld11-framing/>;
21-
doap:license <https://unlicense.org/>;
21+
doap:license <https://unlicense.org/1.0/>;
2222
doap:maintainer <https://greggkellogg.net/foaf#me>;
2323
doap:name "JSON::LD"^^xsd:string;
2424
doap:programming-language "Ruby"^^xsd:string;

Diff for: etc/earl.ttl

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
doap:implements <https://www.w3.org/TR/json-ld11/>,
1919
<https://www.w3.org/TR/json-ld11-api/>,
2020
<https://www.w3.org/TR/json-ld11-framing/>;
21-
doap:license <https://unlicense.org/>;
21+
doap:license <https://unlicense.org/1.0/>;
2222
doap:maintainer <https://greggkellogg.net/foaf#me>;
2323
doap:name "JSON::LD"^^xsd:string;
2424
doap:programming-language "Ruby"^^xsd:string;

Diff for: example-files/bob-star.jsonld

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"@context": {
3+
"@base": "http://example.org/",
4+
"ex": "http://example.org/",
5+
"foaf": "http://xmlns.com/foaf/0.1/"
6+
},
7+
"@id": {
8+
"@id": "bob",
9+
"foaf:age": 23
10+
},
11+
"ex:certainty": 0.8
12+
}

Diff for: lib/json/ld.rb

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class InvalidLocalContext < JsonLdError; @code = "invalid local context"; end
137137
class InvalidNestValue < JsonLdError; @code = "invalid @nest value"; end
138138
class InvalidPrefixValue < JsonLdError; @code = "invalid @prefix value"; end
139139
class InvalidPropagateValue < JsonLdError; @code = "invalid @propagate value"; end
140+
class InvalidEmbeddedNode < JsonLdError; @code = "invalid reified node"; end
140141
class InvalidRemoteContext < JsonLdError; @code = "invalid remote context"; end
141142
class InvalidReverseProperty < JsonLdError; @code = "invalid reverse property"; end
142143
class InvalidReversePropertyMap < JsonLdError; @code = "invalid reverse property map"; end

Diff for: lib/json/ld/api.rb

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class API
8989
# @option options [String] :processingMode
9090
# Processing mode, json-ld-1.0 or json-ld-1.1.
9191
# 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`.
92+
# @option options [Boolean] rdfstar (false)
93+
# support parsing JSON-LD* statement resources.
9294
# @option options [Boolean] :rename_bnodes (true)
9395
# Rename bnodes as part of expansion, or keep them the same.
9496
# @option options [Boolean] :unique_bnodes (false)

Diff for: lib/json/ld/expand.rb

+17-6
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,23 @@ def expand_object(input, active_property, context, output_object,
273273
context.expand_iri(v, as_string: true, base: @options[:base], documentRelative: true)
274274
end
275275
when Hash
276-
raise JsonLdError::InvalidIdValue,
277-
"value of @id must be a string unless framing: #{value.inspect}" unless framing
278-
raise JsonLdError::InvalidTypeValue,
279-
"value of @id must be a an empty object for framing: #{value.inspect}" unless
280-
value.empty?
281-
[{}]
276+
if framing
277+
raise JsonLdError::InvalidTypeValue,
278+
"value of @id must be a an empty object for framing: #{value.inspect}" unless
279+
value.empty?
280+
[{}]
281+
elsif @options[:rdfstar]
282+
# Result must have just a single statement
283+
rei_node = expand(value, active_property, context, log_depth: log_depth.to_i + 1)
284+
statements = to_enum(:item_to_rdf, rei_node)
285+
raise JsonLdError::InvalidEmbeddedNode,
286+
"Embedded node with #{statements.size} statements" unless
287+
statements.count == 1
288+
rei_node
289+
else
290+
raise JsonLdError::InvalidIdValue,
291+
"value of @id must be a string unless framing: #{value.inspect}" unless framing
292+
end
282293
else
283294
raise JsonLdError::InvalidIdValue,
284295
"value of @id must be a string or hash if framing: #{value.inspect}"

Diff for: lib/json/ld/format.rb

+10-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,16 @@ def self.cli_commands
165165
end
166166
end
167167
end
168-
end
168+
end,
169+
options: [
170+
RDF::CLI::Option.new(
171+
symbol: :context,
172+
datatype: RDF::URI,
173+
control: :url2,
174+
use: :required,
175+
on: ["--context CONTEXT"],
176+
description: "Context to use when compacting.") {|arg| RDF::URI(arg)},
177+
]
169178
},
170179
frame: {
171180
description: "Frame JSON-LD or parsed RDF",

Diff for: lib/json/ld/from_rdf.rb

+36-12
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false)
2222
referenced_once = {}
2323

2424
value = nil
25-
ec = @context
2625

2726
# Create an entry for compound-literal node detection
2827
compound_literal_subjects = {}
@@ -33,38 +32,37 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false)
3332
dataset.each do |statement|
3433
#log_debug("statement") { statement.to_nquads.chomp}
3534

36-
name = statement.graph_name ? ec.expand_iri(statement.graph_name, base: @options[:base]).to_s : '@default'
35+
name = statement.graph_name ? @context.expand_iri(statement.graph_name, base: @options[:base]).to_s : '@default'
3736

3837
# Create a graph entry as needed
3938
node_map = graph_map[name] ||= {}
4039
compound_literal_subjects[name] ||= {}
4140

4241
default_graph[name] ||= {'@id' => name} unless name == '@default'
4342

44-
subject = ec.expand_iri(statement.subject, as_string: true, base: @options[:base])
45-
node = node_map[subject] ||= {'@id' => subject}
43+
subject = statement.subject.to_s
44+
node = node_map[subject] ||= resource_representation(statement.subject,useNativeTypes)
4645

4746
# If predicate is rdf:datatype, note subject in compound literal subjects map
4847
if @options[:rdfDirection] == 'compound-literal' && statement.predicate == RDF.to_uri + 'direction'
4948
compound_literal_subjects[name][subject] ||= true
5049
end
5150

52-
# 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.
53-
node_map[statement.object.to_s] ||= {'@id' => statement.object.to_s} unless
54-
statement.object.literal?
51+
# 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.
52+
unless statement.object.literal?
53+
node_map[statement.object.to_s] ||=
54+
resource_representation(statement.object, useNativeTypes)
55+
end
5556

5657
# 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.
58+
# XXX JSON-LD* does not support embedded value of @type
5759
if statement.predicate == RDF.type && statement.object.resource? && !useRdfType
5860
merge_value(node, '@type', statement.object.to_s)
5961
next
6062
end
6163

6264
# Set value to the result of using the RDF to Object Conversion algorithm, passing object, rdfDirection, and use native types.
63-
value = ec.expand_value(nil,
64-
statement.object,
65-
rdfDirection: @options[:rdfDirection],
66-
useNativeTypes: useNativeTypes,
67-
base: @options[:base])
65+
value = resource_representation(statement.object, useNativeTypes)
6866

6967
merge_value(node, statement.predicate.to_s, value)
7068

@@ -162,5 +160,31 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false)
162160
#log_debug("fromRdf") {result.to_json(JSON_STATE) rescue 'malformed json'}
163161
result
164162
end
163+
164+
private
165+
def resource_representation(resource, useNativeTypes)
166+
case resource
167+
when RDF::Statement
168+
# Note, if either subject or object are a BNode which is used elsewhere,
169+
# this might not work will with the BNode accounting from above.
170+
rep = {'@id' => resource_representation(resource.subject, false)}
171+
if resource.predicate == RDF.type
172+
rep['@id'].merge!('@type' => resource.object.to_s)
173+
else
174+
rep['@id'].merge!(
175+
resource.predicate.to_s =>
176+
as_array(resource_representation(resource.object, useNativeTypes)))
177+
end
178+
rep
179+
when RDF::Literal
180+
@context.expand_value(nil,
181+
resource,
182+
rdfDirection: @options[:rdfDirection],
183+
useNativeTypes: useNativeTypes,
184+
base: @options[:base])
185+
else
186+
{'@id' => resource.to_s}
187+
end
188+
end
165189
end
166190
end

Diff for: lib/json/ld/to_rdf.rb

+9-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ module ToRDF
1616
# @return RDF::Resource the subject of this item
1717
def item_to_rdf(item, graph_name: nil, &block)
1818
# Just return value object as Term
19+
return unless item
20+
1921
if value?(item)
2022
value, datatype = item.fetch('@value'), item.fetch('@type', nil)
2123

@@ -76,11 +78,13 @@ def item_to_rdf(item, graph_name: nil, &block)
7678
return parse_list(item['@list'], graph_name: graph_name, &block)
7779
end
7880

79-
# Skip if '@id' is nil
80-
subject = if item.has_key?('@id')
81-
item['@id'].nil? ? nil : as_resource(item['@id'])
82-
else
83-
node
81+
subject = case item['@id']
82+
when nil then node
83+
when String then as_resource(item['@id'])
84+
when Object
85+
# Embedded statement
86+
# (No error checking, as this is done in expansion)
87+
to_enum(:item_to_rdf, item['@id']).to_a.first
8488
end
8589

8690
#log_debug("item_to_rdf") {"subject: #{subject.to_ntriples rescue 'malformed rdf'}"}

Diff for: script/parse

+2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ OPT_ARGS = [
116116
["--output", "-o", GetoptLong::REQUIRED_ARGUMENT, "Where to store output (default STDOUT)"],
117117
["--profile", GetoptLong::NO_ARGUMENT, "Run profiler with output to doc/profiles/"],
118118
["--quiet", GetoptLong::NO_ARGUMENT, "Reduce output"],
119+
["--rdfstar", GetoptLong::NO_ARGUMENT, "RDF* mode"],
119120
["--stream", GetoptLong::NO_ARGUMENT, "Streaming reader/writer"],
120121
["--uri", GetoptLong::REQUIRED_ARGUMENT, "Run with argument value as base"],
121122
["--validate", GetoptLong::NO_ARGUMENT, "Validate input"],
@@ -156,6 +157,7 @@ opts.each do |opt, arg|
156157
when '--quiet'
157158
options[:quiet] = true
158159
logger.level = Logger::FATAL
160+
when '--rdfstar' then parser_options[:rdfstar] = true
159161
when '--stream' then parser_options[:stream] = true
160162
when '--uri' then parser_options[:base] = arg
161163
when '--validate' then parser_options[:validate] = true

0 commit comments

Comments
 (0)