rdf-rdfa 0.3.1.2 → 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 +2 -1
- data/Gemfile +18 -0
- data/History.md +14 -1
- data/README +291 -0
- data/README.md +236 -36
- data/Rakefile +7 -27
- data/UNLICENSE +24 -0
- data/VERSION +1 -1
- data/etc/basic.html +1 -1
- data/etc/profile.html +40 -0
- data/example-files/payswarm.html +449 -0
- data/example-files/payswarm.n3 +86 -0
- data/lib/rdf/rdfa.rb +5 -0
- data/lib/rdf/rdfa/format.rb +8 -4
- data/lib/rdf/rdfa/patches/graph_properties.rb +34 -0
- data/lib/rdf/rdfa/patches/nokogiri_hacks.rb +6 -0
- data/lib/rdf/rdfa/patches/string_hacks.rb +9 -0
- data/lib/rdf/rdfa/profile.rb +65 -41
- data/lib/rdf/rdfa/profile/xhtml.rb +36 -0
- data/lib/rdf/rdfa/profile/xml.rb +45 -0
- data/lib/rdf/rdfa/reader.rb +168 -92
- data/lib/rdf/rdfa/writer.rb +822 -0
- data/lib/rdf/rdfa/writer/haml_templates.rb +306 -0
- data/rdf-rdfa.gemspec +77 -23
- data/script/intern_vocabulary +83 -0
- data/script/parse +62 -27
- data/script/tc +87 -37
- data/spec/.gitignore +1 -0
- data/spec/matchers.rb +89 -154
- data/spec/profile_spec.rb +36 -21
- data/spec/{rdfa_reader_spec.rb → reader_spec.rb} +86 -159
- data/spec/spec_helper.rb +6 -29
- data/spec/test_helper.rb +97 -0
- data/spec/writer_spec.rb +385 -0
- metadata +203 -37
- data/spec/html4-manifest.yml +0 -1749
- data/spec/html5-manifest.yml +0 -1749
- data/spec/rdfa_helper.rb +0 -257
- data/spec/svgtiny-manifest.yml +0 -37
- data/spec/xhtml-manifest.yml +0 -1749
@@ -0,0 +1,822 @@
|
|
1
|
+
require 'haml'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
require 'rdf/rdfa/patches/graph_properties'
|
5
|
+
|
6
|
+
module RDF::RDFa
|
7
|
+
##
|
8
|
+
# An RDFa 1.1 serialiser in Ruby. The RDFa serializer makes use of Haml templates,
|
9
|
+
# allowing runtime-replacement with alternate templates. Note, however, that templates
|
10
|
+
# should be checked against the W3C test suite to ensure that valid RDFa is emitted.
|
11
|
+
#
|
12
|
+
# Note that the natural interface is to write a whole graph at a time.
|
13
|
+
# Writing statements or Triples will create a graph to add them to
|
14
|
+
# and then serialize the graph.
|
15
|
+
#
|
16
|
+
# The writer will add prefix definitions, and use them for creating @prefix definitions, and minting CURIEs
|
17
|
+
#
|
18
|
+
# @example Obtaining a RDFa writer class
|
19
|
+
# RDF::Writer.for(:html) #=> RDF::RDFa::Writer
|
20
|
+
# RDF::Writer.for("etc/test.html")
|
21
|
+
# RDF::Writer.for(:file_name => "etc/test.html")
|
22
|
+
# RDF::Writer.for(:file_extension => "html")
|
23
|
+
# RDF::Writer.for(:content_type => "application/xhtml+xml")
|
24
|
+
# RDF::Writer.for(:content_type => "text/html")
|
25
|
+
#
|
26
|
+
# @example Serializing RDF graph into an XHTML+RDFa file
|
27
|
+
# RDF::RDFa::Write.open("etc/test.html") do |writer|
|
28
|
+
# writer << graph
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @example Serializing RDF statements into an XHTML+RDFa file
|
32
|
+
# RDF::RDFa::Writer.open("etc/test.html") do |writer|
|
33
|
+
# graph.each_statement do |statement|
|
34
|
+
# writer << statement
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# @example Serializing RDF statements into an XHTML+RDFa string
|
39
|
+
# RDF::RDFa::Writer.buffer do |writer|
|
40
|
+
# graph.each_statement do |statement|
|
41
|
+
# writer << statement
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# @example Creating @base and @prefix definitions in output
|
46
|
+
# RDF::RDFa::Writer.buffer(:base_uri => "http://example.com/", :prefixes => {
|
47
|
+
# :foaf => "http://xmlns.com/foaf/0.1/"}
|
48
|
+
# ) do |writer|
|
49
|
+
# graph.each_statement do |statement|
|
50
|
+
# writer << statement
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# @example Creating @profile definitions in output
|
55
|
+
# RDF::RDFa::Writer.buffer(:profile => "http://example.com/profile") do |writer|
|
56
|
+
# graph.each_statement do |statement|
|
57
|
+
# writer << statement
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# @author [Gregg Kellogg](http://kellogg-assoc.com/)
|
62
|
+
class Writer < RDF::Writer
|
63
|
+
format RDF::RDFa::Format
|
64
|
+
|
65
|
+
# Defines rdf:type of subjects to be emitted at the beginning of the document.
|
66
|
+
# @return [Array<URI>]
|
67
|
+
attr :top_classes
|
68
|
+
|
69
|
+
# Defines order of predicates to to emit at begninning of a resource description. Defaults to
|
70
|
+
# [rdf:type, rdfs:label, dc:title]
|
71
|
+
# @return [Array<URI>]
|
72
|
+
attr :predicate_order
|
73
|
+
|
74
|
+
# Defines order of predicates to use in heading.
|
75
|
+
# @return [Array<URI>]
|
76
|
+
attr :heading_predicates
|
77
|
+
|
78
|
+
HAML_OPTIONS = {
|
79
|
+
:ugly => false, # to preserve whitespace without using entities
|
80
|
+
}
|
81
|
+
|
82
|
+
# @return [Graph] Graph of statements serialized
|
83
|
+
attr_accessor :graph
|
84
|
+
|
85
|
+
# @return [RDF::URI] Base URI used for relativizing URIs
|
86
|
+
attr_accessor :base_uri
|
87
|
+
|
88
|
+
##
|
89
|
+
# Initializes the RDFa writer instance.
|
90
|
+
#
|
91
|
+
# @param [IO, File] output
|
92
|
+
# the output stream
|
93
|
+
# @param [Hash{Symbol => Object}] options
|
94
|
+
# any additional options
|
95
|
+
# @option options [Boolean] :canonicalize (false)
|
96
|
+
# whether to canonicalize literals when serializing
|
97
|
+
# @option options [Hash] :prefixes (Hash.new)
|
98
|
+
# the prefix mappings to use
|
99
|
+
# @option options [#to_a] :profiles (Array.new)
|
100
|
+
# List of profiles to add to document. This will use terms, prefix definitions and default-vocabularies
|
101
|
+
# identified within the profiles (taken in reverse order) to determine how to serialize terms
|
102
|
+
# @option options [#to_s] :base_uri (nil)
|
103
|
+
# the base URI to use when constructing relative URIs, set as html>head>base.href
|
104
|
+
# @option options [#to_s] :lang (nil)
|
105
|
+
# Output as root @lang attribute, and avoid generation _@lang_ where possible
|
106
|
+
# @option options [Boolean] :standard_prefixes (false)
|
107
|
+
# Add standard prefixes to _prefixes_, if necessary.
|
108
|
+
# @option options [Repository] :profile_repository (nil)
|
109
|
+
# Repository to find and save profile graphs.
|
110
|
+
# @option options [Array<RDF::URI>] :top_classes ([RDF::RDFS.Class])
|
111
|
+
# Defines rdf:type of subjects to be emitted at the beginning of the document.
|
112
|
+
# @option options [Array<RDF::URI>] :predicate_order ([RDF.type, RDF::RDFS.label, RDF::DC.title])
|
113
|
+
# Defines order of predicates to to emit at begninning of a resource description..
|
114
|
+
# @option options [Array<RDF::URI>] :heading_predicates ([RDF::RDFS.label, RDF::DC.title])
|
115
|
+
# Defines order of predicates to use in heading.
|
116
|
+
# @option options [String, Symbol, Hash{Symbol => String}] :haml (DEFAULT_HAML)
|
117
|
+
# HAML templates used for generating code
|
118
|
+
# @option options [Hash] :haml_options (HAML_OPTIONS)
|
119
|
+
# Options to pass to Haml::Engine.new. Default options set :ugly => false
|
120
|
+
# to ensure that whitespace in literals with newlines is properly preserved.
|
121
|
+
# @yield [writer]
|
122
|
+
# @yieldparam [RDF::Writer] writer
|
123
|
+
def initialize(output = $stdout, options = {}, &block)
|
124
|
+
self.profile_repository = options[:profile_repository] if options[:profile_repository]
|
125
|
+
super do
|
126
|
+
@uri_to_term_or_curie = {}
|
127
|
+
@uri_to_prefix = {}
|
128
|
+
@top_classes = options[:top_classes] || [RDF::RDFS.Class]
|
129
|
+
@predicate_order = options[:predicate_order] || [RDF.type, RDF::RDFS.label, RDF::DC.title]
|
130
|
+
@heading_predicates = options[:heading_predicates] || [RDF::RDFS.label, RDF::DC.title]
|
131
|
+
@graph = RDF::Graph.new
|
132
|
+
|
133
|
+
block.call(self) if block_given?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [Hash<Symbol => String>]
|
138
|
+
def haml_template
|
139
|
+
case @options[:haml]
|
140
|
+
when Symbol, String then HAML_TEMPLATES.fetch(@options[:haml].to_sym, DEFAULT_HAML)
|
141
|
+
when Hash then @options[:haml]
|
142
|
+
else DEFAULT_HAML
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [RDF::Repository]
|
147
|
+
def profile_repository
|
148
|
+
Profile.repository
|
149
|
+
end
|
150
|
+
|
151
|
+
# @param [RDF::Repository] repo
|
152
|
+
# @return [RDF::Repository]
|
153
|
+
def profile_repository=(repo)
|
154
|
+
Profile.repository = repo
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Write whole graph
|
159
|
+
#
|
160
|
+
# @param [Graph] graph
|
161
|
+
# @return [void]
|
162
|
+
def write_graph(graph)
|
163
|
+
@graph = graph
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Addes a statement to be serialized
|
168
|
+
# @param [RDF::Statement] statement
|
169
|
+
# @return [void]
|
170
|
+
def write_statement(statement)
|
171
|
+
@graph.insert(statement)
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Addes a triple to be serialized
|
176
|
+
# @param [RDF::Resource] subject
|
177
|
+
# @param [RDF::URI] predicate
|
178
|
+
# @param [RDF::Value] object
|
179
|
+
# @return [void]
|
180
|
+
# @raise [NotImplementedError] unless implemented in subclass
|
181
|
+
# @abstract
|
182
|
+
def write_triple(subject, predicate, object)
|
183
|
+
@graph.insert(Statement.new(subject, predicate, object))
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Outputs the XHTML+RDFa representation of all stored triples.
|
188
|
+
#
|
189
|
+
# @return [void]
|
190
|
+
def write_epilogue
|
191
|
+
@base_uri = RDF::URI(@options[:base_uri]) if @options[:base_uri]
|
192
|
+
@lang = @options[:lang]
|
193
|
+
@debug = @options[:debug]
|
194
|
+
self.reset
|
195
|
+
|
196
|
+
add_debug "\nserialize: graph size: #{@graph.size}"
|
197
|
+
|
198
|
+
preprocess
|
199
|
+
|
200
|
+
# Profiles
|
201
|
+
profile = @options[:profiles].join(" ") if @options[:profiles]
|
202
|
+
|
203
|
+
# Prefixes
|
204
|
+
prefix = prefixes.keys.map {|pk| "#{pk}: #{prefixes[pk]}"}.sort.join(" ") unless prefixes.empty?
|
205
|
+
add_debug "\nserialize: prefixes: #{prefix.inspect}"
|
206
|
+
|
207
|
+
subjects = order_subjects
|
208
|
+
|
209
|
+
# Take title from first subject having a heading predicate
|
210
|
+
doc_title = nil
|
211
|
+
titles = {}
|
212
|
+
heading_predicates.each do |pred|
|
213
|
+
@graph.query(:predicate => pred) do |statement|
|
214
|
+
titles[statement.subject] ||= statement.object
|
215
|
+
end
|
216
|
+
end
|
217
|
+
title_subject = subjects.detect {|subject| titles[subject]}
|
218
|
+
doc_title = titles[title_subject]
|
219
|
+
|
220
|
+
# Generate document
|
221
|
+
doc = render_document(subjects,
|
222
|
+
:lang => @lang,
|
223
|
+
:base => @base_uri,
|
224
|
+
:title => doc_title,
|
225
|
+
:profile => profile,
|
226
|
+
:prefix => prefix) do |s|
|
227
|
+
subject(s)
|
228
|
+
end
|
229
|
+
@output.write(doc)
|
230
|
+
end
|
231
|
+
|
232
|
+
protected
|
233
|
+
|
234
|
+
# Render document using haml_template[:doc].
|
235
|
+
# Yields each subject to be rendered separately.
|
236
|
+
#
|
237
|
+
# The default Haml template is:
|
238
|
+
# !!! XML
|
239
|
+
# !!! 5
|
240
|
+
# %html{:xmlns => "http://www.w3.org/1999/xhtml", :lang => lang, :profile => profile, :prefix => prefix}
|
241
|
+
# - if base || title
|
242
|
+
# %head
|
243
|
+
# - if base
|
244
|
+
# %base{:href => base}
|
245
|
+
# - if title
|
246
|
+
# %title= title
|
247
|
+
# %body
|
248
|
+
# - subjects.each do |subject|
|
249
|
+
# != yield(subject)
|
250
|
+
#
|
251
|
+
# @param [Array<RDF::Resource>] subjects
|
252
|
+
# Ordered list of subjects. Template must yield to each subject, which returns
|
253
|
+
# the serialization of that subject (@see subject_template)
|
254
|
+
# @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
|
255
|
+
# @option options [RDF::URI] base (nil)
|
256
|
+
# Base URI added to document, used for shortening URIs within the document.
|
257
|
+
# @option options [Symbol, String] language (nil)
|
258
|
+
# Value of @lang attribute in document, also allows included literals to omit
|
259
|
+
# an @lang attribute if it is equivalent to that of the document.
|
260
|
+
# @option options [String] title (nil)
|
261
|
+
# Value of html>head>title element.
|
262
|
+
# @option options [String] profile (nil)
|
263
|
+
# Value of @profile attribute.
|
264
|
+
# @option options [String] prefix (nil)
|
265
|
+
# Value of @prefix attribute.
|
266
|
+
# @option options [String] haml (haml_template[:doc])
|
267
|
+
# Haml template to render.
|
268
|
+
# @yield [subject]
|
269
|
+
# Yields each subject
|
270
|
+
# @yieldparam [RDF::URI] subject
|
271
|
+
# @yieldreturn [:ignored]
|
272
|
+
# @return String
|
273
|
+
# The rendered document is returned as a string
|
274
|
+
def render_document(subjects, options = {})
|
275
|
+
template = options[:haml] || :doc
|
276
|
+
options = {
|
277
|
+
:prefix => nil,
|
278
|
+
:profile => nil,
|
279
|
+
:subjects => subjects,
|
280
|
+
:title => nil,
|
281
|
+
}.merge(options)
|
282
|
+
hamlify(template, options) do |subject|
|
283
|
+
yield(subject) if block_given?
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Render a subject using haml_template[:subject].
|
288
|
+
#
|
289
|
+
# The _subject_ template may be called either as a top-level element, or recursively under another element
|
290
|
+
# if the _rel_ local is not nil.
|
291
|
+
#
|
292
|
+
# Yields each predicate/property to be rendered separately (@see render_property_value and
|
293
|
+
# render_property_values).
|
294
|
+
#
|
295
|
+
# The default Haml template is:
|
296
|
+
# - if element == :li
|
297
|
+
# %li{:about => get_curie(subject), :typeof => typeof}
|
298
|
+
# - if typeof
|
299
|
+
# %span.type!= typeof
|
300
|
+
# - predicates.each do |predicate|
|
301
|
+
# != yield(predicate)
|
302
|
+
# - elsif rel && typeof
|
303
|
+
# %div{:rel => get_curie(rel)}
|
304
|
+
# %div{:about => get_curie(subject), :typeof => typeof}
|
305
|
+
# %span.type!= typeof
|
306
|
+
# - predicates.each do |predicate|
|
307
|
+
# != yield(predicate)
|
308
|
+
# - elsif rel
|
309
|
+
# %div{:rel => get_curie(rel), :resource => get_curie(subject)}
|
310
|
+
# - predicates.each do |predicate|
|
311
|
+
# != yield(predicate)
|
312
|
+
# - else
|
313
|
+
# %div{:about => get_curie(subject), :typeof => typeof}
|
314
|
+
# - if typeof
|
315
|
+
# %span.type!= typeof
|
316
|
+
# - predicates.each do |predicate|
|
317
|
+
# != yield(predicate)
|
318
|
+
#
|
319
|
+
# @param [Array<RDF::Resource>] subject
|
320
|
+
# Subject to render
|
321
|
+
# @param [Array<RDF::Resource>] predicates
|
322
|
+
# Predicates of subject. Each property is yielded for separate rendering.
|
323
|
+
# @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
|
324
|
+
# @option options [String] about (nil)
|
325
|
+
# About description, a CURIE, URI or Node definition.
|
326
|
+
# May be nil if no @about is rendered (e.g. unreferenced Nodes)
|
327
|
+
# @option options [String] resource (nil)
|
328
|
+
# Resource description, a CURIE, URI or Node definition.
|
329
|
+
# May be nil if no @resource is rendered
|
330
|
+
# @option options [String] rel (nil)
|
331
|
+
# Optional @rel property description, a CURIE, URI or Node definition.
|
332
|
+
# @option options [String] typeof (nil)
|
333
|
+
# RDF type as a CURIE, URI or Node definition.
|
334
|
+
# If :about is nil, this defaults to the empty string ("").
|
335
|
+
# @option options [:li, nil] element (nil)
|
336
|
+
# Render with <li>, otherwise with template default.
|
337
|
+
# @option options [String] haml (haml_template[:subject])
|
338
|
+
# Haml template to render.
|
339
|
+
# @yield [predicate]
|
340
|
+
# Yields each predicate
|
341
|
+
# @yieldparam [RDF::URI] predicate
|
342
|
+
# @yieldreturn [:ignored]
|
343
|
+
# @return String
|
344
|
+
# The rendered document is returned as a string
|
345
|
+
# Return Haml template for document from haml_template[:subject]
|
346
|
+
def render_subject(subject, predicates, options = {})
|
347
|
+
template = options[:haml] || :subject
|
348
|
+
options = {
|
349
|
+
:about => (get_curie(subject) unless options[:rel]),
|
350
|
+
:element => nil,
|
351
|
+
:predicates => predicates,
|
352
|
+
:rel => nil,
|
353
|
+
:resource => (get_curie(subject) if options[:rel]),
|
354
|
+
:subject => subject,
|
355
|
+
:typeof => nil,
|
356
|
+
}.merge(options)
|
357
|
+
hamlify(template, options) do |predicate|
|
358
|
+
yield(predicate) if block_given?
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# Render a single- or multi-valued predicate using haml_template[:property_value] or haml_template[:property_values].
|
363
|
+
# Yields each object for optional rendering. The block should only
|
364
|
+
# render for recursive subject definitions (i.e., where the object
|
365
|
+
# is also a subject and is rendered underneath the first referencing subject).
|
366
|
+
#
|
367
|
+
# The default Haml template for a single-valued property is:
|
368
|
+
# - object = objects.first
|
369
|
+
# - if heading_predicates.include?(predicate) && object.literal?
|
370
|
+
# %h1{:property => get_curie(predicate), :content => get_content(object), :lang => get_lang(object), :datatype => get_dt_curie(object)}&= get_value(object)
|
371
|
+
# - else
|
372
|
+
# %div.property
|
373
|
+
# %span.label
|
374
|
+
# = get_predicate_name(predicate)
|
375
|
+
# - if res = yield(object)
|
376
|
+
# != res
|
377
|
+
# - elsif object.node?
|
378
|
+
# %span{:resource => get_curie(object), :rel => get_curie(predicate)}= get_curie(object)
|
379
|
+
# - elsif object.uri?
|
380
|
+
# %a{:href => object.to_s, :rel => get_curie(predicate)}= object.to_s
|
381
|
+
# - elsif object.datatype == RDF.XMLLiteral
|
382
|
+
# %span{:property => get_curie(predicate), :lang => get_lang(object), :datatype => get_dt_curie(object)}<!= get_value(object)
|
383
|
+
# - else
|
384
|
+
# %span{:property => get_curie(predicate), :content => get_content(object), :lang => get_lang(object), :datatype => get_dt_curie(object)}&= get_value(object)
|
385
|
+
#
|
386
|
+
# The default Haml template for a multi-valued property is:
|
387
|
+
# %div.property
|
388
|
+
# %span.label
|
389
|
+
# = get_predicate_name(predicate)
|
390
|
+
# %ul{:rel => (get_curie(rel) if rel), :property => (get_curie(property) if property)}
|
391
|
+
# - objects.each do |object|
|
392
|
+
# - if res = yield(object)
|
393
|
+
# != res
|
394
|
+
# - elsif object.node?
|
395
|
+
# %li{:resource => get_curie(object)}= get_curie(object)
|
396
|
+
# - elsif object.uri?
|
397
|
+
# %li
|
398
|
+
# %a{:href => object.to_s}= object.to_s
|
399
|
+
# - elsif object.datatype == RDF.XMLLiteral
|
400
|
+
# %li{:lang => get_lang(object), :datatype => get_curie(object.datatype)}<!= get_value(object)
|
401
|
+
# - else
|
402
|
+
# %li{:content => get_content(object), :lang => get_lang(object), :datatype => get_dt_curie(object)}&= get_value(object)
|
403
|
+
#
|
404
|
+
# @param [Array<RDF::Resource>] predicate
|
405
|
+
# Predicate to render.
|
406
|
+
# @param [Array<RDF::Resource>] objects
|
407
|
+
# List of objects to render.
|
408
|
+
# If the list contains only a single element, the :property_value template will be used.
|
409
|
+
# Otherwise, the :property_values template is used.
|
410
|
+
# @param [RDF::Resource] property
|
411
|
+
# Value of @property, which should only be defined for literal objects
|
412
|
+
# @param [RDF::Resource] rel
|
413
|
+
# Value of @rel, which should only be defined for Node or URI objects.
|
414
|
+
# @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
|
415
|
+
# @option options [String] haml (haml_template[:property_value], haml_template[:property_values])
|
416
|
+
# Haml template to render. Otherwise, uses haml_template[:property_value] or haml_template[:property_values]
|
417
|
+
# depending on the cardinality of objects.
|
418
|
+
# @yield [object]
|
419
|
+
# Yields object.
|
420
|
+
# @yieldparam [RDF::Resource] object
|
421
|
+
# @yieldreturn [String, nil]
|
422
|
+
# The block should only return a string for recursive object definitions.
|
423
|
+
# @return String
|
424
|
+
# The rendered document is returned as a string
|
425
|
+
def render_property(predicate, objects, property, rel, options = {})
|
426
|
+
template = options[:haml] || (objects.to_a.length == 1 ? :property_value : :property_values)
|
427
|
+
options = {
|
428
|
+
:objects => objects,
|
429
|
+
:predicate => predicate,
|
430
|
+
:property => property,
|
431
|
+
:rel => rel,
|
432
|
+
}.merge(options)
|
433
|
+
hamlify(template, options) do |object|
|
434
|
+
yield(object) if block_given?
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
# Perform any preprocessing of statements required
|
439
|
+
# @return [ignored]
|
440
|
+
def preprocess
|
441
|
+
# Load profiles
|
442
|
+
# Add terms and prefixes to local store for converting URIs
|
443
|
+
# Keep track of vocabulary from left-most profile
|
444
|
+
[@options[:profiles]].flatten.compact.reverse.each do |uri|
|
445
|
+
prof = Profile.find(uri)
|
446
|
+
prof.prefixes.each_pair do |k, v|
|
447
|
+
@uri_to_prefix[v] = k
|
448
|
+
end
|
449
|
+
|
450
|
+
prof.terms.each_pair do |k, v|
|
451
|
+
@uri_to_term_or_curie[v] = RDF::URI.intern(k)
|
452
|
+
end
|
453
|
+
|
454
|
+
@vocabulary = prof.vocabulary.to_s
|
455
|
+
end
|
456
|
+
|
457
|
+
# Load defined prefixes
|
458
|
+
(@options[:prefixes] || {}).each_pair do |k, v|
|
459
|
+
@uri_to_prefix[v.to_s] = k
|
460
|
+
end
|
461
|
+
@options[:prefixes] = {} # Will define actual used when matched
|
462
|
+
|
463
|
+
# Process each statement to establish CURIEs and Terms
|
464
|
+
@graph.each {|statement| preprocess_statement(statement)}
|
465
|
+
end
|
466
|
+
|
467
|
+
# Order subjects for output. Override this to output subjects in another order.
|
468
|
+
#
|
469
|
+
# Uses #top_classes and #base_uri.
|
470
|
+
# @return [Array<Resource>] Ordered list of subjects
|
471
|
+
def order_subjects
|
472
|
+
seen = {}
|
473
|
+
subjects = []
|
474
|
+
|
475
|
+
# Start with base_uri
|
476
|
+
if base_uri && @subjects.keys.include?(base_uri)
|
477
|
+
subjects << base_uri
|
478
|
+
seen[base_uri] = true
|
479
|
+
end
|
480
|
+
|
481
|
+
# Add distinguished classes
|
482
|
+
top_classes.
|
483
|
+
select {|s| !seen.include?(s)}.
|
484
|
+
each do |class_uri|
|
485
|
+
graph.query(:predicate => RDF.type, :object => class_uri).map {|st| st.subject}.sort.uniq.each do |subject|
|
486
|
+
#add_debug "order_subjects: #{subject.inspect}"
|
487
|
+
subjects << subject
|
488
|
+
seen[subject] = true
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
# Sort subjects by resources over nodes, ref_counts and the subject URI itself
|
493
|
+
recursable = @subjects.keys.
|
494
|
+
select {|s| !seen.include?(s)}.
|
495
|
+
map {|r| [r.is_a?(RDF::Node) ? 1 : 0, ref_count(r), r]}.
|
496
|
+
sort
|
497
|
+
|
498
|
+
subjects += recursable.map{|r| r.last}
|
499
|
+
end
|
500
|
+
|
501
|
+
# Take a hash from predicate uris to lists of values.
|
502
|
+
# Sort the lists of values. Return a sorted list of properties.
|
503
|
+
#
|
504
|
+
# @param [Hash{String => Array<Resource>}] properties A hash of Property to Resource mappings
|
505
|
+
# @return [Array<String>}] Ordered list of properties. Uses predicate_order.
|
506
|
+
def order_properties(properties)
|
507
|
+
properties.keys.each do |k|
|
508
|
+
properties[k] = properties[k].sort do |a, b|
|
509
|
+
a_li = a.is_a?(RDF::URI) && get_curie(a) && get_curie(a).to_s =~ /:_\d+$/ ? a.to_i : a.to_s
|
510
|
+
b_li = b.is_a?(RDF::URI) && get_curie(b) && get_curie(b).to_s =~ /:_\d+$/ ? b.to_i : b.to_s
|
511
|
+
|
512
|
+
a_li <=> b_li
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
# Make sorted list of properties
|
517
|
+
prop_list = []
|
518
|
+
|
519
|
+
predicate_order.each do |prop|
|
520
|
+
next unless properties[prop]
|
521
|
+
prop_list << prop.to_s
|
522
|
+
end
|
523
|
+
|
524
|
+
properties.keys.sort.each do |prop|
|
525
|
+
prop = prop =~ /^_:(.*)$/ ? RDF::Node.intern($1) : RDF::URI.intern(prop)
|
526
|
+
next if prop_list.include?(prop)
|
527
|
+
prop_list << prop
|
528
|
+
end
|
529
|
+
|
530
|
+
add_debug "order_properties: #{prop_list.inspect}"
|
531
|
+
prop_list
|
532
|
+
end
|
533
|
+
|
534
|
+
# Perform any statement preprocessing required. This is used to perform reference counts and determine required
|
535
|
+
# prefixes.
|
536
|
+
# @param [RDF::Statement] statement
|
537
|
+
# @return [ignored]
|
538
|
+
def preprocess_statement(statement)
|
539
|
+
#add_debug "preprocess: #{statement.inspect}"
|
540
|
+
references = ref_count(statement.object) + 1
|
541
|
+
@references[statement.object] = references
|
542
|
+
@subjects[statement.subject] = true
|
543
|
+
get_curie(statement.subject)
|
544
|
+
get_curie(statement.predicate)
|
545
|
+
get_curie(statement.object)
|
546
|
+
get_curie(statement.object.datatype) if statement.object.literal? && statement.object.has_datatype?
|
547
|
+
end
|
548
|
+
|
549
|
+
# Reset parser to run again
|
550
|
+
def reset
|
551
|
+
@depth = 0
|
552
|
+
prefixes = {}
|
553
|
+
@references = {}
|
554
|
+
@serialized = {}
|
555
|
+
@subjects = {}
|
556
|
+
end
|
557
|
+
|
558
|
+
protected
|
559
|
+
|
560
|
+
# Display a subject.
|
561
|
+
#
|
562
|
+
# @example Displays a subject as a Resource Definition:
|
563
|
+
# <div typeof="rdfs:Resource" about="http://example.com/resource">
|
564
|
+
# <h1 property="dc:title">label</h1>
|
565
|
+
# <ul>
|
566
|
+
# <li content="2009-04-30T06:15:51Z" property="dc:created">2009-04-30T06:15:51+00:00</li>
|
567
|
+
# </ul>
|
568
|
+
# </div>
|
569
|
+
#
|
570
|
+
# @param [RDF::Resource] subject
|
571
|
+
# @param [Hash{Symbol => Object}] options
|
572
|
+
# @option options [:li, nil] :element(:div)
|
573
|
+
# Serialize using <li> rather than template default element
|
574
|
+
# @option options [RDF::Resource] :rel (nil)
|
575
|
+
# Optional @rel property
|
576
|
+
# @return [Nokogiri::XML::Element, {Namespace}]
|
577
|
+
def subject(subject, options = {})
|
578
|
+
return if is_done?(subject)
|
579
|
+
|
580
|
+
subject_done(subject)
|
581
|
+
|
582
|
+
properties = @graph.properties(subject)
|
583
|
+
prop_list = order_properties(properties)
|
584
|
+
|
585
|
+
# Find appropriate template
|
586
|
+
curie ||= case
|
587
|
+
when subject.node?
|
588
|
+
subject.to_s if ref_count(subject) >= (@depth == 0 ? 0 : 1)
|
589
|
+
else
|
590
|
+
get_curie(subject)
|
591
|
+
end
|
592
|
+
|
593
|
+
typeof = [properties.delete(RDF.type.to_s)].flatten.compact.map {|r| get_curie(r)}.join(" ")
|
594
|
+
typeof = nil if typeof.empty?
|
595
|
+
|
596
|
+
# Nodes without a curie need a blank @typeof to generate a subject
|
597
|
+
typeof ||= "" unless curie
|
598
|
+
prop_list -= [RDF.type.to_s]
|
599
|
+
|
600
|
+
add_debug "subject: #{curie.inspect}, typeof: #{typeof.inspect}, props: #{prop_list.inspect}"
|
601
|
+
|
602
|
+
# Render this subject
|
603
|
+
# If :rel is specified and :typeof is nil, use @resource instead of @about.
|
604
|
+
# Pass other options from calling context
|
605
|
+
render_opts = {:typeof => typeof}.merge(options)
|
606
|
+
render_subject(subject, prop_list, render_opts) do |pred|
|
607
|
+
depth do
|
608
|
+
values = properties[pred.to_s]
|
609
|
+
add_debug "subject: #{get_curie(subject)}, pred: #{get_curie(pred)}, values: #{values.inspect}"
|
610
|
+
predicate(pred, values)
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
# Write a predicate with one or more values.
|
616
|
+
#
|
617
|
+
# Values may be a combination of Literal and Resource (Node or URI).
|
618
|
+
# @param [RDF::Resource] predicate
|
619
|
+
# Predicate to serialize
|
620
|
+
# @param [Array<RDF::Resource>] objects
|
621
|
+
# Objects to serialize
|
622
|
+
# @return [String]
|
623
|
+
def predicate(predicate, objects)
|
624
|
+
add_debug "predicate: #{predicate.inspect}, objects: #{objects}"
|
625
|
+
|
626
|
+
return if objects.to_a.empty?
|
627
|
+
|
628
|
+
add_debug("predicate: #{get_curie(predicate)}")
|
629
|
+
property = predicate if objects.any?(&:literal?)
|
630
|
+
rel = predicate if objects.any?(&:uri?) || objects.any?(&:node?)
|
631
|
+
render_property(predicate, objects, property, rel) do |o|
|
632
|
+
# Yields each object, for potential recursive definition.
|
633
|
+
# If nil is returned, a leaf is produced
|
634
|
+
depth {subject(o, :rel => rel, :element => (:li if objects.length > 1))} if !is_done?(o) && @subjects.include?(o)
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
# Haml rendering helper. Return CURIE for the literal datatype, if the literal is a typed literal.
|
639
|
+
#
|
640
|
+
# @param [RDF::Resource] resource
|
641
|
+
# @return [String, nil]
|
642
|
+
# @raise [RDF::WriterError]
|
643
|
+
def get_dt_curie(literal)
|
644
|
+
raise RDF::WriterError, "Getting datatype CURIE for #{literal.inspect}, which must be a literal" unless literal.is_a?(RDF::Literal)
|
645
|
+
get_curie(literal.datatype) if literal.literal? && literal.datatype?
|
646
|
+
end
|
647
|
+
|
648
|
+
# Haml rendering helper. Return language for plain literal, if there is no language, or it is the same as the document, return nil
|
649
|
+
#
|
650
|
+
# @param [RDF::Literal] literal
|
651
|
+
# @return [String, nil]
|
652
|
+
# @raise [RDF::WriterError]
|
653
|
+
def get_lang(literal)
|
654
|
+
raise RDF::WriterError, "Getting datatype CURIE for #{literal.inspect}, which must be a literal" unless literal.is_a?(RDF::Literal)
|
655
|
+
literal.language if literal.literal? && literal.language && literal.language != @lang
|
656
|
+
end
|
657
|
+
|
658
|
+
# Haml rendering helper. Data to be added to a @content value
|
659
|
+
#
|
660
|
+
# @param [RDF::Literal] literal
|
661
|
+
# @return [String, nil]
|
662
|
+
# @raise [RDF::WriterError]
|
663
|
+
def get_content(literal)
|
664
|
+
raise RDF::WriterError, "Getting content for #{literal.inspect}, which must be a literal" unless literal.is_a?(RDF::Literal)
|
665
|
+
case literal
|
666
|
+
when RDF::Literal::Date, RDF::Literal::Time, RDF::Literal::DateTime
|
667
|
+
literal.to_s
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
# Haml rendering helper. Display value for object, may be non-canonical if get_content returns a non-nil value
|
672
|
+
#
|
673
|
+
# @param [RDF::Literal] literal
|
674
|
+
# @return [String]
|
675
|
+
# @raise [RDF::WriterError]
|
676
|
+
def get_value(literal)
|
677
|
+
raise RDF::WriterError, "Getting value for #{literal.inspect}, which must be a literal" unless literal.is_a?(RDF::Literal)
|
678
|
+
case literal
|
679
|
+
when RDF::Literal::Date
|
680
|
+
literal.object.strftime("%A, %d %B %Y")
|
681
|
+
when RDF::Literal::Time
|
682
|
+
literal.object.strftime("%H:%M:%S %Z").sub(/\+00:00/, "UTC")
|
683
|
+
when RDF::Literal::DateTime
|
684
|
+
literal.object.strftime("%H:%M:%S %Z on %A, %d %B %Y").sub(/\+00:00/, "UTC")
|
685
|
+
else
|
686
|
+
literal.to_s
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
# Haml rendering helper. Return an appropriate label for a resource.
|
691
|
+
#
|
692
|
+
# @param [RDF::Resource] resource
|
693
|
+
# @return [String]
|
694
|
+
# @raise [RDF::WriterError]
|
695
|
+
def get_predicate_name(resource)
|
696
|
+
raise RDF::WriterError, "Getting predicate name for #{resource.inspect}, which must be a resource" unless resource.is_a?(RDF::Resource)
|
697
|
+
get_curie(resource)
|
698
|
+
end
|
699
|
+
|
700
|
+
# Haml rendering helper. Return appropriate, term, CURIE or URI for the given resource.
|
701
|
+
#
|
702
|
+
# @param [RDF::Value] resource
|
703
|
+
# @return [String] value to use to identify URI
|
704
|
+
# @raise [RDF::WriterError]
|
705
|
+
def get_curie(resource)
|
706
|
+
raise RDF::WriterError, "Getting CURIE for #{resource.inspect}, which must be an RDF value" unless resource.is_a?(RDF::Value)
|
707
|
+
return resource.to_s unless resource.uri?
|
708
|
+
|
709
|
+
@rdfcore_prefixes ||= RDF::RDFa::Profile.find(RDF::URI("http://www.w3.org/profile/rdfa-1.1")).prefixes
|
710
|
+
|
711
|
+
uri = resource.to_s
|
712
|
+
|
713
|
+
curie = case
|
714
|
+
when @uri_to_term_or_curie.has_key?(uri)
|
715
|
+
#add_debug("get_curie(#{uri}): uri_to_term_or_curie")
|
716
|
+
return @uri_to_term_or_curie[uri]
|
717
|
+
when @base_uri && uri.index(@base_uri.to_s) == 0
|
718
|
+
#add_debug("get_curie(#{uri}): base_uri (#{uri.sub(@base_uri.to_s, "")})")
|
719
|
+
uri.sub(@base_uri.to_s, "")
|
720
|
+
when @vocabulary && uri.index(@vocabulary) == 0
|
721
|
+
#add_debug("get_curie(#{uri}): vocabulary")
|
722
|
+
uri.sub(@vocabulary, "")
|
723
|
+
when u = @uri_to_prefix.keys.detect {|u| uri.index(u.to_s) == 0}
|
724
|
+
#add_debug("get_curie(#{uri}): uri_to_prefix")
|
725
|
+
# Use a defined prefix
|
726
|
+
prefix = @uri_to_prefix[u]
|
727
|
+
prefix(prefix, u) # Define for output
|
728
|
+
uri.sub(u.to_s, "#{prefix}:")
|
729
|
+
when u = @rdfcore_prefixes.values.detect {|u| uri.index(u.to_s) == 0}
|
730
|
+
#add_debug("get_curie(#{uri}): rdfcore_prefixes")
|
731
|
+
# Use standard profile prefixes
|
732
|
+
pfx = @rdfcore_prefixes.invert[u]
|
733
|
+
prefix(pfx, u) # Define for output
|
734
|
+
uri.sub(u.to_s, "#{pfx}:")
|
735
|
+
when @options[:standard_prefixes] && vocab = RDF::Vocabulary.detect {|v| uri.index(v.to_uri.to_s) == 0}
|
736
|
+
#add_debug("get_curie(#{uri}): standard_prefixes")
|
737
|
+
prefix = vocab.__name__.to_s.split('::').last.downcase
|
738
|
+
prefix(prefix, vocab.to_uri) # Define for output
|
739
|
+
uri.sub(vocab.to_uri.to_s, "#{prefix}:")
|
740
|
+
else
|
741
|
+
#add_debug("get_curie(#{uri}): none")
|
742
|
+
uri
|
743
|
+
end
|
744
|
+
|
745
|
+
#add_debug("get_curie(#{resource}) => #{curie}")
|
746
|
+
|
747
|
+
@uri_to_term_or_curie[uri] = curie
|
748
|
+
rescue Addressable::URI::InvalidURIError => e
|
749
|
+
raise RDF::WriterError, "Invalid URI #{uri.inspect}: #{e.message}"
|
750
|
+
end
|
751
|
+
private
|
752
|
+
|
753
|
+
##
|
754
|
+
# Haml rendering helper. Escape entities to avoid whitespace issues.
|
755
|
+
#
|
756
|
+
# # In addtion to "&<>, encode \n and \r to ensure that whitespace is properly preserved
|
757
|
+
#
|
758
|
+
# @param [String] str
|
759
|
+
# @return [String]
|
760
|
+
# Entity-encoded string
|
761
|
+
def escape_entities(str)
|
762
|
+
CGI.escapeHTML(str).gsub(/[\n\r]/) {|c| '&#x' + c.unpack('h').first + ';'}
|
763
|
+
end
|
764
|
+
|
765
|
+
# Increase depth around a method invocation
|
766
|
+
def depth
|
767
|
+
@depth += 1
|
768
|
+
ret = yield
|
769
|
+
@depth -= 1
|
770
|
+
ret
|
771
|
+
end
|
772
|
+
|
773
|
+
# Render HAML
|
774
|
+
# @param [Symbol, String] template
|
775
|
+
# If a symbol, finds a matching template from haml_template, otherwise uses template as is
|
776
|
+
# @param [Hash{Symbol => Object}] locals
|
777
|
+
# Locals to pass to render
|
778
|
+
# @return [String]
|
779
|
+
# @raise [RDF::WriterError]
|
780
|
+
def hamlify(template, locals = {})
|
781
|
+
template = haml_template[template] if template.is_a?(Symbol)
|
782
|
+
|
783
|
+
template = template.align_left
|
784
|
+
add_debug "hamlify template: #{template}"
|
785
|
+
add_debug "hamlify locals: #{locals.inspect}"
|
786
|
+
|
787
|
+
Haml::Engine.new(template, @options[:haml_options] || HAML_OPTIONS).render(self, locals) do |*args|
|
788
|
+
yield(*args) if block_given?
|
789
|
+
end
|
790
|
+
rescue Haml::Error => e
|
791
|
+
raise RDF::WriterError, "#{e.inspect}\n" +
|
792
|
+
"rendering #{template}\n" +
|
793
|
+
"with options #{(@options[:haml_options] || HAML_OPTIONS).inspect}\n" +
|
794
|
+
"and locals #{locals.inspect}"
|
795
|
+
end
|
796
|
+
|
797
|
+
# Mark a subject as done.
|
798
|
+
def subject_done(subject)
|
799
|
+
@serialized[subject] = true
|
800
|
+
end
|
801
|
+
|
802
|
+
def is_done?(subject)
|
803
|
+
@serialized.include?(subject)
|
804
|
+
end
|
805
|
+
|
806
|
+
# Return the number of times this node has been referenced in the object position
|
807
|
+
def ref_count(node)
|
808
|
+
@references.fetch(node, 0)
|
809
|
+
end
|
810
|
+
|
811
|
+
# Add debug event to debug array, if specified
|
812
|
+
#
|
813
|
+
# @param [String] message::
|
814
|
+
def add_debug(message)
|
815
|
+
msg = "#{' ' * @depth}#{message}"
|
816
|
+
STDERR.puts msg if ::RDF::RDFa.debug?
|
817
|
+
@debug << msg if @debug.is_a?(Array)
|
818
|
+
end
|
819
|
+
end
|
820
|
+
end
|
821
|
+
|
822
|
+
require 'rdf/rdfa/writer/haml_templates'
|