• 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.55
/lib/rdf/rdfa/reader.rb
1
begin
2
  require 'nokogiri'
2✔
3
rescue LoadError
4
  :rexml
×
5
end
6
require 'rdf/ntriples'
2✔
7
require 'rdf/xsd'
2✔
8

9
module RDF::RDFa
2✔
10
  ##
11
  # An RDFa parser in Ruby
12
  #
13
  # This class supports [Nokogiri][] for HTML
14
  # processing, and will automatically select the most performant
15
  # implementation (Nokogiri or LibXML) that is available. If need be, you
16
  # can explicitly override the used implementation by passing in a
17
  # `:library` option to `Reader.new` or `Reader.open`.
18
  #
19
  # [Nokogiri]: https://nokogiri.org/
20
  #
21
  # Based on processing rules described here:
22
  # @see https://www.w3.org/TR/rdfa-syntax/#s_model RDFa 1.0
23
  # @see https://www.w3.org/TR/2012/REC-rdfa-core-20120607/
24
  # @see https://www.w3.org/TR/2012/CR-xhtml-rdfa-20120313/
25
  # @see https://dev.w3.org/html5/rdfa/
26
  #
27
  # @author [Gregg Kellogg](https://greggkellogg.net/)
28
  class Reader < RDF::Reader
2✔
29
    format Format
2✔
30
    include Expansion
2✔
31
    include RDF::Util::Logger
2✔
32

33
    XHTML = "http://www.w3.org/1999/xhtml"
2✔
34

35
    # Content model for @about and @resource. In RDFa 1.0, this was URIorSafeCURIE
36
    SafeCURIEorCURIEorIRI = {
37
      :"rdfa1.0" => [:safe_curie, :uri, :bnode],
2✔
38
      :"rdfa1.1" => [:safe_curie, :curie, :uri, :bnode],
39
    }
40

41
    # Content model for @datatype. In RDFa 1.0, this was CURIE
42
    # Also plural TERMorCURIEorAbsIRIs, content model for @rel, @rev, @property and @typeof
43
    TERMorCURIEorAbsIRI = {
44
      :"rdfa1.0" => [:term, :curie],
2✔
45
      :"rdfa1.1" => [:term, :curie, :absuri],
46
    }
47

48
    # This expression matches an NCName as defined in
49
    # [XML-NAMES](https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-NCName)
50
    #
51
    # @see https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-NCName
52
    NC_REGEXP = Regexp.new(
2✔
53
      %{^
54
        (  [a-zA-Z_]
55
         | \\\\u[0-9a-fA-F]{4}
56
        )
57
        (  [0-9a-zA-Z_\.-/]
58
         | \\\\u([0-9a-fA-F]{4})
59
        )*
60
      $},
61
      Regexp::EXTENDED)
62

63
    # This expression matches an term as defined in
64
    # [RDFA-CORE](https://www.w3.org/TR/2012/REC-rdfa-core-20120607/#s_terms)
65
    #
66
    # For the avoidance of doubt, this definition means a 'term'
67
    # in RDFa is an XML NCName that also permits slash as a non-leading character.
68
    # @see https://www.w3.org/TR/2012/REC-rdfa-core-20120607/#s_terms
69
    TERM_REGEXP = Regexp.new(
2✔
70
      %{^
71
        (?!\\\\u0301)             # &#x301; is a non-spacing acute accent.
72
                                  # It is legal within an XML Name, but not as the first character.
73
        (  [a-zA-Z_]
74
         | \\\\u[0-9a-fA-F]{4}
75
        )
76
        (  [-0-9a-zA-Z_\.\/]
77
         | \\\\u([0-9a-fA-F]{4})
78
        )*
79
      $},
80
      Regexp::EXTENDED)
81

82
    # Host language
83
    # @!attribute [r] host_language
84
    # @return [:xml, :xhtml1, :xhtml5, :html4, :html5, :svg]
85
    attr_reader :host_language
2✔
86

87
    # Version
88
    # @!attribute [r] version
89
    # @return [:"rdfa1.0", :"rdfa1.1"]
90
    attr_reader :version
2✔
91

92
    # Repository used for collecting triples.
93
    # @!attribute [r] repository
94
    # @return [RDF::Repository]
95
    attr_reader :repository
2✔
96

97
    # Returns the XML implementation module for this reader instance.
98
    #
99
    # @!attribute [rw] implementation
100
    # @return [Module]
101
    attr_reader :implementation
2✔
102

103
    # The Recursive Baggage
104
    # @private
105
    class EvaluationContext # :nodoc:
2✔
106
      ##
107
      # The base.
108
      #
109
      # This will usually be the URL of the document being processed,
110
      # but it could be some other URL, set by some other mechanism,
111
      # such as the (X)HTML base element. The important thing is that it establishes
112
      # a URL against which relative paths can be resolved.
113
      #
114
      # @!attribute [rw] base
115
      # @return [RDF::URI]
116
      attr_accessor :base
2✔
117

118
      ##
119
      # The parent subject.
120
      #
121
      # The initial value will be the same as the initial value of base,
122
      # but it will usually change during the course of processing.
123
      #
124
      # @!attribute [rw] parent_subject
125
      # @return [RDF::URI]
126
      attr_accessor :parent_subject
2✔
127

128
      ##
129
      # The parent object.
130
      #
131
      # In some situations the object of a statement becomes the subject of any nested statements,
132
      # and this property is used to convey this value.
133
      # Note that this value may be a bnode, since in some situations a number of nested statements
134
      # are grouped together on one bnode.
135
      # This means that the bnode must be set in the containing statement and passed down,
136
      # and this property is used to convey this value.
137
      #
138
      # @!attribute [rw] parent_object
139
      # @return [RDF::URI]
140
      attr_accessor :parent_object
2✔
141

142
      ##
143
      # A list of current, in-scope URI mappings.
144
      #
145
      # @!attribute [rw] uri_mappings
146
      # @return [Hash{Symbol => String}]
147
      attr_accessor :uri_mappings
2✔
148

149
      ##
150
      # A list of current, in-scope Namespaces. This is the subset of uri_mappings
151
      # which are defined using xmlns.
152
      #
153
      # @!attribute [rw] namespaces
154
      # @return [Hash{String => Namespace}]
155
      attr_accessor :namespaces
2✔
156

157
      ##
158
      # A list of incomplete triples.
159
      #
160
      # A triple can be incomplete when no object resource
161
      # is provided alongside a predicate that requires a resource (i.e., @rel or @rev).
162
      # The triples can be completed when a resource becomes available,
163
      # which will be when the next subject is specified (part of the process called chaining).
164
      #
165
      # @!attribute [rw] incomplete_triples
166
      # @return [Array<Array<RDF::URI, RDF::Resource>>]
167
      attr_accessor :incomplete_triples
2✔
168

169
      ##
170
      # The language. Note that there is no default language.
171
      #
172
      # @!attribute [rw] language
173
      # @return [Symbol]
174
      attr_accessor :language
2✔
175

176
      ##
177
      # The term mappings, a list of terms and their associated URIs.
178
      #
179
      # This specification does not define an initial list.
180
      # Host Languages may define an initial list.
181
      # If a Host Language provides an initial list, it should do so via an RDFa Context document.
182
      #
183
      # @!attribute [rw] term_mappings
184
      # @return [Hash{Symbol => RDF::URI}]
185
      attr_accessor :term_mappings
2✔
186

187
      ##
188
      # The default vocabulary
189
      #
190
      # A value to use as the prefix URI when a term is used.
191
      # This specification does not define an initial setting for the default vocabulary.
192
      # Host Languages may define an initial setting.
193
      #
194
      # @!attribute [rw] default_vocabulary
195
      # @return [RDF::URI]
196
      attr_accessor :default_vocabulary
2✔
197

198
      ##
199
      # lists
200
      #
201
      # A hash associating lists with properties.
202
      # @!attribute [rw] list_mapping
203
      # @return [Hash{RDF:URI: Array<RDF::Resource>}]
204
      attr_accessor :list_mapping
2✔
205

206
      # @param [RDF::URI] base
207
      # @param [Hash] host_defaults
208
      # @option host_defaults [Hash{String => RDF::URI}] :term_mappings Hash of NCName => URI
209
      # @option host_defaults [Hash{String => RDF::URI}] :vocabulary Hash of prefix => URI
210
      def initialize(base, host_defaults)
2✔
211
        # Initialize the evaluation context, [5.1]
212
        @base = base
4,372✔
213
        @parent_subject = @base
4,372✔
214
        @parent_object = nil
4,372✔
215
        @namespaces = {}
4,372✔
216
        @incomplete_triples = []
4,372✔
217
        @language = nil
4,372✔
218
        @uri_mappings = host_defaults.fetch(:uri_mappings, {})
4,372✔
219
        @term_mappings = host_defaults.fetch(:term_mappings, {})
4,372✔
220
        @default_vocabulary = host_defaults.fetch(:vocabulary, nil)
4,372✔
221
      end
222

223
      # Copy this Evaluation Context
224
      #
225
      # @param [EvaluationContext] from
226
      def initialize_copy(from)
2✔
227
        # clone the evaluation context correctly
228
        @uri_mappings = from.uri_mappings.clone
50✔
229
        @incomplete_triples = from.incomplete_triples.clone
50✔
230
        @namespaces = from.namespaces.clone
50✔
231
        @list_mapping = from.list_mapping # Don't clone
50✔
232
      end
233

234
      def inspect
2✔
235
        v = ['base', 'parent_subject', 'parent_object', 'language', 'default_vocabulary'].map do |a|
2,396✔
236
          "#{a}=#{o = self.send(a); o.respond_to?(:to_ntriples) ? o.to_ntriples : o.inspect}"
11,980✔
237
        end
238
        v << "uri_mappings[#{uri_mappings.keys.length}]"
2,396✔
239
        v << "incomplete_triples[#{incomplete_triples.length}]"
2,396✔
240
        v << "term_mappings[#{term_mappings.keys.length}]"
2,396✔
241
        v << "lists[#{list_mapping.keys.length}]" if list_mapping
2,396✔
242
        v.join(", ")
2,396✔
243
      end
244
    end
245

246
    ##
247
    # RDFa Reader options
248
    # @see https://ruby-rdf.github.io/rdf/RDF/Reader#options-class_method
249
    def self.options
2✔
250
      super + [
×
251
        RDF::CLI::Option.new(
252
          symbol: :vocab_expansion,
253
          datatype: TrueClass,
254
          default: false,
255
          control: :checkbox,
256
          on: ["--vocab-expansion"],
257
          description: "Perform OWL2 expansion on the resulting graph.") {true},
×
258
        RDF::CLI::Option.new(
259
          symbol: :host_language,
260
          datatype: %w(xml xhtml1 xhtml5 html4 html5 svg),
261
          default: :html5,
262
          control: :select,
263
          on: ["--host-language HOSTLANG", %w(xml xhtml1 xhtml5 html4 html5 svg)],
264
          description: "Host Language. One of xml, xhtml1, xhtml5, html4, or svg") do |arg|
265
            arg.to_sym
×
266
        end,
267
        RDF::CLI::Option.new(
268
          symbol: :rdfagraph,
269
          datatype: %w(output processor both),
270
          default: :output,
271
          control: :select,
272
          on: ["--rdfagraph RDFAGRAPH", %w(output processor both)],
273
          description: "Used to indicate if either or both of the :output or :processor graphs are output.") {|arg| arg.to_sym},
×
274
      ]
275
    end
276

277
    ##
278
    # Initializes the RDFa reader instance.
279
    #
280
    # @param  [IO, File, String] input
281
    #   the input stream to read
282
    # @param  [Hash{Symbol => Object}] options
283
    #   any additional options (see `RDF::Reader#initialize`)
284
    # @option options [Symbol] :library
285
    #   One of :nokogiri or :rexml. If nil/unspecified uses :nokogiri if available, :rexml otherwise.
286
    # @option options [Boolean]  :vocab_expansion (false)
287
    #   whether to perform OWL2 expansion on the resulting graph
288
    # @option options [Boolean]  :reference_folding (true)
289
    #   whether to perform RDFa property copying on the resulting graph
290
    # @option options [:xml, :xhtml1, :xhtml5, :html4, :html5, :svg] :host_language (:html5)
291
    #   Host Language
292
    # @option options [:"rdfa1.0", :"rdfa1.1"] :version (:"rdfa1.1")
293
    #   Parser version information
294
    # @option options [Proc]    :processor_callback (nil)
295
    #   Callback used to provide processor graph triples.
296
    # @option options [Array<Symbol>]    :rdfagraph ([:output])
297
    #   Used to indicate if either or both of the :output or :processor graphs are output.
298
    #   Value is an array containing on or both of :output or :processor.
299
    # @option options [Repository] :vocab_repository (nil)
300
    #   Repository to save loaded vocabularies.
301
    # @return [reader]
302
    # @yield  [reader] `self`
303
    # @yieldparam  [RDF::Reader] reader
304
    # @yieldreturn [void] ignored
305
    # @raise [RDF::ReaderError] if _validate_
306
    def initialize(input = $stdin, **options, &block)
2✔
307
      super do
1,106✔
308
        @options = {reference_folding: true}.merge(@options)
1,106✔
309
        @repository = RDF::Repository.new
1,106✔
310

311
        @options[:rdfagraph] = case @options[:rdfagraph]
1,106✔
312
        when 'all' then [:output, :processor]
×
313
        when String, Symbol then @options[:rdfagraph].to_s.split(',').map(&:strip).map(&:to_sym)
38✔
314
        when Array then @options[:rdfagraph].map {|o| o.to_s.to_sym}
16✔
315
        else  []
1,062✔
316
        end.select {|o| [:output, :processor].include?(o)}
52✔
317
        @options[:rdfagraph] << :output if @options[:rdfagraph].empty?
1,106✔
318

319
        @library = case options[:library]
1,106✔
320
          when nil
321
            # Use Nokogiri when available, and REXML otherwise:
322
            defined?(::Nokogiri) ? :nokogiri : :rexml
424✔
323
          when :nokogiri, :rexml
324
            options[:library]
682✔
325
          else
326
            raise ArgumentError.new("expected :rexml or :nokogiri, but got #{options[:library].inspect}")
×
327
        end
328

329
        require "rdf/rdfa/reader/#{@library}"
1,106✔
330
        @implementation = case @library
1,106✔
331
          when :nokogiri then Nokogiri
766✔
332
          when :rexml    then REXML
340✔
333
        end
334
        self.extend(@implementation)
1,106✔
335

336
        detect_host_language_version(input, **options)
1,106✔
337

338
        add_info(@doc, "version = #{@version},  host_language = #{@host_language}, library = #{@library}, rdfagraph = #{@options[:rdfagraph].inspect}, expand = #{@options[:vocab_expansion]}")
1,106✔
339

340
        begin
341
          initialize_xml(input, **options)
1,106✔
342
        rescue
343
          add_error(nil, "Malformed document: #{$!.message}")
2✔
344
        end
345
        add_error(nil, "Empty document") if root.nil?
1,106✔
346
        add_error(nil, doc_errors.map(&:message).uniq.join("\n")) if !doc_errors.empty?
1,106✔
347

348
        # Section 4.2 RDFa Host Language Conformance
349
        #
350
        # The Host Language may require the automatic inclusion of one or more Initial Contexts
351
        @host_defaults = {
352
          vocabulary:       nil,
1,106✔
353
          uri_mappings:     {},
354
          initial_contexts: [],
355
        }
356

357
        if @version == :"rdfa1.0"
1,106✔
358
          # Add default term mappings
359
          @host_defaults[:term_mappings] = %w(
4✔
360
            alternate appendix bookmark cite chapter contents copyright first glossary help icon index
361
            last license meta next p3pv1 prev role section stylesheet subsection start top up
362
            ).inject({}) { |hash, term| hash[term] = RDF::URI("http://www.w3.org/1999/xhtml/vocab#") + term; hash }
100✔
363
        end
364

365
        case @host_language
1,106✔
366
        when :xml, :svg
367
          @host_defaults[:initial_contexts] = [XML_RDFA_CONTEXT]
22✔
368
        when :xhtml1
369
          @host_defaults[:initial_contexts] = [XML_RDFA_CONTEXT, XHTML_RDFA_CONTEXT]
22✔
370
        when :xhtml5, :html4, :html5
371
          @host_defaults[:initial_contexts] = [XML_RDFA_CONTEXT, HTML_RDFA_CONTEXT]
1,062✔
372
        end
373

374
        block.call(self) if block_given?
1,106✔
375
      end
376
    end
377

378
    ##
379
    # Extracts RDF from script element, or embeded RDF/XML
380
    def extract_script(el, input, type, **options, &block)
2✔
381
      add_debug(el, "script element of type #{type}")
40✔
382
      begin
383
        # Formats don't exist unless they've been required
384
        case type.to_s
40✔
385
        when 'application/csvm+json' then require 'rdf/tabular'
×
386
        when 'application/ld+json'   then require 'json/ld'
20✔
387
        when 'application/rdf+xml'   then require 'rdf/rdfxml'
4✔
388
        when 'text/ntriples'         then require 'rdf/ntriples'
×
389
        when 'text/turtle'           then require 'rdf/turtle'
12✔
390
        end
391
      rescue LoadError
392
      end
393

394
      @readers ||= {}
40✔
395
      reader = @readers[type.to_s] = RDF::Reader.for(content_type: type.to_s) unless @readers.has_key?(type.to_s)
40✔
396
      if reader = @readers[type.to_s]
40✔
397
        add_debug(el, "=> reader #{reader.to_sym}")
40✔
398
        # Wrap input in a RemoteDocument with appropriate content-type and base
399
        doc = if input.is_a?(String)
40✔
400
          RDF::Util::File::RemoteDocument.new(input, content_type: type.to_s, **options)
36✔
401
        else
402
          input
4✔
403
        end
404
        reader.new(doc, **options).each(&block)
40✔
405
      else
406
        add_debug(el, "=> no reader found")
×
407
      end
408
    end
409

410
    ##
411
    # Iterates the given block for each RDF statement in the input.
412
    #
413
    # Reads to graph and performs expansion if required.
414
    #
415
    # @yield  [statement]
416
    # @yieldparam [RDF::Statement] statement
417
    # @return [void]
418
    def each_statement(&block)
2✔
419
      if block_given?
768✔
420
        unless @processed || @root.nil?
766✔
421
          # Add prefix definitions from host defaults
422
          @host_defaults[:uri_mappings].each_pair do |prefix, value|
762✔
423
            prefix(prefix, value)
×
424
          end
425

426
          # parse
427
          parse_whole_document(@doc, RDF::URI(base_uri))
762✔
428

429
          # Look for Embedded RDF/XML
430
          unless @root.xpath("//rdf:RDF", "rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#").empty?
660✔
431
            extract_script(@root, @doc, "application/rdf+xml", **@options.merge(base_uri: base_uri)) do |statement|
4✔
432
              @repository << statement
4✔
433
            end
434
          end
435

436
          # Look for Embedded microdata
437
          unless @root.xpath("//@itemscope").empty?
660✔
438
            begin
439
              require 'rdf/microdata'
2✔
440
              add_debug(@doc, "process microdata")
2✔
441
              @repository << RDF::Microdata::Reader.new(@doc, **options)
2✔
442
            rescue LoadError
443
              add_debug(@doc, "microdata detected, not processed")
×
444
            end
445
          end
446

447
          # Perform property copying
448
          copy_properties(@repository) if @options[:reference_folding]
660✔
449

450
          # Perform vocabulary expansion
451
          expand(@repository) if @options[:vocab_expansion]
660✔
452

453
          @processed = true
660✔
454
        end
455

456
        # Return statements in the default graph for
457
        # statements in the associated named or default graph from the
458
        # processed repository
459
        @repository.each do |statement|
664✔
460
          case statement.graph_name
6,366✔
461
          when nil
462
            yield statement if @options[:rdfagraph].include?(:output)
2,726✔
463
          when RDF::RDFA.ProcessorGraph
464
            yield RDF::Statement.new(*statement.to_triple) if @options[:rdfagraph].include?(:processor)
3,640✔
465
          end
466
        end
467

468
        if validate? && log_statistics[:error]
662✔
469
          raise RDF::ReaderError, "Errors found during processing"
2✔
470
        end
471
      end
472
      enum_for(:each_statement)
662✔
473
    end
474

475
    ##
476
    # Iterates the given block for each RDF triple in the input.
477
    #
478
    # @yield  [subject, predicate, object]
479
    # @yieldparam [RDF::Resource] subject
480
    # @yieldparam [RDF::URI]      predicate
481
    # @yieldparam [RDF::Value]    object
482
    # @return [void]
483
    def each_triple(&block)
2✔
484
      if block_given?
10✔
485
        each_statement do |statement|
8✔
486
          block.call(*statement.to_triple)
112✔
487
        end
488
      end
489
      enum_for(:each_triple)
10✔
490
    end
491

492
    private
2✔
493

494
    # Keep track of allocated BNodes
495
    def bnode(value = nil)
2✔
496
      @bnode_cache ||= {}
66✔
497
      @bnode_cache[value.to_s] ||= RDF::Node.new(value)
66✔
498
    end
499

500
    # Figure out the document path, if it is an Element or Attribute
501
    def node_path(node)
2✔
502
      "<#{base_uri}>#{node.respond_to?(:display_path) ? node.display_path : node}"
49,866✔
503
    end
504

505
    # Add debug event to debug array, if specified
506
    #
507
    # @param [#display_path, #to_s] node XML Node or string for showing context
508
    # @param [String] message
509
    # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
510
    def add_debug(node, message = "", &block)
2✔
511
      add_processor_message(node, message, nil, &block)
44,048✔
512
    end
513

514
    def add_info(node, message, process_class = RDF::RDFA.Info, &block)
2✔
515
      add_processor_message(node, message, process_class, &block)
5,236✔
516
    end
517

518
    def add_warning(node, message, process_class = RDF::RDFA.Warning)
2✔
519
      add_processor_message(node, message, process_class)
60✔
520
    end
521

522
    def add_error(node, message, process_class = RDF::RDFA.Error)
2✔
523
      add_processor_message(node, message, process_class)
522✔
524
    end
525

526
    def add_processor_message(node, message, process_class, &block)
2✔
527
      case process_class
49,866✔
528
      when RDF::RDFA.Error    then log_error(node_path(node), message, &block)
522✔
529
      when RDF::RDFA.Warning  then log_warn(node_path(node), message, &block)
12✔
530
      when RDF::RDFA.Info     then log_info(node_path(node), message, &block)
5,236✔
531
      else                         log_debug(node_path(node), message, &block)
44,096✔
532
      end
533
      process_class ||= RDF::RDFA.Info
49,866✔
534
      if @options[:processor_callback] || @options[:rdfagraph].include?(:processor)
49,866✔
535
        n = RDF::Node.new
1,188✔
536
        processor_statements = [
537
          RDF::Statement.new(n, RDF["type"], process_class, graph_name: RDF::RDFA.ProcessorGraph),
1,188✔
538
          RDF::Statement.new(n, RDF::URI("http://purl.org/dc/terms/description"), message, graph_name: RDF::RDFA.ProcessorGraph),
539
          RDF::Statement.new(n, RDF::URI("http://purl.org/dc/terms/date"), RDF::Literal::Date.new(DateTime.now), graph_name: RDF::RDFA.ProcessorGraph)
540
        ]
541
        processor_statements << RDF::Statement.new(n, RDF::RDFA.context, base_uri, graph_name: RDF::RDFA.ProcessorGraph) if base_uri
1,188✔
542
        if node.respond_to?(:path)
1,188✔
543
          nc = RDF::Node.new
×
544
          processor_statements += [
×
545
            RDF::Statement.new(n, RDF::RDFA.context, nc, graph_name: RDF::RDFA.ProcessorGraph),
546
            RDF::Statement.new(nc, RDF["type"], RDF::PTR.XPathPointer, graph_name: RDF::RDFA.ProcessorGraph),
547
            RDF::Statement.new(nc, RDF::PTR.expression, node.path, graph_name: RDF::RDFA.ProcessorGraph)
548
          ]
549
        end
550

551
        @repository.insert(*processor_statements)
1,188✔
552
        if cb = @options[:processor_callback]
1,188✔
553
          processor_statements.each {|s| cb.call(s)}
380✔
554
        end
555
      end
556
    end
557

558
    ##
559
    # add a statement, object can be literal or URI or bnode
560
    # Yields {RDF::Statement} to the saved callback
561
    #
562
    # @param [#display_path, #to_s] node XML Node or string for showing context
563
    # @param [RDF::Resource] subject the subject of the statement
564
    # @param [RDF::URI] predicate the predicate of the statement
565
    # @param [RDF::Value] object the object of the statement
566
    # @param [RDF::Value] graph_name the graph name of the statement
567
    # @raise [RDF::ReaderError] Checks parameter types and raises if they are incorrect if parsing mode is _validate_.
568
    def add_triple(node, subject, predicate, object, graph_name = nil)
2✔
569
      statement = RDF::Statement.new(subject, predicate, object)
2,630✔
570
      add_error(node, "statement #{RDF::NTriples.serialize(statement)} is invalid") unless statement.valid?
2,630✔
571
      if subject && predicate && object # Basic sanity checking
2,630✔
572
        add_info(node, "statement: #{RDF::NTriples.serialize(statement)}")
2,614✔
573
        repository << statement
2,614✔
574
      end
575
    end
576

577
    # Parsing an RDFa document (this is *not* the recursive method)
578
    def parse_whole_document(doc, base)
2✔
579
      base = doc_base(base)
762✔
580
      if (base)
762✔
581
        # Strip any fragment from base
582
        base = base.to_s.split("#").first
762✔
583
        base = uri(base)
762✔
584
        add_debug("") {"parse_whole_doc: base='#{base}'"}
1,284✔
585
      end
586

587
      # initialize the evaluation context with the appropriate base
588
      evaluation_context = EvaluationContext.new(base, @host_defaults)
660✔
589

590
      if @version != :"rdfa1.0"
660✔
591
        # Process default vocabularies
592
        load_initial_contexts(@host_defaults[:initial_contexts]) do |which, value|
656✔
593
          add_debug(root) { "parse_whole_document, #{which}: #{value.inspect}"}
2,668✔
594
          case which
1,334✔
595
          when :uri_mappings        then evaluation_context.uri_mappings.merge!(value)
656✔
596
          when :term_mappings       then evaluation_context.term_mappings.merge!(value)
678✔
597
          when :default_vocabulary  then evaluation_context.default_vocabulary = value
×
598
          end
599
        end
600
      end
601

602
      traverse(root, evaluation_context)
660✔
603
      add_debug("", "parse_whole_doc: traversal complete'")
660✔
604
    end
605

606
    # Parse and process URI mappings, Term mappings and a default vocabulary from @context
607
    #
608
    # Yields each mapping
609
    def load_initial_contexts(initial_contexts)
2✔
610
      initial_contexts.
656✔
611
        map {|uri| uri(uri).normalize}.
1,292✔
612
        each do |uri|
613
          # Don't try to open ourselves!
614
          if base_uri == uri
1,292✔
615
            add_debug(root) {"load_initial_contexts: skip recursive context #{uri.to_base}"}
×
616
            next
×
617
          end
618

619
          old_logger = @options[:logger]
1,292✔
620
          begin
621
            add_info(root, "load_initial_contexts: load #{uri.to_base}")
1,292✔
622
            @options[:logger] = false
1,292✔
623
            context = Context.find(uri)
1,292✔
624

625
            # Add URI Mappings to prefixes
626
            context.prefixes.each_pair do |prefix, value|
1,292✔
627
              prefix(prefix, value)
30,176✔
628
            end
629
            yield :uri_mappings, context.prefixes unless context.prefixes.empty?
1,292✔
630
            yield :term_mappings, context.terms unless context.terms.empty?
1,292✔
631
            yield :default_vocabulary, context.vocabulary if context.vocabulary
1,292✔
632
          rescue Exception => e
633
            options[:logger] = old_logger
×
634
            add_error(root, e.message)
×
635
            raise # In case we're not in strict mode, we need to be sure processing stops
×
636
          ensure
637
            @options[:logger] = old_logger
1,292✔
638
          end
639
        end
640
    end
641

642
    # Extract the prefix mappings from an element
643
    def extract_mappings(element, uri_mappings, namespaces)
2✔
644
      # look for xmlns
645
      # (note, this may be dependent on @host_language)
646
      # Regardless of how the mapping is declared, the value to be mapped must be converted to lower case,
647
      # and the URI is not processed in any way; in particular if it is a relative path it is
648
      # not resolved against the current base.
649
      ns_defs = {}
4,900✔
650
      element.namespaces.each do |prefix, href|
4,900✔
651
        prefix = nil if prefix == "xmlns"
42✔
652
        add_debug("extract_mappings") { "ns: #{prefix}: #{href}"}
82✔
653
        ns_defs[prefix] = href
42✔
654
      end
655

656
      # HTML parsing doesn't create namespace_definitions
657
      if ns_defs.empty?
4,900✔
658
        ns_defs = {}
4,876✔
659
        element.attributes.each do |attr, href|
4,876✔
660
          next unless attr =~ /^xmlns(?:\:(.+))?/
5,724✔
661
          prefix = $1
52✔
662
          add_debug("extract_mappings") { "ns(attr): #{prefix}: #{href}"}
78✔
663
          ns_defs[prefix] = href
52✔
664
        end
665
      end
666

667
      ns_defs.each do |prefix, href|
4,900✔
668
        # A Conforming RDFa Processor must ignore any definition of a mapping for the '_' prefix.
669
        next if prefix == "_"
94✔
670
        href = uri(base_uri, href).to_s
94✔
671

672
        # Downcase prefix for RDFa 1.1
673
        pfx_lc = (@version == :"rdfa1.0" || prefix.nil?) ? prefix : prefix.downcase
94✔
674
        if prefix
94✔
675
          if uri_mappings.fetch(pfx_lc.to_sym, href) != href
36✔
676
            add_warning(element, "Redefining prefix #{pfx_lc}: from <#{uri_mappings[pfx_lc]}> to <#{href}>", RDF::RDFA.PrefixRedefinition)
12✔
677
          end
678
          uri_mappings[pfx_lc.to_sym] = href
36✔
679
          namespaces[pfx_lc] ||= href
36✔
680
          prefix(pfx_lc, href)
36✔
681
          add_info(element, "extract_mappings: #{prefix} => <#{href}>")
36✔
682
        else
683
          add_info(element, "extract_mappings: nil => <#{href}>")
58✔
684
          namespaces[""] ||= href
58✔
685
        end
686
      end
687

688
      # Set mappings from @prefix
689
      # prefix is a whitespace separated list of prefix-name URI pairs of the form
690
      #   NCName ':' ' '+ xs:anyURI
691
      mappings = element.attribute("prefix").to_s.strip.split(/\s+/)
4,900✔
692
      while mappings.length > 0 do
4,886✔
693
        prefix, uri = mappings.shift.downcase, mappings.shift
142✔
694
        #puts "uri_mappings prefix #{prefix} #{uri.to_base}"
695
        next unless prefix.match(/:$/)
142✔
696
        prefix.chop!
142✔
697

698
        unless prefix.empty? || prefix.match(NC_REGEXP)
142✔
699
          add_error(element, "extract_mappings: Prefix #{prefix.inspect} does not match NCName production")
12✔
700
          next
12✔
701
        end
702

703
        # A Conforming RDFa Processor must ignore any definition of a mapping for the '_' prefix.
704
        next if prefix == "_"
130✔
705
        uri = uri(base_uri, uri).to_s
130✔
706

707
        pfx_index = prefix.to_s.empty? ? nil : prefix.to_s.to_sym
130✔
708
        if uri_mappings.fetch(pfx_index, uri) != uri
130✔
709
          add_warning(element, "Redefining prefix #{prefix}: from <#{uri_mappings[pfx_index]}> to <#{uri}>", RDF::RDFA.PrefixRedefinition)
×
710
        end
711
        uri_mappings[pfx_index] = uri
130✔
712
        prefix(prefix, uri)
130✔
713
        add_info(element, "extract_mappings: prefix #{prefix} => <#{uri}>")
130✔
714
      end unless @version == :"rdfa1.0"
4,900✔
715
    end
716

717
    # The recursive helper function
718
    def traverse(element, evaluation_context)
2✔
719
      if element.nil?
4,900✔
720
        add_error(element, "Can't parse nil element")
×
721
        return nil
×
722
      end
723

724
      add_debug(element) { "ec: #{evaluation_context.inspect}" }
7,296✔
725

726
      # local variables [7.5 Step 1]
727
      recurse = true
4,900✔
728
      skip = false
4,900✔
729
      new_subject = nil
4,900✔
730
      typed_resource = nil
4,900✔
731
      current_object_resource = nil
4,900✔
732
      uri_mappings = evaluation_context.uri_mappings.clone
4,900✔
733
      namespaces = evaluation_context.namespaces.clone
4,900✔
734
      incomplete_triples = []
4,900✔
735
      language = evaluation_context.language
4,900✔
736
      term_mappings = evaluation_context.term_mappings.clone
4,900✔
737
      default_vocabulary = evaluation_context.default_vocabulary
4,900✔
738
      list_mapping = evaluation_context.list_mapping
4,900✔
739

740
      xml_base = element.base
4,900✔
741
      base = xml_base.to_s if xml_base && ![:xhtml1, :html4, :html5].include?(@host_language)
4,900✔
742
      add_debug(element) {"base: #{base.inspect}"} if base
4,944✔
743
      base ||= evaluation_context.base
4,900✔
744

745
      # Pull out the attributes needed for the skip test.
746
      attrs = {}
4,900✔
747
      %w(
748
        about
4,900✔
749
        content
750
        datatype
751
        datetime
752
        href
753
        id
754
        inlist
755
        property
756
        rel
757
        resource
758
        rev
759
        role
760
        src
761
        type
762
        typeof
763
        value
764
        vocab
765
      ).each do |a|
766
        attrs[a.to_sym] = element.attributes[a].to_s.strip if element.attributes[a]
83,300✔
767
      end
768

769
      add_debug(element) {"attrs " + attrs.inspect} unless attrs.empty?
6,062✔
770

771
      # If @property and @rel/@rev are on the same elements, the non-CURIE and non-URI @rel/@rev values are ignored. If, after this, the value of @rel/@rev becomes empty, then the then the processor must act as if the attribute is not present.
772
      if attrs.has_key?(:property) && @version == :"rdfa1.1" && (@host_language == :html5 || @host_language == :xhtml5 || @host_language == :html4)
4,900✔
773
        [:rel, :rev].each do |attr|
1,542✔
774
          next unless attrs.has_key?(attr)
3,084✔
775
          add_debug(element) {"Remove non-CURIE/non-IRI @#{attr} values from #{attrs[attr].inspect}"}
32✔
776
          attrs[attr] = attrs[attr].
16✔
777
            split(/\s+/).
778
            select {|a| a.index(':')}.
20✔
779
            join(" ")
780
          add_debug(element) {" => #{attrs[attr].inspect}"}
32✔
781
          attrs.delete(attr) if attrs[attr].empty?
16✔
782
        end
783
      end
784

785
      # Default vocabulary [7.5 Step 2]
786
      # Next the current element is examined for any change to the default vocabulary via @vocab.
787
      # If @vocab is present and contains a value, its value updates the local default vocabulary.
788
      # If the value is empty, then the local default vocabulary must be reset to the Host Language defined default.
789
      if attrs[:vocab]
4,900✔
790
        default_vocabulary = if attrs[:vocab].empty?
94✔
791
          # Set default_vocabulary to host language default
792
          add_debug(element) {
×
793
            "[Step 3] reset default_vocaulary to #{@host_defaults.fetch(:vocabulary, nil).inspect}"
×
794
          }
795
          @host_defaults.fetch(:vocabulary, nil)
×
796
        else
797
          # Generate a triple indicating that the vocabulary is used
798
          add_triple(element, base, RDF::RDFA.usesVocabulary, uri(attrs[:vocab]))
94✔
799

800
          uri(attrs[:vocab])
94✔
801
        end
802
        add_debug(element) {
94✔
803
          "[Step 2] default_vocaulary: #{default_vocabulary.inspect}"
70✔
804
        }
805
      end
806

807
      # Local term mappings [7.5 Step 3]
808
      # Next, the current element is then examined for URI mapping s and these are added to the local list of URI mappings.
809
      # Note that a URI mapping will simply overwrite any current mapping in the list that has the same name
810
      extract_mappings(element, uri_mappings, namespaces)
4,900✔
811

812
      # Language information [7.5 Step 4]
813
      language = element.language || language
4,900✔
814
      language = nil if language.to_s.empty?
4,900✔
815
      add_debug(element) {"HTML5 [3.2.3.3] lang: #{language.inspect}"} if language
4,924✔
816

817
      # Embedded scripts
818
      if element.name == 'script'
4,900✔
819
        text = element.inner_html.sub(%r(\A\s*\<!\[CDATA\[)m, '').sub(%r(\]\]>\s*\Z)m, '')
36✔
820

821
        extract_script(element, text, attrs[:type], **@options.merge(base_uri: base)) do |statement|
36✔
822
          @repository << statement
132✔
823
        end
824
      end
825

826
      # From HTML5, if the property attribute and the rel and/or rev attribute exists on the same element, the non-CURIE and non-URI rel and rev values are ignored. If, after this, the value of rel and/or rev becomes empty, then the processor must act as if the respective attribute is not present.
827
      if [:html5, :xhtml5].include?(@host_language) && attrs[:property] && (attrs[:rel] || attrs[:rev])
4,900✔
828
        old_rel, old_rev = attrs[:rel], attrs[:rev]
16✔
829
        if old_rel
16✔
830
          attrs[:rel] = (attrs[:rel]).split(/\s+/m).select {|r| !r.index(':').nil?}.join(" ")
32✔
831
          attrs.delete(:rel) if attrs[:rel].empty?
16✔
832
          add_debug(element) {"HTML5: @rel was #{old_rel}, now #{attrs[:rel]}"}
32✔
833
        end
834
        if old_rev
16✔
835
          attrs[:rev] = (attrs[:rev]).split(/\s+/m).select {|r| !r.index(':').nil?}.join(" ")
×
836
          attrs.delete(:rev) if attrs[:rev].empty?
×
837
          add_debug(element) {"HTML5: @rev was #{old_rev}, now #{attrs[:rev]}"}
×
838
        end
839
      end
840

841
      # rels and revs
842
      rels = process_uris(element, attrs[:rel], evaluation_context, base,
4,900✔
843
                          uri_mappings: uri_mappings,
844
                          term_mappings: term_mappings,
845
                          vocab: default_vocabulary,
846
                          restrictions: TERMorCURIEorAbsIRI.fetch(@version, []))
847
      revs = process_uris(element, attrs[:rev], evaluation_context, base,
4,900✔
848
                          uri_mappings: uri_mappings,
849
                          term_mappings: term_mappings,
850
                          vocab: default_vocabulary,
851
                          restrictions: TERMorCURIEorAbsIRI.fetch(@version, []))
852

853
      add_debug(element) do
854
        "rels: #{rels.join(" ")}, revs: #{revs.join(" ")}"
102✔
855
      end unless (rels + revs).empty?
4,900✔
856

857
      if !(attrs[:rel] || attrs[:rev])
4,900✔
858
        # Establishing a new subject if no rel/rev [7.5 Step 5]
859

860
        if @version == :"rdfa1.0"
4,712✔
861
          new_subject = if attrs[:about]
10✔
862
            process_uri(element, attrs[:about], evaluation_context, base,
4✔
863
                        uri_mappings: uri_mappings,
864
                        restrictions: SafeCURIEorCURIEorIRI.fetch(@version, []))
865
          elsif attrs[:resource]
6✔
866
            process_uri(element, attrs[:resource], evaluation_context, base,
×
867
                        uri_mappings: uri_mappings,
868
                        restrictions: SafeCURIEorCURIEorIRI.fetch(@version, []))
869
          elsif attrs[:href] || attrs[:src]
6✔
870
            process_uri(element, (attrs[:href] || attrs[:src]), evaluation_context, base, restrictions: [:uri])
×
871
          end
872

873
          # If no URI is provided by a resource attribute, then the first match from the following rules
874
          # will apply:
875
          new_subject ||= if [:xhtml1, :xhtml5, :html4, :html5].include?(@host_language) && element.name =~ /^(head|body)$/
10✔
876
            # From XHTML+RDFa 1.1:
877
            # if no URI is provided, then first check to see if the element is the head or body element.
878
            # If it is, then act as if the new subject is set to the parent object.
879
            uri(base)
4✔
880
          elsif element == root && base
2✔
881
            # if the element is the root element of the document, then act as if there is an empty @about present,
882
            # and process it according to the rule for @about, above;
883
            uri(base)
2✔
884
          elsif attrs[:typeof]
×
885
            RDF::Node.new
×
886
          else
887
            # otherwise, if parent object is present, new subject is set to the value of parent object.
888
            skip = true unless attrs[:property]
×
889
            evaluation_context.parent_object
×
890
          end
891

892
          # if the @typeof attribute is present, set typed resource to new subject
893
          typed_resource = new_subject if attrs[:typeof]
10✔
894
        else # rdfa1.1
895
          # If the current element contains no @rel or @rev attribute, then the next step is to establish a value for new subject.
896
          # This step has two possible alternatives.
897
          #  1. If the current element contains the @property attribute, but does not contain the @content or the @datatype attributes, then
898
          if attrs[:property] && !(attrs[:content] || attrs[:datatype])
4,702✔
899
            # new subject is set to the resource obtained from the first match from the following rule:
900
            new_subject ||= if attrs[:about]
1,556✔
901
              # by using the resource from @about, if present, obtained according to the section on CURIE and IRI Processing;
902
              process_uri(element, attrs[:about], evaluation_context, base,
664✔
903
                          uri_mappings: uri_mappings,
904
                          restrictions: SafeCURIEorCURIEorIRI.fetch(@version, []))
905
            elsif [:xhtml1, :xhtml5, :html4, :html5].include?(@host_language) && element.name =~ /^(head|body)$/
892✔
906
              # From XHTML+RDFa 1.1:
907
              # if no URI is provided, then first check to see if the element is the head or body element. If it is, then act as if the new subject is set to the parent object.
908
              evaluation_context.parent_object
×
909
            elsif element == root && base
892✔
910
              # otherwise, if the element is the root element of the document, then act as if there is an empty @about present, and process it according to the rule for @about, above;
911
              uri(base)
28✔
912
            end
913

914
            # if the @typeof attribute is present, set typed resource to new subject
915
            typed_resource = new_subject if attrs[:typeof]
1,556✔
916

917
            # otherwise, if parent object is present, new subject is set to the value of parent object.
918
            new_subject ||= evaluation_context.parent_object
1,556✔
919

920
            # If @typeof is present then typed resource is set to the resource obtained from the first match from the following rules:
921

922
            # by using the resource from @about, if present, obtained according to the section on CURIE and IRI Processing; (done above)
923
            # otherwise, if the element is the root element of the document, then act as if there is an empty @about present and process it according to the previous rule; (done above)
924

925
            if attrs[:typeof] && typed_resource.nil?
1,556✔
926
              # otherwise,
927
              typed_resource ||= if attrs[:resource]
28✔
928
                # by using the resource from @resource, if present, obtained according to the section on CURIE and IRI Processing;
929
                process_uri(element, attrs[:resource], evaluation_context, base,
×
930
                            uri_mappings: uri_mappings,
931
                            restrictions: SafeCURIEorCURIEorIRI.fetch(@version, []))
932
              elsif attrs[:href] || attrs[:src]
28✔
933
                # otherwise, by using the IRI from @href, if present, obtained according to the section on CURIE and IRI Processing;
934
                # otherwise, by using the IRI from @src, if present, obtained according to the section on CURIE and IRI Processing;
935
                process_uri(element, (attrs[:href] || attrs[:src]), evaluation_context, base,
8✔
936
                            restrictions: [:uri])
937
              else
938
                # otherwise, the value of typed resource is set to a newly created bnode.
939
                RDF::Node.new
20✔
940
              end
941

942
              # The value of the current object resource is set to the value of typed resource.
943
              current_object_resource = typed_resource
28✔
944
            end
945
          else
946
            # otherwise (ie, the @content or @datatype)
947
            new_subject =
948
              process_uri(element, (attrs[:about] || attrs[:resource]),
424✔
949
                          evaluation_context, base,
950
                          uri_mappings: uri_mappings,
951
                          restrictions: SafeCURIEorCURIEorIRI.fetch(@version, [])) if attrs[:about] ||attrs[:resource]
3,146✔
952
            new_subject ||=
953
              process_uri(element, (attrs[:href] || attrs[:src]), evaluation_context, base,
310✔
954
                          restrictions: [:uri]) if attrs[:href] || attrs[:src]
3,146✔
955

956
            # If no URI is provided by a resource attribute, then the first match from the following rules
957
            # will apply:
958
            new_subject ||= if [:xhtml1, :xhtml5, :html4, :html5].include?(@host_language) && element.name =~ /^(head|body)$/
3,146✔
959
              # From XHTML+RDFa 1.1:
960
              # if no URI is provided, then first check to see if the element is the head or body element.
961
              # If it is, then act as if the new subject is set to the parent object.
962
              evaluation_context.parent_object
678✔
963
            elsif element == root
1,738✔
964
              # if the element is the root element of the document, then act as if there is an empty @about present,
965
              # and process it according to the rule for @about, above;
966
              uri(base)
450✔
967
            elsif attrs[:typeof]
1,288✔
968
              RDF::Node.new
40✔
969
            else
970
              # otherwise, if parent object is present, new subject is set to the value of parent object.
971
              # Additionally, if @property is not present then the skip element flag is set to 'true'.
972
              skip = true unless attrs[:property]
1,248✔
973
              evaluation_context.parent_object
1,248✔
974
            end
975

976
            # If @typeof is present then typed resource is set to the resource obtained from the first match from the following rules:
977
            typed_resource = new_subject if attrs[:typeof]
3,146✔
978
          end
979
        end
980

981
        add_debug(element) {
4,712✔
982
          "[Step 5] new_subject: #{new_subject.to_ntriples rescue 'nil'}, " +
2,290✔
983
          "typed_resource: #{typed_resource.to_ntriples rescue 'nil'}, " +
984
          "current_object_resource: #{current_object_resource.to_ntriples rescue 'nil'}, " +
985
          "skip = #{skip}"
986
        }
987
      else
988
        # [7.5 Step 6]
989
        # If the current element does contain a @rel or @rev attribute, then the next step is to
990
        # establish both a value for new subject and a value for current object resource:
991
        new_subject = process_uri(element, attrs[:about], evaluation_context, base,
188✔
992
                                  uri_mappings: uri_mappings,
993
                                  restrictions: SafeCURIEorCURIEorIRI.fetch(@version, []))
994
        new_subject ||= process_uri(element, attrs[:src], evaluation_context, base,
995
                                  uri_mappings: uri_mappings,
996
                                  restrictions: [:uri]) if @version == :"rdfa1.0"
188✔
997

998
        # if the @typeof attribute is present, set typed resource to new subject
999
        typed_resource = new_subject if attrs[:typeof]
188✔
1000

1001
        # If no URI is provided then the first match from the following rules will apply
1002
        new_subject ||= if element == root && base
188✔
1003
          uri(base)
4✔
1004
        elsif [:xhtml1, :xhtml5, :html4, :html5].include?(@host_language) && element.name =~ /^(head|body)$/
156✔
1005
          # From XHTML+RDFa 1.1:
1006
          # if no URI is provided, then first check to see if the element is the head or body element.
1007
          # If it is, then act as if the new subject is set to the parent object.
1008
          evaluation_context.parent_object
×
1009
        elsif attrs[:typeof] && @version == :"rdfa1.0"
156✔
1010
          RDF::Node.new
×
1011
        else
1012
          # if it's null, it's null and nothing changes
1013
          evaluation_context.parent_object
156✔
1014
          # no skip flag set this time
1015
        end
1016

1017
        # Then the current object resource is set to the URI obtained from the first match from the following rules:
1018
        current_object_resource = process_uri(element, attrs[:resource], evaluation_context, base,
1019
                      uri_mappings: uri_mappings,
1020
                      restrictions: SafeCURIEorCURIEorIRI.fetch(@version, [])) if attrs[:resource]
188✔
1021
        current_object_resource ||= process_uri(element, attrs[:href], evaluation_context, base,
1022
                      restrictions: [:uri]) if attrs[:href]
188✔
1023
        current_object_resource ||= process_uri(element, attrs[:src], evaluation_context, base,
1024
                      restrictions: [:uri]) if attrs[:src] && @version != :"rdfa1.0"
188✔
1025
        current_object_resource ||= RDF::Node.new if attrs[:typeof] && !attrs[:about] && @version != :"rdfa1.0"
188✔
1026

1027
        # and also set the value typed resource to this bnode
1028
        if attrs[:typeof]
188✔
1029
          if @version == :"rdfa1.0"
4✔
1030
            typed_resource = new_subject
×
1031
          else
1032
            typed_resource = current_object_resource if !attrs[:about]
4✔
1033
          end
1034
        end
1035

1036
        add_debug(element) {
188✔
1037
          "[Step 6] new_subject: #{new_subject}, " +
106✔
1038
          "current_object_resource = #{current_object_resource.nil? ? 'nil' : current_object_resource} " +
1039
          "typed_resource: #{typed_resource.to_ntriples rescue 'nil'}, "
1040
        }
1041
      end
1042

1043
      # [Step 7] If in any of the previous steps a typed resource was set to a non-null value, it is now used to provide a subject for type values;
1044
      if typed_resource
4,900✔
1045
        # Typeof is TERMorCURIEorAbsIRIs
1046
        types = process_uris(element, attrs[:typeof], evaluation_context, base,
198✔
1047
                            uri_mappings: uri_mappings,
1048
                            term_mappings: term_mappings,
1049
                            vocab: default_vocabulary,
1050
                            restrictions: TERMorCURIEorAbsIRI.fetch(@version, []))
1051
        add_debug(element, "[Step 7] typeof: #{attrs[:typeof]}")
198✔
1052
        types.each do |one_type|
198✔
1053
          add_triple(element, typed_resource, RDF["type"], one_type)
172✔
1054
        end
1055
      end
1056

1057
      # Create new List mapping [step 8]
1058
      #
1059
      # If in any of the previous steps a new subject was set to a non-null value different from the parent object;
1060
      # The list mapping taken from the evaluation context is set to a new, empty mapping.
1061
      if (new_subject && (new_subject != evaluation_context.parent_subject || list_mapping.nil?))
4,900✔
1062
        list_mapping = {}
1,964✔
1063
        add_debug(element) do
1,964✔
1064
          "[Step 8]: create new list mapping(#{list_mapping.object_id}) " +
848✔
1065
            "ns: #{new_subject.to_ntriples}, " +
1066
            "ps: #{evaluation_context.parent_subject.to_ntriples rescue 'nil'}"
1067
        end
1068
      end
1069

1070
      # Generate triples with given object [Step 9]
1071
      #
1072
      # If in any of the previous steps a current object resource was set to a non-null value, it is now used to generate triples and add entries to the local list mapping:
1073
      if new_subject && current_object_resource && (attrs[:rel] || attrs[:rev])
4,900✔
1074
        add_debug(element) {"[Step 9] rels: #{rels.inspect} revs: #{revs.inspect}"}
146✔
1075
        rels.each do |r|
78✔
1076
          if attrs[:inlist]
82✔
1077
            # If the current list mapping does not contain a list associated with this IRI,
1078
            # instantiate a new list
1079
            unless list_mapping[r]
12✔
1080
              list_mapping[r] = RDF::List.new
4✔
1081
              add_debug(element) {"list(#{r}): create #{list_mapping[r].inspect}"}
8✔
1082
            end
1083
            add_debug(element) {"[Step 9] add #{current_object_resource.to_ntriples} to #{r} #{list_mapping[r].inspect}"}
24✔
1084
            list_mapping[r] << current_object_resource
12✔
1085
          else
1086
            # Predicates for the current object resource can be set by using one or both of the @rel and the @rev attributes but, in case of the @rel attribute, only if the @inlist is not present:
1087
            add_triple(element, new_subject, r, current_object_resource)
70✔
1088
          end
1089
        end
1090

1091
        revs.each do |r|
78✔
1092
          add_triple(element, current_object_resource, r, new_subject)
×
1093
        end
1094
      elsif attrs[:rel] || attrs[:rev]
4,822✔
1095
        # Incomplete triples and bnode creation [Step 10]
1096
        add_debug(element) {"[Step 10] incompletes: rels: #{rels}, revs: #{revs}"}
148✔
1097
        current_object_resource = RDF::Node.new
110✔
1098

1099
        # predicate: full IRI
1100
        # direction: forward/reverse
1101
        # lists: Save into list, don't generate triple
1102

1103
        rels.each do |r|
110✔
1104
          if attrs[:inlist]
106✔
1105
            # If the current list mapping does not contain a list associated with this IRI,
1106
            # instantiate a new list
1107
            unless list_mapping[r]
8✔
1108
              list_mapping[r] = RDF::List.new
8✔
1109
              add_debug(element) {"[Step 10] list(#{r}): create #{list_mapping[r].inspect}"}
16✔
1110
            end
1111
            incomplete_triples << {list: list_mapping[r], direction: :none}
8✔
1112
          else
1113
            incomplete_triples << {predicate: r, direction: :forward}
98✔
1114
          end
1115
        end
1116

1117
        revs.each do |r|
110✔
1118
          incomplete_triples << {predicate: r, direction: :reverse}
×
1119
        end
1120
      end
1121

1122
      # Establish current object literal [Step 11]
1123
      #
1124
      # If the current element has a @inlist attribute, add the property to the
1125
      # list associated with that property, creating a new list if necessary.
1126
      if attrs[:property]
4,900✔
1127
        properties = process_uris(element, attrs[:property], evaluation_context, base,
1,754✔
1128
                                  uri_mappings: uri_mappings,
1129
                                  term_mappings: term_mappings,
1130
                                  vocab: default_vocabulary,
1131
                                  restrictions: TERMorCURIEorAbsIRI.fetch(@version, []))
1132

1133
        properties.reject! do |p|
1,754✔
1134
          if p.is_a?(RDF::URI)
1,848✔
1135
            false
1,848✔
1136
          else
1137
            add_warning(element, "[Step 11] predicate #{p.to_ntriples} must be a URI")
×
1138
            true
×
1139
          end
1140
        end
1141

1142
        datatype = process_uri(element, attrs[:datatype], evaluation_context, base,
1143
                              uri_mappings: uri_mappings,
1144
                              term_mappings: term_mappings,
1145
                              vocab: default_vocabulary,
1146
                              restrictions: TERMorCURIEorAbsIRI.fetch(@version, [])) unless attrs[:datatype].to_s.empty?
1,754✔
1147
        begin
1148
          current_property_value = case
1149
          when datatype && ![RDF.XMLLiteral, RDF.HTML].include?(datatype)
1,754✔
1150
            # typed literal
1151
            add_debug(element, "[Step 11] typed literal (#{datatype})")
142✔
1152
            RDF::Literal.new(attrs[:content] || attrs[:datetime] || attrs[:value] || element.inner_text.to_s, datatype: datatype, validate: validate?, canonicalize: canonicalize?)
142✔
1153
          when @version == :"rdfa1.1"
1154
            case
1155
            when datatype == RDF.XMLLiteral
1,608✔
1156
              # XML Literal
1157
              add_debug(element) {"[Step 11] XML Literal: #{element.inner_html}"}
×
1158

1159
              # In order to maintain maximum portability of this literal, any children of the current node that are
1160
              # elements must have the current in scope XML namespace declarations (if any) declared on the
1161
              # serialized element using their respective attributes. Since the child element node could also
1162
              # declare new XML namespaces, the RDFa Processor must be careful to merge these together when
1163
              # generating the serialized element definition. For avoidance of doubt, any re-declarations on the
1164
              # child node must take precedence over declarations that were active on the current node.
1165
              begin
1166
                c14nxl = element.children.c14nxl(
×
1167
                  library: @library,
1168
                  language: language,
1169
                  namespaces: {nil => XHTML}.merge(namespaces))
1170
                RDF::Literal.new(c14nxl,
×
1171
                  library: @library,
1172
                  datatype: RDF.XMLLiteral,
1173
                  validate: validate?,
1174
                  canonicalize: canonicalize?)
1175
              rescue ArgumentError => e
1176
                add_error(element, e.message)
×
1177
              end
1178
            when datatype == RDF.HTML
1179
              # HTML Literal
1180
              add_debug(element) {"[Step 11] HTML Literal: #{element.inner_html}"}
14✔
1181

1182
              # Just like XMLLiteral, but without the c14nxl
1183
              begin
1184
                RDF::Literal.new(element.children.to_html,
10✔
1185
                  library: @library,
1186
                  datatype: RDF.HTML,
1187
                  validate: validate?,
1188
                  canonicalize: canonicalize?)
1189
              rescue ArgumentError => e
1190
                add_error(element, e.message)
×
1191
              end
1192
            when attrs[:value]
1193
              # Lexically scan value and assign appropriate type, otherwise, leave untyped
1194
              # See https://www.w3.org/2001/sw/wiki/RDFa_1.1._Errata#Using_.3Cdata.3E.2C_.3Cinput.3E_and_.3Cli.3E_along_with_.40value
1195
              add_debug(element, "[Step 11] value literal (#{attrs[:value]})")
12✔
1196
              v = attrs[:value].to_s
12✔
1197
              # Figure it out by parsing
1198
              dt_lit = %w(Integer Decimal Double).map {|t| RDF::Literal.const_get(t)}.detect do |dt|
48✔
1199
                v.match(dt::GRAMMAR)
24✔
1200
              end || RDF::Literal
1201
              dt_lit.new(v)
12✔
1202
            when attrs[:datatype]
1203
              # otherwise, as a plain literal if @datatype is present but has an empty value.
1204
              # The actual literal is either the value of @content (if present) or a string created by
1205
              # concatenating the value of all descendant text nodes, of the current element in turn.
1206
              # typed literal
1207
              add_debug(element, "[Step 11] datatyped literal (#{datatype})")
4✔
1208
              RDF::Literal.new(attrs[:content] || element.inner_text.to_s, language: language, validate: validate?, canonicalize: canonicalize?)
4✔
1209
            when attrs[:content]
1210
              # plain literal
1211
              add_debug(element, "[Step 11] plain literal (content)")
12✔
1212
              RDF::Literal.new(attrs[:content], language: language, validate: validate?, canonicalize: canonicalize?)
12✔
1213
            when element.name == 'time'
1214
              # HTML5 support
1215
              # Lexically scan value and assign appropriate type, otherwise, leave untyped
1216
              v = (attrs[:content] || attrs[:datetime] || element.inner_text).to_s
40✔
1217
              datatype = %w(Date Time DateTime Year YearMonth Duration).map {|t| RDF::Literal.const_get(t)}.detect do |dt|
280✔
1218
                v.match(dt::GRAMMAR)
152✔
1219
              end || RDF::Literal
1220
              add_debug(element) {"[Step 11] <time> literal: #{datatype} #{v.inspect}"}
80✔
1221
              datatype.new(v, language: language)
40✔
1222
            when (attrs[:resource] || attrs[:href] || attrs[:src]) &&
1,530✔
1223
                 !(attrs[:rel] || attrs[:rev]) &&
1,036✔
1224
                 @version != :"rdfa1.0"
1225
              add_debug(element, "[Step 11] resource (resource|href|src)")
1,010✔
1226
              res = process_uri(element, attrs[:resource], evaluation_context, base,
1227
                                uri_mappings: uri_mappings,
1228
                                restrictions: SafeCURIEorCURIEorIRI.fetch(@version, [])) if attrs[:resource]
1,010✔
1229
              res ||= process_uri(element, (attrs[:href] || attrs[:src]), evaluation_context, base, restrictions: [:uri])
1,010✔
1230
            when typed_resource && !attrs[:about] && @version != :"rdfa1.0"
1231
              add_debug(element, "[Step 11] typed_resource")
16✔
1232
              typed_resource
16✔
1233
            else
1234
              # plain literal
1235
              add_debug(element, "[Step 11] plain literal (inner text)")
504✔
1236
              RDF::Literal.new(element.inner_text.to_s, language: language, validate: validate?, canonicalize: canonicalize?)
504✔
1237
            end
1238
          else # rdfa1.0
1239
            if element.text_content? || (element.children.length == 0) || attrs[:datatype] == ""
4✔
1240
              # plain literal
1241
              add_debug(element, "[Step 11 (1.0)] plain literal")
4✔
1242
              RDF::Literal.new(attrs[:content] || element.inner_text.to_s, language: language, validate: validate?, canonicalize: canonicalize?)
4✔
1243
            elsif !element.text_content? and (datatype == nil or datatype == RDF.XMLLiteral)
×
1244
              # XML Literal
1245
              add_debug(element) {"[Step 11 (1.0)] XML Literal: #{element.inner_html}"}
×
1246
              recurse = false
×
1247
              c14nxl = element.children.c14nxl(
×
1248
                library: @library,
1249
                language: language,
1250
                namespaces: {nil => XHTML}.merge(namespaces))
1251
              RDF::Literal.new(c14nxl,
×
1252
                library: @library,
1253
                datatype: RDF.XMLLiteral,
1254
                validate: validate?,
1255
                canonicalize: canonicalize?)
1256
            end
1257
          end
1258
        rescue ArgumentError => e
1259
          add_error(element, e.message)
×
1260
        end
1261

1262
        # add each property
1263
        properties.each do |p|
1,754✔
1264
          # Lists: If element has an @inlist attribute, add the value to a list
1265
          if attrs[:inlist]
1,848✔
1266
            # If the current list mapping does not contain a list associated with this IRI,
1267
            # instantiate a new list
1268
            unless list_mapping[p]
60✔
1269
              list_mapping[p] = RDF::List.new
48✔
1270
              add_debug(element) {"[Step 11] lists(#{p}): create #{list_mapping[p].inspect}"}
96✔
1271
            end
1272
            add_debug(element)  {"[Step 11] add #{current_property_value.to_ntriples} to #{p.to_ntriples} #{list_mapping[p].inspect}"}
120✔
1273
            list_mapping[p] << current_property_value
60✔
1274
          elsif new_subject
1,788✔
1275
            add_triple(element, new_subject, p, current_property_value)
1,788✔
1276
          end
1277
        end
1278
      end
1279

1280
      if !skip and new_subject && !evaluation_context.incomplete_triples.empty?
4,900✔
1281
        # Complete the incomplete triples from the evaluation context [Step 12]
1282
        add_debug(element) do
262✔
1283
          "[Step 12] complete incomplete triples: " +
46✔
1284
          "new_subject=#{new_subject.to_ntriples}, " +
1285
          "completes=#{evaluation_context.incomplete_triples.inspect}"
1286
        end
1287

1288
        evaluation_context.incomplete_triples.each do |trip|
262✔
1289
          case trip[:direction]
262✔
1290
          when :none
1291
            add_debug(element) {"[Step 12] add #{new_subject.to_ntriples} to #{trip[:list].inspect}"}
16✔
1292
            trip[:list] << new_subject
8✔
1293
          when :forward
1294
            add_triple(element, evaluation_context.parent_subject, trip[:predicate], new_subject)
254✔
1295
          when :reverse
1296
            add_triple(element, new_subject, trip[:predicate], evaluation_context.parent_subject)
×
1297
          end
1298
        end
1299
      end
1300

1301
      # Create a new evaluation context and proceed recursively [Step 13]
1302
      if recurse
4,900✔
1303
        if skip
4,900✔
1304
          if language == evaluation_context.language &&
1,188✔
1305
              uri_mappings == evaluation_context.uri_mappings &&
1306
              term_mappings == evaluation_context.term_mappings &&
1307
              default_vocabulary == evaluation_context.default_vocabulary &&
1308
              base == evaluation_context.base &&
1309
              list_mapping == evaluation_context.list_mapping
1310
            new_ec = evaluation_context
1,138✔
1311
            add_debug(element, "[Step 13] skip: reused ec")
1,138✔
1312
          else
1313
            new_ec = evaluation_context.clone
50✔
1314
            new_ec.base = base
50✔
1315
            new_ec.language = language
50✔
1316
            new_ec.uri_mappings = uri_mappings
50✔
1317
            new_ec.namespaces = namespaces
50✔
1318
            new_ec.term_mappings = term_mappings
50✔
1319
            new_ec.default_vocabulary = default_vocabulary
50✔
1320
            new_ec.list_mapping = list_mapping
50✔
1321
            add_debug(element, "[Step 13] skip: cloned ec")
50✔
1322
          end
1323
        else
1324
          # create a new evaluation context
1325
          new_ec = EvaluationContext.new(base, @host_defaults)
3,712✔
1326
          new_ec.parent_subject = new_subject || evaluation_context.parent_subject
3,712✔
1327
          new_ec.parent_object = current_object_resource || new_subject || evaluation_context.parent_subject
3,712✔
1328
          new_ec.uri_mappings = uri_mappings
3,712✔
1329
          new_ec.namespaces = namespaces
3,712✔
1330
          new_ec.incomplete_triples = incomplete_triples
3,712✔
1331
          new_ec.language = language
3,712✔
1332
          new_ec.term_mappings = term_mappings
3,712✔
1333
          new_ec.default_vocabulary = default_vocabulary
3,712✔
1334
          new_ec.list_mapping = list_mapping
3,712✔
1335
          add_debug(element, "[Step 13] new ec")
3,712✔
1336
        end
1337

1338
        element.children.each do |child|
4,900✔
1339
          # recurse only if it's an element
1340
          traverse(child, new_ec) if child.element?
9,846✔
1341
        end
1342

1343
        # Step 14: after traversing through child elements, for each list associated with
1344
        # a property
1345
        (list_mapping || {}).each do |p, l|
4,900✔
1346
          # if that list is different from the evaluation context
1347
          ec_list = evaluation_context.list_mapping[p] if evaluation_context.list_mapping
188✔
1348
          add_debug(element) {"[Step 14] time to create #{l.inspect}? #{(ec_list != l).inspect}"}
376✔
1349
          if ec_list != l
188✔
1350
            add_debug(element) {"[Step 14] list(#{p}) create #{l.inspect}"}
120✔
1351
            # Generate an rdf:List with the elements of that list.
1352
            l.each_statement do |st|
60✔
1353
              add_triple(element, st.subject, st.predicate, st.object) unless st.object == RDF.List
160✔
1354
            end
1355

1356
            # Generate a triple relating new_subject, property and the list BNode,
1357
            # or rdf:nil if the list is empty.
1358
            if l.empty?
60✔
1359
              add_triple(element, new_subject, p, RDF.nil)
4✔
1360
            else
1361
              add_triple(element, new_subject, p, l.subject)
56✔
1362
            end
1363
          end
1364
        end
1365

1366
        # Role processing
1367
        # @id is used as subject, bnode otherwise.
1368
        # Predicate is xhv:role
1369
        # Objects are TERMorCURIEorAbsIRIs.
1370
        # Act as if the default vocabulary is XHV
1371
        if attrs[:role]
4,900✔
1372
          subject = attrs[:id] ? uri(base, "##{attrs[:id]}") : RDF::Node.new
20✔
1373
          roles = process_uris(element, attrs[:role], evaluation_context, base,
20✔
1374
                                    uri_mappings: uri_mappings,
1375
                                    term_mappings: term_mappings,
1376
                                    vocab: "http://www.w3.org/1999/xhtml/vocab#",
1377
                                    restrictions: TERMorCURIEorAbsIRI.fetch(@version, []))
1378

1379
          add_debug(element) {"role: about: #{subject.to_ntriples}, roles: #{roles.map(&:to_ntriples).inspect}"}
40✔
1380
          roles.each do |r|
20✔
1381
            add_triple(element, subject, RDF::URI("http://www.w3.org/1999/xhtml/vocab#role"), r)
32✔
1382
          end
1383
        end
1384
      end
1385
    end
1386

1387
    # space-separated TERMorCURIEorAbsIRI or SafeCURIEorCURIEorIRI
1388
    def process_uris(element, value, evaluation_context, base, **options)
2✔
1389
      return [] if value.to_s.empty?
11,772✔
1390
      add_debug(element) {"process_uris: #{value}"}
3,030✔
1391
      value.to_s.split(/\s+/).map {|v| process_uri(element, v, evaluation_context, base, **options)}.compact
4,394✔
1392
    end
1393

1394
    def process_uri(element, value, evaluation_context, base, **options)
2✔
1395
      return if value.nil?
5,126✔
1396
      restrictions = options[:restrictions]
4,946✔
1397
      add_debug(element) {"process_uri: #{value}, restrictions = #{restrictions.inspect}"}
6,696✔
1398
      options = {uri_mappings: {}}.merge(options)
4,946✔
1399
      if !options[:term_mappings] && options[:uri_mappings] && restrictions.include?(:safe_curie) && value.to_s.match(/^\[(.*)\]$/)
4,946✔
1400
        # SafeCURIEorCURIEorIRI
1401
        # When the value is surrounded by square brackets, then the content within the brackets is
1402
        # evaluated as a CURIE according to the CURIE Syntax definition. If it is not a valid CURIE, the
1403
        # value must be ignored.
1404
        uri = curie_to_resource_or_bnode(element, $1, options[:uri_mappings], evaluation_context.parent_subject, restrictions)
52✔
1405
        if uri
52✔
1406
          add_debug(element) {"process_uri: #{value} => safeCURIE => #{uri.to_base}"}
40✔
1407
        else
1408
          add_warning(element, "#{value} not matched as a safeCURIE", RDF::RDFA.UnresolvedCURIE)
32✔
1409
        end
1410
        uri
52✔
1411
      elsif options[:term_mappings] && TERM_REGEXP.match(value.to_s) && restrictions.include?(:term)
4,894✔
1412
        # TERMorCURIEorAbsIRI
1413
        # If the value is an NCName, then it is evaluated as a term according to General Use of Terms in
1414
        # Attributes. Note that this step may mean that the value is to be ignored.
1415
        uri = process_term(element, value.to_s, **options)
526✔
1416
        add_debug(element) {"process_uri: #{value} => term => #{uri ? uri.to_base : 'nil'}"}
690✔
1417
        uri
526✔
1418
      else
1419
        # SafeCURIEorCURIEorIRI or TERMorCURIEorAbsIRI
1420
        # Otherwise, the value is evaluated as a CURIE.
1421
        # If it is a valid CURIE, the resulting URI is used; otherwise, the value will be processed as a URI.
1422
        uri = curie_to_resource_or_bnode(element, value, options[:uri_mappings], evaluation_context.parent_subject, restrictions)
4,368✔
1423
        if uri
4,368✔
1424
          add_debug(element) {"process_uri: #{value} => CURIE => #{uri.to_base}"}
2,250✔
1425
        elsif @version == :"rdfa1.0" && value.to_s.match(/^xml/i)
2,998✔
1426
          # Special case to not allow anything starting with XML to be treated as a URI
1427
        elsif restrictions.include?(:absuri) || restrictions.include?(:uri)
2,998✔
1428
          # AbsURI does not use xml:base
1429
          if restrictions.include?(:absuri)
2,994✔
1430
            uri = uri(value)
672✔
1431
            unless uri.absolute?
672✔
1432
              uri = nil
12✔
1433
              add_warning(element, "Malformed IRI #{uri.inspect}")
12✔
1434
            end
1435
          else
1436
            uri = uri(base, value)
2,322✔
1437
          end
1438
          add_debug(element) {"process_uri: #{value} => URI => #{uri ? uri.to_base : nil}"}
3,644✔
1439
        end
1440
        uri
4,368✔
1441
      end
1442
    rescue ArgumentError => e
1443
      add_warning(element, "Malformed IRI #{value}")
×
1444
    rescue RDF::ReaderError => e
1445
      add_debug(element, e.message)
×
1446
      if value.to_s =~ /^\(^\w\):/
×
1447
        add_warning(element, "Undefined prefix #{$1}")
×
1448
      else
1449
        add_warning(element, "Relative URI #{value}")
×
1450
      end
1451
    end
1452

1453
    # [7.4.3] General Use of Terms in Attributes
1454
    def process_term(element, value, **options)
2✔
1455
      if options[:vocab]
526✔
1456
        # If there is a local default vocabulary, the IRI is obtained by concatenating that value and the term
1457
        return uri(options[:vocab] + value)
504✔
1458
      elsif options[:term_mappings].is_a?(Hash)
22✔
1459
        # If the term is in the local term mappings, use the associated URI (case sensitive).
1460
        return uri(options[:term_mappings][value.to_s.to_sym]) if options[:term_mappings].has_key?(value.to_s.to_sym)
22✔
1461

1462
        # Otherwise, check for case-insensitive match
1463
        options[:term_mappings].each_pair do |term, uri|
4✔
1464
          return uri(uri) if term.to_s.downcase == value.to_s.downcase
12✔
1465
        end
1466
      end
1467

1468
      # Finally, if there is no local default vocabulary, the term has no associated URI and must be ignored.
1469
      add_warning(element, "Term #{value} is not defined", RDF::RDFA.UnresolvedTerm)
4✔
1470
      nil
1471
    end
1472

1473
    # From section 6. CURIE Syntax Definition
1474
    def curie_to_resource_or_bnode(element, curie, uri_mappings, subject, restrictions)
2✔
1475
      # URI mappings for CURIEs default to XHV, rather than the default doc namespace
1476
      prefix, reference = curie.to_s.split(":", 2)
4,420✔
1477

1478
      # consider the bnode situation
1479
      if prefix == "_"
4,420✔
1480
        # we force a non-nil name, otherwise it generates a new name
1481
        # As a special case, _: is also a valid reference for one specific bnode.
1482
        raise ArgumentError, "BNode not allowed in this position" unless restrictions.include?(:bnode)
66✔
1483
        bnode(reference)
66✔
1484
      elsif curie.to_s.match(/^:/)
4,354✔
1485
        # Default prefix
1486
        RDF::URI("http://www.w3.org/1999/xhtml/vocab#") + reference.to_s
×
1487
      elsif !curie.to_s.match(/:/)
4,354✔
1488
        # No prefix, undefined (in this context, it is evaluated as a term elsewhere)
1489
        nil
1490
      else
1491
        # Prefixes always downcased
1492
        prefix = prefix.to_s.downcase unless @version == :"rdfa1.0"
3,270✔
1493
        add_debug(element) do
3,270✔
1494
          "curie_to_resource_or_bnode check for #{prefix.to_s.to_sym.inspect} in #{uri_mappings.inspect}"
1,060✔
1495
        end
1496
        ns = uri_mappings[prefix.to_s.to_sym]
3,270✔
1497
        if ns
3,270✔
1498
          uri(ns + reference.to_s)
1,324✔
1499
        else
1500
          add_debug(element) {"curie_to_resource_or_bnode No namespace mapping for #{prefix.inspect}"}
2,162✔
1501
          nil
1502
        end
1503
      end
1504
    end
1505

1506
    def uri(value, append = nil)
2✔
1507
      append = RDF::URI(append)
7,810✔
1508
      value = RDF::URI(value)
7,810✔
1509
      value = if append.absolute?
7,810✔
1510
        value = append
1,490✔
1511
      elsif append
6,320✔
1512
        value = value.join(append)
6,320✔
1513
      else
1514
        value
×
1515
      end
1516
      value.validate! if validate?
7,810✔
1517
      value.canonicalize! if canonicalize?
7,708✔
1518
      value = RDF::URI.intern(value) if intern?
7,708✔
1519
      value
7,708✔
1520
    rescue ArgumentError => e
1521
      raise RDF::ReaderError, e.message
102✔
1522
    end
1523
  end
1524
end
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