Class: RDF::RDFXML::Writer

Inherits:
Writer
  • Object
show all
Includes:
Util::Logger
Defined in:
lib/rdf/rdfxml/writer.rb

Overview

An RDF/XML serialiser in Ruby

Note that the natural interface is to write a whole graph at a time. Writing statements or Triples will create a graph to add them to and then serialize the graph.

The writer will add prefix definitions, and use them for creating @prefix definitions, and minting QNames

Examples:

Obtaining a RDF/XML writer class

RDF::Writer.for(:rdf)         #=> RDF::RDFXML::Writer
RDF::Writer.for("etc/test.rdf")
RDF::Writer.for(file_name: "etc/test.rdf")
RDF::Writer.for(file_extension: "rdf")
RDF::Writer.for(content_type: "application/rdf+xml")

Serializing RDF graph into an RDF/XML file

RDF::RDFXML::Write.open("etc/test.rdf") do |writer|
  writer << graph
end

Serializing RDF statements into an RDF/XML file

RDF::RDFXML::Writer.open("etc/test.rdf") do |writer|
  graph.each_statement do |statement|
    writer << statement
  end
end

Serializing RDF statements into an RDF/XML string

RDF::RDFXML::Writer.buffer do |writer|
  graph.each_statement do |statement|
    writer << statement
  end
end

Creating @base and @prefix definitions in output

RDF::RDFXML::Writer.buffer(base_uri: "http://example.com/", prefixes: {
    nil => "http://example.com/ns#",
    foaf: "http://xmlns.com/foaf/0.1/"}
) do |writer|
  graph.each_statement do |statement|
    writer << statement
  end
end

Author:

Constant Summary collapse

VALID_ATTRIBUTES =
[:none, :untyped, :typed]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

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

Initializes the RDF/XML writer instance.

Parameters:

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

    the output stream

  • options (Hash{Symbol => Object})

    any additional options

Options Hash (**options):

  • :attributes (Symbol) — default: nil

    How to use XML attributes when serializing, one of :none, :untyped, :typed. The default is :none.

  • :base_uri (#to_s) — default: nil

    the base URI to use when constructing relative URIs

  • :canonicalize (Boolean) — default: false

    whether to canonicalize literals when serializing

  • :default_namespace (String) — default: nil

    URI to use as default namespace, same as prefix(nil)

  • :lang (#to_s) — default: nil

    Output as root xml:lang attribute, and avoid generation xml:lang where possible

  • :max_depth (Integer) — default: 10

    Maximum depth for recursively defining resources

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

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

  • :standard_prefixes (Boolean) — default: false

    Add standard prefixes to prefixes, if necessary.

  • :stylesheet (String) — default: nil

    URI to use as @href for output stylesheet processing instruction.

  • :top_classes (Array<RDF::URI>) — default: [RDF::RDFS.Class]

    Defines rdf:type of subjects to be emitted at the beginning of the document.

Yields:

  • (writer)

Yield Parameters:

  • writer (RDF::Writer)


128
129
130
131
132
133
134
135
136
137
# File 'lib/rdf/rdfxml/writer.rb', line 128

def initialize(output = $stdout, **options, &block)
  super do
    @graph = RDF::Graph.new
    @uri_to_prefix = {}
    @uri_to_qname = {}
    @top_classes = options[:top_classes] || [RDF::RDFS.Class]

    block.call(self) if block_given?
  end
end

Instance Attribute Details

#base_uriRDF::URI

Returns Base URI used for relativizing URIs.

Returns:

  • (RDF::URI)

    Base URI used for relativizing URIs



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

def base_uri
  @base_uri
end

#graphGraph

Returns Graph of statements serialized.

Returns:

  • (Graph)

    Graph of statements serialized



61
62
63
# File 'lib/rdf/rdfxml/writer.rb', line 61

def graph
  @graph
end

#top_classesArray<URI> (readonly)

Defines rdf:type of subjects to be emitted at the beginning of the document.

Returns:

  • (Array<URI>)


58
59
60
# File 'lib/rdf/rdfxml/writer.rb', line 58

def top_classes
  @top_classes
end

Class Method Details

.optionsObject

RDF/XML Writer options



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/rdf/rdfxml/writer.rb', line 69

def self.options
  super + [
    RDF::CLI::Option.new(
      symbol: :attributes,
      datatype: %w(none untyped typed),
      on: ["--attributes ATTRIBUTES",  %w(none untyped typed)],
      description: "How to use XML attributes when serializing, one of :none, :untyped, :typed. The default is :none.") {|arg| arg.to_sym},
    RDF::CLI::Option.new(
      symbol: :default_namespace,
      datatype: RDF::URI,
      on: ["--default-namespace URI", :REQUIRED],
      description: "URI to use as default namespace, same as prefixes.") {|arg| RDF::URI(arg)},
    RDF::CLI::Option.new(
      symbol: :lang,
      datatype: String,
      on: ["--lang LANG", :REQUIRED],
      description: "Output as root xml:lang attribute, and avoid generation xml:lang, where possible.") {|arg| RDF::URI(arg)},
    RDF::CLI::Option.new(
      symbol: :max_depth,
      datatype: Integer,
      on: ["--max-depth"],
      description: "Maximum depth for recursively defining resources, defaults to 3.") {|arg| arg.to_i},
    RDF::CLI::Option.new(
      symbol: :stylesheet,
      datatype: RDF::URI,
      on: ["--stylesheet URI", :REQUIRED],
      description: "URI to use as @href for output stylesheet processing instruction.") {|arg| RDF::URI(arg)},
  ]
end

Instance Method Details

#prefix_attrsHash{String => String} (protected)

XML namespace attributes for defined prefixes

Returns:

  • (Hash{String => String})


400
401
402
403
404
405
# File 'lib/rdf/rdfxml/writer.rb', line 400

def prefix_attrs
  prefixes.inject({}) do |memo, (k, v)|
    memo[(k ? "xmlns:#{k}" : "xmlns").to_sym] = v.to_s
    memo
  end
end

#preprocessignored (protected)

Perform any preprocessing of statements required

Returns:

  • (ignored)


409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/rdf/rdfxml/writer.rb', line 409

def preprocess
  # Load defined prefixes
  (@options[:prefixes] || {}).each_pair do |k, v|
    @uri_to_prefix[v.to_s] = k
  end
  @options[:prefixes] = {}  # Will define actual used when matched

  prefix(:rdf, RDF.to_uri)
  @uri_to_prefix[RDF.to_uri.to_s] = :rdf
  if base_uri || @options[:lang]
    prefix(:xml, RDF::XML)
    @uri_to_prefix[RDF::XML.to_s] = :xml
  end

  if @options[:default_namespace]
    @uri_to_prefix[@options[:default_namespace]] = nil
    prefix(nil, @options[:default_namespace])
  end

  # Process each statement to establish QNames and Terms
  @graph.each {|statement| preprocess_statement(statement)}
end

#preprocess_statement(statement) ⇒ Object (protected)

Perform any statement preprocessing required. This is used to perform reference counts and determine required prefixes.

For RDF/XML, make sure that all predicates have QNames

Parameters:

  • statement (Statement)


436
437
438
439
440
441
442
443
444
# File 'lib/rdf/rdfxml/writer.rb', line 436

def preprocess_statement(statement)
  #log_debug {"preprocess: #{statement.inspect}"}
  bump_reference(statement.object)
  @subjects[statement.subject] = true
  get_qname(statement.subject)
  ensure_qname(statement.predicate)
  statement.predicate == RDF.type && statement.object.uri? ? ensure_qname(statement.object) : get_qname(statement.object)
  get_qname(statement.object.datatype) if statement.object.literal? && statement.object.datatype?
end

#render_collection(property, list, builder, **options, &block) ⇒ Object (protected)

Render a collection, which may be included in a property declaration, or may be recursive within another collection

Parameters:

  • property (String)

    in QName form

  • list (RDF::List)
  • builder (Builder::RdfXml)
  • options (Hash{Symbol => Object})

Returns:

  • String The rendered collection is returned as a string



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/rdf/rdfxml/writer.rb', line 380

def render_collection(property, list, builder, **options, &block)
  builder.tag!(property, "rdf:parseType": "Collection") do |b|
    list.each do |object|
      if log_depth <= @max_depth && !is_done?(object)
        render_subject(object, b)
      elsif object.node?
        if ref_count(object) > 1
          b.tag!("rdf:Description", "rdf:nodeID": object.id)
        else
          b.tag!("rdf:Description")
        end
      else
        b.tag!("rdf:Description", "rdf:about": object.relativize(base_uri))
      end
    end
  end
end

#render_document(subjects, lang: nil, base: nil, **options) {|subject| ... } ⇒ Object (protected)

Render document using haml_template[:doc]. Yields each subject to be rendered separately.

Parameters:

  • subjects (Array<RDF::Resource>)

    Ordered list of subjects. Template must yield to each subject, which returns the serialization of that subject (@see #subject_template)

  • options (Hash{Symbol => Object})

    Rendering options passed to Haml render.

Options Hash (**options):

  • base (RDF::URI) — default: nil

    Base URI added to document, used for shortening URIs within the document.

  • language (Symbol, String) — default: nil

    Value of @lang attribute in document, also allows included literals to omit an @lang attribute if it is equivalent to that of the document.

  • title (String) — default: nil

    Value of html>head>title element.

  • prefix (String) — default: nil

    Value of @prefix attribute.

  • haml (String) — default: haml_template[:doc]

    Haml template to render.

Yields:

  • (subject)

    Yields each subject

Yield Parameters:

Yield Returns:

  • (:ignored)

Returns:

  • String The rendered document is returned as a string



215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/rdf/rdfxml/writer.rb', line 215

def render_document(subjects, lang: nil, base: nil, **options, &block)
  builder = Builder::RdfXml.new(indent: 2)
  builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
  builder.instruct! :'xml-stylesheet', type: 'text/xsl', href: options[:stylesheet] if options[:stylesheet]
  attrs = prefix_attrs
  attrs[:"xml:lang"] = lang if lang
  attrs[:"xml:base"] = base if base

  builder.rdf(:RDF, **attrs) do |b|
    subjects.each do |subject|
      render_subject(subject, b, **options)
    end
  end
end

#render_property(property, objects, builder, **options) ⇒ Object (protected)

Render a single- or multi-valued property. Yields each object for optional rendering. The block should only render for recursive subject definitions (i.e., where the object is also a subject and is rendered underneath the first referencing subject).

If a multi-valued property definition is not found within the template, the writer will use the single-valued property definition multiple times.

Parameters:

  • property (String)

    Property to render, already in QName form.

  • objects (Array<RDF::Resource>)

    List of objects to render. If the list contains only a single element, the :property_value template will be used. Otherwise, the :property_values template is used.

  • builder (Builder::RdfXml)
  • options (Hash{Symbol => Object})

    Rendering options passed to Haml render.



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/rdf/rdfxml/writer.rb', line 307

def render_property(property, objects, builder, **options)
  log_debug {"render_property(#{property}): #{objects.inspect}"}

  # Separate out the objects which are lists and render separately
  lists = objects.
    select(&:node?).
    map {|o| RDF::List.new(subject: o, graph: @graph)}.
    select {|l| l.valid? && l.none?(&:literal?)}

  objects = objects - lists.map(&:subject)

  unless lists.empty?
    # Render non-list objects
    log_debug(depth: log_depth + 1) {"properties with lists: #{lists} non-lists: #{objects - lists.map(&:subject)}"}

    unless objects.empty?
      render_property(property,  objects, builder, **options)
    end

    # Render each list
    lists.each do |list|
      # Render each list as multiple properties and set :inlist to true
      list.each_statement {|st| subject_done(st.subject)}

      log_depth do
        log_debug {"list: #{list.inspect} #{list.to_a}"}
        render_collection(property, list, builder, **options)
      end
    end
  end

  if objects.length == 1
    recurse = log_depth <= @max_depth
    object = objects.first
    
    if recurse && !is_done?(object)
      builder.tag!(property) do |b|
        render_subject(object, b, **options)
      end
    elsif object.literal? && object.datatype == RDF.XMLLiteral
      builder.tag!(property, "rdf:parseType": "Literal", no_whitespace: true) do |b|
        b << object.value
      end
    elsif object.literal?
      attrs = {}
      attrs[:"xml:lang"] = object.language if object.language?
      attrs[:"rdf:datatype"] = object.datatype if object.datatype?
      builder.tag!(property, object.value.to_s, **attrs)
    elsif object.node?
       builder.tag!(property, "rdf:nodeID": object.id)
    else
      builder.tag!(property, "rdf:resource": object.relativize(base_uri))
    end
  else
    # Render each property using property_value template
    objects.each do |object|
      log_depth do
        render_property(property, [object], builder, **options)
      end
    end
  end
end

#render_subject(subject, builder, **options) {|predicate| ... } ⇒ Object (protected)

Render a subject using haml_template[:subject].

The subject template may be called either as a top-level element, or recursively under another element if the rel local is not nil.

For RDF/XML, removes from predicates those that can be rendered as attributes, and adds the :attr_props local for the Haml template, which includes all attributes to be rendered as properties.

Yields each property to be rendered separately.

Parameters:

  • subject (Array<RDF::Resource>)

    Subject to render

  • builder (Builder::RdfXml)
  • options (Hash{Symbol => Object})

    Rendering options passed to Haml render.

Options Hash (**options):

  • about (String) — default: nil

    About description, a QName, URI or Node definition. May be nil if no @about is rendered (e.g. unreferenced Nodes)

  • resource (String) — default: nil

    Resource description, a QName, URI or Node definition. May be nil if no @resource is rendered

  • rel (String) — default: nil

    Optional @rel property description, a QName, URI or Node definition.

  • typeof (String) — default: nil

    RDF type as a QName, URI or Node definition. If :about is nil, this defaults to the empty string (“”).

  • element (:li, nil) — default: nil

    Render with <li>, otherwise with template default.

  • haml (String) — default: haml_template[:subject]

    Haml template to render.

Yields:

  • (predicate)

    Yields each predicate

Yield Parameters:

  • predicate (RDF::URI)

Yield Returns:

  • (:ignored)

Returns:

  • Builder::RdfXml



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/rdf/rdfxml/writer.rb', line 262

def render_subject(subject, builder, **options, &block)
  return nil if is_done?(subject)

  attr_props, embed_props, types = prop_partition(properties_for_subject(subject))

  # The first type is used for
  first_type = types.shift
  type_qname = get_qname(first_type) if first_type && !first_type.node?
  type_qname = nil unless type_qname.is_a?(String)
  types.unshift(first_type) if first_type && !type_qname
  type_qname ||= "rdf:Description"

  attr_props = attr_props.merge("rdf:nodeID": subject.id) if subject.node? && ref_count(subject) >= 1
  attr_props = attr_props.merge("rdf:about": subject.relativize(base_uri)) if subject.uri?

  log_debug {"render_subject(#{subject.inspect})"}
  subject_done(subject)

  builder.tag!(type_qname, **attr_props) do |b|
    types.each do |type|
      if type.node?
        b.tag!("rdf:type",  "rdf:nodeID": type.id)
      else
        b.tag!("rdf:type",  "rdf:resource": type.to_s)
      end
    end

    log_depth do
      embed_props.each do |p, objects|
        render_property(p, objects, b, **options)
      end
    end
  end
end

#resetObject (protected)

Reset parser to run again



184
185
186
187
188
189
# File 'lib/rdf/rdfxml/writer.rb', line 184

def reset
  @options[:log_depth] = 0
  @references = {}
  @serialized = {}
  @subjects = {}
end

#write_epilogueObject



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/rdf/rdfxml/writer.rb', line 152

def write_epilogue
  @max_depth = @options.fetch(:max_depth, 10)
  @attributes = @options.fetch(:attributes, :none)
  @base_uri = RDF::URI(@options[:base_uri]) if @options[:base_uri]
  @lang = @options[:lang]
  self.reset

  log_debug {"\nserialize: graph size: #{@graph.size}"}

  preprocess
  # Prefixes
  prefix = prefixes.keys.map {|pk| "#{pk}: #{prefixes[pk]}"}.sort.join(" ") unless prefixes.empty?
  log_debug {"\nserialize: prefixes: #{prefix.inspect}"}

  @subjects = order_subjects

  # Generate document
  doc = render_document(@subjects,
    lang:       @lang,
    base:       base_uri,
    prefix:     prefix,
    stylesheet: @options[:stylesheet]) do |s|
    subject(s)
  end
  @output.write(doc)

  super
end

#write_triple(subject, predicate, object)

This method is abstract.

This method returns an undefined value.

Addes a triple to be serialized

Parameters:

  • subject (RDF::Resource)
  • predicate (RDF::URI)
  • object (RDF::Value)

Raises:

  • (NotImplementedError)

    unless implemented in subclass

  • (RDF::WriterError)

    if validating and attempting to write an invalid Term.



148
149
150
# File 'lib/rdf/rdfxml/writer.rb', line 148

def write_triple(subject, predicate, object)
  @graph.insert(RDF::Statement(subject, predicate, object))
end