the-experimenters-rdf-rdfxml 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|