dbf 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dbf/reader.rb CHANGED
@@ -1,21 +1,28 @@
1
- require 'rubygems'
2
- require 'breakpoint'
3
1
  module DBF
4
2
 
5
3
  DBF_HEADER_SIZE = 32
6
4
  FPT_HEADER_SIZE = 512
7
5
  FPT_BLOCK_HEADER_SIZE = 8
8
6
  DATE_REGEXP = /([\d]{4})([\d]{2})([\d]{2})/
9
- VERSION_DESCRIPTIONS = {"02" => "FoxBase", "03" => "dBase III without memo file", "04" => "dBase IV without memo file",
10
- "05" => "dBase V without memo file", "30" => "Visual FoxPro", "31" => "Visual FoxPro with AutoIncrement field",
11
- "7b" => "dBase IV with memo file", "83" => "dBase III with memo file", "8b" => "dBase IV with memo file",
12
- "8e" => "dBase IV with SQL table", "f5" => "FoxPro with memo file", "fb" => "FoxPro without memo file"}
7
+ VERSION_DESCRIPTIONS = {
8
+ "02" => "FoxBase",
9
+ "03" => "dBase III without memo file",
10
+ "04" => "dBase IV without memo file",
11
+ "05" => "dBase V without memo file",
12
+ "30" => "Visual FoxPro",
13
+ "31" => "Visual FoxPro with AutoIncrement field",
14
+ "7b" => "dBase IV with memo file",
15
+ "83" => "dBase III with memo file",
16
+ "8b" => "dBase IV with memo file",
17
+ "8e" => "dBase IV with SQL table",
18
+ "f5" => "FoxPro with memo file",
19
+ "fb" => "FoxPro without memo file"
20
+ }
13
21
 
14
22
  class DBFError < StandardError; end
15
23
  class UnpackError < DBFError; end
16
24
 
17
25
  class Reader
18
-
19
26
  attr_reader :field_count
20
27
  attr_reader :fields
21
28
  attr_reader :record_count
@@ -24,7 +31,7 @@ module DBF
24
31
 
25
32
  def initialize(file)
26
33
  @data_file = File.open(file, 'rb')
27
- @memo_file = File.open(file.gsub(/dbf$/i, 'fpt'), 'rb') rescue File.open(file.gsub(/dbf$/i, 'FPT'), 'rb') rescue nil
34
+ @memo_file = open_memo(file)
28
35
  reload!
29
36
  end
30
37
 
@@ -38,6 +45,17 @@ module DBF
38
45
  @memo_file ? true : false
39
46
  end
40
47
 
48
+ def open_memo(file)
49
+ %w(fpt FPT dbt DBT).each do |extension|
50
+ filename = file.sub(/dbf$/i, extension)
51
+ if File.exists?(filename)
52
+ @memo_file_format = extension.downcase.to_sym
53
+ return File.open(filename)
54
+ end
55
+ end
56
+ nil
57
+ end
58
+
41
59
  def field(field_name)
42
60
  @fields.detect {|f| f.name == field_name.to_s}
43
61
  end
@@ -45,23 +63,42 @@ module DBF
45
63
  def memo(start_block)
46
64
  @memo_file.rewind
47
65
  @memo_file.seek(start_block * @memo_block_size)
48
- memo_type, memo_size, memo_string = @memo_file.read(@memo_block_size).unpack("NNa56")
49
- if memo_size > @memo_block_size - FPT_BLOCK_HEADER_SIZE
50
- memo_string << @memo_file.read(memo_size - @memo_block_size + FPT_BLOCK_HEADER_SIZE)
66
+ if @memo_file_format == :fpt
67
+ memo_type, memo_size, memo_string = @memo_file.read(@memo_block_size).unpack("NNa56")
68
+ if memo_size > @memo_block_size - FPT_BLOCK_HEADER_SIZE
69
+ memo_string << @memo_file.read(memo_size - @memo_block_size + FPT_BLOCK_HEADER_SIZE)
70
+ end
71
+ else
72
+ if version == "83" # dbase iii
73
+ memo_string = ""
74
+ loop do
75
+ memo_string << block = @memo_file.read(512)
76
+ break if block.strip.size < 512
77
+ end
78
+ end
51
79
  end
52
80
  memo_string
53
81
  end
54
82
 
83
+ # An array of all the records contained in the database file
55
84
  def records
56
85
  seek_to_record(0)
57
- @records ||= Array.new(@record_count) { build_record if active_record? }
86
+ @records ||= Array.new(@record_count) do |i|
87
+ if active_record?
88
+ build_record
89
+ else
90
+ seek_to_record(i + 1)
91
+ nil
92
+ end
93
+ end
58
94
  end
59
95
 
60
96
  alias_method :rows, :records
61
97
 
98
+ # Jump to record
62
99
  def record(index)
63
100
  seek_to_record(index)
64
- build_record if active_record?
101
+ active_record? ? build_record : nil
65
102
  end
66
103
 
67
104
  alias_method :row, :record
@@ -81,14 +118,16 @@ module DBF
81
118
  @fields.each do |field|
82
119
  case field.type
83
120
  when 'N' # number
84
- if field.decimal == 0
85
- record[field.name] = unpack_integer(field) rescue nil
86
- else
87
- record[field.name] = unpack_float(field) rescue nil
88
- end
121
+ record[field.name] = field.decimal == 0 ? unpack_integer(field) : unpack_float(field) rescue nil
89
122
  when 'D' # date
90
123
  raw = unpack_string(field).to_s.strip
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
124
+ unless raw.empty?
125
+ begin
126
+ record[field.name] = Time.gm(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i})
127
+ rescue
128
+ record[field.name] = Date.new(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i}) rescue nil
129
+ end
130
+ end
92
131
  when 'M' # memo
93
132
  starting_block = unpack_integer(field)
94
133
  record[field.name] = starting_block == 0 ? nil : memo(starting_block) rescue nil
@@ -108,12 +147,17 @@ module DBF
108
147
  end
109
148
 
110
149
  def get_field_descriptors
111
- @fields = Array.new(@field_count) {|i| Field.new(*@data_file.read(32).unpack('a10xax4cc'))}
150
+ @fields = Array.new(@field_count) {|i| Field.new(*@data_file.read(32).unpack('a10xax4CC'))}
112
151
  end
113
152
 
114
153
  def get_memo_header_info
115
154
  @memo_file.rewind
116
- @memo_next_available_block, @memo_block_size = @memo_file.read(FPT_HEADER_SIZE).unpack('Nxxn')
155
+ if @memo_file_format == :fpt
156
+ @memo_next_available_block, @memo_block_size = @memo_file.read(FPT_HEADER_SIZE).unpack('Nxxn')
157
+ else
158
+ @memo_block_size = 512
159
+ @memo_next_available_block = File.size(@memo_file.path) / @memo_block_size
160
+ end
117
161
  end
118
162
 
119
163
  def seek(offset)
data/test/common.rb CHANGED
@@ -10,6 +10,10 @@ module CommonTests
10
10
  assert_equal @controls[:has_memo_file], @dbf.has_memo_file?
11
11
  end
12
12
 
13
+ def test_memo_file_format
14
+ assert_equal @controls[:memo_file_format], @dbf.instance_eval("@memo_file_format")
15
+ end
16
+
13
17
  def test_records
14
18
  assert_kind_of Array, @dbf.records
15
19
  assert_kind_of Array, @dbf.rows
@@ -59,7 +63,7 @@ module CommonTests
59
63
 
60
64
  def test_date_fields
61
65
  @controls[:testable_date_field_names].each do |name|
62
- assert(@dbf.records.any? {|record| record[name].is_a?(Date)})
66
+ assert(@dbf.records.any? {|record| record[name].is_a?(Date) || record[name].is_a?(Time)})
63
67
  end
64
68
  end
65
69
 
@@ -81,7 +85,7 @@ module CommonTests
81
85
 
82
86
  def test_memo_fields
83
87
  @controls[:testable_memo_field_names].each do |name|
84
- assert(@dbf.records.any? {|record| record[name].is_a?(String)})
88
+ assert(@dbf.records.any? {|record| record[name].is_a?(String) && record[name].size > 1})
85
89
  end
86
90
  end
87
91
 
@@ -10,6 +10,7 @@ class DBaseIIIReadTest < Test::Unit::TestCase
10
10
  @controls = {
11
11
  :version => "03",
12
12
  :has_memo_file => false,
13
+ :memo_file_format => nil,
13
14
  :field_count => 31,
14
15
  :record_count => 14,
15
16
  :record_length => 590,
@@ -10,6 +10,7 @@ class FoxproReadTest < Test::Unit::TestCase
10
10
  @controls = {
11
11
  :version => "f5",
12
12
  :has_memo_file => true,
13
+ :memo_file_format => :fpt,
13
14
  :field_count => 59,
14
15
  :record_count => 975,
15
16
  :record_length => 969,
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
4
+ require 'dbf'
5
+ require 'profiler'
6
+
7
+ dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'foxpro.dbf'))
8
+
9
+ Profiler__::start_profile
10
+
11
+ dbf.records
12
+
13
+ Profiler__::stop_profile
14
+ Profiler__::print_profile($stdout)
metadata CHANGED
@@ -3,9 +3,9 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: dbf
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.0
7
- date: 2006-08-12 00:00:00 -07:00
8
- summary: A library for reading DBase (or XBase, Clipper, Foxpro, etc) database files
6
+ version: 0.4.0
7
+ date: 2006-10-07 00:00:00 -07:00
8
+ summary: A library for reading dBase (or xBase, Clipper, Foxpro, etc) database files
9
9
  require_paths:
10
10
  - lib
11
11
  email: keithm@infused.org
@@ -15,7 +15,7 @@ description:
15
15
  autorequire:
16
16
  default_executable:
17
17
  bindir: bin
18
- has_rdoc: false
18
+ has_rdoc: true
19
19
  required_ruby_version: !ruby/object:Gem::Version::Requirement
20
20
  requirements:
21
21
  - - ">"
@@ -36,6 +36,7 @@ files:
36
36
  - test/databases
37
37
  - test/dbase_iii_read_test.rb
38
38
  - test/foxpro_read_test.rb
39
+ - test/performance.rb
39
40
  - test/databases/dbase_iii.dbf
40
41
  - test/databases/foxpro.dbf
41
42
  - test/databases/foxpro.fpt