rdf-rdfa 0.3.1.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'