Skip to content

Commit

Permalink
Finish 3.2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
gkellogg committed Aug 4, 2022
2 parents 8ee7317 + 6b73ffe commit 690135f
Show file tree
Hide file tree
Showing 53 changed files with 168 additions and 109 deletions.
9 changes: 4 additions & 5 deletions README.md
Expand Up @@ -32,10 +32,10 @@ This gem also implements an optimized streaming writer used for generating JSON-
* Each statement written as a separate node in expanded/flattened form.
* `RDF List`s are written as separate nodes using `rdf:first` and `rdf:rest` properties.

The order of triples retrieved from the `RDF::Enumerable` dataset determines the way that JSON-LD node objects are written; for best results, statements should be ordered by _graph name_, _subect_, _predicate_ and _object_.
The order of triples retrieved from the `RDF::Enumerable` dataset determines the way that JSON-LD node objects are written; for best results, statements should be ordered by _graph name_, _subject_, _predicate_ and _object_.

### 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.
The [MultiJson](https://rubygems.org/gems/multi_json) gem is used for parsing and serializing JSON; this defaults to the native JSON parser/serializer, 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. Additionally, a custom serialilzer may be specified by passing the `:serializer` option to {JSON::LD::Writer} or methods of {JSON::LD::API}. See [MultiJson](https://rubygems.org/gems/multi_json) for more information.

### JSON-LD-star (RDFStar)

Expand Down Expand Up @@ -624,8 +624,7 @@ To get a local working copy of the development repository, do:
which you will be asked to agree to on the first commit to a repo within the organization.
Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization.

License
-------
## License

This is free and unencumbered public domain software. For more information,
see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
Expand All @@ -641,7 +640,7 @@ see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
[Backports]: https://rubygems.org/gems/backports
[JSON-LD]: https://www.w3.org/TR/json-ld11/ "JSON-LD 1.1"
[JSON-LD API]: https://www.w3.org/TR/json-ld11-api/ "JSON-LD 1.1 Processing Algorithms and API"
[JSON-LD Framing]: https://www.w3.org/TR/json-ld11-framing/ "JSON-LD Framing 1.1"
[JSON-LD Framing]: https://www.w3.org/TR/json-ld11-framing/ "JSON-LD 1.1 Framing"
[Promises]: https://dom.spec.whatwg.org/#promises
[jsonlint]: https://rubygems.org/gems/jsonlint
[Sinatra]: https://www.sinatrarb.com/
Expand Down
2 changes: 1 addition & 1 deletion VERSION
@@ -1 +1 @@
3.2.1
3.2.2
3 changes: 3 additions & 0 deletions lib/json/ld.rb
Expand Up @@ -46,6 +46,9 @@ module LD
# Default context when compacting without one being specified
DEFAULT_CONTEXT = "http://schema.org"

# Acceptable MultiJson adapters
MUTLI_JSON_ADAPTERS = %i(oj json_gem json_pure ok_json yajl nsjsonseerialization)

KEYWORDS = Set.new(%w(
@annotation
@base
Expand Down
69 changes: 53 additions & 16 deletions lib/json/ld/api.rb
Expand Up @@ -126,7 +126,8 @@ def initialize(input, context, **options, &block)

case remote_doc.document
when String
MultiJson.load(remote_doc.document, **options)
mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
MultiJson.load(remote_doc.document, **mj_opts)
else
# Already parsed
remote_doc.document
Expand Down Expand Up @@ -155,6 +156,9 @@ def initialize(input, context, **options, &block)
#
# @param [String, #read, Hash, Array] input
# The JSON-LD object to copy and perform the expansion upon.
# @param [Proc] serializer (nil)
# A Serializer method used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
# See {JSON::LD::API.serializer}.
# @param [Hash{Symbol => Object}] options
# @option options (see #initialize)
# @raise [JsonLdError]
Expand All @@ -167,7 +171,7 @@ def initialize(input, context, **options, &block)
# @return [Object, Array<Hash>]
# If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
# @see https://www.w3.org/TR/json-ld11-api/#expansion-algorithm
def self.expand(input, framing: false, **options, &block)
def self.expand(input, framing: false, serializer: nil, **options, &block)
result = doc_base = nil
API.new(input, options[:expandContext], **options) do
result = self.expand(self.value, nil, self.context,
Expand All @@ -180,6 +184,7 @@ def self.expand(input, framing: false, **options, &block)

# Finally, if element is a JSON object, it is wrapped into an array.
result = [result].compact unless result.is_a?(Array)
result = serializer.call(result, **options) if serializer

if block_given?
case block.arity
Expand All @@ -204,6 +209,9 @@ def self.expand(input, framing: false, **options, &block)
# The JSON-LD object to copy and perform the compaction upon.
# @param [String, #read, Hash, Array, JSON::LD::Context] context
# The base context to use when compacting the input.
# @param [Proc] serializer (nil)
# A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
# See {JSON::LD::API.serializer}.
# @param [Boolean] expanded (false) Input is already expanded
# @param [Hash{Symbol => Object}] options
# @option options (see #initialize)
Expand All @@ -215,7 +223,7 @@ def self.expand(input, framing: false, **options, &block)
# If a block is given, the result of evaluating the block is returned, otherwise, the compacted JSON-LD document
# @raise [JsonLdError]
# @see https://www.w3.org/TR/json-ld11-api/#compaction-algorithm
def self.compact(input, context, expanded: false, **options)
def self.compact(input, context, expanded: false, serializer: nil, **options)
result = nil
options = {compactToRelative: true}.merge(options)

Expand All @@ -238,6 +246,7 @@ def self.compact(input, context, expanded: false, **options)
end
result = ctx.merge(result) unless ctx.fetch('@context', {}).empty?
end
result = serializer.call(result, **options) if serializer
block_given? ? yield(result) : result
end

Expand All @@ -251,6 +260,9 @@ def self.compact(input, context, expanded: false, **options)
# @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context
# An optional external context to use additionally to the context embedded in input when expanding the input.
# @param [Boolean] expanded (false) Input is already expanded
# @param [Proc] serializer (nil)
# A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
# See {JSON::LD::API.serializer}.
# @param [Hash{Symbol => Object}] options
# @option options (see #initialize)
# @option options [Boolean] :createAnnotations
Expand All @@ -262,7 +274,7 @@ def self.compact(input, context, expanded: false, **options)
# @return [Object, Hash]
# If a block is given, the result of evaluating the block is returned, otherwise, the flattened JSON-LD document
# @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm
def self.flatten(input, context, expanded: false, **options)
def self.flatten(input, context, expanded: false, serializer: nil, **options)
flattened = []
options = {
compactToRelative: true,
Expand Down Expand Up @@ -318,6 +330,7 @@ def self.flatten(input, context, expanded: false, **options)
end
end

flattened = serializer.call(flattened, **options) if serializer
block_given? ? yield(flattened) : flattened
end

Expand Down Expand Up @@ -350,7 +363,7 @@ def self.flatten(input, context, expanded: false, **options)
# If a block is given, the result of evaluating the block is returned, otherwise, the framed JSON-LD document
# @raise [InvalidFrame]
# @see https://www.w3.org/TR/json-ld11-api/#framing-algorithm
def self.frame(input, frame, expanded: false, **options)
def self.frame(input, frame, expanded: false, serializer: nil, **options)
result = nil
options = {
base: (RDF::URI(input) if input.is_a?(String)),
Expand Down Expand Up @@ -379,7 +392,8 @@ def self.frame(input, frame, expanded: false, **options)
requestProfile: 'http://www.w3.org/ns/json-ld#frame',
**options)
if remote_doc.document.is_a?(String)
MultiJson.load(remote_doc.document)
mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
MultiJson.load(remote_doc.document, **mj_opts)
else
remote_doc.document
end
Expand Down Expand Up @@ -467,6 +481,7 @@ def self.frame(input, frame, expanded: false, **options)
result
end

result = serializer.call(result, **options) if serializer
block_given? ? yield(result) : result
end

Expand Down Expand Up @@ -528,18 +543,21 @@ def self.toRdf(input, expanded: false, **options, &block)
# The resulting `Array` is either returned or yielded, if a block is given.
#
# @param [RDF::Enumerable] input
# @param [Boolean] useRdfType (false)
# If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
# @param [Boolean] useNativeTypes (false) use native representations
# @param [Proc] serializer (nil)
# A Serializer instance used for generating the JSON serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to JSON externally via `#to_json`.
# See {JSON::LD::API.serializer}.
# @param [Hash{Symbol => Object}] options
# @option options (see #initialize)
# @option options [Boolean] :useRdfType (false)
# If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
# @option options [Boolean] :useNativeTypes (false) use native representations
# @yield jsonld
# @yieldparam [Hash] jsonld
# The JSON-LD document in expanded form
# @yieldreturn [Object] returned object
# @return [Object, Hash]
# If a block is given, the result of evaluating the block is returned, otherwise, the expanded JSON-LD document
def self.fromRdf(input, useRdfType: false, useNativeTypes: false, **options, &block)
def self.fromRdf(input, useRdfType: false, useNativeTypes: false, serializer: nil, **options, &block)
result = nil

API.new(nil, nil, **options) do
Expand All @@ -548,6 +566,7 @@ def self.fromRdf(input, useRdfType: false, useNativeTypes: false, **options, &bl
useNativeTypes: useNativeTypes)
end

result = serializer.call(result, **options) if serializer
block_given? ? yield(result) : result
end

Expand Down Expand Up @@ -648,7 +667,8 @@ def self.loadRemoteDocument(url,
end
else
validate_input(remote_doc.document, url: remote_doc.documentUrl) if validate
MultiJson.load(remote_doc.document, **options)
mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
MultiJson.load(remote_doc.document, **mj_opts)
end
end

Expand Down Expand Up @@ -682,8 +702,8 @@ def self.documentLoader(url, extractAllScripts: false, profile: nil, requestProf
base_uri ||= url.base_uri if url.respond_to?(:base_uri)
content_type = options[:content_type]
content_type ||= url.content_type if url.respond_to?(:content_type)
context_url = if url.respond_to?(:links) && url.links
(content_type == 'appliaction/json' || content_type.match?(%r(application/(^ld)+json)))
context_url = if url.respond_to?(:links) && url.links &&
(content_type == 'application/json' || content_type.match?(%r(application/(^ld)+json)))
link = url.links.find_link(LINK_REL_CONTEXT)
link.href if link
end
Expand Down Expand Up @@ -759,7 +779,8 @@ def self.load_html(input, url:,
raise JSON::LD::JsonLdError::LoadingDocumentFailed, "Script tag has type=#{element.attributes['type']}" unless element.attributes['type'].to_s.start_with?('application/ld+json')
content = element.inner_html
validate_input(content, url: url) if options[:validate]
MultiJson.load(content, **options)
mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
MultiJson.load(content, **mj_opts)
elsif extractAllScripts
res = []
elements = if profile
Expand All @@ -773,7 +794,8 @@ def self.load_html(input, url:,
elements.each do |element|
content = element.inner_html
validate_input(content, url: url) if options[:validate]
r = MultiJson.load(content, **options)
mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
r = MultiJson.load(content, **mj_opts)
if r.is_a?(Hash)
res << r
elsif r.is_a?(Array)
Expand All @@ -788,12 +810,27 @@ def self.load_html(input, url:,
raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found" unless element
content = element.inner_html
validate_input(content, url: url) if options[:validate]
MultiJson.load(content, **options)
mj_opts = options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
MultiJson.load(content, **mj_opts)
end
rescue MultiJson::ParseError => e
raise JSON::LD::JsonLdError::InvalidScriptElement, e.message
end

##
# The default serializer for serialzing Ruby Objects to JSON.
#
# Defaults to `MultiJson.dump`
#
# @param [Object] object
# @param [Array<Object>] args
# other arguments that may be passed for some specific implementation.
# @param [Hash<Symbol, Object>] options
# options passed from the invoking context.
def self.serializer(object, *args, **options)
MultiJson.dump(object, JSON_STATE)
end

##
# Validate JSON using JsonLint, if loaded
private
Expand Down
2 changes: 1 addition & 1 deletion lib/json/ld/reader.rb
Expand Up @@ -57,7 +57,7 @@ def self.options
end

##
# Initializes the RDF/JSON reader instance.
# Initializes the JSON-LD reader instance.
#
# @param [IO, File, String] input
# @param [Hash{Symbol => Object}] options
Expand Down
3 changes: 2 additions & 1 deletion lib/json/ld/streaming_reader.rb
Expand Up @@ -26,7 +26,8 @@ def stream_statement(&block)
unique_bnodes, rename_bnodes = @options[:unique_bnodes], @options.fetch(:rename_bnodes, true)
# FIXME: document loader doesn't stream
@base = RDF::URI(@options[:base] || base_uri)
value = MultiJson.load(@doc, **@options)
mj_opts = @options.keep_if {|k,v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v)}
value = MultiJson.load(@doc, mj_opts)
context_ref = @options[:expandContext]
#context_ref = @options.fetch(:expandContext, remote_doc.contextUrl)
context = Context.parse(context_ref, **@options)
Expand Down
2 changes: 1 addition & 1 deletion lib/json/ld/to_rdf.rb
Expand Up @@ -11,7 +11,7 @@ module ToRDF
##
# @param [Hash{String => Object}] item
# @param [RDF::Resource] graph_name
# @param [Boolean] emitted triples are quoted triples.
# @param [Boolean] quoted emitted triples are quoted triples.
# @yield statement
# @yieldparam [RDF::Statement] statement
# @return RDF::Resource the subject of this item
Expand Down
17 changes: 8 additions & 9 deletions lib/json/ld/writer.rb
Expand Up @@ -186,10 +186,6 @@ class << self
# @return [Boolean]
# @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
def accept?(accept_params)
# Profiles that aren't specific IANA relations represent the URL
# of a context or frame that may be subject to black- or white-listing
profile = accept_params[:profile].to_s.split(/\s+/)

if block_given?
yield(accept_params)
else
Expand Down Expand Up @@ -229,6 +225,8 @@ def default_context=(url); @default_context = url; end
# frame to use when serializing.
# @option options [Boolean] :unique_bnodes (false)
# Use unique bnode identifiers, defaults to using the identifier which the node was originall initialized with (if any).
# @option options [Proc] serializer (JSON::LD::API.serializer)
# A Serializer method used for generating the JSON serialization of the result.
# @option options [Boolean] :stream (false)
# Do not attempt to optimize graph presentation, suitable for streaming large graphs.
# @yield [writer] `self`
Expand All @@ -239,6 +237,7 @@ def default_context=(url); @default_context = url; end
def initialize(output = $stdout, **options, &block)
options[:base_uri] ||= options[:base] if options.key?(:base)
options[:base] ||= options[:base_uri] if options.key?(:base_uri)
@serializer = options.fetch(:serializer, JSON::LD::API.method(:serializer))
super do
@repo = RDF::Repository.new

Expand Down Expand Up @@ -300,7 +299,7 @@ def write_epilogue
else

log_debug("writer") { "serialize #{@repo.count} statements, #{@options.inspect}"}
result = API.fromRdf(@repo, **@options)
result = API.fromRdf(@repo, **@options.merge(serializer: nil))

# Some options may be indicated from accept parameters
profile = @options.fetch(:accept_params, {}).fetch(:profile, "").split(' ')
Expand All @@ -322,20 +321,20 @@ def write_epilogue

# Rename BNodes to uniquify them, if necessary
if options[:unique_bnodes]
result = API.flatten(result, context, **@options)
result = API.flatten(result, context, **@options.merge(serializer: nil))
end

if frame = @options[:frame]
# Perform framing, if given a frame
log_debug("writer") { "frame result"}
result = API.frame(result, frame, **@options)
result = API.frame(result, frame, **@options.merge(serializer: nil))
elsif context
# Perform compaction, if we have a context
log_debug("writer") { "compact result"}
result = API.compact(result, context, **@options)
result = API.compact(result, context, **@options.merge(serializer: nil))
end

@output.write(result.to_json(JSON_STATE))
@output.write(@serializer.call(result, **@options))
end

super
Expand Down

0 comments on commit 690135f

Please sign in to comment.