rdf-rdfxml 1.1.0p0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  require 'nokogiri' # FIXME: Implement using different modules as in RDF::TriX
2
- require 'rdf/rdfxml/patches/graph_properties'
2
+ require 'rdf/rdfa'
3
3
 
4
4
  module RDF::RDFXML
5
5
  ##
@@ -48,16 +48,11 @@ module RDF::RDFXML
48
48
  # end
49
49
  #
50
50
  # @author [Gregg Kellogg](http://kellogg-assoc.com/)
51
- class Writer < RDF::Writer
51
+ class Writer < RDF::RDFa::Writer
52
52
  format RDF::RDFXML::Format
53
53
 
54
54
  VALID_ATTRIBUTES = [:none, :untyped, :typed]
55
55
 
56
- # @return [Graph] Graph of statements serialized
57
- attr_accessor :graph
58
- # @return [URI] Base URI used for relativizing URIs
59
- attr_accessor :base_uri
60
-
61
56
  ##
62
57
  # Initializes the RDF/XML writer instance.
63
58
  #
@@ -71,11 +66,9 @@ module RDF::RDFXML
71
66
  # the prefix mappings to use (not supported by all writers)
72
67
  # @option options [#to_s] :base_uri (nil)
73
68
  # the base URI to use when constructing relative URIs
74
- # @option options [Integer] :max_depth (3)
75
- # Maximum depth for recursively defining resources
76
69
  # @option options [#to_s] :lang (nil)
77
70
  # Output as root xml:lang attribute, and avoid generation _xml:lang_ where possible
78
- # @option options [Array] :attributes (nil)
71
+ # @option options [Symbol] :attributes (nil)
79
72
  # How to use XML attributes when serializing, one of :none, :untyped, :typed. The default is :none.
80
73
  # @option options [Boolean] :standard_prefixes (false)
81
74
  # Add standard prefixes to _prefixes_, if necessary.
@@ -86,489 +79,297 @@ module RDF::RDFXML
86
79
  # @yield [writer]
87
80
  # @yieldparam [RDF::Writer] writer
88
81
  def initialize(output = $stdout, options = {}, &block)
89
- super do
90
- @graph = RDF::Repository.new
91
- @uri_to_qname = {}
92
- @uri_to_prefix = {}
93
- block.call(self) if block_given?
94
- end
95
- end
96
-
97
- ##
98
- # Write whole graph
99
- #
100
- # @param [Graph] graph
101
- # @return [void]
102
- def write_graph(graph)
103
- @graph = graph
82
+ super
104
83
  end
105
84
 
106
- ##
107
- # Addes a statement to be serialized
108
- # @param [RDF::Statement] statement
109
- # @return [void]
110
- def write_statement(statement)
111
- @graph.insert(statement)
112
- end
113
-
114
- ##
115
- # Addes a triple to be serialized
116
- # @param [RDF::Resource] subject
117
- # @param [RDF::URI] predicate
118
- # @param [RDF::Value] object
119
- # @return [void]
120
- # @abstract
121
- def write_triple(subject, predicate, object)
122
- @graph.insert(Statement.new(subject, predicate, object))
85
+ # @return [Hash<Symbol => String>]
86
+ def haml_template
87
+ return @haml_template if @haml_template
88
+ case @options[:haml]
89
+ when Hash then @options[:haml]
90
+ else DEFAULT_HAML
91
+ end
123
92
  end
124
93
 
125
- ##
126
- # Outputs the RDF/XML representation of all stored triples.
127
- #
128
- # @return [void]
129
- # @raise [RDF::WriterError] when attempting to write non-conformant graph
130
- # @see #write_triple
131
94
  def write_epilogue
132
95
  @force_RDF_about = {}
133
- @max_depth = @options[:max_depth] || 3
134
- @base_uri = @options[:base_uri]
135
- @lang = @options[:lang]
136
96
  @attributes = @options[:attributes] || :none
137
- @debug = @options[:debug]
138
- raise RDF::WriterError, "Invalid attribute option '#{@attributes}', should be one of #{VALID_ATTRIBUTES.to_sentence}" unless VALID_ATTRIBUTES.include?(@attributes.to_sym)
139
- self.reset
140
-
141
- doc = Nokogiri::XML::Document.new
142
-
143
- add_debug {"\nserialize: graph of size #{@graph.size}"}
144
- add_debug {"options: #{@options.inspect}"}
145
97
 
146
- preprocess
147
-
148
- prefix(:rdf, RDF.to_uri)
149
- prefix(:xml, RDF::XML) if base_uri || @lang
150
-
151
- add_debug {"\nserialize: graph namespaces: #{prefixes.inspect}"}
152
-
153
- doc.root = Nokogiri::XML::Element.new("rdf:RDF", doc)
154
- doc.root["xml:lang"] = @lang if @lang
155
- doc.root["xml:base"] = base_uri if base_uri
156
-
157
- if @options[:stylesheet]
158
- pi = Nokogiri::XML::ProcessingInstruction.new(
159
- doc, "xml-stylesheet",
160
- "type=\"text/xsl\" href=\"#{@options[:stylesheet]}\""
161
- )
162
- doc.root.add_previous_sibling pi
163
- end
164
-
165
- # Add statements for each subject
166
- order_subjects.each do |subject|
167
- #add_debug "{subj: #{subject.inspect}"}
168
- subject(subject, doc.root)
169
- end
98
+ super
99
+ end
170
100
 
171
- prefixes.each_pair do |p, uri|
172
- if p == nil
173
- doc.root.default_namespace = uri.to_s
174
- else
175
- doc.root.add_namespace(p.to_s, uri.to_s)
101
+ protected
102
+ # Render a subject using `haml_template[:subject]`.
103
+ #
104
+ # The _subject_ template may be called either as a top-level element, or recursively under another element if the _rel_ local is not nil.
105
+ #
106
+ # 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.
107
+ #
108
+ # Yields each predicate/property to be rendered separately (@see #render_property_value and `#render_property_values`).
109
+ #
110
+ # @param [Array<RDF::Resource>] subject
111
+ # Subject to render
112
+ # @param [Array<RDF::Resource>] predicates
113
+ # Predicates of subject. Each property is yielded for separate rendering.
114
+ # @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
115
+ # @option options [String] about (nil)
116
+ # About description, a CURIE, URI or Node definition.
117
+ # May be nil if no @about is rendered (e.g. unreferenced Nodes)
118
+ # @option options [String] resource (nil)
119
+ # Resource description, a CURIE, URI or Node definition.
120
+ # May be nil if no @resource is rendered
121
+ # @option options [String] rel (nil)
122
+ # Optional @rel property description, a CURIE, URI or Node definition.
123
+ # @option options [String] typeof (nil)
124
+ # RDF type as a CURIE, URI or Node definition.
125
+ # If :about is nil, this defaults to the empty string ("").
126
+ # @option options [:li, nil] element (nil)
127
+ # Render with &lt;li&gt;, otherwise with template default.
128
+ # @option options [String] haml (haml_template[:subject])
129
+ # Haml template to render.
130
+ # @yield [predicate]
131
+ # Yields each predicate
132
+ # @yieldparam [RDF::URI] predicate
133
+ # @yieldreturn [:ignored]
134
+ # @return String
135
+ # The rendered document is returned as a string
136
+ # Return Haml template for document from `haml_template[:subject]`
137
+ def render_subject(subject, predicates, options = {}, &block)
138
+ # extract those properties that can be rendered as attributes
139
+ attr_props = if [:untyped, :typed].include?(@attributes)
140
+ options[:property_values].inject({}) do |memo, (prop, values)|
141
+ object = values.first
142
+ if values.length == 1 &&
143
+ object.literal? &&
144
+ (object.plain? || @attributes == :typed) &&
145
+ get_lang(object).nil?
146
+
147
+ memo[get_qname(RDF::URI(prop))] = object.value
148
+ end
149
+ memo
176
150
  end
177
- end
178
-
179
- add_debug {"doc:\n #{doc.to_xml(:encoding => "UTF-8", :indent => 2)}"}
180
- doc.write_xml_to(@output, :encoding => "UTF-8", :indent => 2)
181
- end
182
-
183
- # Return a QName for the URI, or nil. Adds namespace of QName to defined prefixes
184
- # @param [URI,#to_s] resource
185
- # @param [Hash<Symbol => Object>] options
186
- # @option [Boolean] :with_default (false) If a default mapping exists, use it, otherwise if a prefixed mapping exists, use it
187
- # @return [String, nil] value to use to identify URI
188
- def get_qname(resource, options = {})
189
- case resource
190
- when RDF::Node
191
- add_debug {"qname(#{resource.inspect}): #{resource}"}
192
- return resource.to_s
193
- when RDF::URI
194
- uri = resource.to_s
195
151
  else
196
- add_debug {"qname(#{resource.inspect}): nil"}
197
- return nil
152
+ {}
198
153
  end
199
154
 
200
- qname = case
201
- when options[:with_default] && prefix(nil) && uri.index(prefix(nil)) == 0
202
- # Don't cache
203
- add_debug {"qname(#{resource.inspect}): #{uri.sub(prefix(nil), '').inspect} (default)"}
204
- return uri.sub(prefix(nil), '')
205
- when @uri_to_qname.has_key?(uri)
206
- add_debug {"qname(#{resource.inspect}): #{@uri_to_qname[uri].inspect} (cached)"}
207
- return @uri_to_qname[uri]
208
- when u = @uri_to_prefix.keys.detect {|u| uri.index(u.to_s) == 0 && NC_REGEXP.match(uri[u.to_s.length..-1])}
209
- # Use a defined prefix
210
- prefix = @uri_to_prefix[u]
211
- prefix(prefix, u) # Define for output
212
- uri.sub(u.to_s, "#{prefix}:")
213
- when @options[:standard_prefixes] && vocab = RDF::Vocabulary.detect {|v| uri.index(v.to_uri.to_s) == 0 && NC_REGEXP.match(uri[v.to_uri.to_s.length..-1])}
214
- prefix = vocab.__name__.to_s.split('::').last.downcase
215
- @uri_to_prefix[vocab.to_uri.to_s] = prefix
216
- prefix(prefix, vocab.to_uri) # Define for output
217
- uri.sub(vocab.to_uri.to_s, "#{prefix}:")
218
- else
219
-
220
- # No vocabulary found, invent one
221
- # Add bindings for predicates not already having bindings
222
- # From RDF/XML Syntax and Processing:
223
- # An XML namespace-qualified name (QName) has restrictions on the legal characters such that not all
224
- # property URIs can be expressed as these names. It is recommended that implementors of RDF serializers,
225
- # in order to break a URI into a namespace name and a local name, split it after the last XML non-NCName
226
- # character, ensuring that the first character of the name is a Letter or '_'. If the URI ends in a
227
- # non-NCName character then throw a "this graph cannot be serialized in RDF/XML" exception or error.
228
- separation = uri.rindex(%r{[^a-zA-Z_0-9-][a-zA-Z_][a-z0-9A-Z_-]*$})
229
- return @uri_to_qname[uri] = nil unless separation
230
- base_uri = uri.to_s[0..separation]
231
- suffix = uri.to_s[separation+1..-1]
232
- @gen_prefix = @gen_prefix ? @gen_prefix.succ : "ns0"
233
- @uri_to_prefix[base_uri] = @gen_prefix
234
- prefix(@gen_prefix, base_uri)
235
- "#{@gen_prefix}:#{suffix}"
236
- end
237
-
238
- add_debug {"qname(#{resource.inspect}): #{qname.inspect}"}
239
- @uri_to_qname[uri] = qname
240
- rescue ArgumentError => e
241
- raise RDF::WriterError, "Invalid URI #{uri.inspect}: #{e.message}"
155
+ predicates -= attr_props.keys
156
+ super(subject, predicates, options.merge(:attr_props => attr_props), &block)
242
157
  end
243
-
244
- protected
245
- # If base_uri is defined, use it to try to make uri relative
246
- # @param [#to_s] uri
247
- # @return [String]
248
- def relativize(uri)
249
- uri = uri.to_s
250
- base_uri ? uri.sub(base_uri.to_s, "") : uri
158
+ # See if we can serialize as attribute.
159
+ # * untyped attributes that aren't duplicated where xml:lang == @lang
160
+ # * typed attributes that aren't duplicated if @dt_as_attr is true
161
+ # * rdf:type
162
+ def predicate_as_attribute?(prop, object)
163
+ [:untyped, :typed].include?(@attributes) && (
164
+ prop == RDF.type ||
165
+ [:typed].include?(@attributes) && object.literal? && object.typed? ||
166
+ (object.literal? && object.simple? || @lang && object.language.to_s == @lang.to_s)
167
+ )
251
168
  end
252
169
 
253
- # Defines rdf:type of subjects to be emitted at the beginning of the graph. Defaults to none
254
- # @return [Array<URI>]
255
- def top_classes; []; end
256
-
257
- # Defines order of predicates to to emit at begninning of a resource description. Defaults to
258
- # `\[rdf:type, rdfs:label, dc:title\]`
259
- # @return [Array<URI>]
260
- def predicate_order; [RDF.type, RDF::RDFS.label, RDF::DC.title]; end
261
-
262
- # Order subjects for output. Override this to output subjects in another order.
170
+ # Render document using `haml_template[:doc]`. Yields each subject to be rendered separately.
263
171
  #
264
- # Uses top_classes
265
- # @return [Array<Resource>] Ordered list of subjects
266
- def order_subjects
267
- seen = {}
268
- subjects = []
269
-
270
- top_classes.each do |class_uri|
271
- graph.query(:predicate => RDF.type, :object => class_uri).map {|st| st.subject}.sort.uniq.each do |subject|
272
- #add_debug "{order_subjects: #{subject.inspect}"}
273
- subjects << subject
274
- seen[subject] = @top_levels[subject] = true
275
- end
276
- end
277
-
278
- # Sort subjects by resources over bnodes, ref_counts and the subject URI itself
279
- recursable = @subjects.keys.
280
- select {|s| !seen.include?(s)}.
281
- map {|r| [(r.is_a?(RDF::Node) ? 1 : 0) + ref_count(r), r]}.
282
- sort_by {|l| l.first }
283
-
284
- subjects += recursable.map{|r| r.last}
172
+ # For RDF/XML pass along a stylesheet option.
173
+ #
174
+ # @param [Array<RDF::Resource>] subjects
175
+ # Ordered list of subjects. Template must yield to each subject, which returns
176
+ # the serialization of that subject (@see #subject_template)
177
+ # @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
178
+ # @option options [RDF::URI] base (nil)
179
+ # Base URI added to document, used for shortening URIs within the document.
180
+ # @option options [Symbol, String] language (nil)
181
+ # Value of @lang attribute in document, also allows included literals to omit
182
+ # an @lang attribute if it is equivalent to that of the document.
183
+ # @option options [String] title (nil)
184
+ # Value of html>head>title element.
185
+ # @option options [String] prefix (nil)
186
+ # Value of @prefix attribute.
187
+ # @option options [String] haml (haml_template[:doc])
188
+ # Haml template to render.
189
+ # @yield [subject]
190
+ # Yields each subject
191
+ # @yieldparam [RDF::URI] subject
192
+ # @yieldreturn [:ignored]
193
+ # @return String
194
+ # The rendered document is returned as a string
195
+ def render_document(subjects, options = {}, &block)
196
+ super(subjects, options.merge(:stylesheet => @options[:stylesheet]), &block)
285
197
  end
286
-
287
- # Perform any preprocessing of statements required
288
- def preprocess
289
- default_namespace = @options[:default_namespace] || prefix(nil)
290
198
 
291
- # Load defined prefixes
292
- (@options[:prefixes] || {}).each_pair do |k, v|
293
- @uri_to_prefix[v.to_s] = k
199
+ # Render a single- or multi-valued predicate using `haml_template[:property_value]` or `haml_template[:property_values]`. 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).
200
+ #
201
+ # For RDF/XML, pass the `:no_list_literals` option onto the `RDFa` implementation because of special considerations for lists in RDF/XML.
202
+ #
203
+ # If a multi-valued property definition is not found within the template, the writer will use the single-valued property definition multiple times.
204
+ #
205
+ # @param [Array<RDF::Resource>] predicate
206
+ # Predicate to render.
207
+ # @param [Array<RDF::Resource>] objects
208
+ # 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.
209
+ # @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
210
+ # @option options [String] :haml (haml_template[:property_value], haml_template[:property_values])
211
+ # Haml template to render. Otherwise, uses `haml_template[:property_value] or haml_template[:property_values]`
212
+ # depending on the cardinality of objects.
213
+ # @option options [Boolean] :no_list_literals
214
+ # Do not serialize as a list if any elements are literal.
215
+ # @yield object, inlist
216
+ # Yields object and if it is contained in a list.
217
+ # @yieldparam [RDF::Resource] object
218
+ # @yieldparam [Boolean] inlist
219
+ # @yieldreturn [String, nil]
220
+ # The block should only return a string for recursive object definitions.
221
+ # @return String
222
+ # The rendered document is returned as a string
223
+ def render_property(predicate, objects, options = {}, &block)
224
+ add_debug {"render_property(#{predicate}): #{objects.inspect}, #{options.inspect}"}
225
+ # If there are multiple objects, and no :property_values is defined, call recursively with
226
+ # each object
227
+
228
+ template = options[:haml]
229
+ template ||= haml_template[:property_value]
230
+
231
+ # Separate out the objects which are lists and render separately
232
+ lists = objects.
233
+ map {|o| RDF::List.new(o, @graph)}.
234
+ select {|l| l.valid? && l.none?(&:literal?)}
235
+
236
+ unless lists.empty?
237
+ # Render non-list objects
238
+ add_debug {"properties with lists: #{lists} non-lists: #{objects - lists.map(&:subject)}"}
239
+ nl = depth {render_property(predicate, objects - lists.map(&:subject), options, &block)} unless objects == lists.map(&:subject)
240
+ return nl.to_s + lists.map do |list|
241
+ # Render each list as multiple properties and set :inlist to true
242
+ list.each_statement {|st| subject_done(st.subject)}
243
+
244
+ add_debug {"list: #{list.inspect} #{list.to_a}"}
245
+ depth do
246
+ render_collection(predicate, list, options) do |object|
247
+ yield(object, true) if block_given?
248
+ end
249
+ end
250
+ end.join(" ")
294
251
  end
295
- @options[:prefixes] = {} # Will define actual used when matched
296
252
 
297
- if default_namespace
298
- add_debug {"preprocess: default_namespace: #{default_namespace}"}
299
- prefix(nil, default_namespace)
253
+ if objects.length > 1
254
+ # Render each property using property_value template
255
+ objects.map do |object|
256
+ depth {render_property(predicate, [object], options, &block)}
257
+ end.join(" ")
258
+ else
259
+ raise RDF::WriterError, "Missing property template" if template.nil?
260
+
261
+ options = {
262
+ :object => objects.first,
263
+ :predicate => predicate,
264
+ :property => get_qname(predicate),
265
+ }.merge(options)
266
+ hamlify(template, options, &block)
300
267
  end
301
-
302
- @graph.each {|statement| preprocess_statement(statement)}
303
- end
304
-
305
- # Perform any statement preprocessing required. This is used to perform reference counts and determine required
306
- # prefixes.
307
- # @param [Statement] statement
308
- def preprocess_statement(statement)
309
- #add_debug {"preprocess: #{statement.inspect}"}
310
- references = ref_count(statement.object) + 1
311
- @references[statement.object] = references
312
- @subjects[statement.subject] = true
313
- end
314
-
315
- # Returns indent string multiplied by the depth
316
- # @param [Integer] modifier Increase depth by specified amount
317
- # @return [String] A number of spaces, depending on current depth
318
- def indent(modifier = 0)
319
- " " * (@depth + modifier)
320
268
  end
321
269
 
322
- def reset
323
- @depth = 0
324
- @lists = {}
325
- prefixes = {}
326
- @references = {}
327
- @serialized = {}
328
- @subjects = {}
329
- @top_levels = {}
330
- end
331
-
332
- private
333
- def subject(subject, parent_node)
334
- node = nil
335
-
336
- raise RDF::WriterError, "Illegal use of subject #{subject.inspect}, not supported in RDF/XML" unless subject.resource?
337
-
338
- if !is_done?(subject)
339
- subject_done(subject)
340
- properties = @graph.properties(subject)
341
- add_debug {"subject: #{subject.inspect}, props: #{properties.inspect}"}
342
-
343
- @graph.query(:subject => subject).each do |st|
344
- raise RDF::WriterError, "Illegal use of predicate #{st.predicate.inspect}, not supported in RDF/XML" unless st.predicate.uri?
345
- end
346
-
347
- rdf_type, *rest = properties.fetch(RDF.type.to_s, [])
348
- qname = get_qname(rdf_type, :with_default => true)
349
- if rdf_type.is_a?(RDF::Node)
350
- # Must serialize with an element
351
- qname = rdf_type = nil
352
- elsif rest.empty?
353
- properties.delete(RDF.type.to_s)
354
- else
355
- properties[RDF.type.to_s] = Array(rest)
356
- end
357
- prop_list = order_properties(properties)
358
- add_debug {"=> property order: #{prop_list.to_sentence}"}
359
-
360
- if qname
361
- rdf_type = nil
362
- else
363
- qname = "rdf:Description"
364
- prefixes[:rdf] = RDF.to_uri
365
- end
366
-
367
- node = Nokogiri::XML::Element.new(qname, parent_node.document)
368
-
369
- node["rdf:type"] = rdf_type if rdf_type
370
-
371
- if subject.is_a?(RDF::Node)
372
- # Only need nodeID if it's referenced elsewhere
373
- if ref_count(subject) > (@depth == 0 ? 0 : 1)
374
- node["rdf:nodeID"] = subject.id
375
- else
376
- node.add_child(Nokogiri::XML::Comment.new(node.document, "Serialization for #{subject}")) if RDF::RDFXML::debug?
377
- end
378
- else
379
- node["rdf:about"] = relativize(subject)
380
- end
381
-
382
- prop_list.each do |prop|
383
- prop_ref = RDF::URI.intern(prop)
384
-
385
- properties[prop].each do |object|
386
- raise RDF::WriterError, "Illegal use of object #{object.inspect}, not supported in RDF/XML" unless object.resource? || object.literal?
387
-
388
- @depth += 1
389
- predicate(prop_ref, object, node, properties[prop].length == 1)
390
- @depth -= 1
391
- end
392
- end
393
- elsif @force_RDF_about.include?(subject)
394
- add_debug {"subject: #{subject.inspect}, force about"}
395
- node = Nokogiri::XML::Element.new("rdf:Description", parent_node.document)
396
- if subject.is_a?(RDF::Node)
397
- node["rdf:nodeID"] = subject.id
398
- else
399
- node["rdf:about"] = relativize(subject)
400
- end
270
+ ##
271
+ # Render a collection, which may be included in a property declaration, or
272
+ # may be recursive within another collection
273
+ #
274
+ # @param [RDF::URI] predicate
275
+ # @param [RDF::List] list
276
+ # @param [Hash{Symbol => Object}] options
277
+ # @yield object
278
+ # Yields object, unless it is an included list
279
+ # @yieldparam [RDF::Resource] object
280
+ # @yieldreturn [String, nil]
281
+ # The block should only return a string for recursive object definitions.
282
+ # @return String
283
+ # The rendered collection is returned as a string
284
+ def render_collection(predicate, list, options = {}, &block)
285
+ template = options[:haml] || haml_template[:collection]
286
+
287
+ options = {
288
+ :list => list,
289
+ :predicate => predicate,
290
+ :property => get_qname(predicate),
291
+ }.merge(options)
292
+ hamlify(template, options) do |object|
293
+ yield object
401
294
  end
402
- @force_RDF_about.delete(subject)
403
-
404
- parent_node.add_child(node) if node
405
295
  end
406
-
407
- # Output a predicate into the specified node.
408
- #
409
- # If _is_unique_ is true, this predicate may be able to be serialized as an attribute
410
- def predicate(prop, object, node, is_unique)
411
- as_attr = predicate_as_attribute?(prop, object) && is_unique
412
-
413
- qname = get_qname(prop, :with_default => !as_attr)
414
- raise RDF::WriterError, "No qname generated for <#{prop}>" unless qname
415
296
 
416
- add_debug do
417
- "predicate: #{qname}, " +
418
- "as_attr: #{as_attr}, " +
419
- "object: #{object.inspect}, " +
420
- "done: #{is_done?(object)}, " +
421
- "subject: #{@subjects.include?(object)}"
297
+ # XML namespace attributes for defined prefixes
298
+ # @return [Hash{String => String}]
299
+ def prefix_attrs
300
+ prefixes.inject({}) do |memo, (k, v)|
301
+ memo[k ? "xmlns:#{k}" : "xmlns"] = v.to_s
302
+ memo
422
303
  end
423
- #qname = "rdf:li" if qname.match(/rdf:_\d+/)
424
- pred_node = Nokogiri::XML::Element.new(qname, node.document)
425
-
426
- o_props = @graph.properties(object)
427
-
428
- list = RDF::List.new(object, @graph)
429
- col = list.to_a
430
- conformant_list = list.valid? && col.none?(&:literal?)
431
- args = xml_args(object)
432
- attrs = args.pop
304
+ end
433
305
 
434
- # Check to see if it can be serialized as a collection
435
- if conformant_list
436
- add_debug {"=> as collection: [#{col.map(&:to_s).join(", ")}]"}
437
- # Serialize list as parseType="Collection"
438
- pred_node.add_child(Nokogiri::XML::Comment.new(node.document, "Serialization for #{object}")) if RDF::RDFXML::debug?
439
- pred_node["rdf:parseType"] = "Collection"
440
- while o_props[RDF.first.to_s]
441
- # Object is used only for referencing collection item and next
442
- subject_done(object)
443
- item = o_props[RDF.first.to_s].first
444
- object = o_props[RDF.rest.to_s].first
445
- o_props = @graph.properties(object)
446
- add_debug {"=> li first: #{item}, rest: #{object}"}
447
- @force_RDF_about[item] = true
448
- subject(item, pred_node)
449
- end
450
- elsif as_attr
451
- # Serialize as attribute
452
- pred_node.unlink
453
- pred_node = nil
454
- node[qname] = object.is_a?(RDF::URI) ? relativize(object) : object.value
455
- add_debug {"=> as attribute: node[#{qname}]=#{node[qname]}, #{object.class}"}
456
- elsif object.literal?
457
- # Serialize as element
458
- add_debug {"predicate as element: #{attrs.inspect}"}
459
- attrs.each_pair do |a, av|
460
- next if a.to_s == "xml:lang" && av.to_s == @lang # Lang already specified, don't repeat
461
- add_debug {"=> elt attr #{a}=#{av}"}
462
- pred_node[a] = av.to_s
463
- end
464
- add_debug {"=> elt #{'xmllit ' if object.literal? && object.datatype == RDF.XMLLiteral}content=#{args.first}"} if !args.empty?
465
- if object.datatype == RDF.XMLLiteral
466
- pred_node.inner_html = args.first.to_s
467
- elsif args.first
468
- pred_node.content = args.first
469
- end
470
- elsif @depth < @max_depth && !is_done?(object) && @subjects.include?(object)
471
- add_debug(" as element (recurse)")
472
- @depth += 1
473
- subject(object, pred_node)
474
- @depth -= 1
475
- elsif object.is_a?(RDF::Node)
476
- add_debug("=> as element (nodeID)")
477
- pred_node["rdf:nodeID"] = object.id
478
- else
479
- add_debug("=> as element (resource)")
480
- pred_node["rdf:resource"] = relativize(object)
306
+ # Perform any preprocessing of statements required
307
+ # @return [ignored]
308
+ def preprocess
309
+ if @options[:default_namespace]
310
+ prefix(nil, @options[:default_namespace])
481
311
  end
482
-
483
- node.add_child(pred_node) if pred_node
312
+
313
+ super
314
+ prefix(:rdf, RDF.to_uri)
315
+ prefix(:xml, RDF::XML) if base_uri || @options[:lang]
484
316
  end
485
317
 
486
- # Mark a subject as done.
487
- def subject_done(subject)
488
- add_debug {"subject_done: #{subject}"}
489
- @serialized[subject] = true
490
- end
491
-
492
- # Return the number of times this node has been referenced in the object position
493
- def ref_count(node)
494
- @references.fetch(node, 0)
318
+ ##
319
+ # Turn CURIE into a QNAME
320
+ def get_qname(uri)
321
+ curie = get_curie(uri)
322
+ curie.start_with?(":") ? curie[1..-1] : curie
495
323
  end
496
324
 
497
- def is_done?(subject)
498
- #add_debug {"is_done?(#{subject}): #{@serialized.include?(subject)}"}
499
- @serialized.include?(subject)
500
- end
501
-
502
- # See if we can serialize as attribute.
503
- # * untyped attributes that aren't duplicated where xml:lang == @lang
504
- # * typed attributes that aren't duplicated if @dt_as_attr is true
505
- # * rdf:type
506
- def predicate_as_attribute?(prop, object)
507
- [:untyped, :typed].include?(@attributes) && (
508
- prop == RDF.type ||
509
- [:typed].include?(@attributes) && object.literal? && object.typed? ||
510
- (object.literal? && object.simple? || @lang && object.language.to_s == @lang.to_s)
511
- )
512
- end
513
-
514
- # Take a hash from predicate uris to lists of values.
515
- # Sort the lists of values. Return a sorted list of properties.
516
- # @param [Hash{String => Array<Resource>}] properties A hash of Property to Resource mappings
517
- # @return [Array<String>}] Ordered list of properties. Uses predicate_order.
518
- def order_properties(properties)
519
- properties.keys.each do |k|
520
- properties[k] = properties[k].sort_by(&:to_s)
521
- end
522
-
523
- # Make sorted list of properties
524
- prop_list = []
525
-
526
- predicate_order.each do |prop|
527
- next unless properties[prop]
528
- prop_list << prop.to_s
529
- end
530
-
531
- properties.keys.sort.each do |prop|
532
- next if prop_list.include?(prop.to_s)
533
- prop_list << prop.to_s
534
- end
535
-
536
- prop_list
325
+ # Perform any statement preprocessing required. This is used to perform reference counts and determine required prefixes.
326
+ #
327
+ # For RDF/XML, make sure that all predicates have CURIEs
328
+ # @param [Statement] statement
329
+ def preprocess_statement(statement)
330
+ super
331
+
332
+ # Invent a prefix for the predicate, if necessary
333
+ ensure_curie(statement.predicate)
334
+ ensure_curie(statement.object) if statement.predicate == RDF.type
537
335
  end
538
336
 
539
- # XML content and arguments for serialization
540
- # Encoding.the_null_encoding.xml_args("foo", "en-US") => ["foo", {"xml:lang" => "en-US"}]
541
- def xml_args(object)
542
- case object
543
- when RDF::Literal
544
- if object.simple?
545
- [object.value, {}]
546
- elsif object.has_language?
547
- [object.value, {"xml:lang" => object.language}]
548
- elsif object.datatype == RDF.XMLLiteral
549
- [object.value, {"rdf:parseType" => "Literal"}]
550
- else
551
- [object.value, {"rdf:datatype" => object.datatype.to_s}]
552
- end
553
- when RDF::Node
554
- [{"rdf:nodeID" => object.id}]
555
- when RDF::URI
556
- [{"rdf:resource" => object.to_s}]
557
- else
558
- raise RDF::WriterError, "Attempt to serialize #{object.inspect}, not supported in RDF/XML"
337
+ # Make sure a CURIE is defined
338
+ def ensure_curie(resource)
339
+ if get_curie(resource) == resource.to_s || get_curie(resource).split(':', 2).last =~ /[\.#]/
340
+ uri = resource.to_s
341
+ # No vocabulary found, invent one
342
+ # Add bindings for predicates not already having bindings
343
+ # From RDF/XML Syntax and Processing:
344
+ # An XML namespace-qualified name (QName) has restrictions on the legal characters such that not all property URIs can be expressed as these names. It is recommended that implementors of RDF serializers, in order to break a URI into a namespace name and a local name, split it after the last XML non-NCName character, ensuring that the first character of the name is a Letter or '_'. If the URI ends in a non-NCName character then throw a "this graph cannot be serialized in RDF/XML" exception or error.
345
+ separation = uri.rindex(%r{[^a-zA-Z_0-9-][a-zA-Z_][a-z0-9A-Z_-]*$})
346
+ return @uri_to_prefix[uri] = nil unless separation
347
+ base_uri = uri.to_s[0..separation]
348
+ suffix = uri.to_s[separation+1..-1]
349
+ @gen_prefix = @gen_prefix ? @gen_prefix.succ : "ns0"
350
+ add_debug {"ensure_curie: generated prefix #{@gen_prefix} for #{base_uri}"}
351
+ @uri_to_prefix[base_uri] = @gen_prefix
352
+ @uri_to_term_or_curie[uri] = "#{@gen_prefix}:#{suffix}"
353
+ prefix(@gen_prefix, base_uri)
354
+ get_curie(resource)
559
355
  end
560
356
  end
561
357
 
562
- # Add debug event to debug array, if specified
563
- #
564
- # @param [String] message
565
- # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
566
- def add_debug(message = "")
567
- return unless ::RDF::RDFXML.debug? || @debug
568
- message = message + yield if block_given?
569
- msg = "#{' ' * @depth}#{message}"
570
- STDERR.puts msg if ::RDF::RDFXML.debug?
571
- @debug << msg.force_encoding("utf-8") if @debug.is_a?(Array)
358
+ # If base_uri is defined, use it to try to make uri relative
359
+ # @param [#to_s] uri
360
+ # @return [String]
361
+ def relativize(uri)
362
+ uri = expand_curie(uri.to_s)
363
+ base_uri ? uri.sub(base_uri.to_s, "") : uri
364
+ end
365
+
366
+ # Undo CURIE
367
+ # @return [RDF::URI]
368
+ def expand_curie(curie)
369
+ pfx, suffix = curie.split(":", 2)
370
+ prefix(pfx) ? prefix(pfx) + suffix : curie
572
371
  end
573
372
  end
574
- end
373
+ end
374
+
375
+ require 'rdf/rdfxml/writer/haml_templates'