csvlint 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- return Date.new(match["year"].to_i, match["month"].to_i, match["day"].to_i)
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
- return 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"] : '')
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
- match.names.each do |field|
89
- unless match[field].nil?
90
- case field
91
- when "timezone"
92
- tz = match["timezone"]
93
- tz = "+00:00" if tz == 'Z'
94
- tz += ':00' if tz.length == 3
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
- value[field] = match[field].to_i
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
- return value
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
- @regexp = Regexp.new("^(([-+]?[0-9]+(#{Regexp.escape(@decimal_separator)}[0-9]+)?([Ee][-+]?[0-9]+)?[%‰]?)|NaN|INF|-INF)$")
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
- @integer_pattern = exponent_part == "" && fractional_part == ""
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
- if value =~ INTEGER_REGEXP
164
- case value[-1]
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
- return value.to_f
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"].gsub(@grouping_separator, "")
197
- return number.to_i if @integer_pattern
198
- return number.to_f
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" => array_property(:common),
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
- raise Csvlint::Csvw::MetadataError.new(""), "datatype cannot specify both minimum/minInclusive (#{value["minInclusive"]}) and minExclusive (#{value["minExclusive"]}" if value["minInclusive"] && value["minExclusive"]
274
- raise Csvlint::Csvw::MetadataError.new(""), "datatype cannot specify both maximum/maxInclusive (#{value["maxInclusive"]}) and maxExclusive (#{value["maxExclusive"]}" if value["maxInclusive"] && value["maxExclusive"]
275
- raise Csvlint::Csvw::MetadataError.new(""), "datatype minInclusive (#{value["minInclusive"]}) cannot be more than maxInclusive (#{value["maxInclusive"]}" if value["minInclusive"] && value["maxInclusive"] && value["minInclusive"] > value["maxInclusive"]
276
- raise Csvlint::Csvw::MetadataError.new(""), "datatype minInclusive (#{value["minInclusive"]}) cannot be more than or equal to maxExclusive (#{value["maxExclusive"]}" if value["minInclusive"] && value["maxExclusive"] && value["minInclusive"] >= value["maxExclusive"]
277
- raise Csvlint::Csvw::MetadataError.new(""), "datatype minExclusive (#{value["minExclusive"]}) cannot be more than or equal to maxExclusive (#{value["maxExclusive"]}" if value["minExclusive"] && value["maxExclusive"] && value["minExclusive"] > value["maxExclusive"]
278
- raise Csvlint::Csvw::MetadataError.new(""), "datatype minExclusive (#{value["minExclusive"]}) cannot be more than maxInclusive (#{value["maxInclusive"]}" if value["minExclusive"] && value["maxInclusive"] && value["minExclusive"] >= value["maxInclusive"]
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" => string_property(:inherited),
330
- "propertyUrl" => string_property(:inherited),
331
- "valueUrl" => string_property(:inherited),
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
- NUMERIC_FORMAT_DATATYPES = [
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",
@@ -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
- else
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
- unless @primary_key.nil?
59
- key = @primary_key.map { |column| column.parse(values[column.number - 1], row) }
60
- build_errors(:duplicate_key, :schema, row, nil, key.join(","), @primary_key_values[key]) if @primary_key_values.include?(key)
61
- @primary_key_values[key] = row
62
- end
63
- # build a record of the unique values that are referenced by foreign keys from other tables
64
- # so that later we can check whether those foreign keys reference these values
65
- @foreign_key_references.each do |foreign_key|
66
- referenced_columns = foreign_key["referenced_columns"]
67
- key = referenced_columns.map{ |column| column.parse(values[column.number - 1], row) }
68
- known_values = @foreign_key_reference_values[foreign_key] = @foreign_key_reference_values[foreign_key] || {}
69
- known_values[key] = known_values[key] || []
70
- known_values[key] << row
71
- end
72
- # build a record of the references from this row to other tables
73
- # we can't check yet whether these exist in the other tables because
74
- # we might not have parsed those other tables
75
- @foreign_keys.each do |foreign_key|
76
- referencing_columns = foreign_key["referencing_columns"]
77
- key = referencing_columns.map{ |column| column.parse(values[column.number - 1], row) }
78
- known_values = @foreign_key_values[foreign_key] = @foreign_key_values[foreign_key] || []
79
- known_values << key unless known_values.include?(key)
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
- notes = []
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
- raise Csvlint::Csvw::MetadataError.new("$.tables[?(@.url = '#{table_desc["url"]}')].tableSchema.columns"), "schema columns is not an array" unless table_schema["columns"].instance_of? Array
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
- row_titles.each_with_index do |row_title,i|
184
- raise Csvlint::Csvw::MetadataError.new("$.tables[?(@.url = '#{table_desc["url"]}')].tableSchema.rowTitles[#{i}]"), "rowTitles references non-existant column" unless column_names.include? row_title
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
  )