rdf-rdfxml 0.2.3 → 0.3.0
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 +2 -0
- data/{History.txt → History.rdoc} +21 -0
- data/README.rdoc +11 -12
- data/Rakefile +11 -19
- data/VERSION +1 -1
- data/lib/rdf/rdfxml.rb +3 -2
- data/lib/rdf/rdfxml/patches/literal_hacks.rb +15 -17
- data/lib/rdf/rdfxml/reader.rb +115 -79
- data/lib/rdf/rdfxml/version.rb +4 -7
- data/lib/rdf/rdfxml/writer.rb +239 -190
- data/rdf-rdfxml.gemspec +14 -20
- data/script/parse +16 -10
- data/script/tc +10 -9
- data/spec/graph_spec.rb +0 -16
- data/spec/literal_spec.rb +2 -3
- data/spec/rdf_helper.rb +6 -6
- data/spec/reader_spec.rb +17 -17
- data/spec/spec_helper.rb +11 -4
- data/spec/writer_spec.rb +384 -331
- metadata +22 -18
- data/lib/rdf/rdfxml/patches/qname_hacks.rb +0 -57
- data/lib/rdf/rdfxml/patches/seq.rb +0 -34
- data/lib/rdf/rdfxml/patches/uri_hacks.rb +0 -24
- data/spec/uri_spec.rb +0 -115
data/lib/rdf/rdfxml/version.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
module RDF::RDFXML::VERSION
|
2
|
-
|
3
|
-
MINOR =
|
4
|
-
TINY = 3
|
5
|
-
EXTRA = nil
|
2
|
+
VERSION_FILE = File.join(File.expand_path(File.dirname(__FILE__)), "..", "..", "..", "VERSION")
|
3
|
+
MAJOR, MINOR, TINY, EXTRA = File.read(VERSION_FILE).chop.split(".")
|
6
4
|
|
7
|
-
STRING = [MAJOR, MINOR, TINY].join('.')
|
8
|
-
STRING << ".#{EXTRA}" if EXTRA
|
5
|
+
STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.')
|
9
6
|
|
10
7
|
##
|
11
8
|
# @return [String]
|
@@ -17,5 +14,5 @@ module RDF::RDFXML::VERSION
|
|
17
14
|
|
18
15
|
##
|
19
16
|
# @return [Array(Integer, Integer, Integer)]
|
20
|
-
def self.to_a()
|
17
|
+
def self.to_a() STRING.split(".") end
|
21
18
|
end
|
data/lib/rdf/rdfxml/writer.rb
CHANGED
@@ -9,6 +9,8 @@ module RDF::RDFXML
|
|
9
9
|
# Writing statements or Triples will create a graph to add them to
|
10
10
|
# and then serialize the graph.
|
11
11
|
#
|
12
|
+
# The writer will add prefix definitions, and use them for creating @prefix definitions, and minting QNames
|
13
|
+
#
|
12
14
|
# @example Obtaining a RDF/XML writer class
|
13
15
|
# RDF::Writer.for(:rdf) #=> RDF::RDFXML::Writer
|
14
16
|
# RDF::Writer.for("etc/test.rdf")
|
@@ -35,62 +37,88 @@ module RDF::RDFXML
|
|
35
37
|
# end
|
36
38
|
# end
|
37
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
|
+
#
|
38
50
|
# @author [Gregg Kellogg](http://kellogg-assoc.com/)
|
39
51
|
class Writer < RDF::Writer
|
40
52
|
format RDF::RDFXML::Format
|
41
53
|
|
42
54
|
VALID_ATTRIBUTES = [:none, :untyped, :typed]
|
43
55
|
|
44
|
-
|
45
|
-
|
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
|
+
|
46
61
|
##
|
47
62
|
# Initializes the RDF/XML writer instance.
|
48
63
|
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
# base_uri:: Base URI of graph, used to shorting URI references
|
52
|
-
# lang:: Output as root xml:lang attribute, and avoid generation _xml:lang_ where possible
|
53
|
-
# attributes:: How to use XML attributes when serializing, one of :none, :untyped, :typed. The default is :none.
|
54
|
-
# default_namespace:: URI to use as default namespace
|
55
|
-
#
|
56
|
-
# @param [IO, File] output
|
64
|
+
# @param [IO, File] output
|
65
|
+
# the output stream
|
57
66
|
# @param [Hash{Symbol => Object}] options
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
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)
|
63
84
|
# @yield [writer]
|
64
85
|
# @yieldparam [RDF::Writer] writer
|
65
86
|
def initialize(output = $stdout, options = {}, &block)
|
66
|
-
|
67
|
-
|
87
|
+
super do
|
88
|
+
@graph = RDF::Graph.new
|
89
|
+
@uri_to_qname = {}
|
90
|
+
prefix(nil, @options[:default_namespace]) if @options[:default_namespace]
|
91
|
+
block.call(self) if block_given?
|
92
|
+
end
|
68
93
|
end
|
69
94
|
|
70
95
|
##
|
96
|
+
# Write whole graph
|
97
|
+
#
|
71
98
|
# @param [Graph] graph
|
72
99
|
# @return [void]
|
73
|
-
def
|
100
|
+
def write_graph(graph)
|
74
101
|
@graph = graph
|
75
102
|
end
|
76
103
|
|
77
104
|
##
|
78
|
-
#
|
105
|
+
# Addes a statement to be serialized
|
106
|
+
# @param [RDF::Statement] statement
|
79
107
|
# @return [void]
|
80
|
-
def
|
81
|
-
@graph
|
108
|
+
def write_statement(statement)
|
109
|
+
@graph.insert(statement)
|
82
110
|
end
|
83
111
|
|
84
112
|
##
|
85
|
-
#
|
86
|
-
#
|
113
|
+
# Addes a triple to be serialized
|
87
114
|
# @param [RDF::Resource] subject
|
88
115
|
# @param [RDF::URI] predicate
|
89
116
|
# @param [RDF::Value] object
|
90
117
|
# @return [void]
|
91
|
-
# @
|
92
|
-
|
93
|
-
|
118
|
+
# @raise [NotImplementedError] unless implemented in subclass
|
119
|
+
# @abstract
|
120
|
+
def write_triple(subject, predicate, object)
|
121
|
+
@graph.insert(Statement.new(subject, predicate, object))
|
94
122
|
end
|
95
123
|
|
96
124
|
##
|
@@ -99,14 +127,12 @@ module RDF::RDFXML
|
|
99
127
|
# @return [void]
|
100
128
|
# @see #write_triple
|
101
129
|
def write_epilogue
|
102
|
-
@base_uri = nil
|
103
130
|
@force_RDF_about = {}
|
104
131
|
@max_depth = @options[:max_depth] || 3
|
105
132
|
@base_uri = @options[:base_uri]
|
106
133
|
@lang = @options[:lang]
|
107
134
|
@attributes = @options[:attributes] || :none
|
108
135
|
@debug = @options[:debug]
|
109
|
-
@default_namespace = @options[:default_namespace]
|
110
136
|
raise "Invalid attribute option '#{@attributes}', should be one of #{VALID_ATTRIBUTES.to_sentence}" unless VALID_ATTRIBUTES.include?(@attributes.to_sym)
|
111
137
|
self.reset
|
112
138
|
|
@@ -116,28 +142,12 @@ module RDF::RDFXML
|
|
116
142
|
|
117
143
|
preprocess
|
118
144
|
|
119
|
-
# Get QNames and necessary namespaces from predicates and objects
|
120
|
-
@graph.predicates.each {|pred| add_debug("serialize pred: #{pred.inspect}"); get_qname(pred)}
|
121
|
-
@graph.objects.each {|obj| add_debug("serialize obj: #{obj.inspect}"); get_qname(obj)}
|
122
145
|
prefix(:rdf, RDF.to_uri)
|
123
146
|
prefix(:xml, RDF::XML) if @base_uri || @lang
|
124
147
|
|
125
|
-
if @default_namespace
|
126
|
-
prefix(:__default__, @default_namespace.respond_to?(:to_uri) ? @default_namespace.to_uri : @default_namespace)
|
127
|
-
@default_namespace_prefix = prefixes.invert[@default_namespace]
|
128
|
-
add_debug("def_namespace: #{@default_namespace}, prefix: #{@default_namespace_prefix}")
|
129
|
-
end
|
130
|
-
|
131
148
|
add_debug "\nserialize: graph namespaces: #{prefixes.inspect}"
|
132
149
|
|
133
150
|
doc.root = Nokogiri::XML::Element.new("rdf:RDF", doc)
|
134
|
-
prefixes.each_pair do |p, uri|
|
135
|
-
if p == :__default__
|
136
|
-
doc.root.default_namespace = uri.to_s
|
137
|
-
else
|
138
|
-
doc.root.add_namespace(p.to_s, uri.to_s)
|
139
|
-
end
|
140
|
-
end
|
141
151
|
doc.root["xml:lang"] = @lang if @lang
|
142
152
|
doc.root["xml:base"] = @base_uri if @base_uri
|
143
153
|
|
@@ -147,38 +157,176 @@ module RDF::RDFXML
|
|
147
157
|
subject(subject, doc.root)
|
148
158
|
end
|
149
159
|
|
160
|
+
prefixes.each_pair do |p, uri|
|
161
|
+
if p == nil
|
162
|
+
doc.root.default_namespace = uri.to_s
|
163
|
+
else
|
164
|
+
doc.root.add_namespace(p.to_s, uri.to_s)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
150
168
|
doc.write_xml_to(@output, :encoding => "UTF-8", :indent => 2)
|
151
169
|
end
|
152
170
|
|
171
|
+
# Return a QName for the URI, or nil. Adds namespace of QName to defined prefixes
|
172
|
+
# @param [URI,#to_s] uri
|
173
|
+
# @return [Array<Symbol,Symbol>, nil] Prefix, Suffix pair or nil, if none found
|
174
|
+
def get_qname(uri)
|
175
|
+
uri = RDF::URI.intern(uri.to_s) unless uri.is_a?(URI)
|
176
|
+
|
177
|
+
unless @uri_to_qname.has_key?(uri)
|
178
|
+
# Find in defined prefixes
|
179
|
+
prefixes.each_pair do |prefix, vocab|
|
180
|
+
if uri.to_s.index(vocab.to_s) == 0
|
181
|
+
local_name = uri.to_s[(vocab.to_s.length)..-1]
|
182
|
+
add_debug "get_qname(ns): #{prefix}:#{local_name}"
|
183
|
+
return @uri_to_qname[uri] = [prefix, local_name.to_sym]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Use a default vocabulary
|
188
|
+
if @options[:standard_prefixes] && vocab = RDF::Vocabulary.detect {|v| uri.to_s.index(v.to_uri.to_s) == 0}
|
189
|
+
prefix = vocab.__name__.to_s.split('::').last.downcase
|
190
|
+
prefixes[prefix.to_sym] = vocab.to_uri
|
191
|
+
suffix = uri.to_s[vocab.to_uri.to_s.size..-1]
|
192
|
+
return @uri_to_qname[uri] = [prefix.to_sym, suffix.empty? ? nil : suffix.to_sym] if prefix && suffix
|
193
|
+
end
|
194
|
+
|
195
|
+
# No vocabulary found, invent one
|
196
|
+
# Add bindings for predicates not already having bindings
|
197
|
+
# From RDF/XML Syntax and Processing:
|
198
|
+
# An XML namespace-qualified name (QName) has restrictions on the legal characters such that not all
|
199
|
+
# property URIs can be expressed as these names. It is recommended that implementors of RDF serializers,
|
200
|
+
# in order to break a URI into a namespace name and a local name, split it after the last XML non-NCName
|
201
|
+
# character, ensuring that the first character of the name is a Letter or '_'. If the URI ends in a
|
202
|
+
# non-NCName character then throw a "this graph cannot be serialized in RDF/XML" exception or error.
|
203
|
+
separation = uri.to_s.rindex(%r{[^a-zA-Z_0-9-][a-zA-Z_][a-z0-9A-Z_-]*$})
|
204
|
+
return @uri_to_qname[uri] = nil unless separation
|
205
|
+
base_uri = uri.to_s[0..separation]
|
206
|
+
suffix = uri.to_s[separation+1..-1]
|
207
|
+
@gen_prefix = @gen_prefix ? @gen_prefix.succ : "ns0"
|
208
|
+
add_debug "create prefix definition for #{uri}"
|
209
|
+
prefix(@gen_prefix, base_uri)
|
210
|
+
add_debug "get_qname(tmp_ns): #{@gen_prefix}:#{suffix}"
|
211
|
+
return @uri_to_qname[uri] = [@gen_prefix.to_sym, suffix.to_sym]
|
212
|
+
end
|
213
|
+
|
214
|
+
@uri_to_qname[uri]
|
215
|
+
rescue Addressable::URI::InvalidURIError
|
216
|
+
@uri_to_qname[uri] = nil
|
217
|
+
end
|
218
|
+
|
153
219
|
protected
|
220
|
+
# If @base_uri is defined, use it to try to make uri relative
|
221
|
+
# @param [#to_s] uri
|
222
|
+
# @return [String]
|
223
|
+
def relativize(uri)
|
224
|
+
uri = uri.to_s
|
225
|
+
@base_uri ? uri.sub(@base_uri.to_s, "") : uri
|
226
|
+
end
|
227
|
+
|
228
|
+
# Defines rdf:type of subjects to be emitted at the beginning of the graph. Defaults to rdfs:Class
|
229
|
+
# @return [Array<URI>]
|
230
|
+
def top_classes; [RDF::RDFS.Class]; end
|
231
|
+
|
232
|
+
# Defines order of predicates to to emit at begninning of a resource description. Defaults to
|
233
|
+
# [rdf:type, rdfs:label, dc:title]
|
234
|
+
# @return [Array<URI>]
|
235
|
+
def predicate_order; [RDF.type, RDF::RDFS.label, RDF::DC.title]; end
|
236
|
+
|
237
|
+
# Order subjects for output. Override this to output subjects in another order.
|
238
|
+
#
|
239
|
+
# Uses top_classes
|
240
|
+
# @return [Array<Resource>] Ordered list of subjects
|
241
|
+
def order_subjects
|
242
|
+
seen = {}
|
243
|
+
subjects = []
|
244
|
+
|
245
|
+
top_classes.each do |class_uri|
|
246
|
+
graph.query(:predicate => RDF.type, :object => class_uri).map {|st| st.subject}.sort.uniq.each do |subject|
|
247
|
+
#add_debug "order_subjects: #{subject.inspect}"
|
248
|
+
subjects << subject
|
249
|
+
seen[subject] = @top_levels[subject] = true
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Sort subjects by resources over bnodes, ref_counts and the subject URI itself
|
254
|
+
recursable = @subjects.keys.
|
255
|
+
select {|s| !seen.include?(s)}.
|
256
|
+
map {|r| [r.is_a?(RDF::Node) ? 1 : 0, ref_count(r), r]}.
|
257
|
+
sort
|
258
|
+
|
259
|
+
subjects += recursable.map{|r| r.last}
|
260
|
+
end
|
261
|
+
|
262
|
+
# Perform any preprocessing of statements required
|
263
|
+
def preprocess
|
264
|
+
@graph.each {|statement| preprocess_statement(statement)}
|
265
|
+
end
|
266
|
+
|
267
|
+
# Perform any statement preprocessing required. This is used to perform reference counts and determine required
|
268
|
+
# prefixes.
|
269
|
+
# @param [Statement] statement
|
270
|
+
def preprocess_statement(statement)
|
271
|
+
#add_debug "preprocess: #{statement.inspect}"
|
272
|
+
references = ref_count(statement.object) + 1
|
273
|
+
@references[statement.object] = references
|
274
|
+
@subjects[statement.subject] = true
|
275
|
+
end
|
276
|
+
|
277
|
+
# Returns indent string multiplied by the depth
|
278
|
+
# @param [Integer] modifier Increase depth by specified amount
|
279
|
+
# @return [String] A number of spaces, depending on current depth
|
280
|
+
def indent(modifier = 0)
|
281
|
+
" " * (@depth + modifier)
|
282
|
+
end
|
283
|
+
|
284
|
+
def reset
|
285
|
+
@depth = 0
|
286
|
+
@lists = {}
|
287
|
+
prefixes = {}
|
288
|
+
@references = {}
|
289
|
+
@serialized = {}
|
290
|
+
@subjects = {}
|
291
|
+
@top_levels = {}
|
292
|
+
end
|
293
|
+
|
294
|
+
private
|
154
295
|
def subject(subject, parent_node)
|
155
296
|
node = nil
|
156
297
|
|
157
298
|
if !is_done?(subject)
|
158
299
|
subject_done(subject)
|
159
300
|
properties = @graph.properties(subject)
|
160
|
-
prop_list = sort_properties(properties)
|
161
301
|
add_debug "subject: #{subject.inspect}, props: #{properties.inspect}"
|
162
302
|
|
163
303
|
rdf_type, *rest = properties.fetch(RDF.type.to_s, [])
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
end
|
304
|
+
qname = get_qname_string(rdf_type, :with_default => true)
|
305
|
+
add_debug "subject: #{subject.inspect}, qname: #{qname.inspect}"
|
306
|
+
if rdf_type.is_a?(RDF::Node)
|
307
|
+
# Must serialize with an element
|
308
|
+
rdf_type = nil
|
309
|
+
elsif rest.empty?
|
310
|
+
properties.delete(RDF.type.to_s)
|
311
|
+
else
|
312
|
+
properties[RDF.type.to_s] = [rest].flatten.compact
|
174
313
|
end
|
175
|
-
|
314
|
+
prop_list = order_properties(properties)
|
176
315
|
|
177
|
-
|
316
|
+
if qname
|
317
|
+
rdf_type = nil
|
318
|
+
else
|
319
|
+
qname = "rdf:Description"
|
320
|
+
prefixes[:rdf] = RDF.to_uri
|
321
|
+
end
|
322
|
+
|
323
|
+
node = Nokogiri::XML::Element.new(qname, parent_node.document)
|
324
|
+
|
325
|
+
node["rdf:type"] = rdf_type if rdf_type
|
178
326
|
|
179
327
|
if subject.is_a?(RDF::Node)
|
180
328
|
# Only need nodeID if it's referenced elsewhere
|
181
|
-
node["rdf:nodeID"] = subject.
|
329
|
+
node["rdf:nodeID"] = subject.id if ref_count(subject) > (@depth == 0 ? 0 : 1)
|
182
330
|
else
|
183
331
|
node["rdf:about"] = relativize(subject)
|
184
332
|
end
|
@@ -206,15 +354,12 @@ module RDF::RDFXML
|
|
206
354
|
#
|
207
355
|
# If _is_unique_ is true, this predicate may be able to be serialized as an attribute
|
208
356
|
def predicate(prop, object, node, is_unique)
|
209
|
-
qname = get_qname(prop)
|
210
|
-
raise RDF::WriterError, "No qname generated for <#{prop}>" unless qname
|
211
|
-
|
212
357
|
# See if we can serialize as attribute.
|
213
358
|
# * untyped attributes that aren't duplicated where xml:lang == @lang
|
214
359
|
# * typed attributes that aren't duplicated if @dt_as_attr is true
|
215
360
|
# * rdf:type
|
216
361
|
as_attr = false
|
217
|
-
as_attr
|
362
|
+
as_attr = true if [:untyped, :typed].include?(@attributes) && prop == RDF.type
|
218
363
|
|
219
364
|
# Untyped attribute with no lang, or whos lang is the same as the default and RDF.type
|
220
365
|
add_debug("as_attr? #{@attributes}, plain? #{object.plain?}, lang #{@lang || 'nil'}:#{object.language || 'nil'}") if object.is_a?(RDF::Literal)
|
@@ -224,22 +369,15 @@ module RDF::RDFXML
|
|
224
369
|
as_attr ||= true if [:typed].include?(@attributes) && object.is_a?(RDF::Literal) && object.typed?
|
225
370
|
|
226
371
|
as_attr = false unless is_unique
|
227
|
-
|
372
|
+
|
373
|
+
qname = get_qname_string(prop, :with_default => !as_attr)
|
374
|
+
raise RDF::WriterError, "No qname generated for <#{prop}>" unless qname
|
375
|
+
|
228
376
|
# Can't do as an attr if the qname has no prefix and there is no prefixed version
|
229
|
-
if
|
230
|
-
if as_attr
|
231
|
-
if @default_namespace_prefix
|
232
|
-
qname = "#{@default_namespace_prefix}:#{prop.qname.last}"
|
233
|
-
else
|
234
|
-
as_attr = false
|
235
|
-
end
|
236
|
-
else
|
237
|
-
qname = prop.qname.last.to_s
|
238
|
-
end
|
239
|
-
end
|
377
|
+
as_attr = false if as_attr && qname !~ /:/
|
240
378
|
|
241
379
|
add_debug "predicate: #{qname}, as_attr: #{as_attr}, object: #{object.inspect}, done: #{is_done?(object)}, sub: #{@subjects.include?(object)}"
|
242
|
-
qname = "rdf:li" if qname.match(/rdf:_\d+/)
|
380
|
+
#qname = "rdf:li" if qname.match(/rdf:_\d+/)
|
243
381
|
pred_node = Nokogiri::XML::Element.new(qname, node.document)
|
244
382
|
|
245
383
|
if object.is_a?(RDF::Literal) || is_done?(object) || !@subjects.include?(object)
|
@@ -266,16 +404,15 @@ module RDF::RDFXML
|
|
266
404
|
end
|
267
405
|
add_debug " elt #{'xmllit ' if object.is_a?(RDF::Literal) && object.datatype == RDF.XMLLiteral}content=#{args.first}" if !args.empty?
|
268
406
|
if object.is_a?(RDF::Literal) && object.datatype == RDF.XMLLiteral
|
269
|
-
pred_node.
|
407
|
+
pred_node.inner_html = args.first.to_s
|
270
408
|
elsif args.first
|
271
409
|
pred_node.content = args.first unless args.empty?
|
272
410
|
end
|
273
411
|
end
|
274
412
|
else
|
275
|
-
require 'rdf/rdfxml/patches/seq' unless RDF::Graph.respond_to?(:seq)
|
276
413
|
|
277
414
|
# Check to see if it can be serialized as a collection
|
278
|
-
col =
|
415
|
+
col = RDF::List.new(object, @graph).to_a
|
279
416
|
conformant_list = col.all? {|item| !item.is_a?(RDF::Literal)}
|
280
417
|
o_props = @graph.properties(object)
|
281
418
|
if conformant_list && o_props[RDF.first.to_s]
|
@@ -304,130 +441,30 @@ module RDF::RDFXML
|
|
304
441
|
node.add_child(pred_node) if pred_node
|
305
442
|
end
|
306
443
|
|
307
|
-
def relativize(uri)
|
308
|
-
uri = uri.to_s
|
309
|
-
self.base_uri ? uri.sub(/^#{self.base_uri}/, "") : uri
|
310
|
-
end
|
311
|
-
|
312
|
-
def preprocess_triple(triple)
|
313
|
-
super
|
314
|
-
|
315
|
-
# Pre-fetch qnames, to fill namespaces
|
316
|
-
get_qname(triple.predicate)
|
317
|
-
get_qname(triple.object) if triple.predicate == RDF.type
|
318
|
-
|
319
|
-
@references[triple.predicate] = ref_count(triple.predicate) + 1
|
320
|
-
end
|
321
|
-
|
322
|
-
MAX_DEPTH = 10
|
323
|
-
INDENT_STRING = " "
|
324
|
-
|
325
|
-
def top_classes; [RDF::RDFS.Class]; end
|
326
|
-
def predicate_order; [RDF.type, RDF::RDFS.label, RDF::DC.title]; end
|
327
|
-
|
328
|
-
def is_done?(subject)
|
329
|
-
@serialized.include?(subject)
|
330
|
-
end
|
331
|
-
|
332
444
|
# Mark a subject as done.
|
333
445
|
def subject_done(subject)
|
334
446
|
@serialized[subject] = true
|
335
447
|
end
|
336
448
|
|
337
|
-
def order_subjects
|
338
|
-
seen = {}
|
339
|
-
subjects = []
|
340
|
-
|
341
|
-
top_classes.each do |class_uri|
|
342
|
-
graph.query(:predicate => RDF.type, :object => class_uri).map {|st| st.subject}.sort.uniq.each do |subject|
|
343
|
-
#add_debug "order_subjects: #{subject.inspect}"
|
344
|
-
subjects << subject
|
345
|
-
seen[subject] = @top_levels[subject] = true
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
# Sort subjects by resources over bnodes, ref_counts and the subject URI itself
|
350
|
-
recursable = @subjects.keys.
|
351
|
-
select {|s| !seen.include?(s)}.
|
352
|
-
map {|r| [r.is_a?(RDF::Node) ? 1 : 0, ref_count(r), r]}.
|
353
|
-
sort
|
354
|
-
|
355
|
-
subjects += recursable.map{|r| r.last}
|
356
|
-
end
|
357
|
-
|
358
|
-
def preprocess
|
359
|
-
@graph.each {|statement| preprocess_statement(statement)}
|
360
|
-
end
|
361
|
-
|
362
|
-
def preprocess_statement(statement)
|
363
|
-
#add_debug "preprocess: #{statement.inspect}"
|
364
|
-
references = ref_count(statement.object) + 1
|
365
|
-
@references[statement.object] = references
|
366
|
-
@subjects[statement.subject] = true
|
367
|
-
end
|
368
|
-
|
369
449
|
# Return the number of times this node has been referenced in the object position
|
370
450
|
def ref_count(node)
|
371
451
|
@references.fetch(node, 0)
|
372
452
|
end
|
373
453
|
|
374
|
-
|
375
|
-
|
376
|
-
if uri.is_a?(RDF::URI)
|
377
|
-
# Duplicate logic from URI#qname to remember namespace assigned
|
378
|
-
if uri.qname
|
379
|
-
prefix(uri.qname.first, uri.vocab.to_uri)
|
380
|
-
add_debug "get_qname(uri.qname): #{uri.qname.join(':')}"
|
381
|
-
return uri.qname.join(":")
|
382
|
-
end
|
383
|
-
|
384
|
-
# No vocabulary assigned, find one from cache of created namespace URIs
|
385
|
-
prefixes.each_pair do |prefix, vocab|
|
386
|
-
if uri.to_s.index(vocab.to_s) == 0
|
387
|
-
uri.vocab = vocab
|
388
|
-
local_name = uri.to_s[(vocab.to_s.length)..-1]
|
389
|
-
add_debug "get_qname(ns): #{prefix}:#{local_name}"
|
390
|
-
return "#{prefix}:#{local_name}"
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
|
-
# No vocabulary found, invent one
|
395
|
-
# Add bindings for predicates not already having bindings
|
396
|
-
# From RDF/XML Syntax and Processing:
|
397
|
-
# An XML namespace-qualified name (QName) has restrictions on the legal characters such that not all property URIs can be expressed
|
398
|
-
# as these names. It is recommended that implementors of RDF serializers, in order to break a URI into a namespace name and a local
|
399
|
-
# name, split it after the last XML non-NCName character, ensuring that the first character of the name is a Letter or '_'. If the
|
400
|
-
# URI ends in a non-NCName character then throw a "this graph cannot be serialized in RDF/XML" exception or error.
|
401
|
-
separation = uri.to_s.rindex(%r{[^a-zA-Z_0-9-](?=[a-zA-Z_])})
|
402
|
-
return nil unless separation
|
403
|
-
base_uri = uri.to_s[0..separation]
|
404
|
-
local_name = uri.to_s[separation+1..-1]
|
405
|
-
@tmp_ns = @tmp_ns ? @tmp_ns.succ : "ns0"
|
406
|
-
add_debug "create namespace definition for #{uri}"
|
407
|
-
uri.vocab = RDF::Vocabulary(base_uri)
|
408
|
-
prefix(@tmp_ns.to_sym, uri.vocab.to_uri)
|
409
|
-
add_debug "get_qname(tmp_ns): #{@tmp_ns}:#{local_name}"
|
410
|
-
return "#{@tmp_ns}:#{local_name}"
|
411
|
-
end
|
454
|
+
def is_done?(subject)
|
455
|
+
@serialized.include?(subject)
|
412
456
|
end
|
413
457
|
|
414
|
-
|
415
|
-
@depth = 0
|
416
|
-
@lists = {}
|
417
|
-
prefixes = {}
|
418
|
-
@references = {}
|
419
|
-
@serialized = {}
|
420
|
-
@subjects = {}
|
421
|
-
@top_levels = {}
|
422
|
-
end
|
423
|
-
|
458
|
+
|
424
459
|
# Take a hash from predicate uris to lists of values.
|
425
460
|
# Sort the lists of values. Return a sorted list of properties.
|
426
|
-
|
461
|
+
# @param [Hash{String => Array<Resource>}] properties A hash of Property to Resource mappings
|
462
|
+
# @return [Array<String>}] Ordered list of properties. Uses predicate_order.
|
463
|
+
def order_properties(properties)
|
427
464
|
properties.keys.each do |k|
|
428
465
|
properties[k] = properties[k].sort do |a, b|
|
429
|
-
a_li = a.is_a?(RDF::URI) && a
|
430
|
-
b_li = b.is_a?(RDF::URI) && b
|
466
|
+
a_li = a.is_a?(RDF::URI) && get_qname(a) && get_qname(a).last.to_s =~ /^_\d+$/ ? a.to_i : a.to_s
|
467
|
+
b_li = b.is_a?(RDF::URI) && get_qname(b) && get_qname(b).last.to_s =~ /^_\d+$/ ? b.to_i : b.to_s
|
431
468
|
|
432
469
|
a_li <=> b_li
|
433
470
|
end
|
@@ -446,7 +483,7 @@ module RDF::RDFXML
|
|
446
483
|
prop_list << prop.to_s
|
447
484
|
end
|
448
485
|
|
449
|
-
add_debug "
|
486
|
+
add_debug "order_properties: #{prop_list.to_sentence}"
|
450
487
|
prop_list
|
451
488
|
end
|
452
489
|
|
@@ -475,12 +512,24 @@ module RDF::RDFXML
|
|
475
512
|
#
|
476
513
|
# @param [String] message::
|
477
514
|
def add_debug(message)
|
515
|
+
STDERR.puts message if ::RDF::RDFXML.debug?
|
478
516
|
@debug << message if @debug.is_a?(Array)
|
479
517
|
end
|
480
518
|
|
481
|
-
#
|
482
|
-
|
483
|
-
|
519
|
+
# Return string representation of QName pair
|
520
|
+
#
|
521
|
+
# @option [Boolean] :with_default (false) If a default mapping exists, use it, otherwise if a prefixed mapping exists, use it
|
522
|
+
def get_qname_string(uri, options = {})
|
523
|
+
if qname = get_qname(uri)
|
524
|
+
if options[:with_default]
|
525
|
+
qname[0] = nil if !qname.first.nil? && prefix(qname.first).to_s == prefix(nil).to_s
|
526
|
+
elsif qname.first.nil?
|
527
|
+
prefix = nil
|
528
|
+
prefixes.each_pair {|k, v| prefix = k if !k.nil? && v.to_s == prefix(nil).to_s}
|
529
|
+
qname[0] = prefix if prefix
|
530
|
+
end
|
531
|
+
qname.first == nil ? qname.last.to_s : qname.map(&:to_s).join(":")
|
532
|
+
end
|
484
533
|
end
|
485
534
|
end
|
486
535
|
end
|