csvlint 0.1.4 → 0.2.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.
Files changed (38) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +7 -1
  3. data/CHANGELOG.md +19 -1
  4. data/README.md +93 -36
  5. data/bin/csvlint +68 -27
  6. data/csvlint.gemspec +2 -0
  7. data/features/csvw_schema_validation.feature +127 -0
  8. data/features/fixtures/spreadsheet.xlsx +0 -0
  9. data/features/sources.feature +3 -4
  10. data/features/step_definitions/parse_csv_steps.rb +13 -1
  11. data/features/step_definitions/schema_validation_steps.rb +27 -1
  12. data/features/step_definitions/sources_steps.rb +1 -1
  13. data/features/step_definitions/validation_errors_steps.rb +48 -1
  14. data/features/step_definitions/validation_info_steps.rb +5 -1
  15. data/features/step_definitions/validation_warnings_steps.rb +15 -1
  16. data/features/support/load_tests.rb +114 -0
  17. data/features/validation_errors.feature +12 -24
  18. data/features/validation_warnings.feature +18 -6
  19. data/lib/csvlint.rb +10 -0
  20. data/lib/csvlint/csvw/column.rb +359 -0
  21. data/lib/csvlint/csvw/date_format.rb +182 -0
  22. data/lib/csvlint/csvw/metadata_error.rb +13 -0
  23. data/lib/csvlint/csvw/number_format.rb +211 -0
  24. data/lib/csvlint/csvw/property_checker.rb +761 -0
  25. data/lib/csvlint/csvw/table.rb +204 -0
  26. data/lib/csvlint/csvw/table_group.rb +165 -0
  27. data/lib/csvlint/schema.rb +40 -23
  28. data/lib/csvlint/validate.rb +142 -19
  29. data/lib/csvlint/version.rb +1 -1
  30. data/spec/csvw/column_spec.rb +112 -0
  31. data/spec/csvw/date_format_spec.rb +49 -0
  32. data/spec/csvw/number_format_spec.rb +403 -0
  33. data/spec/csvw/table_group_spec.rb +143 -0
  34. data/spec/csvw/table_spec.rb +90 -0
  35. data/spec/schema_spec.rb +27 -1
  36. data/spec/spec_helper.rb +0 -1
  37. data/spec/validator_spec.rb +16 -10
  38. metadata +53 -2
@@ -4,7 +4,7 @@ module Csvlint
4
4
 
5
5
  include Csvlint::ErrorCollector
6
6
 
7
- attr_reader :encoding, :content_type, :extension, :headers, :line_breaks, :dialect, :csv_header, :schema, :data
7
+ attr_reader :encoding, :content_type, :extension, :headers, :link_headers, :line_breaks, :dialect, :csv_header, :schema, :data
8
8
 
9
9
  ERROR_MATCHERS = {
10
10
  "Missing or stray quote" => :stray_quote,
@@ -14,36 +14,33 @@ module Csvlint
14
14
  }
15
15
 
16
16
  def initialize(source, dialect = nil, schema = nil, options = {})
17
-
17
+ reset
18
18
  @source = source
19
19
  @formats = []
20
20
  @schema = schema
21
21
 
22
22
  @supplied_dialect = dialect != nil
23
23
 
24
- @dialect = {
25
- "header" => true,
26
- "delimiter" => ",",
27
- "skipInitialSpace" => true,
28
- "lineTerminator" => :auto,
29
- "quoteChar" => '"'
30
- }.merge(dialect || {})
31
-
32
- @csv_header = @dialect["header"]
33
24
  @limit_lines = options[:limit_lines]
34
- @csv_options = dialect_to_csv_options(@dialect)
35
25
  @extension = parse_extension(source) unless @source.nil?
36
- reset
37
- validate
26
+ @errors += @schema.errors unless @schema.nil?
27
+ @warnings += @schema.warnings unless @schema.nil?
28
+ validate(dialect)
38
29
 
39
30
  end
40
31
 
41
- def validate
32
+ def validate(dialect = nil)
42
33
  single_col = false
43
34
  io = nil
44
35
  begin
36
+ if @extension =~ /.xls(x)?/
37
+ build_warnings(:excel, :context)
38
+ return
39
+ end
45
40
  io = @source.respond_to?(:gets) ? @source : open(@source, :allow_redirections=>:all)
46
41
  validate_metadata(io)
42
+ locate_schema unless @schema.instance_of?(Csvlint::Schema)
43
+ set_dialect(dialect)
47
44
  parse_csv(io)
48
45
  sum = @col_counts.inject(:+)
49
46
  unless sum.nil?
@@ -51,17 +48,20 @@ module Csvlint
51
48
  end
52
49
  build_warnings(:check_options, :structure) if @expected_columns == 1
53
50
  check_consistency
51
+ check_foreign_keys
54
52
  rescue OpenURI::HTTPError, Errno::ENOENT
55
- build_errors(:not_found)
53
+ build_errors(:not_found, nil, nil, nil, @source)
56
54
  ensure
57
55
  io.close if io && io.respond_to?(:close)
58
56
  end
59
57
  end
60
58
 
61
59
  def validate_metadata(io)
60
+ @csv_header = true
62
61
  @encoding = io.charset rescue nil
63
62
  @content_type = io.content_type rescue nil
64
63
  @headers = io.meta rescue nil
64
+ @link_headers = @headers["link"].split(",") rescue nil
65
65
  assumed_header = undeclared_header = !@supplied_dialect
66
66
  if @headers
67
67
  if @headers["content-type"] =~ /text\/csv/
@@ -81,7 +81,6 @@ module Csvlint
81
81
  build_warnings(:encoding, :context) if @encoding != "utf-8"
82
82
  end
83
83
  build_warnings(:no_content_type, :context) if @content_type == nil
84
- build_warnings(:excel, :context) if @content_type == nil && @extension =~ /.xls(x)?/
85
84
  build_errors(:wrong_content_type, :context) unless (@content_type && @content_type =~ /text\/csv/)
86
85
 
87
86
  if undeclared_header
@@ -93,6 +92,25 @@ module Csvlint
93
92
  build_info_messages(:assumed_header, :structure) if assumed_header
94
93
  end
95
94
 
95
+ def set_dialect(dialect)
96
+ begin
97
+ schema_dialect = @schema.tables[@source_url].dialect || {}
98
+ rescue
99
+ schema_dialect = {}
100
+ end
101
+ @dialect = {
102
+ "header" => true,
103
+ "delimiter" => ",",
104
+ "skipInitialSpace" => true,
105
+ "lineTerminator" => :auto,
106
+ "quoteChar" => '"',
107
+ "trim" => :true
108
+ }.merge(schema_dialect).merge(dialect || {})
109
+
110
+ @csv_header = @csv_header && @dialect["header"]
111
+ @csv_options = dialect_to_csv_options(@dialect)
112
+ end
113
+
96
114
  # analyses the provided csv and builds errors, warnings and info messages
97
115
  def parse_csv(io)
98
116
  @expected_columns = 0
@@ -134,7 +152,7 @@ module Csvlint
134
152
  build_errors(:blank_rows, :structure, current_line, nil, wrapper.line) if row.reject{ |c| c.nil? || c.empty? }.size == 0
135
153
  # Builds errors and warnings related to the provided schema file
136
154
  if @schema
137
- @schema.validate_row(row, current_line, all_errors)
155
+ @schema.validate_row(row, current_line, all_errors, @source)
138
156
  @errors += @schema.errors
139
157
  all_errors += @schema.errors
140
158
  @warnings += @schema.warnings
@@ -163,6 +181,7 @@ module Csvlint
163
181
 
164
182
  def validate_header(header)
165
183
  names = Set.new
184
+ header.map{|h| h.strip! } if @dialect["trim"] == :true
166
185
  header.each_with_index do |name,i|
167
186
  build_warnings(:empty_column_name, :schema, nil, i+1) if name == ""
168
187
  if names.include?(name)
@@ -172,7 +191,7 @@ module Csvlint
172
191
  end
173
192
  end
174
193
  if @schema
175
- @schema.validate_header(header)
194
+ @schema.validate_header(header, @source)
176
195
  @errors += @schema.errors
177
196
  @warnings += @schema.warnings
178
197
  end
@@ -249,6 +268,96 @@ module Csvlint
249
268
  end
250
269
  end
251
270
 
271
+ def check_foreign_keys
272
+ if @schema.instance_of? Csvlint::Csvw::TableGroup
273
+ @schema.validate_foreign_keys
274
+ @errors += @schema.errors
275
+ @warnings += @schema.warnings
276
+ end
277
+ end
278
+
279
+ def locate_schema
280
+ @source_url = nil
281
+ warn_if_unsuccessful = false
282
+ case @source
283
+ when StringIO
284
+ return
285
+ when File
286
+ @source_url = "file:#{File.expand_path(@source)}"
287
+ else
288
+ @source_url = @source
289
+ end
290
+ unless @schema.nil?
291
+ if @schema.tables[@source_url]
292
+ return
293
+ else
294
+ @schema = nil
295
+ end
296
+ end
297
+ link_schema = nil
298
+ @link_headers.each do |link_header|
299
+ match = LINK_HEADER_REGEXP.match(link_header)
300
+ uri = match["uri"].gsub(/(^\<|\>$)/, "") rescue nil
301
+ rel = match["rel-relationship"].gsub(/(^\"|\"$)/, "") rescue nil
302
+ param = match["param"]
303
+ param_value = match["param-value"].gsub(/(^\"|\"$)/, "") rescue nil
304
+ if rel == "describedby" && param == "type" && ["application/csvm+json", "application/ld+json", "application/json"].include?(param_value)
305
+ begin
306
+ url = URI.join(@source_url, uri)
307
+ schema = Schema.load_from_json(url)
308
+ if schema.instance_of? Csvlint::Csvw::TableGroup
309
+ if schema.tables[@source_url]
310
+ link_schema = schema
311
+ else
312
+ warn_if_unsuccessful = true
313
+ build_warnings(:schema_mismatch, :context, nil, nil, @source_url, schema)
314
+ end
315
+ end
316
+ rescue OpenURI::HTTPError
317
+ end
318
+ end
319
+ end if @link_headers
320
+ @schema = link_schema if link_schema
321
+
322
+ paths = []
323
+ if @source_url =~ /^http(s)?/
324
+ begin
325
+ well_known_uri = URI.join(@source_url, "/.well-known/csvm")
326
+ well_known = open(well_known_uri).read
327
+ # TODO
328
+ rescue OpenURI::HTTPError
329
+ end
330
+ end
331
+ paths = ["{+url}-metadata.json", "csv-metadata.json"] if paths.empty?
332
+ paths.each do |template|
333
+ begin
334
+ template = URITemplate.new(template)
335
+ path = template.expand('url' => @source_url)
336
+ url = URI.join(@source_url, path)
337
+ url = File.new(url.to_s.sub(/^file:/, "")) if url.to_s =~ /^file:/
338
+ schema = Schema.load_from_json(url)
339
+ if schema.instance_of? Csvlint::Csvw::TableGroup
340
+ if schema.tables[@source_url]
341
+ @schema = schema
342
+ else
343
+ warn_if_unsuccessful = true
344
+ build_warnings(:schema_mismatch, :context, nil, nil, @source_url, schema)
345
+ end
346
+ end
347
+ rescue Errno::ENOENT
348
+ rescue OpenURI::HTTPError
349
+ rescue ArgumentError
350
+ rescue => e
351
+ STDERR.puts e.class
352
+ STDERR.puts e.message
353
+ STDERR.puts e.backtrace
354
+ raise e
355
+ end
356
+ end
357
+ build_warnings(:schema_mismatch, :context, nil, nil, @source_url, schema) if warn_if_unsuccessful
358
+ @schema = nil
359
+ end
360
+
252
361
  private
253
362
 
254
363
  def parse_extension(source)
@@ -302,5 +411,19 @@ module Csvlint
302
411
  :dateTime_short => /\A\d\d (?:#{Date::ABBR_MONTHNAMES.join('|')}) \d\d:\d\d\z/, # "01 Jan 00:00"
303
412
  :dateTime_time => /\A\d\d:\d\d\z/, # "00:00"
304
413
  }.freeze
414
+
415
+ URI_REGEXP = /(?<uri>.*?)/
416
+ TOKEN_REGEXP = /([^\(\)\<\>@,;:\\"\/\[\]\?=\{\} \t]+)/
417
+ QUOTED_STRING_REGEXP = /("[^"]*")/
418
+ SGML_NAME_REGEXP = /([A-Za-z][-A-Za-z0-9\.]*)/
419
+ RELATIONSHIP_REGEXP = Regexp.new("(?<relationship>#{SGML_NAME_REGEXP}|(\"#{SGML_NAME_REGEXP}(\\s+#{SGML_NAME_REGEXP})*\"))")
420
+ REL_REGEXP = Regexp.new("(?<rel>\\s*rel\\s*=\\s*(?<rel-relationship>#{RELATIONSHIP_REGEXP}))")
421
+ REV_REGEXP = Regexp.new("(?<rev>\\s*rev\\s*=\\s*#{RELATIONSHIP_REGEXP})")
422
+ TITLE_REGEXP = Regexp.new("(?<title>\\s*title\\s*=\\s*#{QUOTED_STRING_REGEXP})")
423
+ ANCHOR_REGEXP = Regexp.new("(?<anchor>\\s*anchor\\s*=\\s*\\<#{URI_REGEXP}\\>)")
424
+ LINK_EXTENSION_REGEXP = Regexp.new("(?<link-extension>(?<param>#{TOKEN_REGEXP})(\\s*=\\s*(?<param-value>#{TOKEN_REGEXP}|#{QUOTED_STRING_REGEXP}))?)")
425
+ LINK_PARAM_REGEXP = Regexp.new("(#{REL_REGEXP}|#{REV_REGEXP}|#{TITLE_REGEXP}|#{ANCHOR_REGEXP}|#{LINK_EXTENSION_REGEXP})")
426
+ LINK_HEADER_REGEXP = Regexp.new("\<#{URI_REGEXP}\>(\\s*;\\s*#{LINK_PARAM_REGEXP})*")
427
+
305
428
  end
306
429
  end
@@ -1,3 +1,3 @@
1
1
  module Csvlint
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe Csvlint::Csvw::Column do
4
+
5
+ it "shouldn't generate errors for string values" do
6
+ column = Csvlint::Csvw::Column.new(1, "foo")
7
+ valid = column.validate("bar", 2)
8
+ expect(valid).to eq(true)
9
+ end
10
+
11
+ it "should generate errors for string values that aren't long enough" do
12
+ column = Csvlint::Csvw::Column.new(1, "foo", datatype: { "base" => "http://www.w3.org/2001/XMLSchema#string", "minLength" => 4 })
13
+ valid = column.validate("bar", 2)
14
+ expect(valid).to eq(false)
15
+ expect(column.errors.length).to eq(1)
16
+ end
17
+
18
+ it "shouldn't generate errors for string values that are long enough" do
19
+ column = Csvlint::Csvw::Column.new(1, "foo", datatype: { "base" => "http://www.w3.org/2001/XMLSchema#string", "minLength" => 4 })
20
+ valid = column.validate("barn", 2)
21
+ expect(valid).to eq(true)
22
+ expect(column.errors.length).to eq(0)
23
+ end
24
+
25
+ context "when parsing CSVW column descriptions" do
26
+ it "should provide appropriate default values" do
27
+ @desc=<<-EOL
28
+ {
29
+ "name": "countryCode"
30
+ }
31
+ EOL
32
+ json = JSON.parse( @desc )
33
+ column = Csvlint::Csvw::Column.from_json(1, json)
34
+
35
+ expect(column).to be_a(Csvlint::Csvw::Column)
36
+ expect(column.number).to eq(1)
37
+ expect(column.name).to eq("countryCode")
38
+ expect(column.about_url).to eq(nil)
39
+ expect(column.datatype).to eq({ "@id" => "http://www.w3.org/2001/XMLSchema#string" })
40
+ expect(column.default).to eq("")
41
+ expect(column.lang).to eq("und")
42
+ expect(column.null).to eq([""])
43
+ expect(column.ordered).to eq(false)
44
+ expect(column.property_url).to eq(nil)
45
+ expect(column.required).to eq(false)
46
+ expect(column.separator).to eq(nil)
47
+ expect(column.source_number).to eq(1)
48
+ expect(column.suppress_output).to eq(false)
49
+ expect(column.text_direction).to eq(:inherit)
50
+ expect(column.titles).to eq(nil)
51
+ expect(column.value_url).to eq(nil)
52
+ expect(column.virtual).to eq(false)
53
+ expect(column.annotations).to eql({})
54
+ end
55
+
56
+ it "should override default values" do
57
+ @desc=<<-EOL
58
+ {
59
+ "name": "countryCode",
60
+ "titles": "countryCode",
61
+ "propertyUrl": "http://www.geonames.org/ontology{#_name}"
62
+ }
63
+ EOL
64
+ json = JSON.parse( @desc )
65
+ column = Csvlint::Csvw::Column.from_json(2, json)
66
+
67
+ expect(column).to be_a(Csvlint::Csvw::Column)
68
+ expect(column.number).to eq(2)
69
+ expect(column.name).to eq("countryCode")
70
+ expect(column.about_url).to eq(nil)
71
+ expect(column.datatype).to eq({ "@id" => "http://www.w3.org/2001/XMLSchema#string" })
72
+ expect(column.default).to eq("")
73
+ expect(column.lang).to eq("und")
74
+ expect(column.null).to eq([""])
75
+ expect(column.ordered).to eq(false)
76
+ expect(column.property_url).to eq("http://www.geonames.org/ontology{#_name}")
77
+ expect(column.required).to eq(false)
78
+ expect(column.separator).to eq(nil)
79
+ expect(column.source_number).to eq(2)
80
+ expect(column.suppress_output).to eq(false)
81
+ expect(column.text_direction).to eq(:inherit)
82
+ expect(column.titles).to eql({ "und" => [ "countryCode" ]})
83
+ expect(column.value_url).to eq(nil)
84
+ expect(column.virtual).to eq(false)
85
+ expect(column.annotations).to eql({})
86
+ end
87
+
88
+ it "should include the datatype" do
89
+ @desc=<<-EOL
90
+ { "name": "Id", "required": true, "datatype": { "base": "string", "minLength": 3 } }
91
+ EOL
92
+ json = JSON.parse(@desc)
93
+ column = Csvlint::Csvw::Column.from_json(1, json)
94
+ expect(column.name).to eq("Id")
95
+ expect(column.required).to eq(true)
96
+ expect(column.datatype).to eql({ "base" => "http://www.w3.org/2001/XMLSchema#string", "minLength" => 3 })
97
+ end
98
+
99
+ it "should generate warnings for invalid null values" do
100
+ @desc=<<-EOL
101
+ {
102
+ "name": "countryCode",
103
+ "null": true
104
+ }
105
+ EOL
106
+ json = JSON.parse( @desc )
107
+ column = Csvlint::Csvw::Column.from_json(1, json)
108
+ expect(column.warnings.length).to eq(1)
109
+ expect(column.warnings[0].type).to eq(:invalid_value)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe Csvlint::Csvw::NumberFormat do
4
+
5
+ it "should parse dates that match yyyy-MM-dd correctly" do
6
+ format = Csvlint::Csvw::DateFormat.new("yyyy-MM-dd")
7
+ expect(format.parse("2015-03-22")).to eql(Date.new(2015, 3, 22))
8
+ expect(format.parse("2015-02-30")).to eql(nil)
9
+ expect(format.parse("22/03/2015")).to eq(nil)
10
+ end
11
+
12
+ it "should parse times that match HH:mm:ss correctly" do
13
+ format = Csvlint::Csvw::DateFormat.new("HH:mm:ss")
14
+ expect(format.parse("12:34:56")).to eql({ "hour" => 12, "minute" => 34, "second" => 56.0 })
15
+ expect(format.parse("22/03/2015")).to eq(nil)
16
+ end
17
+
18
+ it "should parse times that match HH:mm:ss.SSS correctly" do
19
+ format = Csvlint::Csvw::DateFormat.new("HH:mm:ss.SSS")
20
+ expect(format.parse("12:34:56")).to eql({ "hour" => 12, "minute" => 34, "second" => 56.0 })
21
+ expect(format.parse("12:34:56.78")).to eql({ "hour" => 12, "minute" => 34, "second" => 56.78 })
22
+ expect(format.parse("12:34:56.789")).to eql({ "hour" => 12, "minute" => 34, "second" => 56.789 })
23
+ expect(format.parse("12:34:56.7890")).to eql(nil)
24
+ expect(format.parse("22/03/2015")).to eq(nil)
25
+ end
26
+
27
+ it "should parse dateTimes that match yyyy-MM-ddTHH:mm:ss correctly" do
28
+ format = Csvlint::Csvw::DateFormat.new("yyyy-MM-ddTHH:mm:ss")
29
+ expect(format.parse("2015-03-15T15:02:37")).to eql(DateTime.new(2015, 3, 15, 15, 2, 37))
30
+ expect(format.parse("12:34:56")).to eql(nil)
31
+ expect(format.parse("22/03/2015")).to eq(nil)
32
+ end
33
+
34
+ it "should parse dateTimes that match yyyy-MM-ddTHH:mm:ss.S correctly" do
35
+ format = Csvlint::Csvw::DateFormat.new("yyyy-MM-ddTHH:mm:ss.S")
36
+ expect(format.parse("2015-03-15T15:02:37")).to eql(DateTime.new(2015, 3, 15, 15, 2, 37.0))
37
+ expect(format.parse("2015-03-15T15:02:37.4")).to eql(DateTime.new(2015, 3, 15, 15, 2, 37.4))
38
+ expect(format.parse("2015-03-15T15:02:37.45")).to eql(nil)
39
+ expect(format.parse("12:34:56")).to eql(nil)
40
+ expect(format.parse("22/03/2015")).to eq(nil)
41
+ end
42
+
43
+ it "should parse dateTimes that match M/d/yyyy HH:mm correctly" do
44
+ format = Csvlint::Csvw::DateFormat.new("M/d/yyyy HH:mm")
45
+ expect(format.parse("2015-03-15T15:02:37")).to eql(nil)
46
+ expect(format.parse("3/15/2015 15:02")).to eql(DateTime.new(2015, 3, 15, 15, 2))
47
+ end
48
+
49
+ end
@@ -0,0 +1,403 @@
1
+ require 'spec_helper'
2
+
3
+ describe Csvlint::Csvw::NumberFormat do
4
+
5
+ it "should correctly parse #,##0.##" do
6
+ format = Csvlint::Csvw::NumberFormat.new("#,##0.##")
7
+ expect(format.pattern).to eq("#,##0.##")
8
+ expect(format.prefix).to eq("")
9
+ expect(format.numeric_part).to eq("#,##0.##")
10
+ expect(format.suffix).to eq("")
11
+ expect(format.grouping_separator).to eq(",")
12
+ expect(format.decimal_separator).to eq(".")
13
+ expect(format.primary_grouping_size).to eq(3)
14
+ expect(format.secondary_grouping_size).to eq(3)
15
+ expect(format.fractional_grouping_size).to eq(0)
16
+ end
17
+
18
+ it "should correctly parse ###0.#####" do
19
+ format = Csvlint::Csvw::NumberFormat.new("###0.#####")
20
+ expect(format.primary_grouping_size).to eq(0)
21
+ expect(format.secondary_grouping_size).to eq(0)
22
+ expect(format.fractional_grouping_size).to eq(0)
23
+ end
24
+
25
+ it "should correctly parse ###0.0000#" do
26
+ format = Csvlint::Csvw::NumberFormat.new("###0.0000#")
27
+ expect(format.primary_grouping_size).to eq(0)
28
+ expect(format.secondary_grouping_size).to eq(0)
29
+ expect(format.fractional_grouping_size).to eq(0)
30
+ end
31
+
32
+ it "should correctly parse #,##,###,####" do
33
+ format = Csvlint::Csvw::NumberFormat.new("#,##,###,####")
34
+ expect(format.primary_grouping_size).to eq(4)
35
+ expect(format.secondary_grouping_size).to eq(3)
36
+ expect(format.fractional_grouping_size).to eq(0)
37
+ end
38
+
39
+ it "should correctly parse #,##0.###,#" do
40
+ format = Csvlint::Csvw::NumberFormat.new("#,##0.###,#")
41
+ expect(format.primary_grouping_size).to eq(3)
42
+ expect(format.secondary_grouping_size).to eq(3)
43
+ expect(format.fractional_grouping_size).to eq(3)
44
+ end
45
+
46
+ it "should correctly parse #0.###E#0" do
47
+ format = Csvlint::Csvw::NumberFormat.new("#0.###E#0")
48
+ expect(format.prefix).to eq("")
49
+ expect(format.numeric_part).to eq("#0.###E#0")
50
+ expect(format.suffix).to eq("")
51
+ end
52
+
53
+ it "should match numbers that match ##0 correctly" do
54
+ format = Csvlint::Csvw::NumberFormat.new("##0")
55
+ expect(format.match("1")).to eq(true)
56
+ expect(format.match("12")).to eq(true)
57
+ expect(format.match("123")).to eq(true)
58
+ expect(format.match("1234")).to eq(true)
59
+ expect(format.match("1,234")).to eq(false)
60
+ expect(format.match("123.4")).to eq(false)
61
+ end
62
+
63
+ it "should match numbers that match #,#00 correctly" do
64
+ format = Csvlint::Csvw::NumberFormat.new("#,#00")
65
+ expect(format.match("1")).to eq(false)
66
+ expect(format.match("12")).to eq(true)
67
+ expect(format.match("123")).to eq(true)
68
+ expect(format.match("1234")).to eq(false)
69
+ expect(format.match("1,234")).to eq(true)
70
+ expect(format.match("1,234,568")).to eq(true)
71
+ expect(format.match("12,34,568")).to eq(false)
72
+ expect(format.match("12,34")).to eq(false)
73
+ expect(format.match("123.4")).to eq(false)
74
+ end
75
+
76
+ it "should match numbers that match #,000 correctly" do
77
+ format = Csvlint::Csvw::NumberFormat.new("#,000")
78
+ expect(format.match("1")).to eq(false)
79
+ expect(format.match("12")).to eq(false)
80
+ expect(format.match("123")).to eq(true)
81
+ expect(format.match("1234")).to eq(false)
82
+ expect(format.match("1,234")).to eq(true)
83
+ expect(format.match("1,234,568")).to eq(true)
84
+ expect(format.match("12,34,568")).to eq(false)
85
+ expect(format.match("12,34")).to eq(false)
86
+ expect(format.match("123.4")).to eq(false)
87
+ end
88
+
89
+ it "should match numbers that match #,##,#00 correctly" do
90
+ format = Csvlint::Csvw::NumberFormat.new("#,##,#00")
91
+ expect(format.match("1")).to eq(false)
92
+ expect(format.match("12")).to eq(true)
93
+ expect(format.match("123")).to eq(true)
94
+ expect(format.match("1234")).to eq(false)
95
+ expect(format.match("1,234")).to eq(true)
96
+ expect(format.match("1,234,568")).to eq(false)
97
+ expect(format.match("12,34,568")).to eq(true)
98
+ expect(format.match("12,34")).to eq(false)
99
+ expect(format.match("123.4")).to eq(false)
100
+ end
101
+
102
+ it "should match numbers that match #0.# correctly" do
103
+ format = Csvlint::Csvw::NumberFormat.new("#0.#")
104
+ expect(format.match("1")).to eq(true)
105
+ expect(format.match("12")).to eq(true)
106
+ expect(format.match("12.3")).to eq(true)
107
+ expect(format.match("12.34")).to eq(false)
108
+ expect(format.match("1234.5")).to eq(true)
109
+ expect(format.match("1,234.5")).to eq(false)
110
+ end
111
+
112
+ it "should match numbers that match #0.0 correctly" do
113
+ format = Csvlint::Csvw::NumberFormat.new("#0.0")
114
+ expect(format.match("1")).to eq(false)
115
+ expect(format.match("12")).to eq(false)
116
+ expect(format.match("12.3")).to eq(true)
117
+ expect(format.match("12.34")).to eq(false)
118
+ expect(format.match("1234.5")).to eq(true)
119
+ expect(format.match("1,234.5")).to eq(false)
120
+ end
121
+
122
+ it "should match numbers that match #0.0# correctly" do
123
+ format = Csvlint::Csvw::NumberFormat.new("#0.0#")
124
+ expect(format.match("1")).to eq(false)
125
+ expect(format.match("12")).to eq(false)
126
+ expect(format.match("12.3")).to eq(true)
127
+ expect(format.match("12.34")).to eq(true)
128
+ expect(format.match("12.345")).to eq(false)
129
+ end
130
+
131
+ it "should match numbers that match #0.0#,# correctly" do
132
+ format = Csvlint::Csvw::NumberFormat.new("#0.0#,#")
133
+ expect(format.match("1")).to eq(false)
134
+ expect(format.match("12.3")).to eq(true)
135
+ expect(format.match("12.34")).to eq(true)
136
+ expect(format.match("12.345")).to eq(false)
137
+ expect(format.match("12.34,5")).to eq(true)
138
+ expect(format.match("12.34,56")).to eq(false)
139
+ expect(format.match("12.34,567")).to eq(false)
140
+ expect(format.match("12.34,56,7")).to eq(false)
141
+ end
142
+
143
+ it "should match numbers that match #0.###E#0 correctly" do
144
+ format = Csvlint::Csvw::NumberFormat.new("#0.###E#0")
145
+ expect(format.match("1")).to eq(false)
146
+ expect(format.match("12.3")).to eq(false)
147
+ expect(format.match("12.34")).to eq(false)
148
+ expect(format.match("12.3E4")).to eq(true)
149
+ expect(format.match("12.3E45")).to eq(true)
150
+ expect(format.match("12.34E5")).to eq(true)
151
+ end
152
+
153
+ it "should parse numbers that match ##0 correctly" do
154
+ format = Csvlint::Csvw::NumberFormat.new("##0")
155
+ expect(format.parse("1")).to eql(1)
156
+ expect(format.parse("12")).to eql(12)
157
+ expect(format.parse("123")).to eql(123)
158
+ expect(format.parse("1234")).to eql(1234)
159
+ expect(format.parse("1,234")).to eq(nil)
160
+ expect(format.parse("123.4")).to eq(nil)
161
+ end
162
+
163
+ it "should parse numbers that match #,#00 correctly" do
164
+ format = Csvlint::Csvw::NumberFormat.new("#,#00")
165
+ expect(format.parse("1")).to eq(nil)
166
+ expect(format.parse("12")).to eql(12)
167
+ expect(format.parse("123")).to eql(123)
168
+ expect(format.parse("1234")).to eq(nil)
169
+ expect(format.parse("1,234")).to eql(1234)
170
+ expect(format.parse("1,234,568")).to eql(1234568)
171
+ expect(format.parse("12,34,568")).to eq(nil)
172
+ expect(format.parse("12,34")).to eq(nil)
173
+ expect(format.parse("123.4")).to eq(nil)
174
+ end
175
+
176
+ it "should parse numbers that match #,000 correctly" do
177
+ format = Csvlint::Csvw::NumberFormat.new("#,000")
178
+ expect(format.parse("1")).to eq(nil)
179
+ expect(format.parse("12")).to eq(nil)
180
+ expect(format.parse("123")).to eql(123)
181
+ expect(format.parse("1234")).to eq(nil)
182
+ expect(format.parse("1,234")).to eql(1234)
183
+ expect(format.parse("1,234,568")).to eql(1234568)
184
+ expect(format.parse("12,34,568")).to eq(nil)
185
+ expect(format.parse("12,34")).to eq(nil)
186
+ expect(format.parse("123.4")).to eq(nil)
187
+ end
188
+
189
+ it "should parse numbers that match #0,000 correctly" do
190
+ format = Csvlint::Csvw::NumberFormat.new("#0,000")
191
+ expect(format.parse("1")).to eq(nil)
192
+ expect(format.parse("12")).to eq(nil)
193
+ expect(format.parse("123")).to eql(nil)
194
+ expect(format.parse("1234")).to eq(nil)
195
+ expect(format.parse("1,234")).to eql(1234)
196
+ expect(format.parse("1,234,568")).to eql(1234568)
197
+ expect(format.parse("12,34,568")).to eq(nil)
198
+ expect(format.parse("12,34")).to eq(nil)
199
+ expect(format.parse("123.4")).to eq(nil)
200
+ end
201
+
202
+ it "should parse numbers that match #,##,#00 correctly" do
203
+ format = Csvlint::Csvw::NumberFormat.new("#,##,#00")
204
+ expect(format.parse("1")).to eq(nil)
205
+ expect(format.parse("12")).to eql(12)
206
+ expect(format.parse("123")).to eql(123)
207
+ expect(format.parse("1234")).to eq(nil)
208
+ expect(format.parse("1,234")).to eql(1234)
209
+ expect(format.parse("12,345")).to eql(12345)
210
+ expect(format.parse("1,234,568")).to eq(nil)
211
+ expect(format.parse("12,34,568")).to eql(1234568)
212
+ expect(format.parse("12,34")).to eq(nil)
213
+ expect(format.parse("123.4")).to eq(nil)
214
+ end
215
+
216
+ it "should parse numbers that match #,00,000 correctly" do
217
+ format = Csvlint::Csvw::NumberFormat.new("#,00,000")
218
+ expect(format.parse("1")).to eq(nil)
219
+ expect(format.parse("12")).to eql(nil)
220
+ expect(format.parse("123")).to eql(nil)
221
+ expect(format.parse("1234")).to eq(nil)
222
+ expect(format.parse("1,234")).to eql(nil)
223
+ expect(format.parse("12,345")).to eql(12345)
224
+ expect(format.parse("1,234,568")).to eq(nil)
225
+ expect(format.parse("1,34,568")).to eql(134568)
226
+ expect(format.parse("12,34,568")).to eql(1234568)
227
+ expect(format.parse("1,23,45,678")).to eql(12345678)
228
+ expect(format.parse("12,34")).to eq(nil)
229
+ expect(format.parse("123.4")).to eq(nil)
230
+ end
231
+
232
+ it "should parse numbers that match 0,00,000 correctly" do
233
+ format = Csvlint::Csvw::NumberFormat.new("0,00,000")
234
+ expect(format.parse("1")).to eq(nil)
235
+ expect(format.parse("12")).to eql(nil)
236
+ expect(format.parse("123")).to eql(nil)
237
+ expect(format.parse("1234")).to eq(nil)
238
+ expect(format.parse("1,234")).to eql(nil)
239
+ expect(format.parse("12,345")).to eql(nil)
240
+ expect(format.parse("1,234,568")).to eq(nil)
241
+ expect(format.parse("1,34,568")).to eql(134568)
242
+ expect(format.parse("12,34,568")).to eql(1234568)
243
+ expect(format.parse("1,23,45,678")).to eql(12345678)
244
+ expect(format.parse("12,34")).to eq(nil)
245
+ expect(format.parse("123.4")).to eq(nil)
246
+ end
247
+
248
+ it "should parse numbers that match #0.# correctly" do
249
+ format = Csvlint::Csvw::NumberFormat.new("#0.#")
250
+ expect(format.parse("1")).to eql(1.0)
251
+ expect(format.parse("12")).to eql(12.0)
252
+ expect(format.parse("12.3")).to eql(12.3)
253
+ expect(format.parse("12.34")).to eq(nil)
254
+ expect(format.parse("1234.5")).to eql(1234.5)
255
+ expect(format.parse("1,234.5")).to eq(nil)
256
+ end
257
+
258
+ it "should parse numbers that match #0.0 correctly" do
259
+ format = Csvlint::Csvw::NumberFormat.new("#0.0")
260
+ expect(format.parse("1")).to eq(nil)
261
+ expect(format.parse("12")).to eq(nil)
262
+ expect(format.parse("12.3")).to eql(12.3)
263
+ expect(format.parse("12.34")).to eq(nil)
264
+ expect(format.parse("1234.5")).to eql(1234.5)
265
+ expect(format.parse("1,234.5")).to eq(nil)
266
+ end
267
+
268
+ it "should parse numbers that match #0.0# correctly" do
269
+ format = Csvlint::Csvw::NumberFormat.new("#0.0#")
270
+ expect(format.parse("1")).to eq(nil)
271
+ expect(format.parse("12")).to eq(nil)
272
+ expect(format.parse("12.3")).to eql(12.3)
273
+ expect(format.parse("12.34")).to eql(12.34)
274
+ expect(format.parse("12.345")).to eq(nil)
275
+ end
276
+
277
+ it "should parse numbers that match #0.0#,# correctly" do
278
+ format = Csvlint::Csvw::NumberFormat.new("#0.0#,#")
279
+ expect(format.parse("1")).to eq(nil)
280
+ expect(format.parse("12.3")).to eql(12.3)
281
+ expect(format.parse("12.34")).to eql(12.34)
282
+ expect(format.parse("12.345")).to eq(nil)
283
+ expect(format.parse("12.34,5")).to eql(12.345)
284
+ expect(format.parse("12.34,56")).to eql(nil)
285
+ expect(format.parse("12.34,567")).to eq(nil)
286
+ expect(format.parse("12.34,56,7")).to eql(nil)
287
+ end
288
+
289
+ it "should parse numbers that match 0.0##,### correctly" do
290
+ format = Csvlint::Csvw::NumberFormat.new("0.0##,###")
291
+ expect(format.parse("1")).to eq(nil)
292
+ expect(format.parse("12.3")).to eql(12.3)
293
+ expect(format.parse("12.34")).to eql(12.34)
294
+ expect(format.parse("12.345")).to eq(12.345)
295
+ expect(format.parse("12.3456")).to eql(nil)
296
+ expect(format.parse("12.345,6")).to eql(12.3456)
297
+ expect(format.parse("12.34,56")).to eql(nil)
298
+ expect(format.parse("12.345,67")).to eq(12.34567)
299
+ expect(format.parse("12.345,678")).to eql(12.345678)
300
+ expect(format.parse("12.345,67,8")).to eql(nil)
301
+ end
302
+
303
+ it "should parse numbers that match 0.000,### correctly" do
304
+ format = Csvlint::Csvw::NumberFormat.new("0.000,###")
305
+ expect(format.parse("1")).to eq(nil)
306
+ expect(format.parse("12.3")).to eql(nil)
307
+ expect(format.parse("12.34")).to eql(nil)
308
+ expect(format.parse("12.345")).to eq(12.345)
309
+ expect(format.parse("12.3456")).to eql(nil)
310
+ expect(format.parse("12.345,6")).to eql(12.3456)
311
+ expect(format.parse("12.34,56")).to eql(nil)
312
+ expect(format.parse("12.345,67")).to eq(12.34567)
313
+ expect(format.parse("12.345,678")).to eql(12.345678)
314
+ expect(format.parse("12.345,67,8")).to eql(nil)
315
+ end
316
+
317
+ it "should parse numbers that match 0.000,0# correctly" do
318
+ format = Csvlint::Csvw::NumberFormat.new("0.000,0#")
319
+ expect(format.parse("1")).to eq(nil)
320
+ expect(format.parse("12.3")).to eql(nil)
321
+ expect(format.parse("12.34")).to eql(nil)
322
+ expect(format.parse("12.345")).to eq(nil)
323
+ expect(format.parse("12.3456")).to eql(nil)
324
+ expect(format.parse("12.345,6")).to eql(12.3456)
325
+ expect(format.parse("12.34,56")).to eql(nil)
326
+ expect(format.parse("12.345,67")).to eq(12.34567)
327
+ expect(format.parse("12.345,678")).to eql(nil)
328
+ expect(format.parse("12.345,67,8")).to eql(nil)
329
+ end
330
+
331
+ it "should parse numbers that match 0.000,0## correctly" do
332
+ format = Csvlint::Csvw::NumberFormat.new("0.000,0##")
333
+ expect(format.parse("1")).to eq(nil)
334
+ expect(format.parse("12.3")).to eql(nil)
335
+ expect(format.parse("12.34")).to eql(nil)
336
+ expect(format.parse("12.345")).to eq(nil)
337
+ expect(format.parse("12.3456")).to eql(nil)
338
+ expect(format.parse("12.345,6")).to eql(12.3456)
339
+ expect(format.parse("12.34,56")).to eql(nil)
340
+ expect(format.parse("12.345,67")).to eq(12.34567)
341
+ expect(format.parse("12.345,678")).to eql(12.345678)
342
+ expect(format.parse("12.345,67,8")).to eql(nil)
343
+ end
344
+
345
+ it "should parse numbers that match 0.000,000 correctly" do
346
+ format = Csvlint::Csvw::NumberFormat.new("0.000,000")
347
+ expect(format.parse("1")).to eq(nil)
348
+ expect(format.parse("12.3")).to eql(nil)
349
+ expect(format.parse("12.34")).to eql(nil)
350
+ expect(format.parse("12.345")).to eq(nil)
351
+ expect(format.parse("12.3456")).to eql(nil)
352
+ expect(format.parse("12.345,6")).to eql(nil)
353
+ expect(format.parse("12.34,56")).to eql(nil)
354
+ expect(format.parse("12.345,67")).to eq(nil)
355
+ expect(format.parse("12.345,678")).to eql(12.345678)
356
+ expect(format.parse("12.345,67,8")).to eql(nil)
357
+ end
358
+
359
+ it "should parse numbers that match #0.###E#0 correctly" do
360
+ format = Csvlint::Csvw::NumberFormat.new("#0.###E#0")
361
+ expect(format.parse("1")).to eq(nil)
362
+ expect(format.parse("12.3")).to eq(nil)
363
+ expect(format.parse("12.34")).to eq(nil)
364
+ expect(format.parse("12.3E4")).to eql(12.3E4)
365
+ expect(format.parse("12.3E45")).to eql(12.3E45)
366
+ expect(format.parse("12.34E5")).to eql(12.34E5)
367
+ end
368
+
369
+ it "should parse numbers normally when there is no pattern" do
370
+ format = Csvlint::Csvw::NumberFormat.new()
371
+ expect(format.parse("1")).to eql(1)
372
+ expect(format.parse("-1")).to eql(-1)
373
+ expect(format.parse("12.3")).to eql(12.3)
374
+ expect(format.parse("12.34")).to eql(12.34)
375
+ expect(format.parse("12.3E4")).to eql(12.3E4)
376
+ expect(format.parse("12.3E45")).to eql(12.3E45)
377
+ expect(format.parse("12.34E5")).to eql(12.34E5)
378
+ expect(format.parse("12.34e5")).to eql(12.34E5)
379
+ expect(format.parse("-12.34")).to eql(-12.34)
380
+ expect(format.parse("1,234")).to eq(nil)
381
+ expect(format.parse("NaN").nan?).to eq(true)
382
+ expect(format.parse("INF")).to eql(Float::INFINITY)
383
+ expect(format.parse("-INF")).to eql(-Float::INFINITY)
384
+ expect(format.parse("123456.789F10")).to eq(nil)
385
+ end
386
+
387
+ it "should parse numbers including grouping separators when they are specified" do
388
+ format = Csvlint::Csvw::NumberFormat.new(nil, ",")
389
+ expect(format.parse("1")).to eql(1)
390
+ expect(format.parse("12.3")).to eql(12.3)
391
+ expect(format.parse("12.34")).to eql(12.34)
392
+ expect(format.parse("12.3E4")).to eql(12.3E4)
393
+ expect(format.parse("12.3E45")).to eql(12.3E45)
394
+ expect(format.parse("12.34E5")).to eql(12.34E5)
395
+ expect(format.parse("1,234")).to eql(1234)
396
+ expect(format.parse("1,234,567")).to eql(1234567)
397
+ expect(format.parse("1,,234")).to eq(nil)
398
+ expect(format.parse("NaN").nan?).to eq(true)
399
+ expect(format.parse("INF")).to eql(Float::INFINITY)
400
+ expect(format.parse("-INF")).to eql(-Float::INFINITY)
401
+ end
402
+
403
+ end