csvlint 0.2.6 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.gitattributes +1 -1
- data/.gitignore +1 -1
- data/csvlint.gemspec +2 -0
- data/features/fixtures/w3.org/.well-known/csvm +4 -0
- data/features/step_definitions/validation_errors_steps.rb +1 -1
- data/features/support/earl_formatter.rb +66 -0
- data/features/support/load_tests.rb +20 -15
- data/lib/csvlint/csvw/column.rb +103 -55
- data/lib/csvlint/csvw/date_format.rb +49 -16
- data/lib/csvlint/csvw/number_format.rb +36 -21
- data/lib/csvlint/csvw/property_checker.rb +90 -45
- data/lib/csvlint/csvw/table.rb +57 -39
- data/lib/csvlint/csvw/table_group.rb +9 -9
- data/lib/csvlint/schema.rb +2 -2
- data/lib/csvlint/validate.rb +10 -13
- data/lib/csvlint/version.rb +1 -1
- data/spec/csvw/column_spec.rb +6 -6
- data/spec/csvw/date_format_spec.rb +10 -10
- data/spec/csvw/number_format_spec.rb +29 -0
- metadata +34 -2
@@ -70,39 +70,72 @@ module Csvlint
|
|
70
70
|
# STDERR.puts(@regexp)
|
71
71
|
# STDERR.puts(value)
|
72
72
|
# STDERR.puts(match.inspect)
|
73
|
+
value = {}
|
74
|
+
match.names.each do |field|
|
75
|
+
unless match[field].nil?
|
76
|
+
case field
|
77
|
+
when "timezone"
|
78
|
+
tz = match["timezone"]
|
79
|
+
tz = "+00:00" if tz == 'Z'
|
80
|
+
tz += ':00' if tz.length == 3
|
81
|
+
tz = "#{tz[0..2]}:#{tz[3..4]}" unless tz =~ /:/
|
82
|
+
value[:timezone] = tz
|
83
|
+
when "second"
|
84
|
+
value[:second] = match["second"].to_f
|
85
|
+
else
|
86
|
+
value[field.to_sym] = match[field].to_i
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
73
90
|
case @type
|
74
91
|
when "http://www.w3.org/2001/XMLSchema#date"
|
75
92
|
begin
|
76
|
-
|
93
|
+
value[:dateTime] = Date.new(match["year"].to_i, match["month"].to_i, match["day"].to_i)
|
77
94
|
rescue ArgumentError
|
78
95
|
return nil
|
79
96
|
end
|
80
97
|
when "http://www.w3.org/2001/XMLSchema#dateTime"
|
81
98
|
begin
|
82
|
-
|
99
|
+
value[:dateTime] = DateTime.new(match["year"].to_i, match["month"].to_i, match["day"].to_i, match["hour"].to_i, match["minute"].to_i, (match.names.include?("second") ? match["second"].to_f : 0), match.names.include?("timezone") && match["timezone"] ? match["timezone"] : '')
|
83
100
|
rescue ArgumentError
|
84
101
|
return nil
|
85
102
|
end
|
86
103
|
else
|
87
|
-
value =
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
tz = "#{tz[0..2]}:#{tz[3..4]}" unless tz =~ /:/
|
96
|
-
value["timezone"] = tz
|
97
|
-
when "second"
|
98
|
-
value["second"] = match["second"].to_f
|
104
|
+
value[:dateTime] = DateTime.new(value[:year] || 0, value[:month] || 1, value[:day] || 1, value[:hour] || 0, value[:minute] || 0, value[:second] || 0, value[:timezone] || "+00:00")
|
105
|
+
end
|
106
|
+
if value[:year]
|
107
|
+
if value[:month]
|
108
|
+
if value[:day]
|
109
|
+
if value[:hour]
|
110
|
+
# dateTime
|
111
|
+
value[:string] = "#{format('%04d', value[:year])}-#{format('%02d', value[:month])}-#{format('%02d', value[:day])}T#{format('%02d', value[:hour])}:#{format('%02d', value[:minute] || 0)}:#{format('%02g', value[:second] || 0)}#{value[:timezone] ? value[:timezone].sub("+00:00", "Z") : ''}"
|
99
112
|
else
|
100
|
-
|
113
|
+
# date
|
114
|
+
value[:string] = "#{format('%04d', value[:year])}-#{format('%02d', value[:month])}-#{format('%02d', value[:day])}#{value[:timezone] ? value[:timezone].sub("+00:00", "Z") : ''}"
|
101
115
|
end
|
116
|
+
else
|
117
|
+
# gYearMonth
|
118
|
+
value[:string] = "#{format('%04d', value[:year])}-#{format('%02d', value[:month])}#{value[:timezone] ? value[:timezone].sub("+00:00", "Z") : ''}"
|
102
119
|
end
|
120
|
+
else
|
121
|
+
# gYear
|
122
|
+
value[:string] = "#{format('%04d', value[:year])}#{value[:timezone] ? value[:timezone].sub("+00:00", "Z") : ''}"
|
103
123
|
end
|
104
|
-
|
124
|
+
elsif value[:month]
|
125
|
+
if value[:day]
|
126
|
+
# gMonthDay
|
127
|
+
value[:string] = "--#{format('%02d', value[:month])}-#{format('%02d', value[:day])}#{value[:timezone] ? value[:timezone].sub("+00:00", "Z") : ''}"
|
128
|
+
else
|
129
|
+
# gMonth
|
130
|
+
value[:string] = "--#{format('%02d', value[:month])}#{value[:timezone] ? value[:timezone].sub("+00:00", "Z") : ''}"
|
131
|
+
end
|
132
|
+
elsif value[:day]
|
133
|
+
# gDay
|
134
|
+
value[:string] = "---#{format('%02d', value[:day])}#{value[:timezone] ? value[:timezone].sub("+00:00", "Z") : ''}"
|
135
|
+
else
|
136
|
+
value[:string] = "#{format('%02d', value[:hour])}:#{format('%02d', value[:minute])}:#{format('%02g', value[:second] || 0)}#{value[:timezone] ? value[:timezone].sub("+00:00", "Z") : ''}"
|
105
137
|
end
|
138
|
+
return value
|
106
139
|
end
|
107
140
|
|
108
141
|
private
|
@@ -2,16 +2,28 @@ module Csvlint
|
|
2
2
|
module Csvw
|
3
3
|
class NumberFormat
|
4
4
|
|
5
|
-
attr_reader :pattern, :prefix, :numeric_part, :suffix, :grouping_separator, :decimal_separator, :primary_grouping_size, :secondary_grouping_size, :fractional_grouping_size
|
5
|
+
attr_reader :integer, :pattern, :prefix, :numeric_part, :suffix, :grouping_separator, :decimal_separator, :primary_grouping_size, :secondary_grouping_size, :fractional_grouping_size
|
6
6
|
|
7
|
-
def initialize(pattern=nil, grouping_separator=nil, decimal_separator=".")
|
7
|
+
def initialize(pattern=nil, grouping_separator=nil, decimal_separator=".", integer=nil)
|
8
8
|
@pattern = pattern
|
9
|
+
@integer = integer
|
10
|
+
if @integer.nil?
|
11
|
+
if @pattern.nil?
|
12
|
+
@integer = nil
|
13
|
+
else
|
14
|
+
@integer = !@pattern.include?(decimal_separator)
|
15
|
+
end
|
16
|
+
end
|
9
17
|
@grouping_separator = grouping_separator || (@pattern.nil? ? nil : ",")
|
10
18
|
@decimal_separator = decimal_separator || "."
|
11
19
|
if pattern.nil?
|
12
|
-
|
20
|
+
if integer
|
21
|
+
@regexp = INTEGER_REGEXP
|
22
|
+
else
|
23
|
+
@regexp = Regexp.new("^(([-+]?[0-9]+(\\.[0-9]+)?([Ee][-+]?[0-9]+)?[%‰]?)|NaN|INF|-INF)$")
|
24
|
+
end
|
13
25
|
else
|
14
|
-
numeric_part_regexp = Regexp.new("(?<numeric_part>([0#Ee]|#{Regexp.escape(@grouping_separator)}|#{Regexp.escape(@decimal_separator)})+)")
|
26
|
+
numeric_part_regexp = Regexp.new("(?<numeric_part>[-+]?([0#Ee]|#{Regexp.escape(@grouping_separator)}|#{Regexp.escape(@decimal_separator)})+)")
|
15
27
|
number_format_regexp = Regexp.new("^(?<prefix>.*?)#{numeric_part_regexp}(?<suffix>.*?)$")
|
16
28
|
match = number_format_regexp.match(pattern)
|
17
29
|
raise Csvw::NumberFormatError, "invalid number format" if match.nil?
|
@@ -28,7 +40,12 @@ module Csvlint
|
|
28
40
|
integer_part = mantissa_parts[0]
|
29
41
|
fractional_part = mantissa_parts[1] || ""
|
30
42
|
|
31
|
-
|
43
|
+
if ["+", "-"].include?(integer_part[0])
|
44
|
+
numeric_part_regexp = "\\#{integer_part[0]}"
|
45
|
+
integer_part = integer_part[1..-1]
|
46
|
+
else
|
47
|
+
numeric_part_regexp = "[-+]?"
|
48
|
+
end
|
32
49
|
|
33
50
|
min_integer_digits = integer_part.gsub(@grouping_separator, "").gsub("#", "").length
|
34
51
|
min_fraction_digits = fractional_part.gsub(@grouping_separator, "").gsub("#", "").length
|
@@ -43,8 +60,6 @@ module Csvlint
|
|
43
60
|
fractional_parts = fractional_part.split(@grouping_separator)[0..-2]
|
44
61
|
@fractional_grouping_size = fractional_parts[0].length rescue 0
|
45
62
|
|
46
|
-
numeric_part_regexp = "[-+]?"
|
47
|
-
|
48
63
|
if @primary_grouping_size == 0
|
49
64
|
integer_regexp = "[0-9]*[0-9]{#{min_integer_digits}}"
|
50
65
|
else
|
@@ -160,16 +175,8 @@ module Csvlint
|
|
160
175
|
if @pattern.nil?
|
161
176
|
return nil if !@grouping_separator.nil? && value =~ Regexp.new("((^#{Regexp.escape(@grouping_separator)})|#{Regexp.escape(@grouping_separator)}{2})")
|
162
177
|
value.gsub!(@grouping_separator, "") unless @grouping_separator.nil?
|
163
|
-
|
164
|
-
|
165
|
-
when "%"
|
166
|
-
return value.to_f / 100
|
167
|
-
when "‰"
|
168
|
-
return value.to_f / 1000
|
169
|
-
else
|
170
|
-
return value.to_i
|
171
|
-
end
|
172
|
-
elsif value =~ @regexp
|
178
|
+
value.gsub!(@decimal_separator, ".") unless @decimal_separator.nil?
|
179
|
+
if value =~ @regexp
|
173
180
|
case value
|
174
181
|
when "NaN"
|
175
182
|
return Float::NAN
|
@@ -184,7 +191,11 @@ module Csvlint
|
|
184
191
|
when "‰"
|
185
192
|
return value.to_f / 1000
|
186
193
|
else
|
187
|
-
|
194
|
+
if @integer.nil?
|
195
|
+
return value.include?(".") ? value.to_f : value.to_i
|
196
|
+
else
|
197
|
+
return @integer ? value.to_i : value.to_f
|
198
|
+
end
|
188
199
|
end
|
189
200
|
end
|
190
201
|
else
|
@@ -193,9 +204,13 @@ module Csvlint
|
|
193
204
|
else
|
194
205
|
match = @regexp.match(value)
|
195
206
|
return nil if match.nil?
|
196
|
-
number = match["numeric_part"]
|
197
|
-
|
198
|
-
|
207
|
+
number = match["numeric_part"]
|
208
|
+
number.gsub!(@grouping_separator, "") unless @grouping_separator.nil?
|
209
|
+
number.gsub!(@decimal_separator, ".") unless @decimal_separator.nil?
|
210
|
+
number = @integer ? number.to_i : number.to_f
|
211
|
+
number = number.to_f / 100 if match["prefix"].include?("%") || match["suffix"].include?("%")
|
212
|
+
number = number.to_f / 1000 if match["prefix"].include?("‰") || match["suffix"].include?("‰")
|
213
|
+
return number
|
199
214
|
end
|
200
215
|
end
|
201
216
|
|
@@ -58,15 +58,29 @@ module Csvlint
|
|
58
58
|
else
|
59
59
|
if p[0] == "@"
|
60
60
|
raise Csvlint::Csvw::MetadataError.new(), "common property has property other than @id, @type, @value or @language beginning with @ (#{p})"
|
61
|
+
else
|
62
|
+
v, w = check_common_property_value(v, base_url, lang)
|
63
|
+
warnings += Array(w)
|
61
64
|
end
|
62
65
|
end
|
63
|
-
if v.instance_of? Hash
|
64
|
-
v, w = check_common_property_value(v, base_url, lang)
|
65
|
-
warnings += Array(w)
|
66
|
-
end
|
67
66
|
value[p] = v
|
68
67
|
end
|
69
68
|
return value, warnings
|
69
|
+
when String
|
70
|
+
if lang == "und"
|
71
|
+
return value, nil
|
72
|
+
else
|
73
|
+
return { "@value" => value, "@language" => lang }, nil
|
74
|
+
end
|
75
|
+
when Array
|
76
|
+
values = []
|
77
|
+
warnings = []
|
78
|
+
value.each do |v|
|
79
|
+
v, w = check_common_property_value(v, base_url, lang)
|
80
|
+
warnings += Array(w)
|
81
|
+
values << v
|
82
|
+
end
|
83
|
+
return values, warnings
|
70
84
|
else
|
71
85
|
return value, nil
|
72
86
|
end
|
@@ -114,6 +128,13 @@ module Csvlint
|
|
114
128
|
}
|
115
129
|
end
|
116
130
|
|
131
|
+
def uri_template_property(type)
|
132
|
+
return lambda { |value, base_url, lang|
|
133
|
+
return URITemplate.new(value), nil, type if value.instance_of? String
|
134
|
+
return URITemplate.new(""), :invalid_value, type
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
117
138
|
def numeric_property(type)
|
118
139
|
return lambda { |value, base_url, lang|
|
119
140
|
return value, nil, type if value.kind_of?(Integer) && value >= 0
|
@@ -192,8 +213,43 @@ module Csvlint
|
|
192
213
|
"@base" => link_property(:context),
|
193
214
|
# common properties
|
194
215
|
"@id" => link_property(:common),
|
195
|
-
"notes" =>
|
216
|
+
"notes" => lambda { |value, base_url, lang|
|
217
|
+
return false, :invalid_value, :common unless value.instance_of? Array
|
218
|
+
values = []
|
219
|
+
warnings = []
|
220
|
+
value.each do |v|
|
221
|
+
v, w = check_common_property_value(v, base_url, lang)
|
222
|
+
values << v
|
223
|
+
warnings += w
|
224
|
+
end
|
225
|
+
return values, warnings, :common
|
226
|
+
},
|
196
227
|
"suppressOutput" => boolean_property(:common),
|
228
|
+
"dialect" => lambda { |value, base_url, lang|
|
229
|
+
if value.instance_of? Hash
|
230
|
+
value = value.clone
|
231
|
+
warnings = []
|
232
|
+
value.each do |p,v|
|
233
|
+
if p == "@id"
|
234
|
+
raise Csvlint::Csvw::MetadataError.new("dialect.@id"), "@id starts with _:" if v =~ /^_:/
|
235
|
+
elsif p == "@type"
|
236
|
+
raise Csvlint::Csvw::MetadataError.new("dialect.@type"), "@type of dialect is not 'Dialect'" if v != 'Dialect'
|
237
|
+
else
|
238
|
+
v, warning, type = check_property(p, v, base_url, lang)
|
239
|
+
if type == :dialect && (warning.nil? || warning.empty?)
|
240
|
+
value[p] = v
|
241
|
+
else
|
242
|
+
value.delete(p)
|
243
|
+
warnings << :invalid_property unless type == :dialect
|
244
|
+
warnings += Array(warning)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
return value, warnings, :common
|
249
|
+
else
|
250
|
+
return {}, :invalid_value, :common
|
251
|
+
end
|
252
|
+
},
|
197
253
|
# inherited properties
|
198
254
|
"null" => lambda { |value, base_url, lang|
|
199
255
|
case value
|
@@ -270,12 +326,17 @@ module Csvlint
|
|
270
326
|
warnings += convert_value_facet(value, "maxInclusive", value["base"])
|
271
327
|
warnings += convert_value_facet(value, "maxExclusive", value["base"])
|
272
328
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
raise Csvlint::Csvw::MetadataError.new(""), "datatype
|
329
|
+
minInclusive = value["minInclusive"].is_a?(Hash) ? value["minInclusive"][:dateTime] : value["minInclusive"]
|
330
|
+
maxInclusive = value["maxInclusive"].is_a?(Hash) ? value["maxInclusive"][:dateTime] : value["maxInclusive"]
|
331
|
+
minExclusive = value["minExclusive"].is_a?(Hash) ? value["minExclusive"][:dateTime] : value["minExclusive"]
|
332
|
+
maxExclusive = value["maxExclusive"].is_a?(Hash) ? value["maxExclusive"][:dateTime] : value["maxExclusive"]
|
333
|
+
|
334
|
+
raise Csvlint::Csvw::MetadataError.new(""), "datatype cannot specify both minimum/minInclusive (#{minInclusive}) and minExclusive (#{minExclusive}" if minInclusive && minExclusive
|
335
|
+
raise Csvlint::Csvw::MetadataError.new(""), "datatype cannot specify both maximum/maxInclusive (#{maxInclusive}) and maxExclusive (#{maxExclusive}" if maxInclusive && maxExclusive
|
336
|
+
raise Csvlint::Csvw::MetadataError.new(""), "datatype minInclusive (#{minInclusive}) cannot be more than maxInclusive (#{maxInclusive}" if minInclusive && maxInclusive && minInclusive > maxInclusive
|
337
|
+
raise Csvlint::Csvw::MetadataError.new(""), "datatype minInclusive (#{minInclusive}) cannot be more than or equal to maxExclusive (#{maxExclusive}" if minInclusive && maxExclusive && minInclusive >= maxExclusive
|
338
|
+
raise Csvlint::Csvw::MetadataError.new(""), "datatype minExclusive (#{minExclusive}) cannot be more than or equal to maxExclusive (#{maxExclusive}" if minExclusive && maxExclusive && minExclusive > maxExclusive
|
339
|
+
raise Csvlint::Csvw::MetadataError.new(""), "datatype minExclusive (#{minExclusive}) cannot be more than maxInclusive (#{maxInclusive}" if minExclusive && maxInclusive && minExclusive >= maxInclusive
|
279
340
|
|
280
341
|
raise Csvlint::Csvw::MetadataError.new(""), "datatype length (#{value["length"]}) cannot be less than minLength (#{value["minLength"]}" if value["length"] && value["minLength"] && value["length"] < value["minLength"]
|
281
342
|
raise Csvlint::Csvw::MetadataError.new(""), "datatype length (#{value["length"]}) cannot be more than maxLength (#{value["maxLength"]}" if value["length"] && value["maxLength"] && value["length"] > value["maxLength"]
|
@@ -292,9 +353,9 @@ module Csvlint
|
|
292
353
|
elsif NUMERIC_FORMAT_DATATYPES.include?(value["base"])
|
293
354
|
value["format"] = { "pattern" => value["format"] } if value["format"].instance_of? String
|
294
355
|
begin
|
295
|
-
value["format"] = Csvlint::Csvw::NumberFormat.new(value["format"]["pattern"], value["format"]["groupChar"], value["format"]["decimalChar"] || ".")
|
356
|
+
value["format"] = Csvlint::Csvw::NumberFormat.new(value["format"]["pattern"], value["format"]["groupChar"], value["format"]["decimalChar"] || ".", INTEGER_FORMAT_DATATYPES.include?(value["base"]))
|
296
357
|
rescue Csvlint::Csvw::NumberFormatError
|
297
|
-
value["format"] = Csvlint::Csvw::NumberFormat.new(nil, value["format"]["groupChar"], value["format"]["decimalChar"] || ".")
|
358
|
+
value["format"] = Csvlint::Csvw::NumberFormat.new(nil, value["format"]["groupChar"], value["format"]["decimalChar"] || ".", INTEGER_FORMAT_DATATYPES.include?(value["base"]))
|
298
359
|
warnings << :invalid_number_format
|
299
360
|
end
|
300
361
|
elsif value["base"] == "http://www.w3.org/2001/XMLSchema#boolean"
|
@@ -326,9 +387,9 @@ module Csvlint
|
|
326
387
|
},
|
327
388
|
"required" => boolean_property(:inherited),
|
328
389
|
"ordered" => boolean_property(:inherited),
|
329
|
-
"aboutUrl" =>
|
330
|
-
"propertyUrl" =>
|
331
|
-
"valueUrl" =>
|
390
|
+
"aboutUrl" => uri_template_property(:inherited),
|
391
|
+
"propertyUrl" => uri_template_property(:inherited),
|
392
|
+
"valueUrl" => uri_template_property(:inherited),
|
332
393
|
"textDirection" => lambda { |value, base_url, lang|
|
333
394
|
value = value.to_sym
|
334
395
|
return value, nil, :inherited if [:ltr, :rtl, :auto, :inherit].include? value
|
@@ -421,31 +482,6 @@ module Csvlint
|
|
421
482
|
return schema, warnings, :table
|
422
483
|
},
|
423
484
|
"url" => link_property(:table),
|
424
|
-
"dialect" => lambda { |value, base_url, lang|
|
425
|
-
if value.instance_of? Hash
|
426
|
-
value = value.clone
|
427
|
-
warnings = []
|
428
|
-
value.each do |p,v|
|
429
|
-
if p == "@id"
|
430
|
-
raise Csvlint::Csvw::MetadataError.new("dialect.@id"), "@id starts with _:" if v =~ /^_:/
|
431
|
-
elsif p == "@type"
|
432
|
-
raise Csvlint::Csvw::MetadataError.new("dialect.@type"), "@type of dialect is not 'Dialect'" if v != 'Dialect'
|
433
|
-
else
|
434
|
-
v, warning, type = check_property(p, v, base_url, lang)
|
435
|
-
if type == :dialect && (warning.nil? || warning.empty?)
|
436
|
-
value[p] = v
|
437
|
-
else
|
438
|
-
value.delete(p)
|
439
|
-
warnings << :invalid_property unless type == :dialect
|
440
|
-
warnings += Array(warning)
|
441
|
-
end
|
442
|
-
end
|
443
|
-
end
|
444
|
-
return value, warnings, :table
|
445
|
-
else
|
446
|
-
return {}, :invalid_value, :table
|
447
|
-
end
|
448
|
-
},
|
449
485
|
# dialect properties
|
450
486
|
"commentPrefix" => string_property(:dialect),
|
451
487
|
"delimiter" => string_property(:dialect),
|
@@ -566,6 +602,12 @@ module Csvlint
|
|
566
602
|
"xhv" => "http://www.w3.org/1999/xhtml/vocab#",
|
567
603
|
"xml" => "http://www.w3.org/XML/1998/namespace",
|
568
604
|
"xsd" => "http://www.w3.org/2001/XMLSchema#",
|
605
|
+
"csvw" => "http://www.w3.org/ns/csvw#",
|
606
|
+
"cnt" => "http://www.w3.org/2008/content",
|
607
|
+
"earl" => "http://www.w3.org/ns/earl#",
|
608
|
+
"ht" => "http://www.w3.org/2006/http#",
|
609
|
+
"oa" => "http://www.w3.org/ns/oa#",
|
610
|
+
"ptr" => "http://www.w3.org/2009/pointers#",
|
569
611
|
"cc" => "http://creativecommons.org/ns#",
|
570
612
|
"ctag" => "http://commontag.org/ns#",
|
571
613
|
"dc" => "http://purl.org/dc/terms/",
|
@@ -638,8 +680,7 @@ module Csvlint
|
|
638
680
|
"http://www.w3.org/2001/XMLSchema#hexBinary"
|
639
681
|
]
|
640
682
|
|
641
|
-
|
642
|
-
"http://www.w3.org/2001/XMLSchema#decimal",
|
683
|
+
INTEGER_FORMAT_DATATYPES = [
|
643
684
|
"http://www.w3.org/2001/XMLSchema#integer",
|
644
685
|
"http://www.w3.org/2001/XMLSchema#long",
|
645
686
|
"http://www.w3.org/2001/XMLSchema#int",
|
@@ -652,10 +693,14 @@ module Csvlint
|
|
652
693
|
"http://www.w3.org/2001/XMLSchema#unsignedShort",
|
653
694
|
"http://www.w3.org/2001/XMLSchema#unsignedByte",
|
654
695
|
"http://www.w3.org/2001/XMLSchema#nonPositiveInteger",
|
655
|
-
"http://www.w3.org/2001/XMLSchema#negativeInteger"
|
696
|
+
"http://www.w3.org/2001/XMLSchema#negativeInteger"
|
697
|
+
]
|
698
|
+
|
699
|
+
NUMERIC_FORMAT_DATATYPES = [
|
700
|
+
"http://www.w3.org/2001/XMLSchema#decimal",
|
656
701
|
"http://www.w3.org/2001/XMLSchema#double",
|
657
702
|
"http://www.w3.org/2001/XMLSchema#float"
|
658
|
-
]
|
703
|
+
] + INTEGER_FORMAT_DATATYPES
|
659
704
|
|
660
705
|
DATE_FORMAT_DATATYPES = [
|
661
706
|
"http://www.w3.org/2001/XMLSchema#date",
|
data/lib/csvlint/csvw/table.rb
CHANGED
@@ -4,9 +4,9 @@ module Csvlint
|
|
4
4
|
|
5
5
|
include Csvlint::ErrorCollector
|
6
6
|
|
7
|
-
attr_reader :columns, :dialect, :table_direction, :foreign_keys, :foreign_key_references, :id, :notes, :primary_key, :schema, :suppress_output, :transformations, :url, :annotations
|
7
|
+
attr_reader :columns, :dialect, :table_direction, :foreign_keys, :foreign_key_references, :id, :notes, :primary_key, :row_title_columns, :schema, :suppress_output, :transformations, :url, :annotations
|
8
8
|
|
9
|
-
def initialize(url, columns: [], dialect: {}, table_direction: :auto, foreign_keys: [], id: nil, notes: [], primary_key: nil, schema: nil, suppress_output: false, transformations: [], annotations: [], warnings: [])
|
9
|
+
def initialize(url, columns: [], dialect: {}, table_direction: :auto, foreign_keys: [], id: nil, notes: [], primary_key: nil, row_title_columns: [], schema: nil, suppress_output: false, transformations: [], annotations: [], warnings: [])
|
10
10
|
@url = url
|
11
11
|
@columns = columns
|
12
12
|
@dialect = dialect
|
@@ -19,6 +19,7 @@ module Csvlint
|
|
19
19
|
@notes = notes
|
20
20
|
@primary_key = primary_key
|
21
21
|
@primary_key_values = {}
|
22
|
+
@row_title_columns = row_title_columns
|
22
23
|
@schema = schema
|
23
24
|
@suppress_output = suppress_output
|
24
25
|
@transformations = transformations
|
@@ -29,54 +30,59 @@ module Csvlint
|
|
29
30
|
@warnings += columns.map{|c| c.warnings}.flatten
|
30
31
|
end
|
31
32
|
|
32
|
-
def validate_header(headers)
|
33
|
+
def validate_header(headers, strict)
|
33
34
|
reset
|
34
35
|
headers.each_with_index do |header,i|
|
35
36
|
if columns[i]
|
36
|
-
columns[i].validate_header(header)
|
37
|
+
columns[i].validate_header(header, strict)
|
37
38
|
@errors += columns[i].errors
|
38
39
|
@warnings += columns[i].warnings
|
39
|
-
|
40
|
+
elsif strict
|
40
41
|
build_errors(:malformed_header, :schema, 1, nil, header, nil)
|
42
|
+
else
|
43
|
+
build_warnings(:malformed_header, :schema, 1, nil, header, nil)
|
41
44
|
end
|
42
|
-
end unless columns.empty?
|
45
|
+
end # unless columns.empty?
|
43
46
|
return valid?
|
44
47
|
end
|
45
48
|
|
46
|
-
def validate_row(values, row=nil)
|
49
|
+
def validate_row(values, row=nil, validate=false)
|
47
50
|
reset
|
48
51
|
values.each_with_index do |value,i|
|
49
52
|
column = columns[i]
|
50
53
|
if column
|
51
|
-
column.validate(value, row)
|
54
|
+
v = column.validate(value, row)
|
55
|
+
values[i] = v
|
52
56
|
@errors += column.errors
|
53
57
|
@warnings += column.warnings
|
54
58
|
else
|
55
59
|
build_errors(:too_many_values, :schema, row, nil, value, nil)
|
56
60
|
end
|
57
61
|
end unless columns.empty?
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
62
|
+
if validate
|
63
|
+
unless @primary_key.nil?
|
64
|
+
key = @primary_key.map { |column| column.validate(values[column.number - 1], row) }
|
65
|
+
build_errors(:duplicate_key, :schema, row, nil, key.join(","), @primary_key_values[key]) if @primary_key_values.include?(key)
|
66
|
+
@primary_key_values[key] = row
|
67
|
+
end
|
68
|
+
# build a record of the unique values that are referenced by foreign keys from other tables
|
69
|
+
# so that later we can check whether those foreign keys reference these values
|
70
|
+
@foreign_key_references.each do |foreign_key|
|
71
|
+
referenced_columns = foreign_key["referenced_columns"]
|
72
|
+
key = referenced_columns.map{ |column| column.validate(values[column.number - 1], row) }
|
73
|
+
known_values = @foreign_key_reference_values[foreign_key] = @foreign_key_reference_values[foreign_key] || {}
|
74
|
+
known_values[key] = known_values[key] || []
|
75
|
+
known_values[key] << row
|
76
|
+
end
|
77
|
+
# build a record of the references from this row to other tables
|
78
|
+
# we can't check yet whether these exist in the other tables because
|
79
|
+
# we might not have parsed those other tables
|
80
|
+
@foreign_keys.each do |foreign_key|
|
81
|
+
referencing_columns = foreign_key["referencing_columns"]
|
82
|
+
key = referencing_columns.map{ |column| column.validate(values[column.number - 1], row) }
|
83
|
+
known_values = @foreign_key_values[foreign_key] = @foreign_key_values[foreign_key] || []
|
84
|
+
known_values << key unless known_values.include?(key)
|
85
|
+
end
|
80
86
|
end
|
81
87
|
return valid?
|
82
88
|
end
|
@@ -107,19 +113,16 @@ module Csvlint
|
|
107
113
|
return valid?
|
108
114
|
end
|
109
115
|
|
110
|
-
def self.from_json(table_desc, base_url=nil, lang="und", inherited_properties={})
|
116
|
+
def self.from_json(table_desc, base_url=nil, lang="und", common_properties={}, inherited_properties={})
|
111
117
|
annotations = {}
|
112
118
|
warnings = []
|
113
|
-
table_properties = {}
|
114
119
|
columns = []
|
115
|
-
|
120
|
+
table_properties = common_properties.clone
|
116
121
|
inherited_properties = inherited_properties.clone
|
117
122
|
|
118
123
|
table_desc.each do |property,value|
|
119
124
|
if property =="@type"
|
120
125
|
raise Csvlint::Csvw::MetadataError.new("$.tables[?(@.url = '#{table_desc["url"]}')].@type"), "@type of table is not 'Table'" unless value == 'Table'
|
121
|
-
elsif property == "notes"
|
122
|
-
notes = value
|
123
126
|
else
|
124
127
|
v, warning, type = Csvw::PropertyChecker.check_property(property, value, base_url, lang)
|
125
128
|
warnings += Array(warning).map{ |w| Csvlint::ErrorMessage.new(w, :metadata, nil, nil, "#{property}: #{value}", nil) } unless warning.nil? || warning.empty?
|
@@ -140,7 +143,17 @@ module Csvlint
|
|
140
143
|
foreign_keys = []
|
141
144
|
primary_key = nil
|
142
145
|
if table_schema
|
143
|
-
|
146
|
+
unless table_schema["columns"].instance_of? Array
|
147
|
+
table_schema["columns"] = []
|
148
|
+
warnings << Csvlint::ErrorMessage.new(:invalid_value, :metadata, nil, nil, "columns", nil)
|
149
|
+
end
|
150
|
+
|
151
|
+
table_schema.each do |p,v|
|
152
|
+
unless ["columns", "primaryKey", "foreignKeys", "rowTitles"].include? p
|
153
|
+
inherited_properties[p] = v
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
144
157
|
virtual_columns = false
|
145
158
|
table_schema["columns"].each_with_index do |column_desc,i|
|
146
159
|
if column_desc.instance_of? Hash
|
@@ -180,8 +193,11 @@ module Csvlint
|
|
180
193
|
end if foreign_keys
|
181
194
|
|
182
195
|
row_titles = table_schema["rowTitles"]
|
183
|
-
|
184
|
-
|
196
|
+
row_title_columns = []
|
197
|
+
row_titles.each_with_index do |row_title|
|
198
|
+
i = column_names.index(row_title)
|
199
|
+
raise Csvlint::Csvw::MetadataError.new("$.tables[?(@.url = '#{table_desc["url"]}')].tableSchema.rowTitles[#{i}]"), "rowTitles references non-existant column" unless i
|
200
|
+
row_title_columns << columns[i]
|
185
201
|
end if row_titles
|
186
202
|
|
187
203
|
end
|
@@ -191,9 +207,11 @@ module Csvlint
|
|
191
207
|
columns: columns,
|
192
208
|
dialect: table_properties["dialect"],
|
193
209
|
foreign_keys: foreign_keys || [],
|
194
|
-
notes: notes,
|
210
|
+
notes: table_properties["notes"] || [],
|
195
211
|
primary_key: primary_key_valid && !primary_key_columns.empty? ? primary_key_columns : nil,
|
212
|
+
row_title_columns: row_title_columns,
|
196
213
|
schema: table_schema ? table_schema["@id"] : nil,
|
214
|
+
suppress_output: table_properties["suppressOutput"] ? table_properties["suppressOutput"] : false,
|
197
215
|
annotations: annotations,
|
198
216
|
warnings: warnings
|
199
217
|
)
|