rdf 3.2.2 → 3.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,91 +9,41 @@ module RDF; class Literal
9
9
  #
10
10
  # @see http://www.w3.org/TR/xmlschema11-2/#time
11
11
  # @since 0.2.1
12
- class Time < Literal
12
+ class Time < Temporal
13
13
  DATATYPE = RDF::URI("http://www.w3.org/2001/XMLSchema#time")
14
14
  GRAMMAR = %r(\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze
15
- FORMAT = '%H:%M:%S.%L%:z'.freeze
15
+ FORMAT = '%H:%M:%S.%L'.freeze
16
16
 
17
17
  ##
18
+ # Internally, a `DateTime` is represented using a native `::DateTime`. If initialized from a `::DateTime`, the timezone is taken from that native object, otherwise, a timezone (or no timezone) is taken from the string representation having a matching `zzzzzz` component.
19
+ #
18
20
  # @param [String, DateTime, #to_datetime] value
19
21
  # @param (see Literal#initialize)
20
22
  def initialize(value, datatype: nil, lexical: nil, **options)
21
23
  @datatype = RDF::URI(datatype || self.class.const_get(:DATATYPE))
22
24
  @string = lexical || (value if value.is_a?(String))
23
25
  @object = case
24
- when value.is_a?(::DateTime) then value
25
- when value.respond_to?(:to_datetime) then value.to_datetime rescue ::DateTime.parse(value.to_s)
26
- else ::DateTime.parse(value.to_s)
27
- end rescue ::DateTime.new
28
- end
29
-
30
- ##
31
- # Converts this literal into its canonical lexical representation.
32
- #
33
- # §3.2.8.2 Canonical representation
34
- #
35
- # The canonical representation for time is defined by prohibiting
36
- # certain options from the Lexical representation (§3.2.8.1).
37
- # Specifically, either the time zone must be omitted or, if present, the
38
- # time zone must be Coordinated Universal Time (UTC) indicated by a "Z".
39
- # Additionally, the canonical representation for midnight is 00:00:00.
40
- #
41
- # @return [RDF::Literal] `self`
42
- # @see http://www.w3.org/TR/xmlschema11-2/#time
43
- def canonicalize!
44
- if self.valid?
45
- @string = if timezone?
46
- @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z').sub('.000', '')
26
+ when value.respond_to?(:to_datetime)
27
+ dt = value.to_datetime
28
+ @zone = dt.zone
29
+ # Normalize to 1972-12-31 dateTime base
30
+ hms = dt.strftime(FORMAT)
31
+ ::DateTime.parse("1972-12-31T#{hms}#{@zone}")
47
32
  else
48
- @object.strftime(FORMAT[0..-4]).sub('.000', '')
49
- end
50
- end
51
- self
52
- end
53
-
54
- ##
55
- # Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone.
56
- #
57
- # @return [RDF::Literal]
58
- # @see http://www.w3.org/TR/sparql11-query/#func-tz
59
- def tz
60
- zone = timezone? ? object.zone : ""
61
- zone = "Z" if zone == "+00:00"
62
- RDF::Literal(zone)
63
- end
64
-
65
- ##
66
- # Returns `true` if the value adheres to the defined grammar of the
67
- # datatype.
68
- #
69
- # Special case for date and dateTime, for which '0000' is not a valid year
70
- #
71
- # @return [Boolean]
72
- # @since 0.2.1
73
- def valid?
74
- super && !object.nil?
75
- end
76
-
77
- ##
78
- # Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option.
79
- #
80
- # @return [Boolean]
81
- # @since 1.1.6
82
- def timezone?
83
- md = self.to_s.match(GRAMMAR)
84
- md && !!md[2]
85
- end
86
- alias_method :tz?, :timezone?
87
- alias_method :has_tz?, :timezone?
88
- alias_method :has_timezone?, :timezone?
89
-
90
- ##
91
- # Returns the value as a string.
92
- # Does not normalize timezone
93
- #
94
- # @return [String]
95
- def to_s
96
- @string || @object.strftime(FORMAT).sub("+00:00", 'Z').sub('.000', '')
33
+ md = value.to_s.match(GRAMMAR)
34
+ _, tm, tz = Array(md)
35
+ if tz
36
+ @zone = tz == 'Z' ? '+00:00' : tz
37
+ else
38
+ @zone = nil # No timezone
39
+ end
40
+ # Normalize 24:00:00 to 00:00:00
41
+ hr, mi, se = tm.split(':')
42
+ hr = "%.2i" % (hr.to_i % 24) if hr.to_i > 23
43
+ value = "#{hr}:#{mi}:#{se}"
44
+ # Normalize to 1972-12-31 dateTime base
45
+ ::DateTime.parse("1972-12-31T#{hr}:#{mi}:#{se}#{@zone}")
46
+ end rescue ::DateTime.new
97
47
  end
98
48
 
99
49
  ##
@@ -104,32 +54,10 @@ module RDF; class Literal
104
54
  def humanize(lang = :en)
105
55
  t = object.strftime("%r")
106
56
  if timezone?
107
- t += if self.tz == 'Z'
108
- " UTC"
109
- else
110
- " #{self.tz}"
111
- end
57
+ z = @zone == '+00:00' ? "UTC" : @zone
58
+ t += " #{z}"
112
59
  end
113
60
  t
114
61
  end
115
-
116
- ##
117
- # Equal compares as Time objects
118
- def ==(other)
119
- # If lexically invalid, use regular literal testing
120
- return super unless self.valid?
121
-
122
- case other
123
- when Literal::Time
124
- return super unless other.valid?
125
- # Compare as strings, as time includes a date portion, and adjusting for UTC
126
- # can create a mismatch in the date portion.
127
- self.object.new_offset.strftime('%H%M%S.%L') == other.object.new_offset.strftime('%H%M%S.%L')
128
- when Literal::DateTime, Literal::Date
129
- false
130
- else
131
- super
132
- end
133
- end
134
62
  end # Time
135
63
  end; end # RDF::Literal
@@ -77,6 +77,7 @@ module RDF
77
77
  require 'rdf/model/literal/decimal'
78
78
  require 'rdf/model/literal/integer'
79
79
  require 'rdf/model/literal/double'
80
+ require 'rdf/model/literal/temporal'
80
81
  require 'rdf/model/literal/date'
81
82
  require 'rdf/model/literal/datetime'
82
83
  require 'rdf/model/literal/time'
@@ -295,7 +296,7 @@ module RDF
295
296
  when self.simple? && other.simple?
296
297
  self.value_hash == other.value_hash && self.value == other.value
297
298
  when other.comperable_datatype?(self) || self.comperable_datatype?(other)
298
- # Comoparing plain with undefined datatypes does not generate an error, but returns false
299
+ # Comparing plain with undefined datatypes does not generate an error, but returns false
299
300
  # From data-r2/expr-equal/eq-2-2.
300
301
  false
301
302
  else
@@ -71,6 +71,7 @@ module RDF
71
71
  # @option options [RDF::Term] :graph_name (nil)
72
72
  # Note, in RDF 1.1, a graph name MUST be an {Resource}.
73
73
  # @option options [Boolean] :inferred used as a marker to record that this statement was inferred based on semantic relationships (T-Box).
74
+ # @option options [Boolean] :quoted used as a marker to record that this statement quoted and appears as the subject or object of another RDF::Statement.
74
75
  # @return [RDF::Statement]
75
76
  #
76
77
  # @overload initialize(subject, predicate, object, **options)
@@ -83,6 +84,7 @@ module RDF
83
84
  # @option options [RDF::Term] :graph_name (nil)
84
85
  # Note, in RDF 1.1, a graph name MUST be an {Resource}.
85
86
  # @option options [Boolean] :inferred used as a marker to record that this statement was inferred based on semantic relationships (T-Box).
87
+ # @option options [Boolean] :quoted used as a marker to record that this statement quoted and appears as the subject or object of another RDF::Statement.
86
88
  # @return [RDF::Statement]
87
89
  def initialize(subject = nil, predicate = nil, object = nil, options = {})
88
90
  if subject.is_a?(Hash)
@@ -209,7 +211,7 @@ module RDF
209
211
  ##
210
212
  # @return [Boolean]
211
213
  def quoted?
212
- false
214
+ !!@options[:quoted]
213
215
  end
214
216
 
215
217
  ##
data/lib/rdf/model/uri.rb CHANGED
@@ -111,6 +111,11 @@ module RDF
111
111
  tag tel turn turns tv urn javascript
112
112
  ).freeze
113
113
 
114
+ # Characters in a PName which must be escaped
115
+ # Note: not all reserved characters need to be escaped in SPARQL/Turtle, but they must be unescaped when encountered
116
+ PN_ESCAPE_CHARS = /[~\.!\$&'\(\)\*\+,;=\/\?\#@%]/.freeze
117
+ PN_ESCAPES = /\\#{Regexp.union(PN_ESCAPE_CHARS, /[\-_]/)}/.freeze
118
+
114
119
  ##
115
120
  # Cache size may be set through {RDF.config} using `uri_cache_size`.
116
121
  #
@@ -627,10 +632,14 @@ module RDF
627
632
  # RDF::URI('http://www.w3.org/2000/01/rdf-schema#').qname #=> [:rdfs, nil]
628
633
  # RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').qname #=> [:rdfs, :label]
629
634
  # RDF::RDFS.label.qname #=> [:rdfs, :label]
635
+ # RDF::Vocab::DC.title.qname(
636
+ # prefixes: {dcterms: 'http://purl.org/dc/terms/'}) #=> [:dcterms, :title]
637
+ #
638
+ # @note within this software, the term QName is used to describe the tuple of prefix and suffix for a given IRI, where the prefix identifies some defined vocabulary. This somewhat contrasts with the notion of a [Qualified Name](https://www.w3.org/TR/2006/REC-xml-names11-20060816/#ns-qualnames) from XML, which are a subset of Prefixed Names.
630
639
  #
631
640
  # @param [Hash{Symbol => String}] prefixes
632
641
  # Explicit set of prefixes to look for matches, defaults to loaded vocabularies.
633
- # @return [Array(Symbol, Symbol)] or `nil` if no QName found
642
+ # @return [Array(Symbol, Symbol)] or `nil` if no QName found. The suffix component will not have [reserved characters](https://www.w3.org/TR/turtle/#reserved) escaped.
634
643
  def qname(prefixes: nil)
635
644
  if prefixes
636
645
  prefixes.each do |prefix, uri|
@@ -659,13 +668,25 @@ module RDF
659
668
  end
660
669
 
661
670
  ##
662
- # Returns a string version of the QName or the full IRI
671
+ # Returns a Prefixed Name (PName) or the full IRI with any [reserved characters](https://www.w3.org/TR/turtle/#reserved) in the suffix escaped.
672
+ #
673
+ # @example Using a custom prefix for creating a PNname.
674
+ # RDF::URI('http://purl.org/dc/terms/creator').
675
+ # pname(prefixes: {dcterms: 'http://purl.org/dc/terms/'})
676
+ # #=> "dcterms:creator"
663
677
  #
664
678
  # @param [Hash{Symbol => String}] prefixes
665
679
  # Explicit set of prefixes to look for matches, defaults to loaded vocabularies.
666
680
  # @return [String] or `nil`
681
+ # @see #qname
682
+ # @see https://www.w3.org/TR/rdf-sparql-query/#prefNames
667
683
  def pname(prefixes: nil)
668
- (q = self.qname(prefixes: prefixes)) ? q.join(":") : to_s
684
+ q = self.qname(prefixes: prefixes)
685
+ return self.to_s unless q
686
+ prefix, suffix = q
687
+ suffix = suffix.to_s.gsub(PN_ESCAPE_CHARS) {|c| "\\#{c}"} if
688
+ suffix.to_s.match?(PN_ESCAPE_CHARS)
689
+ [prefix, suffix].join(":")
669
690
  end
670
691
 
671
692
  ##
@@ -255,7 +255,7 @@ module RDF::NTriples
255
255
  if !match(ST_END)
256
256
  log_error("Expected end of statement (found: #{current_line.inspect})", lineno: lineno, exception: RDF::ReaderError)
257
257
  end
258
- RDF::Statement.new(subject, predicate, object)
258
+ RDF::Statement.new(subject, predicate, object, quoted: true)
259
259
  end
260
260
  end
261
261