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 +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: []
|