Class: RDF::Writer Abstract

Inherits:
Object
  • Object
show all
Extended by:
Enumerable, Util::Aliasing::LateBound
Includes:
Util::Logger, Writable
Defined in:
lib/rdf/writer.rb

Overview

This class is abstract.

The base class for RDF serializers.

Examples:

Loading an RDF writer implementation

require 'rdf/ntriples'

Iterating over known RDF writer classes

RDF::Writer.each { |klass| puts klass.name }

Obtaining an RDF writer class

RDF::Writer.for(:ntriples)     #=> RDF::NTriples::Writer
RDF::Writer.for("spec/data/output.nt")
RDF::Writer.for(file_name:      "spec/data/output.nt")
RDF::Writer.for(file_extension: "nt")
RDF::Writer.for(content_type:   "application/n-triples")

Instantiating an RDF writer class

RDF::Writer.for(:ntriples).new($stdout) { |writer| ... }

Serializing RDF statements into a file

RDF::Writer.open("spec/data/output.nt") do |writer|
  graph.each_statement do |statement|
    writer << statement
  end
end

Serializing RDF statements into a string

RDF::Writer.for(:ntriples).buffer do |writer|
  graph.each_statement do |statement|
    writer << statement
  end
end

Detecting invalid output

logger = Logger.new([])
RDF::Writer.for(:ntriples).buffer(logger: logger) do |writer|
  statement = RDF::Statement.new(
    RDF::URI("https://rubygems.org/gems/rdf"),
    RDF::URI("http://purl.org/dc/terms/creator"),
    nil)
  writer << statement
end # => RDF::WriterError
logger.empty? => false

See Also:

Direct Known Subclasses

NTriples::Writer, Vocabulary::Writer

Constant Summary

Constants included from Util::Logger

Util::Logger::IOWrapper

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Util::Aliasing::LateBound

alias_method

Methods included from Writable

#<<, #insert, #insert_graph, #insert_reader, #insert_statements, #writable?

Methods included from Util::Coercions

#coerce_statements

Methods included from Util::Logger

#log_debug, #log_depth, #log_error, #log_fatal, #log_info, #log_recover, #log_recovering?, #log_statistics, #log_warn, #logger

Constructor Details

#initialize(output = $stdout, **options) {|writer| ... } ⇒ Writer

Initializes the writer.

Parameters:

  • output (IO, File) (defaults to: $stdout)

    the output stream

  • options (Hash{Symbol => Object})

    any additional options

Options Hash (**options):

  • :encoding (Encoding, String, Symbol)

    the encoding to use on the output stream. Defaults to the format associated with content_encoding.

  • :canonicalize (Boolean) — default: false

    whether to canonicalize terms when serializing

  • :validate (Boolean) — default: false

    whether to validate terms when serializing

  • :prefixes (Hash) — default: Hash.new

    the prefix mappings to use (not supported by all writers)

  • :base_uri (#to_s) — default: nil

    the base URI to use when constructing relative URIs (not supported by all writers)

  • :unique_bnodes (Boolean) — default: false

    Use unique Node identifiers, defaults to using the identifier which the node was originall initialized with (if any). Implementations should ensure that Nodes are serialized using a unique representation independent of any identifier used when creating the node. See NTriples::Writer#format_node

  • :accept_params (Hash{Symbol => String})

    Parameters from ACCEPT header entry for the media-range matching this writer.

  • :version (String)

    Parse a specific version of RDF ("1.1', "1.2", or "1.2-basic"")

Yields:

  • (writer)

    self

Yield Parameters:

Yield Returns:

  • (void)


295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/rdf/writer.rb', line 295

def initialize(output = $stdout, **options, &block)
  @output, @options = output, options.dup
  @nodes, @node_id, @node_id_map  = {}, 0, {}

  # The rdfstar option implies version 1.2, but can be overridden
  @options[:version] ||= "1.2" if @options[:rdfstar]

  unless self.version.nil? || RDF::Format::VERSIONS.include?(self.version)
    log_warn("Expected version to be one of #{RDF::Format::VERSIONS.join(', ')}, was #{self.version}")
  end

  if block_given?
    write_prologue
    case block.arity
      when 1 then block.call(self)
      else instance_eval(&block)
    end
    write_epilogue
  end
end

Instance Attribute Details

#optionsHash (readonly)

Any additional options for this writer.

Returns:

  • (Hash)

Since:

  • 0.2.2



321
322
323
# File 'lib/rdf/writer.rb', line 321

def options
  @options
end

Class Method Details

.accept?(accept_params) {|accept_params| ... } ⇒ Boolean

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.

Examples:

rejecting a writer based on a profile

JSON::LD::Writer.accept?(profile: "http://www.w3.org/ns/json-ld#compacted http://example.org/black-listed")
  # => false

Parameters:

  • accept_params (Hash{Symbol => String})

Yields:

  • (accept_params)

    if a block is given, returns the result of evaluating that block

Yield Parameters:

  • accept_params (Hash{Symbol => String})

Returns:

  • (Boolean)

See Also:



179
180
181
# File 'lib/rdf/writer.rb', line 179

def accept?(accept_params)
  block_given? ? yield(accept_params) : true
end

.buffer(*args, **options) {|writer| ... } ⇒ String

Buffers output into a string buffer.

Parameters:

  • options (Hash{Symbol => Object})

    passed to #initialize

Yields:

  • (writer)

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (String)

Raises:

  • (ArgumentError)

    if no block is provided



222
223
224
225
226
227
228
229
230
231
232
# File 'lib/rdf/writer.rb', line 222

def self.buffer(*args, **options, &block)
  raise ArgumentError, "block expected" unless block_given?

  StringIO.open do |buffer|
    self.new(buffer, *args, **options) do |writer|
      buffer.set_encoding(writer.encoding)
      block.call(writer)
    end
    buffer.string
  end
end

.dump(data, io = nil, **options)

This method returns an undefined value.

Parameters:

  • data (RDF::Enumerable, #each)

    the graph or repository to dump

  • io (IO, File, String) (defaults to: nil)

    the output stream or file to write to

  • options (Hash{Symbol => Object})

    passed to #initialize or buffer



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/rdf/writer.rb', line 192

def self.dump(data, io = nil, **options)
  io = File.open(io, 'w') if io.is_a?(String)
  method = data.respond_to?(:each_statement) ? :each_statement : :each
  if io
    new(io, **options) do |writer|
      io.set_encoding(writer.encoding) if io.respond_to?(:set_encoding)
      data.send(method) do |statement|
        writer << statement
      end
      writer.flush
    end
  else
    buffer(**options) do |writer|
      data.send(method) do |statement|
        writer << statement
      end
    end
  end
end

.each {|klass| ... } ⇒ Enumerator

Enumerates known RDF writer classes.

Yields:

  • (klass)

Yield Parameters:

  • klass (Class)

Yield Returns:

  • (void)

    ignored

Returns:

  • (Enumerator)


63
64
65
# File 'lib/rdf/writer.rb', line 63

def self.each(&block)
  RDF::Format.map(&:writer).reject(&:nil?).each(&block)
end

.for(format) ⇒ Class .for(filename) ⇒ Class .for(options = {}) ⇒ Class

Finds an RDF writer class based on the given criteria.

Overloads:

  • .for(format) ⇒ Class

    Finds an RDF writer class based on a symbolic name.

    Parameters:

    • format (Symbol)

    Returns:

    • (Class)
  • .for(filename) ⇒ Class

    Finds an RDF writer class based on a file name.

    Parameters:

    • filename (String)

    Returns:

    • (Class)
  • .for(options = {}) ⇒ Class

    Finds an RDF writer class based on various options.

    Parameters:

    • options (Hash{Symbol => Object}) (defaults to: {})

    Options Hash (options):

    • :file_name (String, #to_s) — default: nil
    • :file_extension (Symbol, #to_sym) — default: nil
    • :content_type (String, #to_s) — default: nil

    Returns:

    • (Class)

Returns:

  • (Class)


92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/rdf/writer.rb', line 92

def self.for(*arg, &block)
  case arg.length
  when 0 then arg = nil
  when 1 then arg = arg.first
  else
    raise ArgumentError, "Format.for accepts zero or one argument, got #{arg.length}."
  end
  arg = arg.merge(has_writer: true) if arg.is_a?(Hash)
  if format = self.format || Format.for(arg)
    format.writer
  end
end

.format(klass = nil) ⇒ Class Also known as: format_class

Retrieves the RDF serialization format class for this writer class.

Returns:

  • (Class)


109
110
111
112
113
114
115
116
117
118
# File 'lib/rdf/writer.rb', line 109

def self.format(klass = nil)
  if klass.nil?
    Format.each do |format|
      if format.writer == self
        return format
      end
    end
    nil # not found
  end
end

.open(filename, format: nil, **options, &block) ⇒ RDF::Writer

Writes output to the given filename.

Parameters:

  • filename (String, #to_s)
  • format (Symbol) (defaults to: nil)

    (nil)

  • options (Hash{Symbol => Object})

    any additional options (see #initialize and Format.for)

Returns:



242
243
244
245
246
247
248
249
250
251
# File 'lib/rdf/writer.rb', line 242

def self.open(filename, format: nil, **options, &block)
  File.open(filename, 'wb') do |file|
    format_options = options.dup
    format_options[:file_name] ||= filename
    self.for(format || format_options).new(file, **options) do |writer|
      file.set_encoding(writer.encoding)
      block.call(writer)
    end
  end
end

.optionsArray<RDF::CLI::Option>

Options suitable for automatic Writer provisioning.

Returns:



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/rdf/writer.rb', line 123

def self.options
  [
    RDF::CLI::Option.new(
      symbol: :canonicalize,
      datatype: TrueClass,
      control: :checkbox,
      on: ["--canonicalize"],
      description: "Canonicalize input/output.") {true},
    RDF::CLI::Option.new(
      symbol: :encoding,
      datatype: Encoding,
      control: :text,
      on: ["--encoding ENCODING"],
      description: "The encoding of the input stream.") {|arg| Encoding.find arg},
    RDF::CLI::Option.new(
      symbol: :prefixes,
      datatype: Hash,
      multiple: true,
      control: :none,
      on: ["--prefixes PREFIX,PREFIX"],
      description: "A comma-separated list of prefix:uri pairs.") do |arg|
        arg.split(',').inject({}) do |memo, pfxuri|
          pfx,uri = pfxuri.split(':', 2)
          memo.merge(pfx.to_sym => RDF::URI(uri))
        end
    end,
    RDF::CLI::Option.new(
      symbol: :unique_bnodes,
      datatype: TrueClass,
      control: :checkbox,
      on: ["--unique-bnodes"],
      description: "Use unique Node identifiers.") {true},
    RDF::CLI::Option.new(
      symbol: :version,
      control: :select,
      datatype: RDF::Format::VERSIONS, # 1.1, 1.2, or 1.2-basic
      on: ["--version VERSION"],
      description: "RDF Version."),
  ]
end

.to_symSymbol

Returns a symbol appropriate to use with RDF::Writer.for()

Returns:

  • (Symbol)


256
257
258
# File 'lib/rdf/writer.rb', line 256

def self.to_sym
  self.format.to_sym
end

Instance Method Details

#base_uriRDF::URI

Returns the base URI used for this writer.

Examples:

writer.prefixes[:dc]  #=> RDF::URI('http://purl.org/dc/terms/')

Returns:

Since:

  • 0.3.4



331
332
333
# File 'lib/rdf/writer.rb', line 331

def base_uri
  RDF::URI(@options[:base_uri]) if @options[:base_uri]
end

#canonicalize?Boolean

Note:

This is for term canonicalization, for graph/dataset canonicalization use RDF::Normalize.

Returns true if terms should be in canonical form.

Returns:

  • (Boolean)

    true or false

Since:

  • 1.0.8



416
417
418
# File 'lib/rdf/writer.rb', line 416

def canonicalize?
  @options[:canonicalize]
end

#encodingEncoding

Returns the encoding of the output stream.

Returns:

  • (Encoding)


389
390
391
392
393
394
395
396
397
398
# File 'lib/rdf/writer.rb', line 389

def encoding
  case @options[:encoding]
  when String, Symbol
    Encoding.find(@options[:encoding].to_s)
  when Encoding
    @options[:encoding]
  else
    @options[:encoding] ||= Encoding.find(self.class.format.content_encoding.to_s)
  end
end

#escaped(string) ⇒ String (protected)

Parameters:

  • string (String)

Returns:

  • (String)


641
642
643
644
645
646
647
648
649
# File 'lib/rdf/writer.rb', line 641

def escaped(string)
  string.gsub('\\', '\\\\\\\\').
         gsub("\b", '\\b').
         gsub("\f", '\\f').
         gsub("\t", '\\t').
         gsub("\n", '\\n').
         gsub("\r", '\\r').
         gsub('"', '\\"')
end

#flushself Also known as: flush!

Flushes the underlying output buffer.

Returns:

  • (self)


424
425
426
427
# File 'lib/rdf/writer.rb', line 424

def flush
  @output.flush if @output.respond_to?(:flush)
  self
end

#format_list(value, **options) ⇒ String

This method is abstract.

Parameters:

  • value (RDF::List)
  • options (Hash{Symbol => Object})

    = ({})

Returns:

  • (String)

Since:

  • 0.2.3



591
592
593
# File 'lib/rdf/writer.rb', line 591

def format_list(value, **options)
  format_term(value.subject, **options)
end

#format_literal(value, **options) ⇒ String

This method is abstract.

Parameters:

  • value (RDF::Literal, String, #to_s)
  • options (Hash{Symbol => Object})

    = ({})

Returns:

  • (String)

Raises:

  • (NotImplementedError)

    unless implemented in subclass



581
582
583
# File 'lib/rdf/writer.rb', line 581

def format_literal(value, **options)
  raise NotImplementedError.new("#{self.class}#format_literal") # override in subclasses
end

#format_node(value, **options) ⇒ String

This method is abstract.

Parameters:

  • value (RDF::Node)
  • options (Hash{Symbol => Object})

    = ({})

Options Hash (**options):

  • :unique_bnodes (Boolean) — default: false

    Serialize node using unique identifier, rather than any used to create the node.

Returns:

  • (String)

Raises:

  • (NotImplementedError)

    unless implemented in subclass



561
562
563
# File 'lib/rdf/writer.rb', line 561

def format_node(value, **options)
  raise NotImplementedError.new("#{self.class}#format_node") # override in subclasses
end

#format_term(term, **options) ⇒ String

Parameters:

Returns:

  • (String)

Since:

  • 0.3.0



541
542
543
544
545
546
547
548
549
550
551
# File 'lib/rdf/writer.rb', line 541

def format_term(term, **options)
  case term
    when String         then format_literal(RDF::Literal(term, **options), **options)
    when RDF::List      then format_list(term, **options)
    when RDF::Literal   then format_literal(term, **options)
    when RDF::URI       then format_uri(term, **options)
    when RDF::Node      then format_node(term, **options)
    when RDF::Statement then format_tripleTerm(term, **options)
    else nil
  end
end

#format_tripleTerm(value, **options) ⇒ String

This method is abstract.

Formats a referenced triple term.

Examples:

<<<s> <p> <o>>> <p> <o> .

Parameters:

Returns:

  • (String)

Raises:

  • (NotImplementedError)

    unless implemented in subclass



606
607
608
# File 'lib/rdf/writer.rb', line 606

def format_tripleTerm(value, **options)
  raise NotImplementedError.new("#{self.class}#format_tripleTerm") # override in subclasses
end

#format_uri(value, **options) ⇒ String

This method is abstract.

Parameters:

  • value (RDF::URI)
  • options (Hash{Symbol => Object})

    = ({})

Returns:

  • (String)

Raises:

  • (NotImplementedError)

    unless implemented in subclass



571
572
573
# File 'lib/rdf/writer.rb', line 571

def format_uri(value, **options)
  raise NotImplementedError.new("#{self.class}#format_uri") # override in subclasses
end

#node_idString (protected)

Returns:

  • (String)


634
635
636
# File 'lib/rdf/writer.rb', line 634

def node_id
  "_:n#{@node_id += 1}"
end

#prefix(name, uri) ⇒ RDF::URI #prefix(name) ⇒ RDF::URI Also known as: prefix!

Defines the given named URI prefix for this writer.

Examples:

Defining a URI prefix

writer.prefix :dc, RDF::URI('http://purl.org/dc/terms/')

Returning a URI prefix

writer.prefix(:dc)    #=> RDF::URI('http://purl.org/dc/terms/')

Overloads:

  • #prefix(name, uri) ⇒ RDF::URI

    Parameters:

    • name (Symbol, #to_s)
    • uri (RDF::URI, #to_s)
  • #prefix(name) ⇒ RDF::URI

    Parameters:

    • name (Symbol, #to_s)

Returns:



379
380
381
382
# File 'lib/rdf/writer.rb', line 379

def prefix(name, uri = nil)
  name = name.to_s.empty? ? nil : (name.respond_to?(:to_sym) ? name.to_sym : name.to_s.to_sym)
  uri.nil? ? prefixes[name] : prefixes[name] = uri
end

#prefixesHash{Symbol => RDF::URI}

Returns the URI prefixes currently defined for this writer.

Examples:

writer.prefixes[:dc]  #=> RDF::URI('http://purl.org/dc/terms/')

Returns:

Since:

  • 0.2.2



343
344
345
# File 'lib/rdf/writer.rb', line 343

def prefixes
  @options[:prefixes] ||= {}
end

#prefixes=(prefixes) ⇒ Hash{Symbol => RDF::URI}

Defines the given URI prefixes for this writer.

Examples:

writer.prefixes = {
  dc: RDF::URI('http://purl.org/dc/terms/'),
}

Parameters:

Returns:

Since:

  • 0.3.0



358
359
360
# File 'lib/rdf/writer.rb', line 358

def prefixes=(prefixes)
  @options[:prefixes] = prefixes
end

#puts(*args) (protected)

This method returns an undefined value.



614
615
616
# File 'lib/rdf/writer.rb', line 614

def puts(*args)
  @output.puts(*args.map {|s| s.encode(encoding)})
end

#quoted(string) ⇒ String (protected)

Parameters:

  • string (String)

Returns:

  • (String)


654
655
656
# File 'lib/rdf/writer.rb', line 654

def quoted(string)
  "\"#{string}\""
end

#to_symSymbol

Returns a symbol appropriate to use with RDF::Writer.for()

Returns:

  • (Symbol)


263
264
265
# File 'lib/rdf/writer.rb', line 263

def to_sym
  self.class.to_sym
end

#uri_for(term) ⇒ String (protected)

Parameters:

Returns:

  • (String)


621
622
623
624
625
626
627
628
629
630
# File 'lib/rdf/writer.rb', line 621

def uri_for(term)
  case
    when term.is_a?(RDF::Node)
      @nodes[term] ||= term.to_base
    when term.respond_to?(:to_uri)
      term.to_uri.to_s
    else
      term.to_s
  end
end

#validate?Boolean

Returns true if statements and terms should be validated.

Returns:

  • (Boolean)

    true or false

Since:

  • 1.0.8



405
406
407
# File 'lib/rdf/writer.rb', line 405

def validate?
  @options[:validate]
end

#versionString

Returns the RDF version determined by this reader.

Examples:

writer.version  #=> "1.2"

Returns:

  • (String)

Since:

  • 3.3.4



438
439
440
# File 'lib/rdf/writer.rb', line 438

def version
  @options[:version]
end

#write_comment(text) ⇒ self

This method is abstract.

Parameters:

  • text (String)

Returns:

  • (self)


465
466
467
# File 'lib/rdf/writer.rb', line 465

def write_comment(text)
  self
end

#write_epilogueself

This method is abstract.

Returns:

  • (self)

Raises:



454
455
456
457
458
459
# File 'lib/rdf/writer.rb', line 454

def write_epilogue
  if log_statistics[:error].to_i > @logged_errors_at_prolog
    raise RDF::WriterError, "Errors found during processing"
  end
  self
end

#write_prologueself

This method is abstract.

Returns:

  • (self)


445
446
447
448
# File 'lib/rdf/writer.rb', line 445

def write_prologue
  @logged_errors_at_prolog = log_statistics[:error].to_i
  self
end

#write_statement(statement) ⇒ self Also known as: insert_statement

Note:

logs error if attempting to write an invalid Statement or if canonicalizing a statement which cannot be canonicalized.

Add a statement to the writer. This will check to ensure that the statement is complete (no nil terms) and is valid, if the :validation option is set.

Additionally, it will de-duplicate BNode terms sharing a common identifier.

Parameters:

Returns:

  • (self)


477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# File 'lib/rdf/writer.rb', line 477

def write_statement(statement)
  statement = statement.canonicalize! if canonicalize?

  # Make sure BNodes in statement use unique identifiers
  if statement.node?
    statement.to_quad.map do |term|
      if term.is_a?(RDF::Node)
        term = term.original while term.original
        @nodes[term] ||= begin
          # Account for duplicated nodes
          @node_id_map[term.to_s] ||= term
          if !@node_id_map[term.to_s].equal?(term)
            # Rename node
            term.make_unique!
            @node_id_map[term.to_s] = term
          end
        end
      else
        term
      end
    end
    statement = RDF::Statement.from(statement.to_quad)
  end

  if statement.incomplete?
    log_error "Statement #{statement.inspect} is incomplete"
  elsif validate? && statement.invalid?
    log_error "Statement #{statement.inspect} is invalid"
  elsif respond_to?(:write_quad)
    write_quad(*statement.to_quad)
  else
    write_triple(*statement.to_triple)
  end
  self
rescue ArgumentError => e
  log_error e.message + " at #{e.backtrace.first}"
end

#write_triple(subject, predicate, object) ⇒ self

This method is abstract.
Note:

logs error if attempting to write an invalid Statement or if canonicalizing a statement which cannot be canonicalized.

Parameters:

Returns:

  • (self)

Raises:

  • (NotImplementedError)

    unless implemented in subclass



533
534
535
# File 'lib/rdf/writer.rb', line 533

def write_triple(subject, predicate, object)
  raise NotImplementedError.new("#{self.class}#write_triple") # override in subclasses
end

#write_triples(*triples) ⇒ self

Note:

logs error if attempting to write an invalid Statement or if canonicalizing a statement which cannot be canonicalized.

Parameters:

Returns:

  • (self)


520
521
522
523
# File 'lib/rdf/writer.rb', line 520

def write_triples(*triples)
  triples.each { |triple| write_triple(*triple) }
  self
end