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