dbf 0.1.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 ADDED
@@ -0,0 +1,2 @@
1
+ require 'date'
2
+ require 'dbf/reader'
data/lib/dbf/reader.rb ADDED
@@ -0,0 +1,116 @@
1
+ module DBF
2
+
3
+ DBF_HEADER_SIZE = 32
4
+ FPT_HEADER_SIZE = 512
5
+ FPT_BLOCK_HEADER_SIZE = 8
6
+
7
+ class Reader
8
+
9
+ attr_reader :field_count
10
+ attr_reader :fields
11
+ attr_reader :record_count
12
+
13
+ def initialize(file)
14
+ @data_file = File.open(file, 'rb')
15
+ @memo_file = File.open(file.gsub(/dbf$/i, 'fpt'), 'rb') rescue File.open(file.gsub(/dbf$/i, 'FPT'), 'rb') rescue nil
16
+ get_header_info
17
+ get_memo_header_info if @memo_file
18
+ get_field_descriptors
19
+ end
20
+
21
+ def get_header_info
22
+ @data_file.rewind
23
+ @record_count, @header_length, @record_length = @data_file.read(DBF_HEADER_SIZE).unpack('xxxxVvv')
24
+ @field_count = (@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE
25
+ end
26
+
27
+ def get_memo_header_info
28
+ @memo_file.rewind
29
+ @memo_next_available_block, @memo_block_size = @memo_file.read(FPT_HEADER_SIZE).unpack('Nxxn')
30
+ end
31
+
32
+ def has_memo_file?
33
+ @memo_file ? true : false
34
+ end
35
+
36
+ def get_field_descriptors
37
+ @fields = Array.new(@field_count) {|i| Field.new(*@data_file.read(32).unpack('a10xax4ss'))}
38
+ end
39
+
40
+ def field(field_name)
41
+ @fields.select {|f| f.name == field_name}
42
+ end
43
+
44
+ def memo(start_block)
45
+ @memo_file.rewind
46
+ @memo_file.seek(start_block * @memo_block_size)
47
+ memo_type, memo_size, memo_string = @memo_file.read(@memo_block_size).unpack("NNa56")
48
+ if memo_size > @memo_block_size - FPT_BLOCK_HEADER_SIZE
49
+ memo_string << @memo_file.read(memo_size - @memo_block_size + FPT_BLOCK_HEADER_SIZE)
50
+ end
51
+ memo_string
52
+ end
53
+
54
+ def unpack_string(field)
55
+ @data_file.read(field.length).unpack("a#{field.length}")
56
+ end
57
+
58
+ def records
59
+ seek(0)
60
+ Array.new(@record_count) {build_record if @data_file.read(1).unpack('c').to_s == '32'}
61
+ end
62
+
63
+ alias_method :rows, :records
64
+
65
+ def record(index)
66
+ seek_to_record(index)
67
+ build_record if @data_file.read(1).unpack('c').to_s == '32'
68
+ end
69
+
70
+ def build_record
71
+ record = Record.new
72
+ @fields.each do |field|
73
+ case field.type
74
+ when 'N' # number
75
+ record[field.name] = unpack_string(field)[0].to_i
76
+ when 'D' # date
77
+ raw = unpack_string(field).to_s.strip
78
+ record[field.name] = raw.strip.empty? ? nil : Date.new(*raw.match(/([\d]{4})([\d]{2})([\d]{2})/).to_a.slice(1,3).map {|n| n.to_i}) rescue nil
79
+ when 'M' # memo
80
+ starting_block = unpack_string(field).first.to_i
81
+ record[field.name] = starting_block == 0 ? nil : memo(starting_block)
82
+ when 'L' # logical
83
+ record[field.name] = unpack_string(field) =~ /(y|t)/i ? true : false
84
+ else
85
+ record[field.name] = unpack_string(field).to_s.strip
86
+ end
87
+ end
88
+ record
89
+ end
90
+
91
+ def seek(offset)
92
+ @data_file.seek(@header_length + offset)
93
+ end
94
+
95
+ def seek_to_record(index)
96
+ seek(@record_length * index)
97
+ end
98
+
99
+ end
100
+
101
+ class Field
102
+ attr_accessor :name, :type, :length, :decimal
103
+
104
+ def initialize(name, type, length, decimal)
105
+ self.name, self.type, self.length, self.decimal = name, type, length, decimal
106
+ end
107
+
108
+ def name=(name)
109
+ @name = name.gsub(/\0/, '')
110
+ end
111
+ end
112
+
113
+ class Record < Hash
114
+ end
115
+
116
+ end
Binary file
Binary file
data/test/read_test.rb ADDED
@@ -0,0 +1,65 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+ require 'test/unit'
3
+ require 'dbf'
4
+
5
+ class ReadTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'foxpro.dbf'))
9
+ end
10
+
11
+ def test_records
12
+ assert_kind_of Array, @dbf.records
13
+ assert_kind_of Array, @dbf.rows
14
+ end
15
+
16
+ def test_record
17
+ assert_equal @dbf.record(0), @dbf.records[0]
18
+ assert_equal @dbf.record(99), @dbf.records[99]
19
+ end
20
+
21
+ def test_field_count
22
+ assert_equal @dbf.field_count, @dbf.fields.size
23
+ end
24
+
25
+ def test_header_info
26
+ assert_equal 59, @dbf.field_count
27
+ assert_equal 975, @dbf.record_count
28
+ assert @dbf.has_memo_file?
29
+ end
30
+
31
+ def test_character_fields
32
+ @dbf.records.each do |record|
33
+ assert record['NOM'].is_a?(String)
34
+ end
35
+ end
36
+
37
+ def test_date_fields
38
+ @dbf.records.each do |record|
39
+ assert record['DATN'].is_a?(Date) || record['DATN'].nil?
40
+ end
41
+ end
42
+
43
+ def test_numeric_fields
44
+ @dbf.records.each do |record|
45
+ assert record['NF'].is_a?(Fixnum)
46
+ end
47
+ end
48
+
49
+ def test_logical_fields
50
+ # need a test database that has a logical field
51
+ end
52
+
53
+ def test_memo_fields
54
+ @dbf.records.each_with_index do |record, index|
55
+ if [1,3,5].include?(index)
56
+ assert record['OBSE'].is_a?(String)
57
+ elsif [2].include?(index)
58
+ assert record['OBSE'].nil?
59
+ else
60
+ assert record['OBSE'].is_a?(String) || record['OBSE'].nil?
61
+ end
62
+ end
63
+ end
64
+
65
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: dbf
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-08-01 00:00:00 -07:00
8
+ summary: A library for reading DBase (or XBase, Clipper, Foxpro, etc) database files
9
+ require_paths:
10
+ - lib
11
+ email: keithm@infused.org
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Keith Morrison
31
+ files:
32
+ - lib/dbf
33
+ - lib/dbf.rb
34
+ - lib/dbf/reader.rb
35
+ - test/databases
36
+ - test/read_test.rb
37
+ - test/databases/foxpro.dbf
38
+ - test/databases/foxpro.fpt
39
+ test_files: []
40
+
41
+ rdoc_options: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ executables: []
46
+
47
+ extensions: []
48
+
49
+ requirements: []
50
+
51
+ dependencies: []
52
+