rdf-rdfxml 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|