• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ruby-rdf / rdf-rdfa / 11317674118

13 Oct 2024 09:03PM UTC coverage: 90.142%. Remained the same
11317674118

push

github

gkellogg
Remove duplicate datatype in writer lang option

1079 of 1197 relevant lines covered (90.14%)

1322.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.43
/lib/rdf/rdfa/writer.rb
1
require 'haml'
2✔
2
require 'cgi'
2✔
3
require 'rdf/xsd'
2✔
4

5
module RDF::RDFa
2✔
6
  ##
7
  # An RDFa 1.1 serialiser in Ruby. The RDFa serializer makes use of Haml templates, allowing runtime-replacement with alternate templates. Note, however, that templates should be checked against the W3C test suite to ensure that valid RDFa is emitted.
8
  #
9
  # Note that the natural interface is to write a whole graph at a time. Writing statements or Triples will create a graph to add them to and then serialize the graph.
10
  #
11
  # The writer will add prefix definitions, and use them for creating @prefix definitions, and minting CURIEs
12
  #
13
  # @example Obtaining a RDFa writer class
14
  #     RDF::Writer.for(:html)          => RDF::RDFa::Writer
15
  #     RDF::Writer.for("etc/test.html")
16
  #     RDF::Writer.for(file_name:      "etc/test.html")
17
  #     RDF::Writer.for(file_extension: "html")
18
  #     RDF::Writer.for(content_type:   "application/xhtml+xml")
19
  #     RDF::Writer.for(content_type:   "text/html")
20
  #
21
  # @example Serializing RDF graph into an XHTML+RDFa file
22
  #     RDF::RDFa::Write.open("etc/test.html") do |writer|
23
  #       writer << graph
24
  #     end
25
  #
26
  # @example Serializing RDF statements into an XHTML+RDFa file
27
  #     RDF::RDFa::Writer.open("etc/test.html") do |writer|
28
  #       graph.each_statement do |statement|
29
  #         writer << statement
30
  #       end
31
  #     end
32
  #
33
  # @example Serializing RDF statements into an XHTML+RDFa string
34
  #     RDF::RDFa::Writer.buffer do |writer|
35
  #       graph.each_statement do |statement|
36
  #         writer << statement
37
  #       end
38
  #     end
39
  #
40
  # @example Creating @base and @prefix definitions in output
41
  #     RDF::RDFa::Writer.buffer(base_uri: "http://example.com/", prefixes: {
42
  #         foaf: "http://xmlns.com/foaf/0.1/"}
43
  #     ) do |writer|
44
  #       graph.each_statement do |statement|
45
  #         writer << statement
46
  #       end
47
  #     end
48
  #
49
  # @author [Gregg Kellogg](https://greggkellogg.net/)
50
  class Writer < RDF::Writer
2✔
51
    format RDF::RDFa::Format
2✔
52
    include RDF::Util::Logger
2✔
53

54
    # Defines rdf:type of subjects to be emitted at the beginning of the document.
55
    # @return [Array<URI>]
56
    attr :top_classes
2✔
57

58
    # Defines order of predicates to to emit at begninning of a resource description. Defaults to `[rdf:type, rdfs:label, dc:title]`
59
    # @return [Array<URI>]
60
    attr :predicate_order
2✔
61

62
    # Defines order of predicates to use in heading.
63
    # @return [Array<URI>]
64
    attr :heading_predicates
2✔
65

66
    HAML_OPTIONS = {
2✔
67
      format: :xhtml
68
    }
69

70
    # @return [Graph] Graph of statements serialized
71
    attr_accessor :graph
2✔
72

73
    # @return [RDF::URI] Base URI used for relativizing URIs
74
    attr_accessor :base_uri
2✔
75

76
    ##
77
    # RDFa Writer options
78
    # @see https://ruby-rdf.github.io/rdf/RDF/Writer#options-class_method
79
    def self.options
2✔
80
      super + [
×
81
        RDF::CLI::Option.new(
82
          symbol: :lang,
83
          on: ["--lang"],
84
          datatype: TrueClass,
85
          default: false,
86
          control: :checkbox,
87
          description: "Output as root @lang attribute, and avoid generation _@lang_ where possible."),
88
      ]
89
    end
90

91
    ##
92
    # Initializes the RDFa writer instance.
93
    #
94
    # @param  [IO, File] output
95
    #   the output stream
96
    # @param  [Hash{Symbol => Object}] options
97
    #   any additional options
98
    # @option options [Boolean]  :canonicalize (false)
99
    #   whether to canonicalize literals when serializing
100
    # @option options [Hash]     :prefixes     (Hash.new)
101
    #   the prefix mappings to use
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 [Boolean]  :validate (false)
105
    #   whether to validate terms when serializing
106
    # @option options [#to_s]   :lang   (nil)
107
    #   Output as root @lang attribute, and avoid generation _@lang_ where possible
108
    # @option options [Boolean]  :standard_prefixes   (false)
109
    #   Add standard prefixes to _prefixes_, if necessary.
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::Vocab::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::Vocab::DC.title])
115
    #   Defines order of predicates to use in heading.
116
    # @option options [String, Symbol, Hash{Symbol => String}] :haml (DEFAULT_HAML) HAML templates used for generating code
117
    # @option options [Hash] :haml_options (HAML_OPTIONS)
118
    #   Options to pass to Haml::Engine.new.
119
    # @yield  [writer]
120
    # @yieldparam [RDF::Writer] writer
121
    def initialize(output = $stdout, **options, &block)
2✔
122
      super do
300✔
123
        @uri_to_term_or_curie = {}
300✔
124
        @uri_to_prefix = {}
300✔
125
        @top_classes = options[:top_classes] || [RDF::RDFS.Class]
300✔
126
        @predicate_order = options[:predicate_order] || [RDF.type, RDF::RDFS.label, RDF::URI("http://purl.org/dc/terms/title")]
300✔
127
        @heading_predicates = options[:heading_predicates] || [RDF::RDFS.label, RDF::URI("http://purl.org/dc/terms/title")]
300✔
128
        @graph = RDF::Graph.new
300✔
129

130
        block.call(self) if block_given?
300✔
131
      end
132
    end
133

134
    # @return [Hash<Symbol => String>]
135
    def haml_template
2✔
136
      return @haml_template if @haml_template
970✔
137
      case @options[:haml]
970✔
138
      when Symbol, String   then HAML_TEMPLATES.fetch(@options[:haml].to_sym, DEFAULT_HAML)
×
139
      when Hash             then @options[:haml]
×
140
      else                       DEFAULT_HAML
970✔
141
      end
142
    end
143

144
    ##
145
    # Addes a triple to be serialized
146
    # @param  [RDF::Resource] subject
147
    # @param  [RDF::URI]      predicate
148
    # @param  [RDF::Value]    object
149
    # @return [void]
150
    # @raise  [NotImplementedError] unless implemented in subclass
151
    # @abstract
152
    # @raise [RDF::WriterError] if validating and attempting to write an invalid {RDF::Term}.
153
    def write_triple(subject, predicate, object)
2✔
154
      @graph.insert(RDF::Statement(subject, predicate, object))
302✔
155
    end
156

157
    ##
158
    # Outputs the XHTML+RDFa representation of all stored triples.
159
    #
160
    # @return [void]
161
    def write_epilogue
2✔
162
      @base_uri = RDF::URI(@options[:base_uri]) if @options[:base_uri]
300✔
163
      @lang = @options[:lang]
300✔
164
      self.reset
300✔
165

166
      log_debug {"\nserialize: graph size: #{@graph.size}"}
482✔
167

168
      preprocess
300✔
169

170
      # Prefixes
171
      prefix = prefixes.keys.map {|pk| "#{pk}: #{prefixes[pk]}"}.sort.join(" ") unless prefixes.empty?
600✔
172
      log_debug {"\nserialize: prefixes: #{prefix.inspect}"}
482✔
173

174
      subjects = order_subjects
300✔
175

176
      # Take title from first subject having a heading predicate
177
      doc_title = nil
300✔
178
      titles = {}
300✔
179
      heading_predicates.each do |pred|
300✔
180
        @graph.query({predicate: pred}) do |statement|
600✔
181
          titles[statement.subject] ||= statement.object
32✔
182
        end
183
      end
184
      title_subject = subjects.detect {|subject| titles[subject]}
526✔
185
      doc_title = titles[title_subject]
300✔
186

187
      # Generate document
188
      doc = render_document(subjects,
300✔
189
        lang:     @lang,
190
        base:     base_uri,
191
        title:    doc_title,
192
        prefix:   prefix) do |s|
193
        subject(s)
236✔
194
      end
195
      @output.write(doc)
300✔
196

197
      super
300✔
198
    end
199

200
    protected
2✔
201

202
    # Render document using `haml_template[:doc]`. Yields each subject to be rendered separately.
203
    #
204
    # @param [Array<RDF::Resource>] subjects
205
    #   Ordered list of subjects. Template must yield to each subject, which returns
206
    #   the serialization of that subject (@see #subject_template)
207
    # @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
208
    # @option options [RDF::URI] base (nil)
209
    #   Base URI added to document, used for shortening URIs within the document.
210
    # @option options [Symbol, String] language (nil)
211
    #   Value of @lang attribute in document, also allows included literals to omit
212
    #   an @lang attribute if it is equivalent to that of the document.
213
    # @option options [String] title (nil)
214
    #   Value of html>head>title element.
215
    # @option options [String] prefix (nil)
216
    #   Value of @prefix attribute.
217
    # @option options [String] haml (haml_template[:doc])
218
    #   Haml template to render.
219
    # @yield [subject]
220
    #   Yields each subject
221
    # @yieldparam [RDF::URI] subject
222
    # @yieldreturn [:ignored]
223
    # @return String
224
    #   The rendered document is returned as a string
225
    def render_document(subjects, **options)
2✔
226
      template = options[:haml] || :doc
300✔
227
      options = {
228
        prefix: nil,
300✔
229
        subjects: subjects,
230
        title: nil,
231
      }.merge(options)
232
      hamlify(template, **options) do |subject|
300✔
233
        yield(subject) if block_given?
236✔
234
      end.gsub(/^\s+$/m, '')
235
    end
236

237
    # Render a subject using `haml_template[:subject]`.
238
    #
239
    # The _subject_ template may be called either as a top-level element, or recursively under another element if the _rel_ local is not nil.
240
    #
241
    # Yields each predicate/property to be rendered separately (@see #render_property_value and `#render_property_values`).
242
    #
243
    # @param [Array<RDF::Resource>] subject
244
    #   Subject to render
245
    # @param [Array<RDF::Resource>] predicates
246
    #   Predicates of subject. Each property is yielded for separate rendering.
247
    # @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
248
    # @option options [String] about (nil)
249
    #   About description, a CURIE, URI or Node definition.
250
    #   May be nil if no @about is rendered (e.g. unreferenced Nodes)
251
    # @option options [String] resource (nil)
252
    #   Resource description, a CURIE, URI or Node definition.
253
    #   May be nil if no @resource is rendered
254
    # @option options [String] rel (nil)
255
    #   Optional @rel property description, a CURIE, URI or Node definition.
256
    # @option options [String] typeof (nil)
257
    #   RDF type as a CURIE, URI or Node definition.
258
    #   If :about is nil, this defaults to the empty string ("").
259
    # @option options [:li, nil] element (nil)
260
    #   Render with &lt;li&gt;, otherwise with template default.
261
    # @option options [String] haml (haml_template[:subject])
262
    #   Haml template to render.
263
    # @yield [predicate]
264
    #   Yields each predicate
265
    # @yieldparam [RDF::URI] predicate
266
    # @yieldreturn [:ignored]
267
    # @return String
268
    #   The rendered document is returned as a string
269
    # Return Haml template for document from `haml_template[:subject]`
270
    def render_subject(subject, predicates, **options)
2✔
271
      template = options[:haml] || :subject
210✔
272
      options = {
273
        about:      (get_curie(subject) unless options[:rel]),
420✔
274
        base:       base_uri,
275
        element:    nil,
276
        predicates: predicates,
277
        rel:        nil,
278
        inlist:     nil,
279
        resource:   (get_curie(subject) if options[:rel]),
210✔
280
        subject:    subject,
281
        typeof:     nil,
282
      }.merge(options)
283
      hamlify(template, **options) do |predicate|
210✔
284
        yield(predicate) if block_given?
200✔
285
      end
286
    end
287

288
    # Render a single- or multi-valued predicate using `haml_template[:property_value]` or `haml_template[:property_values]`. Yields each object for optional rendering. The block should only render for recursive subject definitions (i.e., where the object is also a subject and is rendered underneath the first referencing subject).
289
    #
290
    # If a multi-valued property definition is not found within the template, the writer will use the single-valued property definition multiple times.
291
    #
292
    # @param [Array<RDF::Resource>] predicate
293
    #   Predicate to render.
294
    # @param [Array<RDF::Resource>] objects
295
    #   List of objects to render. If the list contains only a single element, the :property_value template will be used. Otherwise, the :property_values template is used.
296
    # @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
297
    # @option options [String] :haml (haml_template[:property_value], haml_template[:property_values])
298
    #   Haml template to render. Otherwise, uses `haml_template[:property_value] or haml_template[:property_values]`
299
    #   depending on the cardinality of objects.
300
    # @yield object, inlist
301
    #   Yields object and if it is contained in a list.
302
    # @yieldparam [RDF::Resource] object
303
    # @yieldparam [Boolean] inlist
304
    # @yieldreturn [String, nil]
305
    #   The block should only return a string for recursive object definitions.
306
    # @return String
307
    #   The rendered document is returned as a string
308
    def render_property(predicate, objects, **options, &block)
2✔
309
      log_debug {"render_property(#{predicate}): #{objects.inspect}, #{options.inspect}"}
420✔
310
      # If there are multiple objects, and no :property_values is defined, call recursively with
311
      # each object
312

313
      template = options[:haml]
218✔
314
      template ||= objects.length > 1 ? haml_template[:property_values] : haml_template[:property_value]
218✔
315

316
      # Separate out the objects which are lists and render separately
317
      list_objects = objects.reject do |o|
218✔
318
        o == RDF.nil ||
262✔
319
        (l = RDF::List.new(subject: o, graph: @graph)).invalid?
260✔
320
      end
321
      unless list_objects.empty?
218✔
322
        # Render non-list objects
323
        log_debug {"properties with lists: #{list_objects} non-lists: #{objects - list_objects}"}
28✔
324
        nl = log_depth {render_property(predicate, objects - list_objects, **options, &block)} unless objects == list_objects
16✔
325
        return nl.to_s + list_objects.map do |object|
14✔
326
          # Render each list as multiple properties and set :inlist to true
327
          list = RDF::List.new(subject: object, graph: @graph)
16✔
328
          list.each_statement {|st| subject_done(st.subject)}
68✔
329

330
          log_debug {"list: #{list.inspect} #{list.to_a}"}
32✔
331
          log_depth do
16✔
332
            render_property(predicate, list.to_a, **options.merge(inlist: "true")) do |object|
16✔
333
              yield(object, true) if block_given?
26✔
334
            end
335
          end
336
        end.join(" ")
337
      end
338

339
      if objects.length > 1 && template.nil?
204✔
340
        # If there is no property_values template, render each property using property_value template
341
        objects.map do |object|
×
342
          log_depth {render_property(predicate, [object], **options, &block)}
×
343
        end.join(" ")
344
      else
345
        log_fatal("Missing property template", exception: RDF::WriterError) if template.nil?
204✔
346

347
        template = options[:haml] || (
204✔
348
          objects.to_a.length > 1 &&
204✔
349
          haml_template.has_key?(:property_values) ?
350
            :property_values :
351
            :property_value)
352
        options = {
353
          objects:    objects,
204✔
354
          object:     objects.first,
355
          predicate:  predicate,
356
          property:   get_curie(predicate),
357
          rel:        get_curie(predicate),
358
          inlist:     nil,
359
        }.merge(options)
360
        hamlify(template, options, &block)
204✔
361
      end
362
    end
363

364
    # Perform any preprocessing of statements required
365
    # @return [ignored]
366
    def preprocess
2✔
367
      # Load initial contexts
368
      # Add terms and prefixes to local store for converting URIs
369
      # Keep track of vocabulary from left-most context
370
      [XML_RDFA_CONTEXT, HTML_RDFA_CONTEXT].each do |uri|
300✔
371
        ctx = Context.find(uri)
600✔
372
        ctx.prefixes.each_pair do |k, v|
600✔
373
          @uri_to_prefix[v] = k unless k.to_s == "dcterms"
13,800✔
374
        end
375

376
        ctx.terms.each_pair do |k, v|
600✔
377
          @uri_to_term_or_curie[v] = k.to_s
900✔
378
        end
379

380
        @vocabulary = ctx.vocabulary.to_s if ctx.vocabulary
600✔
381
      end
382

383
      # Load defined prefixes
384
      (@options[:prefixes] || {}).each_pair do |k, v|
300✔
385
        @uri_to_prefix[v.to_s] = k
20✔
386
      end
387
      @options[:prefixes] = {}  # Will define actual used when matched
300✔
388

389
      # Process each statement to establish CURIEs and Terms
390
      @graph.each {|statement| preprocess_statement(statement)}
602✔
391
    end
392

393
    # Order subjects for output. Override this to output subjects in another order.
394
    #
395
    # Uses #top_classes and #base_uri.
396
    # @return [Array<Resource>] Ordered list of subjects
397
    def order_subjects
2✔
398
      seen = {}
300✔
399
      subjects = []
300✔
400

401
      # Start with base_uri
402
      if base_uri && @subjects.keys.include?(base_uri)
300✔
403
        subjects << base_uri
×
404
        seen[base_uri] = true
×
405
      end
406

407
      # Add distinguished classes
408
      top_classes.
300✔
409
      select {|s| !seen.include?(s)}.
300✔
410
      each do |class_uri|
411
        graph.query({predicate: RDF.type, object: class_uri}).map {|st| st.subject}.sort.uniq.each do |subject|
300✔
412
          #log_debug {"order_subjects: #{subject.inspect}"}
413
          subjects << subject
×
414
          seen[subject] = true
×
415
        end
416
      end
417

418
      # Sort subjects by resources over nodes, ref_counts and the subject URI itself
419
      recursable = @subjects.keys.
300✔
420
        select {|s| !seen.include?(s)}.
236✔
421
        map {|r| [r.is_a?(RDF::Node) ? 1 : 0, ref_count(r), r]}.
236✔
422
        sort
423

424
      log_debug {"order_subjects: #{recursable.inspect}"}
482✔
425

426
      subjects += recursable.map{|r| r.last}
536✔
427
    end
428

429
    # Take a hash from predicate uris to lists of values.
430
    # Sort the lists of values.  Return a sorted list of properties.
431
    #
432
    # @param [Hash{String => Array<Resource>}] properties A hash of Property to Resource mappings
433
    # @return [Array<String>}] Ordered list of properties. Uses predicate_order.
434
    def order_properties(properties)
2✔
435
      # Make sorted list of properties
436
      prop_list = []
210✔
437

438
      predicate_order.each do |prop|
210✔
439
        next unless properties[prop.to_s]
630✔
440
        prop_list << prop.to_s
32✔
441
      end
442

443
      properties.keys.sort.each do |prop|
210✔
444
        next if prop_list.include?(prop.to_s)
200✔
445
        prop_list << prop.to_s
168✔
446
      end
447

448
      log_debug {"order_properties: #{prop_list.join(', ')}"}
404✔
449
      prop_list
210✔
450
    end
451

452
    # Perform any statement preprocessing required. This is used to perform reference counts and determine required prefixes.
453
    # @param [RDF::Statement] statement
454
    # @return [ignored]
455
    def preprocess_statement(statement)
2✔
456
      #log_debug {"preprocess: #{statement.inspect}"}
457
      bump_reference(statement.object)
302✔
458
      @subjects[statement.subject] = true
302✔
459
      get_curie(statement.subject)
302✔
460
      get_curie(statement.predicate)
302✔
461
      get_curie(statement.object)
302✔
462
      get_curie(statement.object.datatype) if statement.object.literal? && statement.object.has_datatype?
302✔
463
    end
464

465
    # Reset parser to run again
466
    def reset
2✔
467
      @options[:log_depth] = 0
300✔
468
      prefixes = {}
300✔
469
      @references = {}
300✔
470
      @serialized = {}
300✔
471
      @subjects = {}
300✔
472
    end
473

474
    protected
2✔
475

476
    # Display a subject.
477
    #
478
    # If the Haml template contains an entry matching the subject's rdf:type URI, that entry will be used as the template for this subject and it's properties.
479
    #
480
    # @example Displays a subject as a Resource Definition:
481
    #     <div typeof="rdfs:Resource" about="http://example.com/resource">
482
    #       <h1 property="dc:title">label</h1>
483
    #       <ul>
484
    #         <li content="2009-04-30T06:15:51Z" property="dc:created">2009-04-30T06:15:51+00:00</li>
485
    #       </ul>
486
    #     </div>
487
    #
488
    # @param [RDF::Resource] subject
489
    # @param [Hash{Symbol => Object}] options
490
    # @option options [:li, nil] :element(:div)
491
    #   Serialize using &lt;li&gt; rather than template default element
492
    # @option options [RDF::Resource] :rel (nil)
493
    #   Optional @rel property
494
    # @return [String]
495
    def subject(subject, **options)
2✔
496
      return if is_done?(subject)
260✔
497

498
      subject_done(subject)
210✔
499

500
      properties = properties_for_subject(subject)
210✔
501
      typeof = type_of(properties.delete(RDF.type.to_s), subject)
210✔
502
      prop_list = order_properties(properties)
210✔
503

504
      log_debug {"props: #{prop_list.inspect}"}
404✔
505

506
      render_opts = {typeof: typeof, property_values: properties}.merge(options)
210✔
507

508
      render_subject_template(subject, prop_list, **render_opts)
210✔
509
    end
510

511
    # @param [RDF::Resource] subject
512
    # @return [Hash{String => Object}]
513
    def properties_for_subject(subject)
2✔
514
      properties = {}
210✔
515
      @graph.query({subject: subject}) do |st|
210✔
516
        key = st.predicate.to_s.freeze
250✔
517
        properties[key] ||= []
250✔
518
        properties[key] << st.object
250✔
519
      end
520
      properties
210✔
521
    end
522

523
    # @param [Array,NilClass] type
524
    # @param [RDF::Resource] subject
525
    # @return [String] string representation of the specific RDF.type uri
526
    def type_of(type, subject)
2✔
527
      # Find appropriate template
528
      curie = case
529
      when subject.node?
210✔
530
        subject.to_s if ref_count(subject) > 1
16✔
531
      else
532
        get_curie(subject)
194✔
533
      end
534

535
      typeof = Array(type).map {|r| get_curie(r)}.join(" ")
226✔
536
      typeof = nil if typeof.empty?
210✔
537

538
      # Nodes without a curie need a blank @typeof to generate a subject
539
      typeof ||= "" unless curie
210✔
540

541
      log_debug {"subject: #{curie.inspect}, typeof: #{typeof.inspect}" }
404✔
542

543
      typeof.freeze
210✔
544
    end
545

546
    # @param [RDF::Resource] subject
547
    # @param [Array] prop_list
548
    # @param [Hash] render_opts
549
    # @return [String]
550
    def render_subject_template(subject, prop_list, **render_opts)
2✔
551
      # See if there's a template based on the sorted concatenation of all types of this subject
552
      # or any type of this subject
553
      tmpl = find_template(subject)
210✔
554
      log_debug {"subject: found template #{tmpl[:identifier] || tmpl.inspect}"} if tmpl
210✔
555

556
      # Render this subject
557
      # If :rel is specified and :typeof is nil, use @resource instead of @about.
558
      # Pass other options from calling context
559
      with_template(tmpl) do
210✔
560
        render_subject(subject, prop_list, **render_opts) do |pred|
210✔
561
          log_depth do
200✔
562
            pred = RDF::URI(pred) if pred.is_a?(String)
200✔
563
            values = render_opts[:property_values][pred.to_s]
200✔
564
            log_debug {"subject: #{get_curie(subject)}, pred: #{get_curie(pred)}, values: #{values.inspect}"}
384✔
565
            predicate(pred, values)
200✔
566
          end
567
        end
568
      end
569
    end
570

571
    # Write a predicate with one or more values.
572
    #
573
    # Values may be a combination of Literal and Resource (Node or URI).
574
    # @param [RDF::Resource] predicate
575
    #   Predicate to serialize
576
    # @param [Array<RDF::Resource>] objects
577
    #   Objects to serialize
578
    # @return [String]
579
    def predicate(predicate, objects)
2✔
580
      log_debug {"predicate: #{predicate.inspect}, objects: #{objects}"}
384✔
581

582
      return if objects.to_a.empty?
200✔
583

584
      log_debug {"predicate: #{get_curie(predicate)}"}
384✔
585
      render_property(predicate, objects) do |o, inlist=nil|
200✔
586
        # Yields each object, for potential recursive definition.
587
        # If nil is returned, a leaf is produced
588
        log_depth {subject(o, rel: get_curie(predicate), inlist: inlist, element: (:li if objects.length > 1 || inlist))} if !is_done?(o) && @subjects.include?(o)
236✔
589
      end
590
    end
591

592
    # Haml rendering helper. Return CURIE for the literal datatype, if the literal is a typed literal.
593
    #
594
    # @param [RDF::Resource] literal
595
    # @return [String, nil]
596
    # @raise [RDF::WriterError]
597
    def get_dt_curie(literal)
2✔
598
      if literal.is_a?(RDF::Literal)
154✔
599
        get_curie(literal.datatype) if literal.literal? && literal.datatype?
154✔
600
      else
601
        log_error("Getting datatype CURIE for #{literal.inspect}, which must be a literal")
×
602
        nil
603
      end
604
    end
605

606
    # Haml rendering helper. Return language for plain literal, if there is no language, or it is the same as the document, return nil
607
    #
608
    # @param [RDF::Literal] literal
609
    # @return [Symbol, nil]
610
    # @raise [RDF::WriterError]
611
    def get_lang(literal)
2✔
612
      if literal.is_a?(RDF::Literal)
154✔
613
        literal.language if literal.literal? && literal.language && literal.language.to_s != @lang.to_s
154✔
614
      else
615
        log_error("Getting language for #{literal.inspect}, which must be a literal")
×
616
        nil
617
      end
618
    end
619

620
    # Haml rendering helper. Data to be added to a @content value, for specific datatypes
621
    #
622
    # @param [RDF::Literal] literal
623
    # @return [String, nil]
624
    # @raise [RDF::WriterError]
625
    def get_content(literal)
2✔
626
      case literal
148✔
627
      when RDF::Literal::Date, RDF::Literal::Time, RDF::Literal::DateTime, RDF::Literal::Duration
628
        literal.to_s
72✔
629
      when RDF::Literal then nil
630
      else
631
        log_error("Getting content for #{literal.inspect}, which must be a literal")
×
632
        nil
633
      end
634
    end
635

636
    # Haml rendering helper. Display value for object, may be humanized into a non-canonical form
637
    #
638
    # @param [RDF::Literal] literal
639
    # @return [String]
640
    # @raise [RDF::WriterError]
641
    def get_value(literal)
2✔
642
      if literal.is_a?(RDF::Literal)
154✔
643
        literal.humanize
154✔
644
      else
645
        log_error("Getting value for #{literal.inspect}, which must be a literal")
×
646
        nil
647
      end
648
    end
649

650
    # Haml rendering helper. Return an appropriate label for a resource.
651
    #
652
    # @param [RDF::Resource] resource
653
    # @return [String]
654
    # @raise [RDF::WriterError]
655
    def get_predicate_name(resource)
2✔
656
      if resource.is_a?(RDF::URI)
172✔
657
        get_curie(resource)
172✔
658
      else
659
        log_error("Getting predicate name for #{resource.inspect}, which must be a URI")
×
660
        nil
661
      end
662
    end
663

664
    # Haml rendering helper. Return appropriate, term, CURIE or URI for the given resource.
665
    #
666
    # @param [RDF::Value] resource
667
    # @return [String] value to use to identify URI
668
    # @raise [RDF::WriterError]
669
    def get_curie(resource)
2✔
670
      case resource
3,042✔
671
      when RDF::URI
672
        begin
673
          uri = resource.to_s
2,578✔
674

675
          curie = case
676
          when @uri_to_term_or_curie.has_key?(uri)
2,578✔
677
            log_debug {"get_curie(#{uri}): uri_to_term_or_curie #{@uri_to_term_or_curie[uri].inspect}"}
3,900✔
678
            return @uri_to_term_or_curie[uri]
2,002✔
679
          when base_uri && uri.index(base_uri.to_s) == 0
680
            log_debug {"get_curie(#{uri}): base_uri (#{uri.sub(base_uri.to_s, "")})"}
×
681
            uri.sub(base_uri.to_s, "")
×
682
          when @vocabulary && uri.index(@vocabulary) == 0
683
            log_debug {"get_curie(#{uri}): vocabulary"}
×
684
            uri.sub(@vocabulary, "")
×
685
          when u = @uri_to_prefix.keys.detect {|u| uri.index(u.to_s) == 0}
23,162✔
686
            log_debug {"get_curie(#{uri}): uri_to_prefix"}
360✔
687
            # Use a defined prefix
688
            prefix = @uri_to_prefix[u]
180✔
689
            prefix(prefix, u)  # Define for output
180✔
690
            uri.sub(u.to_s, "#{prefix}:")
180✔
691
          when @options[:standard_prefixes] && vocab = RDF::Vocabulary.detect {|v| uri.index(v.to_uri.to_s) == 0}
4,542✔
692
            log_debug {"get_curie(#{uri}): standard_prefixes"}
732✔
693
            prefix = vocab.__name__.to_s.split('::').last.downcase
366✔
694
            prefix(prefix, vocab.to_uri) # Define for output
366✔
695
            uri.sub(vocab.to_uri.to_s, "#{prefix}:")
366✔
696
          else
697
            log_debug {"get_curie(#{uri}): none"}
52✔
698
            uri
30✔
699
          end
700

701
          #log_debug {"get_curie(#{resource}) => #{curie}"}
702

703
          @uri_to_term_or_curie[uri] = curie
576✔
704
        rescue ArgumentError => e
705
          log_error("Invalid URI #{uri.inspect}: #{e.message}")
×
706
          nil
×
707
        end
708
      when RDF::Node then resource.to_s
206✔
709
      when RDF::Literal then nil
710
      else
711
        log_error("Getting CURIE for #{resource.inspect}, which must be a resource")
×
712
        nil
713
      end
714
    end
715
    private
2✔
716

717
    ##
718
    # Haml rendering helper. Escape entities to avoid whitespace issues.
719
    #
720
    # # In addtion to "&<>, encode \n and \r to ensure that whitespace is properly preserved
721
    #
722
    # @param [String] str
723
    # @return [String]
724
    #   Entity-encoded string
725
    def escape_entities(str)
2✔
726
      # Haml 6 does escaping
727
      return str if Haml.const_defined?(:Template)
148✔
728
      CGI.escapeHTML(str).gsub(/[\n\r]/) {|c| '&#x' + c.unpack('h').first + ';'}
×
729
    end
730

731
    # Set the template to use within block
732
    # @param [Hash{Symbol => String}] templ template to use for block evaluation; merged in with the existing template.
733
    # @yield
734
    #   Yields with no arguments
735
    # @yieldreturn [Object] returns the result of yielding
736
    # @return [Object]
737
    def with_template(templ)
2✔
738
      if templ
210✔
739
        new_template = @options[:haml].
×
740
          reject {|k,v| ![:subject, :property_value, :property_values, :rel].include?(k)}.
×
741
          merge(templ || {})
742
        old_template, @haml_template = @haml_template, new_template
×
743
      else
744
        old_template = @haml_template
210✔
745
      end
746

747
      res = yield
210✔
748
      # Restore template
749
      @haml_template = old_template
210✔
750

751
      res
210✔
752
    end
753

754
    # Render HAML
755
    # @param [Symbol, String] template
756
    #   If a symbol, finds a matching template from haml_template, otherwise uses template as is
757
    # @param [Hash{Symbol => Object}] locals
758
    #   Locals to pass to render
759
    # @return [String]
760
    # @raise [RDF::WriterError]
761
    def hamlify(template, locals = {})
2✔
762
      log_debug {"hamlify template: #{template}"}
1,278✔
763
      template = haml_template[template] if template.is_a?(Symbol)
714✔
764

765
      template = template.align_left
714✔
766
      log_debug {"hamlify locals: #{locals.inspect}"}
1,278✔
767

768
      haml_opts = @options[:haml_options] || HAML_OPTIONS
714✔
769
      haml_runner = if Haml::VERSION >= "6"
714✔
770
        Haml::Template.new(**haml_opts) {template}
1,428✔
771
      else
772
        Haml::Engine.new(template, **haml_opts)
×
773
      end
774
      haml_runner.render(self, locals) do |*args|
714✔
775
        yield(*args) if block_given?
648✔
776
      end
777
    rescue Haml::Error => e
778
      log_fatal("#{e.inspect}\n" +
×
779
        "rendering #{template}\n" +
780
        "with options #{(@options[:haml_options] || HAML_OPTIONS).inspect}\n" +
×
781
        "and locals #{locals.inspect}",
782
        exception: RDF::WriterError
783
      )
784
    end
785

786
    ##
787
    # Find a template appropriate for the subject.
788
    # Override this method to provide templates based on attributes of a given subject
789
    #
790
    # @param [RDF::URI] subject
791
    # @return [Hash] # return matched matched template
792
    def find_template(subject); end
2✔
793

794
    # Mark a subject as done.
795
    # @param [RDF::Resource] subject
796
    # @return [Boolean]
797
    def subject_done(subject)
2✔
798
      @serialized[subject] = true
262✔
799
    end
800

801
    # Determine if the subject has been completed
802
    # @param [RDF::Resource] subject
803
    # @return [Boolean]
804
    def is_done?(subject)
2✔
805
      @serialized.include?(subject)
472✔
806
    end
807

808
    # Increase the reference count of this resource
809
    # @param [RDF::Resource] resource
810
    # @return [Integer] resulting reference count
811
    def bump_reference(resource)
2✔
812
      @references[resource] = ref_count(resource) + 1
302✔
813
    end
814

815
    # Return the number of times this node has been referenced in the object position
816
    # @param [RDF::Node] node
817
    # @return [Boolean]
818
    def ref_count(node)
2✔
819
      @references.fetch(node, 0)
554✔
820
    end
821
  end
822
end
823

824
require 'rdf/rdfa/writer/haml_templates'
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc