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 +1 -1
- data/lib/dbf/reader.rb +13 -3
- data/test/common.rb +90 -0
- data/test/databases/dbase_iii.dbf +0 -0
- data/test/dbase_iii_read_test.rb +26 -0
- data/test/foxpro_read_test.rb +15 -83
- metadata +5 -2
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) =~
|
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
|
-
|
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
|
data/test/foxpro_read_test.rb
CHANGED
@@ -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.
|
7
|
-
date: 2006-08-
|
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: []
|