lib/json/ld/writer.rb in json-ld-3.0.2 vs lib/json/ld/writer.rb in json-ld-3.1.0

- old
+ new

@@ -1,8 +1,10 @@ # -*- encoding: utf-8 -*- # frozen_string_literal: true require 'json/ld/streaming_writer' +require 'link_header' + module JSON::LD ## # A JSON-LD parser in Ruby. # # Note that the natural interface is to write a whole graph at a time. @@ -48,12 +50,12 @@ # end # end # # Select the :expand option to output JSON-LD in expanded form # - # @see http://json-ld.org/spec/ED/20110507/ - # @see http://json-ld.org/spec/ED/20110507/#the-normalization-algorithm + # @see https://www.w3.org/TR/json-ld11-api/ + # @see https://www.w3.org/TR/json-ld11-api/#the-normalization-algorithm # @author [Gregg Kellogg](http://greggkellogg.net/) class Writer < RDF::Writer include StreamingWriter include Utils include RDF::Util::Logger @@ -92,22 +94,28 @@ control: :url2, on: ["--context CONTEXT"], description: "Context to use when compacting.") {|arg| RDF::URI(arg)}, RDF::CLI::Option.new( symbol: :embed, - datatype: %w(@always @last @never), - default: '@last', + datatype: %w(@always @once @never), + default: '@once', control: :select, on: ["--embed EMBED"], - description: "How to embed matched objects (@last).") {|arg| RDF::URI(arg)}, + description: "How to embed matched objects (@once).") {|arg| RDF::URI(arg)}, RDF::CLI::Option.new( symbol: :explicit, datatype: TrueClass, control: :checkbox, on: ["--[no-]explicit"], description: "Only include explicitly declared properties in output (false)") {|arg| arg}, RDF::CLI::Option.new( + symbol: :lowercaseLanguage, + datatype: TrueClass, + control: :checkbox, + on: ["--[no-]lowercase-language"], + description: "By default, language tags are left as is. To normalize to lowercase, set this option to `true`."), + RDF::CLI::Option.new( symbol: :omitDefault, datatype: TrueClass, control: :checkbox, on: ["--[no-]omitDefault"], description: "Omit missing properties from output (false)") {|arg| arg}, @@ -122,10 +130,17 @@ datatype: %w(json-ld-1.0 json-ld-1.1), control: :radio, on: ["--processingMode MODE", %w(json-ld-1.0 json-ld-1.1)], description: "Set Processing Mode (json-ld-1.0 or json-ld-1.1)"), RDF::CLI::Option.new( + symbol: :rdfDirection, + datatype: %w(i18n-datatype compound-literal), + default: 'null', + control: :select, + on: ["--rdf-direction DIR", %w(i18n-datatype compound-literal)], + description: "How to serialize literal direction (i18n-datatype compound-literal)") {|arg| RDF::URI(arg)}, + RDF::CLI::Option.new( symbol: :requireAll, datatype: TrueClass, default: true, control: :checkbox, on: ["--[no-]requireAll"], @@ -135,18 +150,59 @@ datatype: TrueClass, control: :checkbox, on: ["--[no-]stream"], description: "Do not attempt to optimize graph presentation, suitable for streaming large graphs.") {|arg| arg}, RDF::CLI::Option.new( + symbol: :useNativeTypes, + datatype: TrueClass, + control: :checkbox, + on: ["--[no-]use-native-types"], + description: "Use native JSON values in value objects.") {|arg| arg}, + RDF::CLI::Option.new( symbol: :useRdfType, datatype: TrueClass, control: :checkbox, on: ["--[no-]use-rdf-type"], description: "Treat `rdf:type` like a normal property instead of using `@type`.") {|arg| arg}, ] end + class << self + attr_reader :white_list + attr_reader :black_list + + ## + # Use parameters from accept-params to determine if the parameters are acceptable to invoke this writer. The `accept_params` will subsequently be provided to the writer instance. + # + # @param [Hash{Symbol => String}] accept_params + # @yield [accept_params] if a block is given, returns the result of evaluating that block + # @yieldparam [Hash{Symbol => String}] accept_params + # @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 + true + end + end + + ## + # Returns default context used for compacted profile without an explicit context URL + # @return [String] + def default_context; @default_context || JSON::LD::DEFAULT_CONTEXT; end + + ## + # Sets default context used for compacted profile without an explicit context URL + # @param [String] url + def default_context=(url); @default_context = url; end + end + ## # Initializes the RDF-LD writer instance. # # @param [IO, File] output # the output stream @@ -235,37 +291,42 @@ if @options[:stream] stream_epilogue else log_debug("writer") { "serialize #{@repo.count} statements, #{@options.inspect}"} - result = API.fromRdf(@repo, @options) + result = API.fromRdf(@repo, **@options) + # Some options may be indicated from accept parameters + profile = @options.fetch(:accept_params, {}).fetch(:profile, "").split(' ') + links = LinkHeader.parse(@options[:link]) + @options[:context] ||= links.find_link(['rel', JSON_LD_NS+"context"]).href rescue nil + @options[:context] ||= Writer.default_context if profile.include?(JSON_LD_NS+"compacted") + @options[:frame] ||= links.find_link(['rel', JSON_LD_NS+"frame"]).href rescue nil + # If we were provided a context, or prefixes, use them to compact the output - context = RDF::Util::File.open_file(@options[:context]) if @options[:context].is_a?(String) - context ||= @options[:context] + context = @options[:context] context ||= if @options[:prefixes] || @options[:language] || @options[:standard_prefixes] - ctx = Context.new(@options) + ctx = Context.new(**@options) ctx.language = @options[:language] if @options[:language] @options[:prefixes].each do |prefix, iri| ctx.set_mapping(prefix, iri) if prefix && iri end if @options[:prefixes] ctx end # Rename BNodes to uniquify them, if necessary if options[:unique_bnodes] - result = API.flatten(result, context, @options) + result = API.flatten(result, context, **@options) end - frame = RDF::Util::File.open_file(@options[:frame]) if @options[:frame].is_a?(String) - if frame ||= @options[:frame] + 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) 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) end @output.write(result.to_json(JSON_STATE)) end