the-experimenters-rdf-rdfxml 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +13 -0
- data/AUTHORS +1 -0
- data/CONTRIBUTORS +1 -0
- data/History.rdoc +100 -0
- data/README +95 -0
- data/README.md +95 -0
- data/Rakefile +59 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/etc/doap.nt +47 -0
- data/etc/doap.xml +73 -0
- data/example.rb +37 -0
- data/lib/rdf/rdfxml.rb +50 -0
- data/lib/rdf/rdfxml/format.rb +43 -0
- data/lib/rdf/rdfxml/patches/array_hacks.rb +53 -0
- data/lib/rdf/rdfxml/patches/graph_properties.rb +34 -0
- data/lib/rdf/rdfxml/patches/literal_hacks.rb +156 -0
- data/lib/rdf/rdfxml/patches/nokogiri_hacks.rb +16 -0
- data/lib/rdf/rdfxml/reader.rb +646 -0
- data/lib/rdf/rdfxml/version.rb +18 -0
- data/lib/rdf/rdfxml/vocab.rb +3 -0
- data/lib/rdf/rdfxml/writer.rb +559 -0
- data/rdf-rdfxml.gemspec +109 -0
- data/script/console +10 -0
- data/script/parse +55 -0
- data/script/tc +50 -0
- data/script/yard-to-rubyforge +2 -0
- data/spec/.gitignore +1 -0
- data/spec/format_spec.rb +28 -0
- data/spec/graph_spec.rb +59 -0
- data/spec/literal_spec.rb +244 -0
- data/spec/matchers.rb +79 -0
- data/spec/rdf_test.rb +69 -0
- data/spec/reader_spec.rb +361 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/writer_spec.rb +714 -0
- metadata +190 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module RDF::RDFXML::VERSION
|
2
|
+
VERSION_FILE = File.join(File.expand_path(File.dirname(__FILE__)), "..", "..", "..", "VERSION")
|
3
|
+
MAJOR, MINOR, TINY, EXTRA = File.read(VERSION_FILE).chop.split(".")
|
4
|
+
|
5
|
+
STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.')
|
6
|
+
|
7
|
+
##
|
8
|
+
# @return [String]
|
9
|
+
def self.to_s() STRING end
|
10
|
+
|
11
|
+
##
|
12
|
+
# @return [String]
|
13
|
+
def self.to_str() STRING end
|
14
|
+
|
15
|
+
##
|
16
|
+
# @return [Array(Integer, Integer, Integer)]
|
17
|
+
def self.to_a() STRING.split(".") end
|
18
|
+
end
|
@@ -0,0 +1,559 @@
|
|
1
|
+
require 'nokogiri' # FIXME: Implement using different modules as in RDF::TriX
|
2
|
+
require 'rdf/rdfxml/patches/graph_properties'
|
3
|
+
|
4
|
+
module RDF::RDFXML
|
5
|
+
##
|
6
|
+
# An RDF/XML serialiser in Ruby
|
7
|
+
#
|
8
|
+
# Note that the natural interface is to write a whole graph at a time.
|
9
|
+
# Writing statements or Triples will create a graph to add them to
|
10
|
+
# and then serialize the graph.
|
11
|
+
#
|
12
|
+
# The writer will add prefix definitions, and use them for creating @prefix definitions, and minting QNames
|
13
|
+
#
|
14
|
+
# @example Obtaining a RDF/XML writer class
|
15
|
+
# RDF::Writer.for(:rdf) #=> RDF::RDFXML::Writer
|
16
|
+
# RDF::Writer.for("etc/test.rdf")
|
17
|
+
# RDF::Writer.for(:file_name => "etc/test.rdf")
|
18
|
+
# RDF::Writer.for(:file_extension => "rdf")
|
19
|
+
# RDF::Writer.for(:content_type => "application/rdf+xml")
|
20
|
+
#
|
21
|
+
# @example Serializing RDF graph into an RDF/XML file
|
22
|
+
# RDF::RDFXML::Write.open("etc/test.rdf") do |writer|
|
23
|
+
# writer << graph
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @example Serializing RDF statements into an RDF/XML file
|
27
|
+
# RDF::RDFXML::Writer.open("etc/test.rdf") do |writer|
|
28
|
+
# graph.each_statement do |statement|
|
29
|
+
# writer << statement
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @example Serializing RDF statements into an RDF/XML string
|
34
|
+
# RDF::RDFXML::Writer.buffer do |writer|
|
35
|
+
# graph.each_statement do |statement|
|
36
|
+
# writer << statement
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @example Creating @base and @prefix definitions in output
|
41
|
+
# RDF::RDFXML::Writer.buffer(:base_uri => "http://example.com/", :prefixes => {
|
42
|
+
# nil => "http://example.com/ns#",
|
43
|
+
# :foaf => "http://xmlns.com/foaf/0.1/"}
|
44
|
+
# ) do |writer|
|
45
|
+
# graph.each_statement do |statement|
|
46
|
+
# writer << statement
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @author [Gregg Kellogg](http://kellogg-assoc.com/)
|
51
|
+
class Writer < RDF::Writer
|
52
|
+
format RDF::RDFXML::Format
|
53
|
+
|
54
|
+
VALID_ATTRIBUTES = [:none, :untyped, :typed]
|
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
|
+
##
|
62
|
+
# Initializes the RDF/XML writer instance.
|
63
|
+
#
|
64
|
+
# @param [IO, File] output
|
65
|
+
# the output stream
|
66
|
+
# @param [Hash{Symbol => Object}] options
|
67
|
+
# any additional options
|
68
|
+
# @option options [Boolean] :canonicalize (false)
|
69
|
+
# whether to canonicalize literals when serializing
|
70
|
+
# @option options [Hash] :prefixes (Hash.new)
|
71
|
+
# the prefix mappings to use (not supported by all writers)
|
72
|
+
# @option options [#to_s] :base_uri (nil)
|
73
|
+
# the base URI to use when constructing relative URIs
|
74
|
+
# @option options [Integer] :max_depth (3)
|
75
|
+
# Maximum depth for recursively defining resources
|
76
|
+
# @option options [#to_s] :lang (nil)
|
77
|
+
# Output as root xml:lang attribute, and avoid generation _xml:lang_ where possible
|
78
|
+
# @option options [Array] :attributes (nil)
|
79
|
+
# How to use XML attributes when serializing, one of :none, :untyped, :typed. The default is :none.
|
80
|
+
# @option options [Boolean] :standard_prefixes (false)
|
81
|
+
# Add standard prefixes to _prefixes_, if necessary.
|
82
|
+
# @option options [String] :default_namespace (nil)
|
83
|
+
# URI to use as default namespace, same as prefix(nil)
|
84
|
+
# @yield [writer]
|
85
|
+
# @yieldparam [RDF::Writer] writer
|
86
|
+
def initialize(output = $stdout, options = {}, &block)
|
87
|
+
super do
|
88
|
+
@graph = RDF::Graph.new
|
89
|
+
@uri_to_qname = {}
|
90
|
+
@uri_to_prefix = {}
|
91
|
+
block.call(self) if block_given?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Write whole graph
|
97
|
+
#
|
98
|
+
# @param [Graph] graph
|
99
|
+
# @return [void]
|
100
|
+
def write_graph(graph)
|
101
|
+
@graph = graph
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Addes a statement to be serialized
|
106
|
+
# @param [RDF::Statement] statement
|
107
|
+
# @return [void]
|
108
|
+
def write_statement(statement)
|
109
|
+
@graph.insert(statement)
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Addes a triple to be serialized
|
114
|
+
# @param [RDF::Resource] subject
|
115
|
+
# @param [RDF::URI] predicate
|
116
|
+
# @param [RDF::Value] object
|
117
|
+
# @return [void]
|
118
|
+
# @abstract
|
119
|
+
def write_triple(subject, predicate, object)
|
120
|
+
@graph.insert(Statement.new(subject, predicate, object))
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Outputs the RDF/XML representation of all stored triples.
|
125
|
+
#
|
126
|
+
# @return [void]
|
127
|
+
# @raise [RDF::WriterError] when attempting to write non-conformant graph
|
128
|
+
# @see #write_triple
|
129
|
+
def write_epilogue
|
130
|
+
@force_RDF_about = {}
|
131
|
+
@max_depth = @options[:max_depth] || 3
|
132
|
+
@base_uri = @options[:base_uri]
|
133
|
+
@lang = @options[:lang]
|
134
|
+
@attributes = @options[:attributes] || :none
|
135
|
+
@debug = @options[:debug]
|
136
|
+
raise RDF::WriterError, "Invalid attribute option '#{@attributes}', should be one of #{VALID_ATTRIBUTES.to_sentence}" unless VALID_ATTRIBUTES.include?(@attributes.to_sym)
|
137
|
+
self.reset
|
138
|
+
|
139
|
+
doc = Nokogiri::XML::Document.new
|
140
|
+
|
141
|
+
add_debug "\nserialize: graph of size #{@graph.size}"
|
142
|
+
add_debug "options: #{@options.inspect}"
|
143
|
+
|
144
|
+
preprocess
|
145
|
+
|
146
|
+
prefix(:rdf, RDF.to_uri)
|
147
|
+
prefix(:xml, RDF::XML) if @base_uri || @lang
|
148
|
+
|
149
|
+
add_debug "\nserialize: graph namespaces: #{prefixes.inspect}"
|
150
|
+
|
151
|
+
doc.root = Nokogiri::XML::Element.new("rdf:RDF", doc)
|
152
|
+
doc.root["xml:lang"] = @lang if @lang
|
153
|
+
doc.root["xml:base"] = @base_uri if @base_uri
|
154
|
+
|
155
|
+
# Add statements for each subject
|
156
|
+
order_subjects.each do |subject|
|
157
|
+
#add_debug "subj: #{subject.inspect}"
|
158
|
+
subject(subject, doc.root)
|
159
|
+
end
|
160
|
+
|
161
|
+
prefixes.each_pair do |p, uri|
|
162
|
+
if p == nil
|
163
|
+
doc.root.default_namespace = uri.to_s
|
164
|
+
else
|
165
|
+
doc.root.add_namespace(p.to_s, uri.to_s)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
add_debug "doc:\n #{doc.to_xml(:encoding => "UTF-8", :indent => 2)}"
|
170
|
+
doc.write_xml_to(@output, :encoding => "UTF-8", :indent => 2)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Return a QName for the URI, or nil. Adds namespace of QName to defined prefixes
|
174
|
+
# @param [URI,#to_s] resource
|
175
|
+
# @param [Hash<Symbol => Object>] options
|
176
|
+
# @option [Boolean] :with_default (false) If a default mapping exists, use it, otherwise if a prefixed mapping exists, use it
|
177
|
+
# @return [String, nil] value to use to identify URI
|
178
|
+
def get_qname(resource, options = {})
|
179
|
+
case resource
|
180
|
+
when RDF::Node
|
181
|
+
add_debug "qname(#{resource.inspect}): #{resource}"
|
182
|
+
return resource.to_s
|
183
|
+
when RDF::URI
|
184
|
+
uri = resource.to_s
|
185
|
+
else
|
186
|
+
add_debug "qname(#{resource.inspect}): nil"
|
187
|
+
return nil
|
188
|
+
end
|
189
|
+
|
190
|
+
qname = case
|
191
|
+
when options[:with_default] && prefix(nil) && uri.index(prefix(nil)) == 0
|
192
|
+
# Don't cache
|
193
|
+
add_debug "qname(#{resource.inspect}): #{uri.sub(prefix(nil), '').inspect} (default)"
|
194
|
+
return uri.sub(prefix(nil), '')
|
195
|
+
when @uri_to_qname.has_key?(uri)
|
196
|
+
add_debug "qname(#{resource.inspect}): #{@uri_to_qname[uri].inspect} (cached)"
|
197
|
+
return @uri_to_qname[uri]
|
198
|
+
when u = @uri_to_prefix.keys.detect {|u| uri.index(u.to_s) == 0 && NC_REGEXP.match(uri[u.to_s.length..-1])}
|
199
|
+
# Use a defined prefix
|
200
|
+
prefix = @uri_to_prefix[u]
|
201
|
+
prefix(prefix, u) # Define for output
|
202
|
+
uri.sub(u.to_s, "#{prefix}:")
|
203
|
+
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])}
|
204
|
+
prefix = vocab.__name__.to_s.split('::').last.downcase
|
205
|
+
@uri_to_prefix[vocab.to_uri.to_s] = prefix
|
206
|
+
prefix(prefix, vocab.to_uri) # Define for output
|
207
|
+
uri.sub(vocab.to_uri.to_s, "#{prefix}:")
|
208
|
+
else
|
209
|
+
|
210
|
+
# No vocabulary found, invent one
|
211
|
+
# Add bindings for predicates not already having bindings
|
212
|
+
# From RDF/XML Syntax and Processing:
|
213
|
+
# An XML namespace-qualified name (QName) has restrictions on the legal characters such that not all
|
214
|
+
# property URIs can be expressed as these names. It is recommended that implementors of RDF serializers,
|
215
|
+
# in order to break a URI into a namespace name and a local name, split it after the last XML non-NCName
|
216
|
+
# character, ensuring that the first character of the name is a Letter or '_'. If the URI ends in a
|
217
|
+
# non-NCName character then throw a "this graph cannot be serialized in RDF/XML" exception or error.
|
218
|
+
separation = uri.rindex(%r{[^a-zA-Z_0-9-][a-zA-Z_][a-z0-9A-Z_-]*$})
|
219
|
+
return @uri_to_qname[uri] = nil unless separation
|
220
|
+
base_uri = uri.to_s[0..separation]
|
221
|
+
suffix = uri.to_s[separation+1..-1]
|
222
|
+
@gen_prefix = @gen_prefix ? @gen_prefix.succ : "ns0"
|
223
|
+
@uri_to_prefix[base_uri] = @gen_prefix
|
224
|
+
prefix(@gen_prefix, base_uri)
|
225
|
+
"#{@gen_prefix}:#{suffix}"
|
226
|
+
end
|
227
|
+
|
228
|
+
add_debug "qname(#{resource.inspect}): #{qname.inspect}"
|
229
|
+
@uri_to_qname[uri] = qname
|
230
|
+
rescue Addressable::URI::InvalidURIError => e
|
231
|
+
raise RDF::WriterError, "Invalid URI #{uri.inspect}: #{e.message}"
|
232
|
+
end
|
233
|
+
|
234
|
+
protected
|
235
|
+
# If @base_uri is defined, use it to try to make uri relative
|
236
|
+
# @param [#to_s] uri
|
237
|
+
# @return [String]
|
238
|
+
def relativize(uri)
|
239
|
+
uri = uri.to_s
|
240
|
+
@base_uri ? uri.sub(@base_uri.to_s, "") : uri
|
241
|
+
end
|
242
|
+
|
243
|
+
# Defines rdf:type of subjects to be emitted at the beginning of the graph. Defaults to none
|
244
|
+
# @return [Array<URI>]
|
245
|
+
def top_classes; []; end
|
246
|
+
|
247
|
+
# Defines order of predicates to to emit at begninning of a resource description. Defaults to
|
248
|
+
# [rdf:type, rdfs:label, dc:title]
|
249
|
+
# @return [Array<URI>]
|
250
|
+
def predicate_order; [RDF.type, RDF::RDFS.label, RDF::DC.title]; end
|
251
|
+
|
252
|
+
# Order subjects for output. Override this to output subjects in another order.
|
253
|
+
#
|
254
|
+
# Uses top_classes
|
255
|
+
# @return [Array<Resource>] Ordered list of subjects
|
256
|
+
def order_subjects
|
257
|
+
seen = {}
|
258
|
+
subjects = []
|
259
|
+
|
260
|
+
top_classes.each do |class_uri|
|
261
|
+
graph.query(:predicate => RDF.type, :object => class_uri).map {|st| st.subject}.sort.uniq.each do |subject|
|
262
|
+
#add_debug "order_subjects: #{subject.inspect}"
|
263
|
+
subjects << subject
|
264
|
+
seen[subject] = @top_levels[subject] = true
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Sort subjects by resources over bnodes, ref_counts and the subject URI itself
|
269
|
+
recursable = @subjects.keys.
|
270
|
+
select {|s| !seen.include?(s)}.
|
271
|
+
map {|r| [(r.is_a?(RDF::Node) ? 1 : 0) + ref_count(r), r]}.
|
272
|
+
sort_by {|l| l.first }
|
273
|
+
|
274
|
+
subjects += recursable.map{|r| r.last}
|
275
|
+
end
|
276
|
+
|
277
|
+
# Perform any preprocessing of statements required
|
278
|
+
def preprocess
|
279
|
+
default_namespace = @options[:default_namespace] || prefix(nil)
|
280
|
+
|
281
|
+
# Load defined prefixes
|
282
|
+
(@options[:prefixes] || {}).each_pair do |k, v|
|
283
|
+
@uri_to_prefix[v.to_s] = k
|
284
|
+
end
|
285
|
+
@options[:prefixes] = {} # Will define actual used when matched
|
286
|
+
|
287
|
+
if default_namespace
|
288
|
+
add_debug("preprocess: default_namespace: #{default_namespace}")
|
289
|
+
prefix(nil, default_namespace)
|
290
|
+
end
|
291
|
+
|
292
|
+
@graph.each {|statement| preprocess_statement(statement)}
|
293
|
+
end
|
294
|
+
|
295
|
+
# Perform any statement preprocessing required. This is used to perform reference counts and determine required
|
296
|
+
# prefixes.
|
297
|
+
# @param [Statement] statement
|
298
|
+
def preprocess_statement(statement)
|
299
|
+
#add_debug "preprocess: #{statement.inspect}"
|
300
|
+
references = ref_count(statement.object) + 1
|
301
|
+
@references[statement.object] = references
|
302
|
+
@subjects[statement.subject] = true
|
303
|
+
end
|
304
|
+
|
305
|
+
# Returns indent string multiplied by the depth
|
306
|
+
# @param [Integer] modifier Increase depth by specified amount
|
307
|
+
# @return [String] A number of spaces, depending on current depth
|
308
|
+
def indent(modifier = 0)
|
309
|
+
" " * (@depth + modifier)
|
310
|
+
end
|
311
|
+
|
312
|
+
def reset
|
313
|
+
@depth = 0
|
314
|
+
@lists = {}
|
315
|
+
prefixes = {}
|
316
|
+
@references = {}
|
317
|
+
@serialized = {}
|
318
|
+
@subjects = {}
|
319
|
+
@top_levels = {}
|
320
|
+
end
|
321
|
+
|
322
|
+
private
|
323
|
+
def subject(subject, parent_node)
|
324
|
+
node = nil
|
325
|
+
|
326
|
+
raise RDF::WriterError, "Illegal use of subject #{subject.inspect}, not supported in RDF/XML" unless subject.resource?
|
327
|
+
|
328
|
+
if !is_done?(subject)
|
329
|
+
subject_done(subject)
|
330
|
+
properties = @graph.properties(subject)
|
331
|
+
add_debug "subject: #{subject.inspect}, props: #{properties.inspect}"
|
332
|
+
|
333
|
+
@graph.query(:subject => subject).each do |st|
|
334
|
+
raise RDF::WriterError, "Illegal use of predicate #{st.predicate.inspect}, not supported in RDF/XML" unless st.predicate.uri?
|
335
|
+
end
|
336
|
+
|
337
|
+
rdf_type, *rest = properties.fetch(RDF.type.to_s, [])
|
338
|
+
qname = get_qname(rdf_type, :with_default => true)
|
339
|
+
if rdf_type.is_a?(RDF::Node)
|
340
|
+
# Must serialize with an element
|
341
|
+
qname = rdf_type = nil
|
342
|
+
elsif rest.empty?
|
343
|
+
properties.delete(RDF.type.to_s)
|
344
|
+
else
|
345
|
+
properties[RDF.type.to_s] = [rest].flatten.compact
|
346
|
+
end
|
347
|
+
prop_list = order_properties(properties)
|
348
|
+
add_debug "=> property order: #{prop_list.to_sentence}"
|
349
|
+
|
350
|
+
if qname
|
351
|
+
rdf_type = nil
|
352
|
+
else
|
353
|
+
qname = "rdf:Description"
|
354
|
+
prefixes[:rdf] = RDF.to_uri
|
355
|
+
end
|
356
|
+
|
357
|
+
node = Nokogiri::XML::Element.new(qname, parent_node.document)
|
358
|
+
|
359
|
+
node["rdf:type"] = rdf_type if rdf_type
|
360
|
+
|
361
|
+
if subject.is_a?(RDF::Node)
|
362
|
+
# Only need nodeID if it's referenced elsewhere
|
363
|
+
if ref_count(subject) > (@depth == 0 ? 0 : 1)
|
364
|
+
node["rdf:nodeID"] = subject.id
|
365
|
+
else
|
366
|
+
node.add_child(Nokogiri::XML::Comment.new(node.document, "Serialization for #{subject}")) if RDF::RDFXML::debug?
|
367
|
+
end
|
368
|
+
else
|
369
|
+
node["rdf:about"] = relativize(subject)
|
370
|
+
end
|
371
|
+
|
372
|
+
prop_list.each do |prop|
|
373
|
+
prop_ref = RDF::URI.intern(prop)
|
374
|
+
|
375
|
+
properties[prop].each do |object|
|
376
|
+
raise RDF::WriterError, "Illegal use of object #{object.inspect}, not supported in RDF/XML" unless object.resource? || object.literal?
|
377
|
+
|
378
|
+
@depth += 1
|
379
|
+
predicate(prop_ref, object, node, properties[prop].length == 1)
|
380
|
+
@depth -= 1
|
381
|
+
end
|
382
|
+
end
|
383
|
+
elsif @force_RDF_about.include?(subject)
|
384
|
+
add_debug "subject: #{subject.inspect}, force about"
|
385
|
+
node = Nokogiri::XML::Element.new("rdf:Description", parent_node.document)
|
386
|
+
if subject.is_a?(RDF::Node)
|
387
|
+
node["rdf:nodeID"] = subject.id
|
388
|
+
else
|
389
|
+
node["rdf:about"] = relativize(subject)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
@force_RDF_about.delete(subject)
|
393
|
+
|
394
|
+
parent_node.add_child(node) if node
|
395
|
+
end
|
396
|
+
|
397
|
+
# Output a predicate into the specified node.
|
398
|
+
#
|
399
|
+
# If _is_unique_ is true, this predicate may be able to be serialized as an attribute
|
400
|
+
def predicate(prop, object, node, is_unique)
|
401
|
+
as_attr = predicate_as_attribute?(prop, object) && is_unique
|
402
|
+
|
403
|
+
qname = get_qname(prop, :with_default => !as_attr)
|
404
|
+
raise RDF::WriterError, "No qname generated for <#{prop}>" unless qname
|
405
|
+
|
406
|
+
add_debug "predicate: #{qname}, as_attr: #{as_attr}, object: #{object.inspect}, done: #{is_done?(object)}, subject: #{@subjects.include?(object)}"
|
407
|
+
#qname = "rdf:li" if qname.match(/rdf:_\d+/)
|
408
|
+
pred_node = Nokogiri::XML::Element.new(qname, node.document)
|
409
|
+
|
410
|
+
o_props = @graph.properties(object)
|
411
|
+
|
412
|
+
col = RDF::List.new(object, @graph).to_a
|
413
|
+
conformant_list = col.all? {|item| !item.literal?} && o_props[RDF.first.to_s]
|
414
|
+
args = xml_args(object)
|
415
|
+
attrs = args.pop
|
416
|
+
|
417
|
+
# Check to see if it can be serialized as a collection
|
418
|
+
if conformant_list
|
419
|
+
add_debug("=> as collection: [#{col.map(&:to_s).join(", ")}]")
|
420
|
+
# Serialize list as parseType="Collection"
|
421
|
+
pred_node.add_child(Nokogiri::XML::Comment.new(node.document, "Serialization for #{object}")) if RDF::RDFXML::debug?
|
422
|
+
pred_node["rdf:parseType"] = "Collection"
|
423
|
+
while o_props[RDF.first.to_s]
|
424
|
+
# Object is used only for referencing collection item and next
|
425
|
+
subject_done(object)
|
426
|
+
item = o_props[RDF.first.to_s].first
|
427
|
+
object = o_props[RDF.rest.to_s].first
|
428
|
+
o_props = @graph.properties(object)
|
429
|
+
add_debug("=> li first: #{item}, rest: #{object}")
|
430
|
+
@force_RDF_about[item] = true
|
431
|
+
subject(item, pred_node)
|
432
|
+
end
|
433
|
+
elsif as_attr
|
434
|
+
# Serialize as attribute
|
435
|
+
pred_node.unlink
|
436
|
+
pred_node = nil
|
437
|
+
node[qname] = object.is_a?(RDF::URI) ? relativize(object) : object.value
|
438
|
+
add_debug("=> as attribute: node[#{qname}]=#{node[qname]}, #{object.class}")
|
439
|
+
elsif object.literal?
|
440
|
+
# Serialize as element
|
441
|
+
add_debug("predicate as element: #{attrs.inspect}")
|
442
|
+
attrs.each_pair do |a, av|
|
443
|
+
next if a.to_s == "xml:lang" && av.to_s == @lang # Lang already specified, don't repeat
|
444
|
+
add_debug "=> elt attr #{a}=#{av}"
|
445
|
+
pred_node[a] = av.to_s
|
446
|
+
end
|
447
|
+
add_debug "=> elt #{'xmllit ' if object.literal? && object.datatype == RDF.XMLLiteral}content=#{args.first}" if !args.empty?
|
448
|
+
if object.datatype == RDF.XMLLiteral
|
449
|
+
pred_node.inner_html = args.first.to_s
|
450
|
+
elsif args.first
|
451
|
+
pred_node.content = args.first
|
452
|
+
end
|
453
|
+
elsif @depth < @max_depth && !is_done?(object) && @subjects.include?(object)
|
454
|
+
add_debug(" as element (recurse)")
|
455
|
+
@depth += 1
|
456
|
+
subject(object, pred_node)
|
457
|
+
@depth -= 1
|
458
|
+
elsif object.is_a?(RDF::Node)
|
459
|
+
add_debug("=> as element (nodeID)")
|
460
|
+
pred_node["rdf:nodeID"] = object.id
|
461
|
+
else
|
462
|
+
add_debug("=> as element (resource)")
|
463
|
+
pred_node["rdf:resource"] = relativize(object)
|
464
|
+
end
|
465
|
+
|
466
|
+
node.add_child(pred_node) if pred_node
|
467
|
+
end
|
468
|
+
|
469
|
+
# Mark a subject as done.
|
470
|
+
def subject_done(subject)
|
471
|
+
add_debug("subject_done: #{subject}")
|
472
|
+
@serialized[subject] = true
|
473
|
+
end
|
474
|
+
|
475
|
+
# Return the number of times this node has been referenced in the object position
|
476
|
+
def ref_count(node)
|
477
|
+
@references.fetch(node, 0)
|
478
|
+
end
|
479
|
+
|
480
|
+
def is_done?(subject)
|
481
|
+
#add_debug("is_done?(#{subject}): #{@serialized.include?(subject)}")
|
482
|
+
@serialized.include?(subject)
|
483
|
+
end
|
484
|
+
|
485
|
+
# See if we can serialize as attribute.
|
486
|
+
# * untyped attributes that aren't duplicated where xml:lang == @lang
|
487
|
+
# * typed attributes that aren't duplicated if @dt_as_attr is true
|
488
|
+
# * rdf:type
|
489
|
+
def predicate_as_attribute?(prop, object)
|
490
|
+
[:untyped, :typed].include?(@attributes) && (
|
491
|
+
prop == RDF.type ||
|
492
|
+
[:typed].include?(@attributes) && object.literal? && object.typed? ||
|
493
|
+
(object.literal? && object.plain? || @lang && object.language.to_s == @lang.to_s)
|
494
|
+
)
|
495
|
+
end
|
496
|
+
|
497
|
+
# Take a hash from predicate uris to lists of values.
|
498
|
+
# Sort the lists of values. Return a sorted list of properties.
|
499
|
+
# @param [Hash{String => Array<Resource>}] properties A hash of Property to Resource mappings
|
500
|
+
# @return [Array<String>}] Ordered list of properties. Uses predicate_order.
|
501
|
+
def order_properties(properties)
|
502
|
+
properties.keys.each do |k|
|
503
|
+
properties[k] = properties[k].sort do |a, b|
|
504
|
+
a_li = a.is_a?(RDF::URI) && get_qname(a) && get_qname(a).to_s =~ /:_\d+$/ ? a.to_i : a.to_s
|
505
|
+
b_li = b.is_a?(RDF::URI) && get_qname(b) && get_qname(b).to_s =~ /:_\d+$/ ? b.to_i : b.to_s
|
506
|
+
|
507
|
+
a_li <=> b_li
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
# Make sorted list of properties
|
512
|
+
prop_list = []
|
513
|
+
|
514
|
+
predicate_order.each do |prop|
|
515
|
+
next unless properties[prop]
|
516
|
+
prop_list << prop.to_s
|
517
|
+
end
|
518
|
+
|
519
|
+
properties.keys.sort.each do |prop|
|
520
|
+
next if prop_list.include?(prop.to_s)
|
521
|
+
prop_list << prop.to_s
|
522
|
+
end
|
523
|
+
|
524
|
+
prop_list
|
525
|
+
end
|
526
|
+
|
527
|
+
# XML content and arguments for serialization
|
528
|
+
# Encoding.the_null_encoding.xml_args("foo", "en-US") => ["foo", {"xml:lang" => "en-US"}]
|
529
|
+
def xml_args(object)
|
530
|
+
case object
|
531
|
+
when RDF::Literal
|
532
|
+
if object.plain?
|
533
|
+
[object.value, {}]
|
534
|
+
elsif object.has_language?
|
535
|
+
[object.value, {"xml:lang" => object.language}]
|
536
|
+
elsif object.datatype == RDF.XMLLiteral
|
537
|
+
[object.value, {"rdf:parseType" => "Literal"}]
|
538
|
+
else
|
539
|
+
[object.value, {"rdf:datatype" => object.datatype.to_s}]
|
540
|
+
end
|
541
|
+
when RDF::Node
|
542
|
+
[{"rdf:nodeID" => object.id}]
|
543
|
+
when RDF::URI
|
544
|
+
[{"rdf:resource" => object.to_s}]
|
545
|
+
else
|
546
|
+
raise RDF::WriterError, "Attempt to serialize #{object.inspect}, not supported in RDF/XML"
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
# Add debug event to debug array, if specified
|
551
|
+
#
|
552
|
+
# @param [String] message::
|
553
|
+
def add_debug(message)
|
554
|
+
msg = "#{indent}#{message}"
|
555
|
+
STDERR.puts msg if ::RDF::RDFXML.debug?
|
556
|
+
@debug << msg if @debug.is_a?(Array)
|
557
|
+
end
|
558
|
+
end
|
559
|
+
end
|