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("http://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

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::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}) (defaults to: {})

    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

Yields:

  • (writer)

    self

Yield Parameters:

Yield Returns:

  • (void)


259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/rdf/writer.rb', line 259

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

  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



278
279
280
# File 'lib/rdf/writer.rb', line 278

def options
  @options
end

Class Method Details

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

Buffers output into a string buffer.

Yields:

  • (writer)

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (String)

Raises:

  • (ArgumentError)

    if no block is provided



192
193
194
195
196
197
198
199
200
201
202
# File 'lib/rdf/writer.rb', line 192

def self.buffer(*args, &block)
  options = args.last.is_a?(Hash) ? args.last : {}
  options[:encoding] ||= Encoding::UTF_8 if RUBY_PLATFORM == "java"
  raise ArgumentError, "block expected" unless block_given?

  StringIO.open do |buffer|
    buffer.set_encoding(options[:encoding]) if options[:encoding]
    self.new(buffer, *args) { |writer| block.call(writer) }
    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}) (defaults to: {})

    passed to #initialize or buffer



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/rdf/writer.rb', line 160

def self.dump(data, io = nil, options = {})
  if io.is_a?(String)
    io = File.open(io, 'w')
  elsif io.respond_to?(:external_encoding) && io.external_encoding
    options = {encoding: io.external_encoding}.merge(options)
  end
  io.set_encoding(options[:encoding]) if io.respond_to?(:set_encoding) && options[:encoding]
  method = data.respond_to?(:each_statement) ? :each_statement : :each
  if io
    new(io, options) do |writer|
      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)
  @@subclasses.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
# File 'lib/rdf/writer.rb', line 92

def self.for(options = {})
  options = options.merge(has_writer: true) if options.is_a?(Hash)
  if format = self.format || Format.for(options)
    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)


103
104
105
106
107
108
109
110
111
112
# File 'lib/rdf/writer.rb', line 103

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, options = {}, &block) ⇒ RDF::Writer

Writes output to the given filename.

Parameters:

  • filename (String, #to_s)
  • options (Hash{Symbol => Object}) (defaults to: {})

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

Options Hash (options):

  • :format (Symbol) — default: nil

Returns:



212
213
214
215
216
217
218
219
# File 'lib/rdf/writer.rb', line 212

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

.optionsArray<RDF::CLI::Option>

Options suitable for automatic Writer provisioning.

Returns:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/rdf/writer.rb', line 117

def self.options
  [
    RDF::CLI::Option.new(
      symbol: :canonicalize,
      datatype: TrueClass,
      on: ["--canonicalize"],
      description: "Canonicalize input/output.") {true},
    RDF::CLI::Option.new(
      symbol: :encoding,
      datatype: Encoding,
      on: ["--encoding ENCODING"],
      description: "The encoding of the input stream.") {|arg| Encoding.find arg},
    RDF::CLI::Option.new(
      symbol: :prefixes,
      datatype: Hash,
      multiple: true,
      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,
      on: ["--unique-bnodes"],
      description: "Use unique Node identifiers.") {true},
  ]
end

.to_symSymbol

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

Returns:

  • (Symbol)


224
225
226
# File 'lib/rdf/writer.rb', line 224

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



288
289
290
# File 'lib/rdf/writer.rb', line 288

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

#canonicalize?Boolean

Returns true if terms should be canonicalized.

Returns:

  • (Boolean)

    true or false

Since:

  • 1.0.8



371
372
373
# File 'lib/rdf/writer.rb', line 371

def canonicalize?
  @options[:canonicalize]
end

#encodingEncoding

Returns the encoding of the output stream.

Returns:

  • (Encoding)


346
347
348
349
350
351
352
353
354
355
# File 'lib/rdf/writer.rb', line 346

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)


567
568
569
570
571
572
573
574
575
# File 'lib/rdf/writer.rb', line 567

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)


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

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}) (defaults to: {})

    = ({})

Returns:

  • (String)

Since:

  • 0.2.3



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

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}) (defaults to: {})

    = ({})

Returns:

  • (String)

Raises:

  • (NotImplementedError)

    unless implemented in subclass



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

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}) (defaults to: {})

    = ({})

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



502
503
504
# File 'lib/rdf/writer.rb', line 502

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



483
484
485
486
487
488
489
490
491
492
# File 'lib/rdf/writer.rb', line 483

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)
    else nil
  end
end

#format_uri(value, options = {}) ⇒ String

This method is abstract.

Parameters:

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

    = ({})

Returns:

  • (String)

Raises:

  • (NotImplementedError)

    unless implemented in subclass



512
513
514
# File 'lib/rdf/writer.rb', line 512

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

#node_idString (protected)

Returns:

  • (String)


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

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:



336
337
338
339
# File 'lib/rdf/writer.rb', line 336

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



300
301
302
# File 'lib/rdf/writer.rb', line 300

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



315
316
317
# File 'lib/rdf/writer.rb', line 315

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

#puts(*args) (protected)

This method returns an undefined value.



540
541
542
# File 'lib/rdf/writer.rb', line 540

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

#quoted(string) ⇒ String (protected)

Parameters:

  • string (String)

Returns:

  • (String)


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

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

#to_symSymbol

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

Returns:

  • (Symbol)


231
232
233
# File 'lib/rdf/writer.rb', line 231

def to_sym
  self.class.to_sym
end

#uri_for(term) ⇒ String (protected)

Parameters:

Returns:

  • (String)


547
548
549
550
551
552
553
554
555
556
# File 'lib/rdf/writer.rb', line 547

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



362
363
364
# File 'lib/rdf/writer.rb', line 362

def validate?
  @options[:validate]
end

#write_comment(text) ⇒ self

This method is abstract.

Parameters:

  • text (String)

Returns:

  • (self)


407
408
409
# File 'lib/rdf/writer.rb', line 407

def write_comment(text)
  self
end

#write_epilogueself

This method is abstract.

Returns:

  • (self)

Raises:



396
397
398
399
400
401
# File 'lib/rdf/writer.rb', line 396

def write_epilogue
  if log_statistics[:error]
    raise RDF::WriterError, "Errors found during processing"
  end
  self
end

#write_prologueself

This method is abstract.

Returns:

  • (self)


388
389
390
# File 'lib/rdf/writer.rb', line 388

def write_prologue
  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)


419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/rdf/writer.rb', line 419

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

  # Make sure BNodes in statement use unique identifiers
  if statement.node?
    terms = 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
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



475
476
477
# File 'lib/rdf/writer.rb', line 475

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)


462
463
464
465
# File 'lib/rdf/writer.rb', line 462

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