dbf 0.3.0 → 0.4.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/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