Class: RDF::RDFXML::Writer
- Inherits:
-
Writer
- Object
- Writer
- RDF::RDFXML::Writer
- 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
Constant Summary collapse
- VALID_ATTRIBUTES =
[:none, :untyped, :typed]
Instance Attribute Summary collapse
-
#base_uri ⇒ RDF::URI
Base URI used for relativizing URIs.
-
#graph ⇒ Graph
Graph of statements serialized.
-
#top_classes ⇒ Array<URI>
readonly
Defines rdf:type of subjects to be emitted at the beginning of the document.
Class Method Summary collapse
-
.options ⇒ Object
RDF/XML Writer options.
Instance Method Summary collapse
-
#initialize(output = $stdout, **options) {|writer| ... } ⇒ Writer
constructor
Initializes the RDF/XML writer instance.
-
#prefix_attrs ⇒ Hash{String => String}
protected
XML namespace attributes for defined prefixes.
-
#preprocess ⇒ ignored
protected
Perform any preprocessing of statements required.
-
#preprocess_statement(statement) ⇒ Object
protected
Perform any statement preprocessing required.
-
#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.
-
#render_document(subjects, lang: nil, base: nil, **options) {|subject| ... } ⇒ Object
protected
Render document using
haml_template[:doc]
. -
#render_property(property, objects, builder, **options) ⇒ Object
protected
Render a single- or multi-valued property.
-
#render_subject(subject, builder, **options) {|predicate| ... } ⇒ Object
protected
Render a subject using
haml_template[:subject]
. -
#reset ⇒ Object
protected
Reset parser to run again.
- #write_epilogue ⇒ Object
-
#write_triple(subject, predicate, object)
abstract
Addes a triple to be serialized.
Constructor Details
#initialize(output = $stdout, **options) {|writer| ... } ⇒ Writer
Initializes the RDF/XML writer instance.
128 129 130 131 132 133 134 135 136 137 |
# File 'lib/rdf/rdfxml/writer.rb', line 128 def initialize(output = $stdout, **, &block) super do @graph = RDF::Graph.new @uri_to_prefix = {} @uri_to_qname = {} @top_classes = [:top_classes] || [RDF::RDFS.Class] block.call(self) if block_given? end end |
Instance Attribute Details
#base_uri ⇒ RDF::URI
Returns Base URI used for relativizing URIs.
64 65 66 |
# File 'lib/rdf/rdfxml/writer.rb', line 64 def base_uri @base_uri end |
#graph ⇒ Graph
Returns Graph of statements serialized.
61 62 63 |
# File 'lib/rdf/rdfxml/writer.rb', line 61 def graph @graph end |
#top_classes ⇒ Array<URI> (readonly)
Defines rdf:type of subjects to be emitted at the beginning of the document.
58 59 60 |
# File 'lib/rdf/rdfxml/writer.rb', line 58 def top_classes @top_classes end |
Class Method Details
.options ⇒ Object
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. 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_attrs ⇒ Hash{String => String} (protected)
XML namespace attributes for defined prefixes
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 |
#preprocess ⇒ ignored (protected)
Perform any preprocessing of statements required
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
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
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, **, &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.
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, **, &block) builder = Builder::RdfXml.new(indent: 2) builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8" builder.instruct! :'xml-stylesheet', type: 'text/xsl', href: [:stylesheet] if [: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, **) 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.
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, **) 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, **) 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, **) 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, **) 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, **) 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.
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, **, &block) return nil if is_done?(subject) attr_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 .each do |p, objects| render_property(p, objects, b, **) end end end end |
#reset ⇒ Object (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_epilogue ⇒ Object
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 returns an undefined value.
Addes a triple to be serialized
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 |