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.
@@ -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'