dbf 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dbf.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  require 'date'
2
- require 'dbf/reader'
2
+ require 'dbf/reader'
data/lib/dbf/reader.rb CHANGED
@@ -10,6 +10,9 @@ module DBF
10
10
  "05" => "dBase V without memo file", "30" => "Visual FoxPro", "31" => "Visual FoxPro with AutoIncrement field",
11
11
  "7b" => "dBase IV with memo file", "83" => "dBase III with memo file", "8b" => "dBase IV with memo file",
12
12
  "8e" => "dBase IV with SQL table", "f5" => "FoxPro with memo file", "fb" => "FoxPro without memo file"}
13
+
14
+ class DBFError < StandardError; end
15
+ class UnpackError < DBFError; end
13
16
 
14
17
  class Reader
15
18
 
@@ -85,12 +88,12 @@ module DBF
85
88
  end
86
89
  when 'D' # date
87
90
  raw = unpack_string(field).to_s.strip
88
- record[field.name] = raw.empty ? nil : Date.new(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i}) rescue nil
91
+ record[field.name] = raw.empty? ? nil : Date.new(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i}) rescue nil
89
92
  when 'M' # memo
90
93
  starting_block = unpack_integer(field)
91
94
  record[field.name] = starting_block == 0 ? nil : memo(starting_block) rescue nil
92
95
  when 'L' # logical
93
- record[field.name] = unpack_string(field) =~ /(y|t)/i ? true : false rescue false
96
+ record[field.name] = unpack_string(field) =~ /^(y|t)$/i ? true : false rescue false
94
97
  else
95
98
  record[field.name] = unpack_string(field)
96
99
  end
@@ -121,8 +124,12 @@ module DBF
121
124
  seek(@record_length * index)
122
125
  end
123
126
 
127
+ def unpack_field(field)
128
+ @data_file.read(field.length).unpack("a#{field.length}")
129
+ end
130
+
124
131
  def unpack_string(field)
125
- @data_file.read(field.length).unpack("a#{field.length}").to_s
132
+ unpack_field(field).to_s
126
133
  end
127
134
 
128
135
  def unpack_integer(field)
@@ -135,10 +142,13 @@ module DBF
135
142
 
136
143
  end
137
144
 
145
+ class FieldError < StandardError; end
146
+
138
147
  class Field
139
148
  attr_accessor :name, :type, :length, :decimal
140
149
 
141
150
  def initialize(name, type, length, decimal)
151
+ raise FieldError, "field length must be greater than 0" unless length > 0
142
152
  self.name, self.type, self.length, self.decimal = name, type, length, decimal
143
153
  end
144
154
 
data/test/common.rb ADDED
@@ -0,0 +1,90 @@
1
+ module CommonTests
2
+ module Read
3
+
4
+ def test_version
5
+ assert_equal @controls[:version], @dbf.version
6
+ assert_equal @dbf.instance_eval("DBF::VERSION_DESCRIPTIONS['#{@dbf.version}']"), @dbf.version_description
7
+ end
8
+
9
+ def test_has_memo_file
10
+ assert_equal @controls[:has_memo_file], @dbf.has_memo_file?
11
+ end
12
+
13
+ def test_records
14
+ assert_kind_of Array, @dbf.records
15
+ assert_kind_of Array, @dbf.rows
16
+ assert(@dbf.records.all? {|record| record.is_a?(DBF::Record)})
17
+ end
18
+
19
+ # Does the header info match the actual fields found?
20
+ def test_field_count
21
+ assert_equal @controls[:field_count], @dbf.field_count
22
+ assert_equal @dbf.field_count, @dbf.fields.size, "header field_count does not equal actual field count"
23
+ end
24
+
25
+ # Does the header info match the actual number of records found?
26
+ def test_record_count
27
+ assert_equal @controls[:record_count], @dbf.record_count
28
+ assert_equal @dbf.record_count, @dbf.records.size, "header record_count does not equal actual record count"
29
+ end
30
+
31
+ def test_record_length
32
+ assert_equal @controls[:record_length], @dbf.instance_eval {@record_length}
33
+ assert_equal @controls[:record_length], @dbf.fields.inject(1) {|sum, field| sum + field.length}
34
+ end
35
+
36
+ def test_field_attributes
37
+ @dbf.fields.each do |field|
38
+ assert_kind_of DBF::Field, field
39
+ assert field.name.is_a?(String) && !field.name.empty?
40
+ assert %w(C N L D M F B G P Y T I V X @ O +).include?(field.type)
41
+ assert_kind_of Fixnum, field.length
42
+ assert field.length > 0
43
+ assert_kind_of Fixnum, field.decimal
44
+ end
45
+ end
46
+
47
+ def test_random_records
48
+ 10.times do
49
+ record_num = rand(@controls[:record_count])
50
+ assert_equal @dbf.records[record_num], @dbf.record(record_num)
51
+ end
52
+ end
53
+
54
+ def test_character_fields
55
+ @controls[:testable_character_field_names].each do |name|
56
+ assert(@dbf.records.any? {|record| record[name].is_a?(String)})
57
+ end
58
+ end
59
+
60
+ def test_date_fields
61
+ @controls[:testable_date_field_names].each do |name|
62
+ assert(@dbf.records.any? {|record| record[name].is_a?(Date)})
63
+ end
64
+ end
65
+
66
+ def test_integer_numeric_fields
67
+ @controls[:testable_integer_field_names].each do |name|
68
+ assert(@dbf.records.any? {|record| record[name].is_a?(Fixnum)})
69
+ end
70
+ end
71
+
72
+ def test_float_numeric_fields
73
+ @controls[:testable_float_field_names].each do |name|
74
+ assert(@dbf.records.any? {|record| record[name].is_a?(Float)})
75
+ end
76
+ end
77
+
78
+ def test_logical_fields
79
+ # need a test database that has a logical field
80
+ end
81
+
82
+ def test_memo_fields
83
+ @controls[:testable_memo_field_names].each do |name|
84
+ assert(@dbf.records.any? {|record| record[name].is_a?(String)})
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ end
Binary file
@@ -0,0 +1,26 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+ require 'test/unit'
3
+ require 'dbf'
4
+ require 'common'
5
+
6
+ class DBaseIIIReadTest < Test::Unit::TestCase
7
+ include CommonTests::Read
8
+
9
+ def setup
10
+ @controls = {
11
+ :version => "03",
12
+ :has_memo_file => false,
13
+ :field_count => 31,
14
+ :record_count => 14,
15
+ :record_length => 590,
16
+ :testable_character_field_names => ["Shape"],
17
+ :testable_date_field_names => ["Date_Visit"],
18
+ :testable_integer_field_names => ["Filt_Pos"],
19
+ :testable_float_field_names => ["Max_PDOP"],
20
+ :testable_logical_field_names => [],
21
+ :testable_memo_field_names => []
22
+ }
23
+ @dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'dbase_iii.dbf'))
24
+ end
25
+
26
+ end
@@ -1,94 +1,26 @@
1
1
  $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
2
  require 'test/unit'
3
3
  require 'dbf'
4
+ require 'common'
4
5
 
5
6
  class FoxproReadTest < Test::Unit::TestCase
7
+ include CommonTests::Read
6
8
 
7
9
  def setup
10
+ @controls = {
11
+ :version => "f5",
12
+ :has_memo_file => true,
13
+ :field_count => 59,
14
+ :record_count => 975,
15
+ :record_length => 969,
16
+ :testable_character_field_names => ["NOM"],
17
+ :testable_date_field_names => ["DATN"],
18
+ :testable_integer_field_names => ["NF"],
19
+ :testable_float_field_names => [],
20
+ :testable_logical_field_names => [],
21
+ :testable_memo_field_names => ["OBSE"]
22
+ }
8
23
  @dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'foxpro.dbf'))
9
24
  end
10
25
 
11
- def test_records
12
- assert_kind_of Array, @dbf.records
13
- assert_kind_of Array, @dbf.rows
14
- @dbf.records.each do |record|
15
- assert_kind_of DBF::Record, record
16
- end
17
- end
18
-
19
- def test_record
20
- assert_equal @dbf.record(0), @dbf.records[0]
21
- assert_equal @dbf.record(99), @dbf.records[99]
22
- end
23
-
24
- def test_fields
25
- assert_kind_of Array, @dbf.fields
26
- end
27
-
28
- def test_field_count
29
- assert_equal 59, @dbf.field_count
30
- assert_equal @dbf.field_count, @dbf.fields.size
31
- end
32
-
33
- def test_record_count
34
- assert_equal 975, @dbf.record_count
35
- assert_equal @dbf.record_count, @dbf.records.size
36
- end
37
-
38
- def test_record_length
39
- assert_equal 969, @dbf.instance_eval {@record_length}
40
- assert_equal 969, @dbf.fields.inject(1) {|sum, field| sum + field.length}
41
- end
42
-
43
- def test_character_fields
44
- @dbf.records.each do |record|
45
- assert record['NOM'].is_a?(String)
46
- end
47
- end
48
-
49
- def test_date_fields
50
- @dbf.records.each do |record|
51
- assert record['DATN'].is_a?(Date) || record['DATN'].nil?
52
- end
53
- end
54
-
55
- def test_numeric_fields
56
- @dbf.records.each do |record|
57
- assert record['NF'].is_a?(Fixnum)
58
- end
59
- end
60
-
61
- def test_logical_fields
62
- # need a test database that has a logical field
63
- end
64
-
65
- def test_memo_fields
66
- @dbf.records.each_with_index do |record, index|
67
- if [1,3,5].include?(index)
68
- assert record['OBSE'].is_a?(String)
69
- elsif [2].include?(index)
70
- assert record['OBSE'].nil?
71
- else
72
- assert record['OBSE'].is_a?(String) || record['OBSE'].nil?
73
- end
74
- end
75
- end
76
-
77
- def test_field
78
- assert_kind_of DBF::Field, @dbf.field("NOM")
79
- assert_equal "NOM", @dbf.field("NOM").name
80
- assert_equal "C", @dbf.field("NOM").type
81
- assert_equal 20, @dbf.field(:NOM).length
82
- assert_equal 0, @dbf.field("NOM").decimal
83
- end
84
-
85
- def test_version
86
- assert_equal "f5", @dbf.version
87
- assert_equal "FoxPro with memo file", @dbf.version_description
88
- end
89
-
90
- def test_has_memo_file
91
- assert @dbf.has_memo_file?
92
- end
93
-
94
26
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: dbf
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.0
7
- date: 2006-08-08 00:00:00 -07:00
6
+ version: 0.3.0
7
+ date: 2006-08-12 00:00:00 -07:00
8
8
  summary: A library for reading DBase (or XBase, Clipper, Foxpro, etc) database files
9
9
  require_paths:
10
10
  - lib
@@ -32,8 +32,11 @@ files:
32
32
  - lib/dbf
33
33
  - lib/dbf.rb
34
34
  - lib/dbf/reader.rb
35
+ - test/common.rb
35
36
  - test/databases
37
+ - test/dbase_iii_read_test.rb
36
38
  - test/foxpro_read_test.rb
39
+ - test/databases/dbase_iii.dbf
37
40
  - test/databases/foxpro.dbf
38
41
  - test/databases/foxpro.fpt
39
42
  test_files: []