dbf 0.2.0 → 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.
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: []