rdf 3.0.9 → 3.0.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42d123aacfde151f76648a3593e62fb0821441c67cf8668983becd2b1800ef41
4
- data.tar.gz: 42ecb0370f71b7487f0255af21a8959365138da7c5791c68a3e7aaa2f7a63a2e
3
+ metadata.gz: 3d464dd558551830f9c3948ffc879108e5619ef12384a0df1b0d25c0859a95ab
4
+ data.tar.gz: a3df4023d8417ffa7f94a95b9c2c2f5cb87eecbdcfb8ab89ae8c9b49d63f3060
5
5
  SHA512:
6
- metadata.gz: 22b1e9556b6327e9e8aaaa0f244ffc04565d9c9ef7d075d14f5636672596c40fbb544d49c1bafcc11f2bf90799f85c2ae2fbfb8afbcc6148e5ee357e2d5cde22
7
- data.tar.gz: d86ba20c17b43f887fc5609897cc24814ccf75c2bb4f0d0a8dd5a164e531e55442960e9733792aad628075b811b31f066e18182b8864bdfa0287317280417630
6
+ metadata.gz: 76afd5faf910dcc364f6367590654d69eabbcc409ee6e3b9892b1871c4c760e70f31aea6958394b80b16b04117868acd0adecf602e64b3030a4bf5fa81eb229f
7
+ data.tar.gz: 277b395451e0109f442a86ae41da2cebac078d41799beec31194d38e0cea4163092bb756ef8f435ead80d6078c48c27ec2a00b9c569be4044e36e9cd3e4b870b
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.9
1
+ 3.0.10
@@ -7,7 +7,7 @@ module RDF; class Literal
7
7
  class DateTime < Literal
8
8
  DATATYPE = RDF::URI("http://www.w3.org/2001/XMLSchema#dateTime")
9
9
  GRAMMAR = %r(\A(-?(?:\d{4}|[1-9]\d{4,})-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze
10
- FORMAT = '%Y-%m-%dT%H:%M:%S%:z'.freeze
10
+ FORMAT = '%Y-%m-%dT%H:%M:%S.%L%:z'.freeze
11
11
 
12
12
  ##
13
13
  # @param [DateTime] value
@@ -31,9 +31,9 @@ module RDF; class Literal
31
31
  def canonicalize!
32
32
  if self.valid?
33
33
  @string = if has_timezone?
34
- @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z')
34
+ @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z').sub('.000', '')
35
35
  else
36
- @object.strftime(FORMAT[0..-4])
36
+ @object.strftime(FORMAT[0..-4]).sub('.000', '')
37
37
  end
38
38
  end
39
39
  self
@@ -80,6 +80,16 @@ module RDF; class Literal
80
80
  super && object && value !~ %r(\A0000)
81
81
  end
82
82
 
83
+ ##
84
+ # Does the literal representation include millisectonds?
85
+ #
86
+ # @return [Boolean]
87
+ # @since 1.1.6
88
+ def has_milliseconds?
89
+ self.format("%L").to_i > 0
90
+ end
91
+ alias_method :has_ms?, :has_milliseconds?
92
+
83
93
  ##
84
94
  # Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option.
85
95
  #
@@ -98,7 +108,7 @@ module RDF; class Literal
98
108
  #
99
109
  # @return [String]
100
110
  def to_s
101
- @string || @object.strftime(FORMAT).sub("+00:00", 'Z')
111
+ @string || @object.strftime(FORMAT).sub("+00:00", 'Z').sub('.000', '')
102
112
  end
103
113
 
104
114
  ##
@@ -12,7 +12,7 @@ module RDF; class Literal
12
12
  class Time < Literal
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%:z'.freeze
15
+ FORMAT = '%H:%M:%S.%L%:z'.freeze
16
16
 
17
17
  ##
18
18
  # @param [String, DateTime, #to_datetime] value
@@ -43,9 +43,9 @@ module RDF; class Literal
43
43
  def canonicalize!
44
44
  if self.valid?
45
45
  @string = if has_timezone?
46
- @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z')
46
+ @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z').sub('.000', '')
47
47
  else
48
- @object.strftime(FORMAT[0..-4])
48
+ @object.strftime(FORMAT[0..-4]).sub('.000', '')
49
49
  end
50
50
  end
51
51
  self
@@ -91,7 +91,7 @@ module RDF; class Literal
91
91
  #
92
92
  # @return [String]
93
93
  def to_s
94
- @string || @object.strftime(FORMAT).sub("+00:00", 'Z')
94
+ @string || @object.strftime(FORMAT).sub("+00:00", 'Z').sub('.000', '')
95
95
  end
96
96
 
97
97
  ##
@@ -122,7 +122,7 @@ module RDF; class Literal
122
122
  return super unless other.valid?
123
123
  # Compare as strings, as time includes a date portion, and adjusting for UTC
124
124
  # can create a mismatch in the date portion.
125
- self.object.new_offset.strftime('%H%M%S') == other.object.new_offset.strftime('%H%M%S')
125
+ self.object.new_offset.strftime('%H%M%S.%L') == other.object.new_offset.strftime('%H%M%S.%L')
126
126
  when Literal::DateTime, Literal::Date
127
127
  false
128
128
  else
@@ -57,6 +57,9 @@ module RDF
57
57
  # @return [RDF::Term]
58
58
  attr_accessor :object
59
59
 
60
+ # @return [Hash{Symbol => Object}]
61
+ attr_accessor :options
62
+
60
63
  ##
61
64
  # @overload initialize(**options)
62
65
  # @param [Hash{Symbol => Object}] options
@@ -169,8 +169,7 @@ module RDF
169
169
  def self.normalize_path(path)
170
170
  output, input = "", path.to_s
171
171
  if input.encoding != Encoding::ASCII_8BIT
172
- input = input.dup if input.frozen?
173
- input = input.force_encoding(Encoding::ASCII_8BIT)
172
+ input = input.dup.force_encoding(Encoding::ASCII_8BIT)
174
173
  end
175
174
  until input.empty?
176
175
  if input.match(RDS_2A)
@@ -228,8 +227,7 @@ module RDF
228
227
  if uri
229
228
  @value = uri.to_s
230
229
  if @value.encoding != Encoding::UTF_8
231
- @value = @value.dup if @value.frozen?
232
- @value.force_encoding(Encoding::UTF_8)
230
+ @value.dup.force_encoding(Encoding::UTF_8)
233
231
  @value.freeze
234
232
  end
235
233
  else
@@ -842,16 +840,16 @@ module RDF
842
840
  user, password = userinfo.to_s.split(':', 2)
843
841
  host, port = hostport.to_s.split(':', 2)
844
842
 
845
- parts[:scheme] = (scheme.force_encoding(Encoding::UTF_8) if scheme)
846
- parts[:authority] = (authority.force_encoding(Encoding::UTF_8) if authority)
847
- parts[:userinfo] = (userinfo.force_encoding(Encoding::UTF_8) if userinfo)
848
- parts[:user] = (user.force_encoding(Encoding::UTF_8) if user)
849
- parts[:password] = (password.force_encoding(Encoding::UTF_8) if password)
850
- parts[:host] = (host.force_encoding(Encoding::UTF_8) if host)
843
+ parts[:scheme] = (scheme.dup.force_encoding(Encoding::UTF_8) if scheme)
844
+ parts[:authority] = (authority.dup.force_encoding(Encoding::UTF_8) if authority)
845
+ parts[:userinfo] = (userinfo.dup.force_encoding(Encoding::UTF_8) if userinfo)
846
+ parts[:user] = (user.dup.force_encoding(Encoding::UTF_8) if user)
847
+ parts[:password] = (password.dup.force_encoding(Encoding::UTF_8) if password)
848
+ parts[:host] = (host.dup.force_encoding(Encoding::UTF_8) if host)
851
849
  parts[:port] = (::URI.decode(port).to_i if port)
852
- parts[:path] = (path.to_s.force_encoding(Encoding::UTF_8) unless path.empty?)
853
- parts[:query] = (query[1..-1].force_encoding(Encoding::UTF_8) if query)
854
- parts[:fragment] = (fragment[1..-1].force_encoding(Encoding::UTF_8) if fragment)
850
+ parts[:path] = (path.to_s.dup.force_encoding(Encoding::UTF_8) unless path.empty?)
851
+ parts[:query] = (query[1..-1].dup.force_encoding(Encoding::UTF_8) if query)
852
+ parts[:fragment] = (fragment[1..-1].dup.force_encoding(Encoding::UTF_8) if fragment)
855
853
  end
856
854
 
857
855
  parts
@@ -869,7 +867,7 @@ module RDF
869
867
  # @param [String, #to_s] value
870
868
  # @return [RDF::URI] self
871
869
  def scheme=(value)
872
- object[:scheme] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
870
+ object[:scheme] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
873
871
  @value = nil
874
872
  self
875
873
  end
@@ -893,7 +891,7 @@ module RDF
893
891
  # @param [String, #to_s] value
894
892
  # @return [RDF::URI] self
895
893
  def user=(value)
896
- object[:user] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
894
+ object[:user] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
897
895
  @object[:userinfo] = format_userinfo("")
898
896
  @object[:authority] = format_authority
899
897
  @value = nil
@@ -919,7 +917,7 @@ module RDF
919
917
  # @param [String, #to_s] value
920
918
  # @return [RDF::URI] self
921
919
  def password=(value)
922
- object[:password] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
920
+ object[:password] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
923
921
  @object[:userinfo] = format_userinfo("")
924
922
  @object[:authority] = format_authority
925
923
  @value = nil
@@ -947,7 +945,7 @@ module RDF
947
945
  # @param [String, #to_s] value
948
946
  # @return [RDF::URI] self
949
947
  def host=(value)
950
- object[:host] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
948
+ object[:host] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
951
949
  @object[:authority] = format_authority
952
950
  @value = nil
953
951
  self
@@ -1010,7 +1008,7 @@ module RDF
1010
1008
  if value
1011
1009
  # Always lead with a slash
1012
1010
  value = "/#{value}" if host && value.to_s.match?(/^[^\/]/)
1013
- object[:path] = value.to_s.force_encoding(Encoding::UTF_8)
1011
+ object[:path] = value.to_s.dup.force_encoding(Encoding::UTF_8)
1014
1012
  else
1015
1013
  object[:path] = nil
1016
1014
  end
@@ -1069,7 +1067,7 @@ module RDF
1069
1067
  # @param [String, #to_s] value
1070
1068
  # @return [RDF::URI] self
1071
1069
  def query=(value)
1072
- object[:query] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1070
+ object[:query] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
1073
1071
  @value = nil
1074
1072
  self
1075
1073
  end
@@ -1093,7 +1091,7 @@ module RDF
1093
1091
  # @param [String, #to_s] value
1094
1092
  # @return [RDF::URI] self
1095
1093
  def fragment=(value)
1096
- object[:fragment] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1094
+ object[:fragment] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
1097
1095
  @value = nil
1098
1096
  self
1099
1097
  end
@@ -1118,7 +1116,7 @@ module RDF
1118
1116
  # @return [RDF::URI] self
1119
1117
  def authority=(value)
1120
1118
  object.delete_if {|k, v| [:user, :password, :host, :port, :userinfo].include?(k)}
1121
- object[:authority] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1119
+ object[:authority] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
1122
1120
  user; password; userinfo; host; port
1123
1121
  @value = nil
1124
1122
  self
@@ -1148,7 +1146,7 @@ module RDF
1148
1146
  # @return [RDF::URI] self
1149
1147
  def userinfo=(value)
1150
1148
  object.delete_if {|k, v| [:user, :password, :authority].include?(k)}
1151
- object[:userinfo] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1149
+ object[:userinfo] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
1152
1150
  user; password; authority
1153
1151
  @value = nil
1154
1152
  self
@@ -1263,6 +1261,26 @@ module RDF
1263
1261
  return res
1264
1262
  end
1265
1263
 
1264
+ ##
1265
+ # Dump of data needed to reconsitute this object using Marshal.load
1266
+ # This override is needed to avoid serializing @mutex.
1267
+ #
1268
+ # @param [Integer] level The maximum depth of objects to dump.
1269
+ # @return [String] The dump of data needed to reconsitute this object.
1270
+ def _dump(level)
1271
+ value
1272
+ end
1273
+
1274
+ ##
1275
+ # Load dumped data to reconsitute marshaled object
1276
+ # This override is needed to avoid serializing @mutex.
1277
+ #
1278
+ # @param [String] data The dump of data needed to reconsitute this object.
1279
+ # @return [RDF::URI] The reconsituted object.
1280
+ def self._load(data)
1281
+ new(data)
1282
+ end
1283
+
1266
1284
  private
1267
1285
 
1268
1286
  ##
@@ -1274,8 +1292,7 @@ module RDF
1274
1292
  # @return [String]
1275
1293
  def normalize_segment(value, expr, downcase = false)
1276
1294
  if value
1277
- value = value.dup if value.frozen?
1278
- value = value.force_encoding(Encoding::UTF_8)
1295
+ value = value.dup.force_encoding(Encoding::UTF_8)
1279
1296
  decoded = ::URI.decode(value)
1280
1297
  decoded.downcase! if downcase
1281
1298
  ::URI.encode(decoded, /[^(?:#{expr})]/)
@@ -32,7 +32,7 @@ module RDF::NTriples
32
32
  format RDF::NTriples::Format
33
33
 
34
34
  # @see http://www.w3.org/TR/rdf-testcases/#ntrip_strings
35
- ESCAPE_CHARS = ["\b", "\f", "\t", "\n", "\r", "\"", "\\"].freeze
35
+ ESCAPE_CHARS = ["\b", "\f", "\t", "\n", "\r", "\"", "'", "\\"].freeze
36
36
  UCHAR4 = /\\u([0-9A-Fa-f]{4,4})/.freeze
37
37
  UCHAR8 = /\\U([0-9A-Fa-f]{8,8})/.freeze
38
38
  UCHAR = Regexp.union(UCHAR4, UCHAR8).freeze
@@ -60,7 +60,7 @@ module RDF::NTriples
60
60
  # 166s
61
61
  PN_CHARS = /-|[0-9]|#{PN_CHARS_U}|#{U_CHARS2}/.freeze
62
62
  # 159s
63
- ECHAR = /\\[tbnrf\\"]/.freeze
63
+ ECHAR = /\\[tbnrf"'\\]/.freeze
64
64
  # 18
65
65
  IRIREF = /<((?:#{IRI_RANGE}|#{UCHAR})*)>/.freeze
66
66
  # 141s
@@ -135,7 +135,6 @@ module RDF::NTriples
135
135
  # @return [RDF::URI]
136
136
  def self.parse_uri(input, intern: false, **options)
137
137
  if input =~ URIREF
138
- uri_str = unescape($1)
139
138
  RDF::URI.send(intern ? :intern : :new, unescape($1))
140
139
  end
141
140
  end
@@ -155,9 +154,16 @@ module RDF::NTriples
155
154
  end
156
155
 
157
156
  # cache constants to optimize escaping the escape chars in self.unescape
158
- ESCAPE_CHARS_ESCAPED = ESCAPE_CHARS.each_with_object({}) do |escape, memo|
159
- memo[escape.inspect[1...-1]] = escape
160
- end.freeze
157
+ ESCAPE_CHARS_ESCAPED = {
158
+ "\\b" => "\b",
159
+ "\\f" => "\f",
160
+ "\\t" => "\t",
161
+ "\\n" => "\n",
162
+ "\\r" => "\r",
163
+ "\\\"" => "\"",
164
+ "\\'" => "'",
165
+ "\\\\" => "\\"
166
+ } .freeze
161
167
  ESCAPE_CHARS_ESCAPED_REGEXP = Regexp.union(
162
168
  ESCAPE_CHARS_ESCAPED.keys
163
169
  ).freeze
@@ -171,26 +177,23 @@ module RDF::NTriples
171
177
  def self.unescape(string)
172
178
  # Note: avoiding copying the input string when no escaping is needed
173
179
  # greatly reduces the number of allocations and the processing time.
174
- unless string.encoding == Encoding::UTF_8
175
- string = string.dup.force_encoding(Encoding::UTF_8)
176
- end
177
-
178
- has_escape_chars = ESCAPE_CHARS_ESCAPED_REGEXP.match?(string)
179
- has_uchar = UCHAR.match?(string)
180
-
181
- string = string.dup if has_escape_chars || has_uchar
180
+ string = string.dup.force_encoding(Encoding::UTF_8) unless string.encoding == Encoding::UTF_8
181
+ scanner = StringScanner.new(string)
182
182
 
183
- # Decode \t|\n|\r|\"|\\ character escapes using Regexp:
184
- string.gsub!(ESCAPE_CHARS_ESCAPED_REGEXP) do
185
- ESCAPE_CHARS_ESCAPED.fetch($~[0])
186
- end if has_escape_chars
183
+ buffer = ""
187
184
 
188
- # Decode \uXXXX and \UXXXXXXXX code points:
189
- string.gsub!(UCHAR) do
190
- [($1 || $2).hex].pack('U*')
191
- end if has_uchar
185
+ while !scanner.eos?
186
+ buffer << if scanner.scan(ESCAPE_CHARS_ESCAPED_REGEXP)
187
+ ESCAPE_CHARS_ESCAPED[scanner.matched]
188
+ elsif scanner.scan(UCHAR)
189
+ scanner.matched.sub(UCHAR) {[($1 || $2).hex].pack('U*')}
190
+ else
191
+ # Scan one character
192
+ scanner.getch
193
+ end
194
+ end
192
195
 
193
- string
196
+ buffer
194
197
  end
195
198
 
196
199
  ##
@@ -250,7 +253,7 @@ module RDF::NTriples
250
253
  uri.canonicalize! if canonicalize?
251
254
  uri
252
255
  end
253
- rescue ArgumentError => e
256
+ rescue ArgumentError
254
257
  log_error("Invalid URI (found: \"<#{uri_str}>\")", lineno: lineno, token: "<#{uri_str}>", exception: RDF::ReaderError)
255
258
  end
256
259
 
@@ -258,7 +261,7 @@ module RDF::NTriples
258
261
  # @return [RDF::Node]
259
262
  # @see http://www.w3.org/TR/rdf-testcases/#ntrip_grammar (nodeID)
260
263
  def read_node
261
- if node_id = match(NODEID)
264
+ if node_id = match(NODEID)
262
265
  @nodes ||= {}
263
266
  @nodes[node_id] ||= RDF::Node.new(node_id)
264
267
  end
@@ -42,7 +42,7 @@ module RDF::NTriples
42
42
  format RDF::NTriples::Format
43
43
 
44
44
  # @see http://www.w3.org/TR/rdf-testcases/#ntrip_strings
45
- ESCAPE_PLAIN = /\A[\x20-\x21\x23-#{Regexp.escape '['}#{Regexp.escape ']'}-\x7E]*\z/m.freeze
45
+ ESCAPE_PLAIN = /\A[\x20-\x21\x23-\x26\x28#{Regexp.escape '['}#{Regexp.escape ']'}-\x7E]*\z/m.freeze
46
46
  ESCAPE_PLAIN_U = /\A(?:#{Reader::IRI_RANGE}|#{Reader::UCHAR})*\z/.freeze
47
47
 
48
48
  ##
@@ -116,6 +116,8 @@ module RDF::NTriples
116
116
  # sequences, otherwise, assume the test-cases escape sequences. Otherwise,
117
117
  # the N-Triples recommendation includes `\b` and `\f` escape sequences.
118
118
  #
119
+ # Within STRING_LITERAL_QUOTE, only the characters `U+0022`, `U+005C`, `U+000A`, `U+000D` are encoded using `ECHAR`. `ECHAR` must not be used for characters that are allowed directly in STRING_LITERAL_QUOTE.
120
+ #
119
121
  # @param [Integer, #ord] u
120
122
  # @return [String]
121
123
  # @raise [ArgumentError] if `u` is not a valid Unicode codepoint
@@ -124,11 +126,7 @@ module RDF::NTriples
124
126
  def self.escape_ascii(u, encoding)
125
127
  case (u = u.ord)
126
128
  when (0x00..0x07) then escape_utf16(u)
127
- when (0x08) then (encoding && encoding == Encoding::ASCII ? escape_utf16(u) : "\\b")
128
- when (0x09) then "\\t"
129
129
  when (0x0A) then "\\n"
130
- when (0x0B) then escape_utf16(u)
131
- when (0x0C) then (encoding && encoding == Encoding::ASCII ? escape_utf16(u) : "\\f")
132
130
  when (0x0D) then "\\r"
133
131
  when (0x0E..0x1F) then escape_utf16(u)
134
132
  when (0x22) then "\\\""
@@ -264,7 +262,7 @@ module RDF::NTriples
264
262
  string.each_char do |u|
265
263
  buffer << case u.ord
266
264
  when (0x00..0x20) then self.class.escape_utf16(u)
267
- when 0x22, 0x3c, 0x3e, 0x5c, 0x5e, 0x60, 0x7b, 0x7c, 0x7d # <>"{}|`\
265
+ when 0x22, 0x3c, 0x3e, 0x5c, 0x5e, 0x60, 0x7b, 0x7c, 0x7d # "<>\^`{|}
268
266
  self.class.escape_utf16(u)
269
267
  else u
270
268
  end
@@ -278,7 +276,7 @@ module RDF::NTriples
278
276
  string.each_byte do |u|
279
277
  buffer << case u
280
278
  when (0x00..0x20) then self.class.escape_utf16(u)
281
- when 0x22, 0x3c, 0x3e, 0x5c, 0x5e, 0x60, 0x7b, 0x7c, 0x7d # <>"{}|`\
279
+ when 0x22, 0x3c, 0x3e, 0x5c, 0x5e, 0x60, 0x7b, 0x7c, 0x7d # "<>\^`{|}
282
280
  self.class.escape_utf16(u)
283
281
  when (0x80..0xFFFF) then self.class.escape_utf16(u)
284
282
  when (0x10000..0x10FFFF) then self.class.escape_utf32(u)
@@ -171,10 +171,12 @@ module RDF
171
171
  # Alias for `:graph_name`.
172
172
  # @param [Hash{Symbol => Object}] options
173
173
  # any additional keyword options
174
+ # @option options [Boolean] validate (false)
175
+ # validate patterns
174
176
  # @yield [query]
175
177
  # @yieldparam [RDF::Query] query
176
178
  # @yieldreturn [void] ignored
177
- def initialize(*patterns, solutions: nil, graph_name: nil, name: nil, **options, &block)
179
+ def initialize(*patterns, solutions: nil, graph_name: nil, name: nil, validate: false, **options, &block)
178
180
  @options = options.dup
179
181
  @solutions = Query::Solutions(solutions)
180
182
  graph_name = name if graph_name.nil?
@@ -195,6 +197,8 @@ module RDF
195
197
  else instance_eval(&block)
196
198
  end
197
199
  end
200
+
201
+ validate! if validate
198
202
  end
199
203
 
200
204
  ##
@@ -291,7 +295,6 @@ module RDF
291
295
  # @see http://www.holygoat.co.uk/blog/entry/2005-10-25-1
292
296
  # @see http://www.w3.org/TR/sparql11-query/#emptyGroupPattern
293
297
  def execute(queryable, solutions: Solution.new, graph_name: nil, name: nil, **options, &block)
294
- validate!
295
298
  options = {bindings: {}}.merge(options)
296
299
 
297
300
  # Use provided solutions to allow for query chaining
@@ -356,8 +359,9 @@ module RDF
356
359
  return @solutions if @solutions.empty?
357
360
 
358
361
  if !pattern.optional?
359
- # We have no solutions for variables we should have solutions for:
360
- need_vars = pattern.variables.keys
362
+ # We have no solutions for variables we should have solutions for
363
+ # (excludes non-distinguished variables):
364
+ need_vars = pattern.variables.select {|k,v| v.distinguished?}.keys
361
365
  @solutions.each do |solution|
362
366
  break if need_vars.empty?
363
367
  need_vars -= solution.bindings.keys
@@ -516,12 +520,12 @@ module RDF
516
520
  end
517
521
 
518
522
  ##
519
- # Determine if the URI is a valid according to RFC3987
523
+ # Determine if the query containts valid patterns
520
524
  #
521
525
  # @return [Boolean] `true` or `false`
522
526
  # @since 0.3.9
523
527
  def valid?
524
- !!validate!
528
+ !!validate! rescue raise false
525
529
  rescue
526
530
  false
527
531
  end
@@ -47,6 +47,13 @@ module RDF; class Query
47
47
  @subject = Variable.new(@subject) if @subject.is_a?(Symbol)
48
48
  @predicate = Variable.new(@predicate) if @predicate.is_a?(Symbol)
49
49
  @object = Variable.new(@object) if @object.is_a?(Symbol)
50
+
51
+ # Estmate cost positionally, with variables being least expensive as objects, then predicates, then subjects, then graph_names.
52
+ # XXX does not consider bound variables, which would need to be dynamically calculated.
53
+ @cost = (@object.nil? || @object.is_a?(Variable) ? 1 : 0) +
54
+ (@predicate.nil? || @predicate.is_a?(Variable) ? 2 : 0) +
55
+ (@subject.nil? || @subject.is_a?(Variable) ? 4 : 0) +
56
+ (@graph_name.is_a?(Variable) ? 8 : 0)
50
57
  super
51
58
  end
52
59
 
@@ -22,6 +22,7 @@ class RDF::Query
22
22
  #
23
23
  class Solution
24
24
  # Undefine all superfluous instance methods:
25
+ alias_method :__send, :send
25
26
  undef_method(*instance_methods.
26
27
  map(&:to_s).
27
28
  select {|m| m.match?(/^\w+$/)}.
@@ -57,10 +58,20 @@ class RDF::Query
57
58
  # @yieldparam [RDF::Term] value
58
59
  # @return [Enumerator]
59
60
  def each_binding(&block)
60
- @bindings.each(&block)
61
+ @bindings.each(&block) if block_given?
62
+ enum_binding
61
63
  end
62
64
  alias_method :each, :each_binding
63
65
 
66
+ ##
67
+ # Returns an enumerator for {#each_binding}.
68
+ #
69
+ # @return [Enumerator<RDF::Resource>]
70
+ # @see #each_subject
71
+ def enum_binding
72
+ enum_for(:each_binding)
73
+ end
74
+
64
75
  ##
65
76
  # Enumerates over every variable name in this solution.
66
77
  #
@@ -68,10 +79,20 @@ class RDF::Query
68
79
  # @yieldparam [Symbol] name
69
80
  # @return [Enumerator]
70
81
  def each_name(&block)
71
- @bindings.each_key(&block)
82
+ @bindings.each_key(&block) if block_given?
83
+ enum_name
72
84
  end
73
85
  alias_method :each_key, :each_name
74
86
 
87
+ ##
88
+ # Returns an enumerator for {#each_name}.
89
+ #
90
+ # @return [Enumerator<RDF::Resource>]
91
+ # @see #each_subject
92
+ def enum_name
93
+ enum_for(:each_name)
94
+ end
95
+
75
96
  ##
76
97
  # Enumerates over every variable value in this solution.
77
98
  #
@@ -79,7 +100,17 @@ class RDF::Query
79
100
  # @yieldparam [RDF::Term] value
80
101
  # @return [Enumerator]
81
102
  def each_value(&block)
82
- @bindings.each_value(&block)
103
+ @bindings.each_value(&block) if block_given?
104
+ enum_value
105
+ end
106
+
107
+ ##
108
+ # Returns an enumerator for {#each_value}.
109
+ #
110
+ # @return [Enumerator<RDF::Resource>]
111
+ # @see #each_subject
112
+ def enum_value
113
+ enum_for(:each_value)
83
114
  end
84
115
 
85
116
  ##
@@ -101,9 +132,21 @@ class RDF::Query
101
132
  # @yieldparam [Variable]
102
133
  # @return [Enumerator]
103
134
  def each_variable
104
- @bindings.each do |name, value|
105
- yield Variable.new(name, value)
135
+ if block_given?
136
+ @bindings.each do |name, value|
137
+ yield Variable.new(name, value)
138
+ end
106
139
  end
140
+ enum_variable
141
+ end
142
+
143
+ ##
144
+ # Returns an enumerator for {#each_variable}.
145
+ #
146
+ # @return [Enumerator<RDF::Resource>]
147
+ # @see #each_subject
148
+ def enum_variable
149
+ enum_for(:each_variable)
107
150
  end
108
151
 
109
152
  ##
@@ -282,5 +325,19 @@ class RDF::Query
282
325
  def respond_to_missing?(name, include_private = false)
283
326
  @bindings.has_key?(name.to_sym) || super
284
327
  end
328
+
329
+ ##
330
+ # @private
331
+ # @param [Symbol, #to_sym] method
332
+ # @return [Enumerator]
333
+ # @see Object#enum_for
334
+ def enum_for(method = :each)
335
+ # Ensure that enumerators are, themselves, queryable
336
+ this = self
337
+ Enumerator.new do |yielder|
338
+ this.__send(method) {|*y| yielder << (y.length > 1 ? y : y.first)}
339
+ end
340
+ end
341
+ alias_method :to_enum, :enum_for
285
342
  end # Solution
286
343
  end # RDF::Query
@@ -104,7 +104,29 @@ module RDF; class Query
104
104
  end
105
105
  bindings
106
106
  end
107
-
107
+
108
+ ##
109
+ # Duplicates each solution.
110
+ # @return [RDF::Query::Solutions]
111
+ def dup
112
+ RDF::Query::Solutions.new(self.compact.map(&:dup))
113
+ end
114
+
115
+ ##
116
+ # Merge solutions in `other` into a new solutions instance. Each solution in `other` is merged into those solutions in `self` that are compatible.
117
+ #
118
+ # @param [RDF::Query::Solutions] other
119
+ # @return [RDF::Query::Solutions]
120
+ def merge(other)
121
+ other ||= RDF::Query::Solutions()
122
+ return other if self.empty?
123
+ return self if other.empty?
124
+
125
+ RDF::Query::Solutions(self.map do |s1|
126
+ other.map { |s2| s2.merge(s1) if s2.compatible?(s1) }
127
+ end.flatten.compact)
128
+ end
129
+
108
130
  ##
109
131
  # Filters this solution sequence by the given `criteria`.
110
132
  #
@@ -65,9 +65,25 @@ class RDF::Query
65
65
  # the variable name
66
66
  # @param [RDF::Term] value
67
67
  # an optional variable value
68
- def initialize(name = nil, value = nil)
69
- @name = (name || "g#{__id__.to_i.abs}").to_sym
68
+ # @param [Boolean] distinguished (true) Also interpreted by leading '??' or '$$' in name.
69
+ # @param [Boolean] existential (true) Also interpreted by leading '$' in name
70
+ def initialize(name = nil, value = nil, distinguished: nil, existential: nil)
71
+ name = (name || "g#{__id__.to_i.abs}").to_s
72
+ if name.start_with?('??')
73
+ name, dis, ex = name[2..-1], false, false
74
+ elsif name.start_with?('?')
75
+ name, dis, ex = name[1..-1], true, false
76
+ elsif name.start_with?('$$')
77
+ name, dis, ex = name[2..-1], false, true
78
+ elsif name.start_with?('$')
79
+ name, dis, ex = name[1..-1], true, true
80
+ else
81
+ dis, ex = true, false
82
+ end
83
+ @name = name.to_sym
70
84
  @value = value
85
+ @distinguished = distinguished.nil? ? dis : distinguished
86
+ @existential = existential.nil? ? ex : existential
71
87
  end
72
88
 
73
89
  ##
@@ -109,7 +125,7 @@ class RDF::Query
109
125
  #
110
126
  # @return [Boolean]
111
127
  def distinguished?
112
- @distinguished.nil? || @distinguished
128
+ @distinguished
113
129
  end
114
130
 
115
131
  ##
@@ -121,6 +137,23 @@ class RDF::Query
121
137
  @distinguished = value
122
138
  end
123
139
 
140
+ ##
141
+ # Returns `true` if this variable is existential.
142
+ #
143
+ # @return [Boolean]
144
+ def existential?
145
+ @existential
146
+ end
147
+
148
+ ##
149
+ # Sets if variable is existential or univeresal.
150
+ # By default, variables are universal
151
+ #
152
+ # @return [Boolean]
153
+ def existential=(value)
154
+ @existential = value
155
+ end
156
+
124
157
  ##
125
158
  # Rebinds this variable to the given `value`.
126
159
  #
@@ -208,6 +241,7 @@ class RDF::Query
208
241
  #
209
242
  # Non-distinguished variables are indicated with a double `??`
210
243
  #
244
+ # Existential variables are indicated using a single `$`, or with `$$` if also non-distinguished
211
245
  # @example
212
246
  # v = Variable.new("a")
213
247
  # v.to_s => '?a'
@@ -216,7 +250,7 @@ class RDF::Query
216
250
  #
217
251
  # @return [String]
218
252
  def to_s
219
- prefix = distinguished? ? '?' : "??"
253
+ prefix = distinguished? ? (existential? ? '$' : '?') : (existential? ? '$$' : '??')
220
254
  unbound? ? "#{prefix}#{name}" : "#{prefix}#{name}=#{value}"
221
255
  end
222
256
  end # Variable
@@ -206,8 +206,9 @@ module RDF
206
206
  # @private
207
207
  # @see RDF::Enumerable#project_graph
208
208
  def project_graph(graph_name, &block)
209
- RDF::Graph.new(graph_name: graph_name, data: self).
210
- project_graph(graph_name, &block)
209
+ graph = RDF::Graph.new(graph_name: graph_name, data: self)
210
+ graph.each(&block) if block_given?
211
+ graph
211
212
  end
212
213
 
213
214
  ##
@@ -324,8 +325,8 @@ module RDF
324
325
  @data.each do |g, ss|
325
326
  ss.each do |s, ps|
326
327
  ps.each do |p, os|
327
- os.each do |o|
328
- yield RDF::Statement.new(s, p, o, graph_name: g.equal?(DEFAULT_GRAPH) ? nil : g)
328
+ os.each do |o, object_options|
329
+ yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: g.equal?(DEFAULT_GRAPH) ? nil : g))
329
330
  end
330
331
  end
331
332
  end
@@ -402,9 +403,9 @@ module RDF
402
403
  []
403
404
  end
404
405
  ps.each do |p, os|
405
- os.each do |o|
406
+ os.each do |o, object_options|
406
407
  next unless object.nil? || object.eql?(o)
407
- yield RDF::Statement.new(s, p, o, graph_name: c.equal?(DEFAULT_GRAPH) ? nil : c)
408
+ yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: c.equal?(DEFAULT_GRAPH) ? nil : c))
408
409
  end
409
410
  end
410
411
  end
@@ -461,7 +462,7 @@ module RDF
461
462
  data.has_key?(g) &&
462
463
  data[g].has_key?(s) &&
463
464
  data[g][s].has_key?(p) &&
464
- data[g][s][p].include?(o)
465
+ data[g][s][p].has_key?(o)
465
466
  end
466
467
 
467
468
  ##
@@ -475,9 +476,9 @@ module RDF
475
476
  c ||= DEFAULT_GRAPH
476
477
 
477
478
  return data.put(c) do |subs|
478
- subs = (subs || Hamster::Hash.new).put(s) do |preds|
479
- preds = (preds || Hamster::Hash.new).put(p) do |objs|
480
- (objs || Hamster::Set.new).add(o)
479
+ (subs || Hamster::Hash.new).put(s) do |preds|
480
+ (preds || Hamster::Hash.new).put(p) do |objs|
481
+ (objs || Hamster::Hash.new).put(o, statement.options)
481
482
  end
482
483
  end
483
484
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.9
4
+ version: 3.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arto Bendiken
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-01-01 00:00:00.000000000 Z
13
+ date: 2019-02-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: link_header
@@ -297,7 +297,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
297
  - !ruby/object:Gem::Version
298
298
  version: '0'
299
299
  requirements: []
300
- rubygems_version: 3.0.1
300
+ rubygems_version: 3.0.2
301
301
  signing_key:
302
302
  specification_version: 4
303
303
  summary: A Ruby library for working with Resource Description Framework (RDF) data.