rdf 3.2.2 → 3.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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