• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ruby-rdf / json-ld / 6003189826

28 Aug 2023 05:52PM UTC coverage: 82.887% (-0.03%) from 82.917%
6003189826

push

github

gkellogg
Simplify dependencies.

2819 of 3401 relevant lines covered (82.89%)

1236.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.05
/lib/json/ld/writer.rb
1
# frozen_string_literal: true
2

3
require 'json/ld/streaming_writer'
2✔
4
require 'link_header'
2✔
5

6
module JSON
2✔
7
  module LD
2✔
8
    ##
9
    # A JSON-LD parser in Ruby.
10
    #
11
    # Note that the natural interface is to write a whole graph at a time.
12
    # Writing statements or Triples will create a graph to add them to
13
    # and then serialize the graph.
14
    #
15
    # @example Obtaining a JSON-LD writer class
16
    #   RDF::Writer.for(:jsonld)         #=> JSON::LD::Writer
17
    #   RDF::Writer.for("etc/test.json")
18
    #   RDF::Writer.for(:file_name      => "etc/test.json")
19
    #   RDF::Writer.for(file_extension: "json")
20
    #   RDF::Writer.for(:content_type   => "application/turtle")
21
    #
22
    # @example Serializing RDF graph into an JSON-LD file
23
    #   JSON::LD::Writer.open("etc/test.json") do |writer|
24
    #     writer << graph
25
    #   end
26
    #
27
    # @example Serializing RDF statements into an JSON-LD file
28
    #   JSON::LD::Writer.open("etc/test.json") do |writer|
29
    #     graph.each_statement do |statement|
30
    #       writer << statement
31
    #     end
32
    #   end
33
    #
34
    # @example Serializing RDF statements into an JSON-LD string
35
    #   JSON::LD::Writer.buffer do |writer|
36
    #     graph.each_statement do |statement|
37
    #       writer << statement
38
    #     end
39
    #   end
40
    #
41
    # The writer will add prefix definitions, and use them for creating @context definitions, and minting CURIEs
42
    #
43
    # @example Creating @@context prefix definitions in output
44
    #   JSON::LD::Writer.buffer(
45
    #     prefixes: {
46
    #       nil => "http://example.com/ns#",
47
    #       foaf: "http://xmlns.com/foaf/0.1/"}
48
    #   ) do |writer|
49
    #     graph.each_statement do |statement|
50
    #       writer << statement
51
    #     end
52
    #   end
53
    #
54
    # Select the :expand option to output JSON-LD in expanded form
55
    #
56
    # @see https://www.w3.org/TR/json-ld11-api/
57
    # @see https://www.w3.org/TR/json-ld11-api/#the-normalization-algorithm
58
    # @author [Gregg Kellogg](http://greggkellogg.net/)
59
    class Writer < RDF::Writer
2✔
60
      include StreamingWriter
2✔
61
      include Utils
2✔
62
      include RDF::Util::Logger
2✔
63
      format Format
2✔
64

65
      # @!attribute [r] graph
66
      # @return [RDF::Graph] Graph of statements serialized
67
      attr_reader :graph
2✔
68

69
      # @!attribute [r] context
70
      # @return [Context] context used to load and administer contexts
71
      attr_reader :context
2✔
72

73
      ##
74
      # JSON-LD Writer options
75
      # @see https://ruby-rdf.github.io/rdf/RDF/Writer#options-class_method
76
      def self.options
2✔
77
        super + [
×
78
          RDF::CLI::Option.new(
79
            symbol: :compactArrays,
80
            datatype: TrueClass,
81
            default: true,
82
            control: :checkbox,
83
            on: ["--[no-]compact-arrays"],
84
            description: "Replaces arrays with just one element with that element during compaction. Default is `true` use --no-compact-arrays to disable."
85
          ) { |arg| arg },
×
86
          RDF::CLI::Option.new(
87
            symbol: :compactToRelative,
88
            datatype: TrueClass,
89
            default: true,
90
            control: :checkbox,
91
            on: ["--[no-]compact-to-relative"],
92
            description: "Creates document relative IRIs when compacting, if `true`, otherwise leaves expanded. Default is `true` use --no-compact-to-relative to disable."
93
          ) { |arg| arg },
×
94
          RDF::CLI::Option.new(
95
            symbol: :context,
96
            datatype: RDF::URI,
97
            control: :url2,
98
            on: ["--context CONTEXT"],
99
            description: "Context to use when compacting."
100
          ) { |arg| RDF::URI(arg).absolute? ? RDF::URI(arg) : StringIO.new(File.read(arg)) },
×
101
          RDF::CLI::Option.new(
102
            symbol: :embed,
103
            datatype: %w[@always @once @never],
104
            default: '@once',
105
            control: :select,
106
            on: ["--embed EMBED"],
107
            description: "How to embed matched objects (@once)."
108
          ) { |arg| RDF::URI(arg) },
×
109
          RDF::CLI::Option.new(
110
            symbol: :explicit,
111
            datatype: TrueClass,
112
            control: :checkbox,
113
            on: ["--[no-]explicit"],
114
            description: "Only include explicitly declared properties in output (false)"
115
          ) { |arg| arg },
×
116
          RDF::CLI::Option.new(
117
            symbol: :frame,
118
            datatype: RDF::URI,
119
            control: :url2,
120
            use: :required,
121
            on: ["--frame FRAME"],
122
            description: "Frame to use when serializing."
123
          ) { |arg| RDF::URI(arg).absolute? ? RDF::URI(arg) : StringIO.new(File.read(arg)) },
×
124
          RDF::CLI::Option.new(
125
            symbol: :lowercaseLanguage,
126
            datatype: TrueClass,
127
            control: :checkbox,
128
            on: ["--[no-]lowercase-language"],
129
            description: "By default, language tags are left as is. To normalize to lowercase, set this option to `true`."
130
          ),
131
          RDF::CLI::Option.new(
132
            symbol: :omitDefault,
133
            datatype: TrueClass,
134
            control: :checkbox,
135
            on: ["--[no-]omitDefault"],
136
            description: "Omit missing properties from output (false)"
137
          ) { |arg| arg },
×
138
          RDF::CLI::Option.new(
139
            symbol: :ordered,
140
            datatype: TrueClass,
141
            control: :checkbox,
142
            on: ["--[no-]ordered"],
143
            description: "Order object member processing lexographically."
144
          ) { |arg| arg },
×
145
          RDF::CLI::Option.new(
146
            symbol: :processingMode,
147
            datatype: %w[json-ld-1.0 json-ld-1.1],
148
            control: :radio,
149
            on: ["--processingMode MODE", %w[json-ld-1.0 json-ld-1.1]],
150
            description: "Set Processing Mode (json-ld-1.0 or json-ld-1.1)"
151
          ),
152
          RDF::CLI::Option.new(
153
            symbol: :rdfDirection,
154
            datatype: %w[i18n-datatype compound-literal],
155
            default: 'null',
156
            control: :select,
157
            on: ["--rdf-direction DIR", %w[i18n-datatype compound-literal]],
158
            description: "How to serialize literal direction (i18n-datatype compound-literal)"
159
          ) { |arg| arg },
×
160
          RDF::CLI::Option.new(
161
            symbol: :requireAll,
162
            datatype: TrueClass,
163
            default: true,
164
            control: :checkbox,
165
            on: ["--[no-]require-all"],
166
            description: "Require all properties to match (true). Default is `true` use --no-require-all to disable."
167
          ) { |arg| arg },
×
168
          RDF::CLI::Option.new(
169
            symbol: :stream,
170
            datatype: TrueClass,
171
            control: :checkbox,
172
            on: ["--[no-]stream"],
173
            description: "Do not attempt to optimize graph presentation, suitable for streaming large graphs."
174
          ) { |arg| arg },
×
175
          RDF::CLI::Option.new(
176
            symbol: :useNativeTypes,
177
            datatype: TrueClass,
178
            control: :checkbox,
179
            on: ["--[no-]use-native-types"],
180
            description: "Use native JSON values in value objects."
181
          ) { |arg| arg },
×
182
          RDF::CLI::Option.new(
183
            symbol: :useRdfType,
184
            datatype: TrueClass,
185
            control: :checkbox,
186
            on: ["--[no-]use-rdf-type"],
187
            description: "Treat `rdf:type` like a normal property instead of using `@type`."
188
          ) { |arg| arg }
×
189
        ]
190
      end
191

192
      class << self
2✔
193
        attr_reader :white_list, :black_list
2✔
194

195
        ##
196
        # 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.
197
        #
198
        # @param [Hash{Symbol => String}] accept_params
199
        # @yield [accept_params] if a block is given, returns the result of evaluating that block
200
        # @yieldparam [Hash{Symbol => String}] accept_params
201
        # @return [Boolean]
202
        # @see    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
203
        def accept?(accept_params)
2✔
204
          if block_given?
122✔
205
            yield(accept_params)
×
206
          else
207
            true
122✔
208
          end
209
        end
210

211
        ##
212
        # Returns default context used for compacted profile without an explicit context URL
213
        # @return [String]
214
        def default_context
2✔
215
          @default_context || JSON::LD::DEFAULT_CONTEXT
18✔
216
        end
217

218
        ##
219
        # Sets default context used for compacted profile without an explicit context URL
220
        # @param [String] url
221
        attr_writer :default_context
2✔
222
      end
223

224
      ##
225
      # Initializes the JSON-LD writer instance.
226
      #
227
      # @param  [IO, File] output
228
      #   the output stream
229
      # @param  [Hash{Symbol => Object}] options
230
      #   any additional options
231
      # @option options [Encoding] :encoding     (Encoding::UTF_8)
232
      #   the encoding to use on the output stream (Ruby 1.9+)
233
      # @option options [Boolean]  :canonicalize (false)
234
      #   whether to canonicalize literals when serializing
235
      # @option options [Hash]     :prefixes     ({})
236
      #   the prefix mappings to use (not supported by all writers)
237
      # @option options [Boolean]  :standard_prefixes   (false)
238
      #   Add standard prefixes to @prefixes, if necessary.
239
      # @option options [IO, Array, Hash, String, Context]     :context     ({})
240
      #   context to use when serializing. Constructed context for native serialization.
241
      # @option options [IO, Array, Hash, String, Context]     :frame     ({})
242
      #   frame to use when serializing.
243
      # @option options [Boolean]  :unique_bnodes   (false)
244
      #   Use unique bnode identifiers, defaults to using the identifier which the node was originall initialized with (if any).
245
      # @option options [Proc] serializer (JSON::LD::API.serializer)
246
      #   A Serializer method used for generating the JSON serialization of the result.
247
      # @option options [Boolean] :stream (false)
248
      #   Do not attempt to optimize graph presentation, suitable for streaming large graphs.
249
      # @yield  [writer] `self`
250
      # @yieldparam  [RDF::Writer] writer
251
      # @yieldreturn [void]
252
      # @yield  [writer]
253
      # @yieldparam [RDF::Writer] writer
254
      def initialize(output = $stdout, **options, &block)
2✔
255
        options[:base_uri] ||= options[:base] if options.key?(:base)
256✔
256
        options[:base] ||= options[:base_uri] if options.key?(:base_uri)
256✔
257
        @serializer = options.fetch(:serializer, JSON::LD::API.method(:serializer))
256✔
258
        super do
256✔
259
          @repo = RDF::Repository.new
256✔
260

261
          if block
256✔
262
            case block.arity
176✔
263
            when 0 then instance_eval(&block)
×
264
            else yield(self)
176✔
265
            end
266
          end
267
        end
268
      end
269

270
      ##
271
      # Addes a triple to be serialized
272
      # @param  [RDF::Resource] subject
273
      # @param  [RDF::URI]      predicate
274
      # @param  [RDF::Value]    object
275
      # @return [void]
276
      # @abstract
277
      def write_triple(subject, predicate, object)
2✔
278
        write_quad(subject, predicate, object, nil)
×
279
      end
280

281
      ##
282
      # Outputs the N-Quads representation of a statement.
283
      #
284
      # @param  [RDF::Resource] subject
285
      # @param  [RDF::URI]      predicate
286
      # @param  [RDF::Term]     object
287
      # @return [void]
288
      def write_quad(subject, predicate, object, graph_name)
2✔
289
        statement = RDF::Statement.new(subject, predicate, object, graph_name: graph_name)
146✔
290
        if @options[:stream]
146✔
291
          stream_statement(statement)
26✔
292
        else
293
          @repo.insert(statement)
120✔
294
        end
295
      end
296

297
      ##
298
      # Necessary for streaming
299
      # @return [void] `self`
300
      def write_prologue
2✔
301
        stream_prologue if @options[:stream]
256✔
302
        super
256✔
303
      end
304

305
      ##
306
      # Outputs the Serialized JSON-LD representation of all stored statements.
307
      #
308
      # If provided a context or prefixes, we'll create a context
309
      # and use it to compact the output. Otherwise, we return un-compacted JSON-LD
310
      #
311
      # @return [void]
312
      # @see    #write_triple
313
      def write_epilogue
2✔
314
        if @options[:stream]
256✔
315
          stream_epilogue
54✔
316
        else
317

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

321
          # Some options may be indicated from accept parameters
322
          profile = @options.fetch(:accept_params, {}).fetch(:profile, "").split
202✔
323
          links = LinkHeader.parse(@options[:link])
202✔
324
          @options[:context] ||= begin
202✔
325
            links.find_link(['rel', JSON_LD_NS + "context"]).href
200✔
326
          rescue StandardError
327
            nil
200✔
328
          end
329
          @options[:context] ||= Writer.default_context if profile.include?(JSON_LD_NS + "compacted")
202✔
330
          @options[:frame] ||= begin
202✔
331
            links.find_link(['rel', JSON_LD_NS + "frame"]).href
202✔
332
          rescue StandardError
333
            nil
202✔
334
          end
335

336
          # If we were provided a context, or prefixes, use them to compact the output
337
          context = @options[:context]
202✔
338
          context ||= if @options[:prefixes] || @options[:language] || @options[:standard_prefixes]
202✔
339
            ctx = Context.new(**@options)
38✔
340
            ctx.language = @options[:language] if @options[:language]
38✔
341
            @options[:prefixes]&.each do |prefix, iri|
38✔
342
              ctx.set_mapping(prefix, iri) if prefix && iri
54✔
343
            end
344
            ctx
38✔
345
          end
346

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

350
          if (frame = @options[:frame])
202✔
351
            # Perform framing, if given a frame
352
            # log_debug("writer") { "frame result"}
353
            result = API.frame(result, frame, **@options.merge(serializer: nil))
×
354
          elsif context
202✔
355
            # Perform compaction, if we have a context
356
            # log_debug("writer") { "compact result"}
357
            result = API.compact(result, context, **@options.merge(serializer: nil))
40✔
358
          end
359

360
          @output.write(@serializer.call(result, **@options))
202✔
361
        end
362

363
        super
256✔
364
      end
365
    end
366
  end
367
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc