lib/json/ld/api.rb in json-ld-3.3.1 vs lib/json/ld/api.rb in json-ld-3.3.2
- old
+ new
@@ -288,12 +288,11 @@
# 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, serializer: nil, **options)
flattened = []
options = {
- compactToRelative: true,
- extractAllScripts: true
+ compactToRelative: true
}.merge(options)
# Expand input to simplify processing
expanded_input = if expanded
input
@@ -516,10 +515,12 @@
# The JSON-LD object to process when outputting statements.
# @param [Boolean] expanded (false) Input is already expanded
# @option options (see #initialize)
# @option options [Boolean] :produceGeneralizedRdf (false)
# If true, output will include statements having blank node predicates, otherwise they are dropped.
+ # @option options [Boolean] :extractAllScripts (true)
+ # If set, when given an HTML input without a fragment identifier, extracts all `script` elements with type `application/ld+json` into an array during expansion.
# @raise [JsonLdError]
# @yield statement
# @yieldparam [RDF::Statement] statement
# @return [RDF::Enumerable] set of statements, unless a block is given.
def self.toRdf(input, expanded: false, **options)
@@ -636,11 +637,11 @@
# Add any request profile
options[:headers]['Accept'] =
options[:headers]['Accept'].sub('application/ld+json,',
"application/ld+json;profile=#{requestProfile}, application/ld+json;q=0.9,")
end
- documentLoader.call(url, **options) do |remote_doc|
+ documentLoader.call(url, extractAllScripts: extractAllScripts, **options) do |remote_doc|
case remote_doc
when RDF::Util::File::RemoteDocument
# Convert to RemoteDocument
context_url = if remote_doc.content_type != 'application/ld+json' &&
(remote_doc.content_type == 'application/json' ||
@@ -757,10 +758,32 @@
alias toRDF toRdf
alias fromRDF fromRdf
end
##
+ # Hash of recognized script types and the loaders that decode them
+ # into a hash or array of hashes.
+ #
+ # @return Hash{type, Proc}
+ SCRIPT_LOADERS = {
+ 'application/ld+json' => ->(content, url:, **options) do
+ validate_input(content, url: url) if options[:validate]
+ mj_opts = options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
+ MultiJson.load(content, **mj_opts)
+ end
+ }
+
+ ##
+ # Adds a loader for some specific content type
+ #
+ # @param [String] type
+ # @param [Proc] loader
+ def self.add_script_loader(type, loader)
+ SCRIPT_LOADERS[type] = loader
+ end
+
+ ##
# Load one or more script tags from an HTML source.
# Unescapes and uncomments input, returns the internal representation
# Yields document base
# @param [String] input
# @param [String] url Original URL
@@ -810,50 +833,55 @@
id = CGI.unescape(url.fragment)
# Find script with an ID based on that fragment.
element = input.at_xpath("//script[@id='#{id}']")
raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found with id=#{id}" unless element
- unless element.attributes['type'].to_s.start_with?('application/ld+json')
+ script_type = SCRIPT_LOADERS.keys.detect {|type| element.attributes['type'].to_s.start_with?(type)}
+ unless script_type
raise JSON::LD::JsonLdError::LoadingDocumentFailed,
"Script tag has type=#{element.attributes['type']}"
end
- content = element.inner_html
- validate_input(content, url: url) if options[:validate]
- mj_opts = options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
- MultiJson.load(content, **mj_opts)
+ loader = SCRIPT_LOADERS[script_type]
+ loader.call(element.inner_html, url: url, **options)
elsif extractAllScripts
res = []
- elements = if profile
- es = input.xpath("//script[starts-with(@type, 'application/ld+json;profile=#{profile}')]")
- # If no profile script, just take a single script without profile
- es = [input.at_xpath("//script[starts-with(@type, 'application/ld+json')]")].compact if es.empty?
- es
- else
- input.xpath("//script[starts-with(@type, 'application/ld+json')]")
- end
- elements.each do |element|
- content = element.inner_html
- validate_input(content, url: url) if options[:validate]
- 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)
- res.concat(r)
+
+ SCRIPT_LOADERS.each do |type, loader|
+ elements = if profile
+ es = input.xpath("//script[starts-with(@type, '#{type};profile=#{profile}')]")
+ # If no profile script, just take a single script without profile
+ es = [input.at_xpath("//script[starts-with(@type, '#{type}')]")].compact if es.empty?
+ es
+ else
+ input.xpath("//script[starts-with(@type, '#{type}')]")
end
+ elements.each do |element|
+ content = element.inner_html
+ r = loader.call(content, url: url, extractAllScripts: true, **options)
+ if r.is_a?(Hash)
+ res << r
+ elsif r.is_a?(Array)
+ res.concat(r)
+ end
+ end
end
res
else
- # Find the first script with type application/ld+json.
- element = input.at_xpath("//script[starts-with(@type, 'application/ld+json;profile=#{profile}')]") if profile
- element ||= input.at_xpath("//script[starts-with(@type, 'application/ld+json')]")
- raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found" unless element
+ # Find the first script with a known type
+ script_type, element = nil, nil
+ SCRIPT_LOADERS.keys.each do |type|
+ next if script_type # already found the type
+ element = input.at_xpath("//script[starts-with(@type, '#{type};profile=#{profile}')]") if profile
+ element ||= input.at_xpath("//script[starts-with(@type, '#{type}')]")
+ script_type = type if element
+ end
+ unless script_type
+ raise JSON::LD::JsonLdError::LoadingDocumentFailed, "No script tag found" unless element
+ end
content = element.inner_html
- validate_input(content, url: url) if options[:validate]
- mj_opts = options.keep_if { |k, v| k != :adapter || MUTLI_JSON_ADAPTERS.include?(v) }
- MultiJson.load(content, **mj_opts)
+ SCRIPT_LOADERS[script_type].call(content, url: url, **options)
end
rescue MultiJson::ParseError => e
raise JSON::LD::JsonLdError::InvalidScriptElement, e.message
end