csvlint 0.2.6 → 0.3.0

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.
@@ -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
  )