dbf 1.7.8 → 2.0.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/CHANGELOG.md +4 -2
- data/README.md +6 -6
- data/Rakefile +0 -2
- data/dbf.gemspec +0 -1
- data/lib/dbf.rb +2 -4
- data/lib/dbf/column/base.rb +15 -2
- data/lib/dbf/memo/base.rb +5 -1
- data/lib/dbf/record.rb +32 -19
- data/lib/dbf/table.rb +11 -6
- data/lib/dbf/table/encodings.rb +66 -0
- data/lib/dbf/version.rb +1 -1
- data/spec/dbf/column_spec.rb +17 -15
- data/spec/dbf/file_formats_spec.rb +35 -61
- data/spec/dbf/record_spec.rb +17 -8
- data/spec/dbf/table_spec.rb +49 -71
- data/spec/spec_helper.rb +7 -1
- metadata +2 -4
- data/lib/dbf/attributes.rb +0 -6
- data/lib/dbf/encodings.yml +0 -61
- data/lib/dbf/util.rb +0 -9
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
#
|
2
|
-
-
|
1
|
+
# 2.0.0
|
2
|
+
- #44 Require FasterCSV gem on all platforms
|
3
3
|
- Remove rdoc development dependency
|
4
|
+
- #42 Fixes encoding of memos
|
5
|
+
- #43 Improve handling of record attributes
|
4
6
|
|
5
7
|
# 1.7.5
|
6
8
|
- fixes FoxPro currency (Y) fields
|
data/README.md
CHANGED
@@ -39,14 +39,14 @@ Find a single record
|
|
39
39
|
|
40
40
|
widget.find(6)
|
41
41
|
|
42
|
-
Attributes can also be accessed through the attributes hash
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
Attributes can also be accessed through the attributes hash or the record
|
43
|
+
object using either the original or underscored attribute name. Note that
|
44
|
+
find() will return nil if the requested record has been deleted and not yet
|
45
|
+
pruned from the database.
|
46
46
|
|
47
47
|
widget.find(4).attributes["SlotNumber"]
|
48
|
-
widget.find(4)
|
49
|
-
widget.find(4)
|
48
|
+
widget.find(4)["SlotNumber"]
|
49
|
+
widget.find(4)[:slot_number]
|
50
50
|
|
51
51
|
Search for records using a simple hash format. Multiple search criteria are
|
52
52
|
ANDed. Use the block form if the resulting recordset could be large, otherwise
|
data/Rakefile
CHANGED
data/dbf.gemspec
CHANGED
data/lib/dbf.rb
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
require 'date'
|
2
2
|
|
3
|
-
require 'yaml'
|
4
3
|
require 'csv'
|
5
4
|
if CSV.const_defined? :Reader
|
6
5
|
require 'fastercsv'
|
7
6
|
end
|
8
7
|
|
9
|
-
require 'dbf/util'
|
10
|
-
require 'dbf/attributes'
|
11
8
|
require 'dbf/record'
|
12
9
|
require 'dbf/column/base'
|
13
10
|
require 'dbf/column/dbase'
|
14
11
|
require 'dbf/column/foxpro'
|
15
12
|
require 'dbf/table'
|
13
|
+
require 'dbf/table/encodings'
|
16
14
|
require 'dbf/memo/base'
|
17
15
|
require 'dbf/memo/dbase3'
|
18
16
|
require 'dbf/memo/dbase4'
|
19
|
-
require 'dbf/memo/foxpro'
|
17
|
+
require 'dbf/memo/foxpro'
|
data/lib/dbf/column/base.rb
CHANGED
@@ -33,6 +33,7 @@ module DBF
|
|
33
33
|
when 'T' then decode_datetime(value)
|
34
34
|
when 'L' then boolean(value)
|
35
35
|
when 'B' then unpack_binary(value)
|
36
|
+
when 'M' then decode_memo(value)
|
36
37
|
else encode_string(value.to_s).strip
|
37
38
|
end
|
38
39
|
end
|
@@ -48,8 +49,16 @@ module DBF
|
|
48
49
|
"\"#{underscored_name}\", #{schema_data_type}\n"
|
49
50
|
end
|
50
51
|
|
52
|
+
def self.underscore_name(string)
|
53
|
+
string.gsub(/::/, '/').
|
54
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
55
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
56
|
+
tr('-', '_').
|
57
|
+
downcase
|
58
|
+
end
|
59
|
+
|
51
60
|
def underscored_name
|
52
|
-
@underscored_name ||=
|
61
|
+
@underscored_name ||= self.class.underscore_name(name)
|
53
62
|
end
|
54
63
|
|
55
64
|
private
|
@@ -66,6 +75,10 @@ module DBF
|
|
66
75
|
seconds = (milliseconds / 1000).to_i
|
67
76
|
DateTime.jd(days, (seconds/3600).to_i, (seconds/60).to_i % 60, seconds % 60) rescue nil
|
68
77
|
end
|
78
|
+
|
79
|
+
def decode_memo(value) #nodoc
|
80
|
+
encode_string(value) if value
|
81
|
+
end
|
69
82
|
|
70
83
|
def unpack_number(value) #nodoc
|
71
84
|
decimal.zero? ? value.to_i : value.to_f
|
@@ -121,4 +134,4 @@ module DBF
|
|
121
134
|
|
122
135
|
end
|
123
136
|
end
|
124
|
-
end
|
137
|
+
end
|
data/lib/dbf/memo/base.rb
CHANGED
data/lib/dbf/record.rb
CHANGED
@@ -10,8 +10,6 @@ module DBF
|
|
10
10
|
def initialize(data, columns, version, memo)
|
11
11
|
@data = StringIO.new(data)
|
12
12
|
@columns, @version, @memo = columns, version, memo
|
13
|
-
@column_names = @columns.map {|column| column.underscored_name}
|
14
|
-
define_accessors
|
15
13
|
end
|
16
14
|
|
17
15
|
# Equality
|
@@ -26,7 +24,7 @@ module DBF
|
|
26
24
|
#
|
27
25
|
# @return [Array]
|
28
26
|
def to_a
|
29
|
-
@
|
27
|
+
@columns.map {|column| attributes[column.name]}
|
30
28
|
end
|
31
29
|
|
32
30
|
# Do all search parameters match?
|
@@ -34,35 +32,50 @@ module DBF
|
|
34
32
|
# @param [Hash] options
|
35
33
|
# @return [Boolean]
|
36
34
|
def match?(options)
|
37
|
-
options.all? {|key, value|
|
35
|
+
options.all? {|key, value| self[key] == value}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Reads attributes by column name
|
39
|
+
def [](key)
|
40
|
+
key = key.to_s
|
41
|
+
if attributes.has_key?(key)
|
42
|
+
attributes[key]
|
43
|
+
elsif index = column_names.index(key)
|
44
|
+
attributes[@columns[index].name]
|
45
|
+
end
|
38
46
|
end
|
39
47
|
|
40
48
|
# @return [Hash]
|
41
49
|
def attributes
|
42
|
-
@attributes ||=
|
43
|
-
attributes = Attributes.new
|
44
|
-
@columns.each {|column| attributes[column.name] = init_attribute(column)}
|
45
|
-
attributes
|
46
|
-
end
|
50
|
+
@attributes ||= Hash[@columns.map {|column| [column.name, init_attribute(column)]}]
|
47
51
|
end
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
def respond_to?(method, *args)
|
54
|
+
return true if column_names.include?(method.to_s)
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(method, *args)
|
59
|
+
if index = column_names.index(method.to_s)
|
60
|
+
attributes[@columns[index].name]
|
61
|
+
else
|
62
|
+
super
|
57
63
|
end
|
58
64
|
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def column_names
|
69
|
+
@column_names ||= @columns.map {|column| column.underscored_name}
|
70
|
+
end
|
59
71
|
|
60
72
|
def init_attribute(column) #nodoc
|
61
|
-
if column.memo?
|
73
|
+
value = if column.memo?
|
62
74
|
@memo.get get_memo_start_block(column)
|
63
75
|
else
|
64
|
-
|
76
|
+
unpack_data(column)
|
65
77
|
end
|
78
|
+
column.type_cast value
|
66
79
|
end
|
67
80
|
|
68
81
|
def get_memo_start_block(column) #nodoc
|
data/lib/dbf/table.rb
CHANGED
@@ -66,8 +66,17 @@ module DBF
|
|
66
66
|
#
|
67
67
|
# @return [TrueClass, FalseClass]
|
68
68
|
def close
|
69
|
+
@data.close
|
69
70
|
@memo && @memo.close
|
70
|
-
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [TrueClass, FalseClass]
|
74
|
+
def closed?
|
75
|
+
if @memo
|
76
|
+
@data.closed? && @memo.closed?
|
77
|
+
else
|
78
|
+
@data.closed?
|
79
|
+
end
|
71
80
|
end
|
72
81
|
|
73
82
|
# @return String
|
@@ -272,7 +281,7 @@ module DBF
|
|
272
281
|
def get_header_info #nodoc
|
273
282
|
@data.rewind
|
274
283
|
@version, @record_count, @header_length, @record_length, @encoding_key = read_header
|
275
|
-
@encoding =
|
284
|
+
@encoding = ENCODINGS[@encoding_key] if supports_encoding?
|
276
285
|
end
|
277
286
|
|
278
287
|
def read_header #nodoc
|
@@ -286,10 +295,6 @@ module DBF
|
|
286
295
|
def csv_class #nodoc
|
287
296
|
@csv_class ||= CSV.const_defined?(:Reader) ? FCSV : CSV
|
288
297
|
end
|
289
|
-
|
290
|
-
def self.encodings #nodoc
|
291
|
-
@encodings ||= YAML.load_file File.expand_path("../encodings.yml", __FILE__)
|
292
|
-
end
|
293
298
|
end
|
294
299
|
|
295
300
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module DBF
|
2
|
+
class Table
|
3
|
+
# inspired by http://trac.osgeo.org/gdal/ticket/2864
|
4
|
+
ENCODINGS = {
|
5
|
+
"01" => "cp437", # U.S. MS–DOS
|
6
|
+
"02" => "cp850", # International MS–DOS
|
7
|
+
"03" => "cp1252", # Windows ANSI
|
8
|
+
"08" => "cp865", # Danish OEM
|
9
|
+
"09" => "cp437", # Dutch OEM
|
10
|
+
"0a" => "cp850", # Dutch OEM*
|
11
|
+
"0b" => "cp437", # Finnish OEM
|
12
|
+
"0d" => "cp437", # French OEM
|
13
|
+
"0e" => "cp850", # French OEM*
|
14
|
+
"0f" => "cp437", # German OEM
|
15
|
+
"10" => "cp850", # German OEM*
|
16
|
+
"11" => "cp437", # Italian OEM
|
17
|
+
"12" => "cp850", # Italian OEM*
|
18
|
+
"13" => "cp932", # Japanese Shift-JIS
|
19
|
+
"14" => "cp850", # Spanish OEM*
|
20
|
+
"15" => "cp437", # Swedish OEM
|
21
|
+
"16" => "cp850", # Swedish OEM*
|
22
|
+
"17" => "cp865", # Norwegian OEM
|
23
|
+
"18" => "cp437", # Spanish OEM
|
24
|
+
"19" => "cp437", # English OEM (Britain)
|
25
|
+
"1a" => "cp850", # English OEM (Britain)*
|
26
|
+
"1b" => "cp437", # English OEM (U.S.)
|
27
|
+
"1c" => "cp863", # French OEM (Canada)
|
28
|
+
"1d" => "cp850", # French OEM*
|
29
|
+
"1f" => "cp852", # Czech OEM
|
30
|
+
"22" => "cp852", # Hungarian OEM
|
31
|
+
"23" => "cp852", # Polish OEM
|
32
|
+
"24" => "cp860", # Portuguese OEM
|
33
|
+
"25" => "cp850", # Portuguese OEM*
|
34
|
+
"26" => "cp866", # Russian OEM
|
35
|
+
"37" => "cp850", # English OEM (U.S.)*
|
36
|
+
"40" => "cp852", # Romanian OEM
|
37
|
+
"4d" => "cp936", # Chinese GBK (PRC)
|
38
|
+
"4e" => "cp949", # Korean (ANSI/OEM)
|
39
|
+
"4f" => "cp950", # Chinese Big5 (Taiwan)
|
40
|
+
"50" => "cp874", # Thai (ANSI/OEM)
|
41
|
+
"57" => "cp1252", # ANSI
|
42
|
+
"58" => "cp1252", # Western European ANSI
|
43
|
+
"59" => "cp1252", # Spanish ANSI
|
44
|
+
"64" => "cp852", # Eastern European MS–DOS
|
45
|
+
"65" => "cp866", # Russian MS–DOS
|
46
|
+
"66" => "cp865", # Nordic MS–DOS
|
47
|
+
"67" => "cp861", # Icelandic MS–DOS
|
48
|
+
"6a" => "cp737", # Greek MS–DOS (437G)
|
49
|
+
"6b" => "cp857", # Turkish MS–DOS
|
50
|
+
"6c" => "cp863", # French–Canadian MS–DOS
|
51
|
+
"78" => "cp950", # Taiwan Big 5
|
52
|
+
"79" => "cp949", # Hangul (Wansung)
|
53
|
+
"7a" => "cp936", # PRC GBK
|
54
|
+
"7b" => "cp932", # Japanese Shift-JIS
|
55
|
+
"7c" => "cp874", # Thai Windows/MS–DOS
|
56
|
+
"86" => "cp737", # Greek OEM
|
57
|
+
"87" => "cp852", # Slovenian OEM
|
58
|
+
"88" => "cp857", # Turkish OEM
|
59
|
+
"c8" => "cp1250", # Eastern European Windows
|
60
|
+
"c9" => "cp1251", # Russian Windows
|
61
|
+
"ca" => "cp1254", # Turkish Windows
|
62
|
+
"cb" => "cp1253", # Greek Windows
|
63
|
+
"cc" => "cp1257", # Baltic Windows
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
data/lib/dbf/version.rb
CHANGED
data/spec/dbf/column_spec.rb
CHANGED
@@ -22,15 +22,21 @@ describe DBF::Column::Dbase do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
describe 'with length of 0' do
|
25
|
-
|
25
|
+
it 'raises DBF::Column::LengthError' do
|
26
|
+
expect { DBF::Column::Dbase.new "ColumnName", "N", 0, 0, "30" }.to raise_error(DBF::Column::LengthError)
|
27
|
+
end
|
26
28
|
end
|
27
29
|
|
28
30
|
describe 'with length less than 0' do
|
29
|
-
|
31
|
+
it 'raises DBF::Column::LengthError' do
|
32
|
+
expect { DBF::Column::Dbase.new "ColumnName", "N", -1, 0, "30" }.to raise_error(DBF::Column::LengthError)
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
describe 'with empty column name' do
|
33
|
-
|
37
|
+
it 'raises DBF::Column::NameError' do
|
38
|
+
expect { DBF::Column::Dbase.new "\xFF\xFC", "N", 1, 0, "30" }.to raise_error(DBF::Column::NameError)
|
39
|
+
end
|
34
40
|
end
|
35
41
|
end
|
36
42
|
|
@@ -97,12 +103,14 @@ describe DBF::Column::Dbase do
|
|
97
103
|
end
|
98
104
|
end
|
99
105
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
+
if ruby_supports_mathn?
|
107
|
+
context 'when requiring mathn' do
|
108
|
+
it "casts to DateTime" do
|
109
|
+
expect do
|
110
|
+
require 'mathn'
|
111
|
+
column.type_cast("Nl%\000\300Z\252\003")
|
112
|
+
end.call.should == DateTime.parse("2002-10-10T17:04:56+00:00")
|
113
|
+
end
|
106
114
|
end
|
107
115
|
end
|
108
116
|
|
@@ -167,12 +175,6 @@ describe DBF::Column::Dbase do
|
|
167
175
|
column.schema_definition.should == "\"column_name\", :integer\n"
|
168
176
|
end
|
169
177
|
end
|
170
|
-
|
171
|
-
context "when non-Foxpro dbf" do
|
172
|
-
it "outputs a text column" do
|
173
|
-
|
174
|
-
end
|
175
|
-
end
|
176
178
|
end
|
177
179
|
|
178
180
|
it "defines a float colmn if type is (N)umber with more than 0 decimals" do
|
@@ -2,179 +2,153 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
shared_examples_for 'DBF' do
|
4
4
|
specify "sum of column lengths should equal record length specified in header plus one" do
|
5
|
-
header_record_length =
|
6
|
-
sum_of_column_lengths =
|
5
|
+
header_record_length = table.instance_eval {@record_length}
|
6
|
+
sum_of_column_lengths = table.columns.inject(1) {|sum, column| sum += column.length}
|
7
7
|
|
8
8
|
header_record_length.should == sum_of_column_lengths
|
9
9
|
end
|
10
10
|
|
11
11
|
specify "records should be instances of DBF::Record" do
|
12
|
-
|
12
|
+
table.all? {|record| record.is_a?(DBF::Record)}.should be_true
|
13
13
|
end
|
14
14
|
|
15
15
|
specify "record count should be the same as reported in the header" do
|
16
|
-
|
16
|
+
table.entries.size.should == table.record_count
|
17
17
|
end
|
18
18
|
|
19
19
|
specify "column names should not be blank" do
|
20
|
-
|
20
|
+
table.columns.all? {|column| !column.name.empty?}.should be_true
|
21
21
|
end
|
22
22
|
|
23
23
|
specify "column types should be valid" do
|
24
24
|
valid_column_types = %w(C N L D M F B G P Y T I V X @ O + 0)
|
25
|
-
|
25
|
+
table.columns.all? {|column| valid_column_types.include?(column.type)}.should be_true
|
26
26
|
end
|
27
27
|
|
28
28
|
specify "column lengths should be instances of Fixnum" do
|
29
|
-
|
29
|
+
table.columns.all? {|column| column.length.is_a?(Fixnum)}.should be_true
|
30
30
|
end
|
31
31
|
|
32
32
|
specify "column lengths should be larger than 0" do
|
33
|
-
|
33
|
+
table.columns.all? {|column| column.length > 0}.should be_true
|
34
34
|
end
|
35
35
|
|
36
36
|
specify "column decimals should be instances of Fixnum" do
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
specify "column read accessors should return the attribute after typecast" do
|
41
|
-
@table.columns do |column|
|
42
|
-
record = @table.records.first
|
43
|
-
record.send(column.name).should == record[column.name]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
specify "column attributes should be accessible in underscored form" do
|
48
|
-
@table.columns do |column|
|
49
|
-
record = @table.records.first
|
50
|
-
record.send(column_name).should == record.send(Util.underscore(column_name))
|
51
|
-
end
|
37
|
+
table.columns.all? {|column| column.decimal.is_a?(Fixnum)}.should be_true
|
52
38
|
end
|
53
39
|
end
|
54
40
|
|
55
41
|
shared_examples_for 'Foxpro DBF' do
|
56
42
|
specify "columns should be instances of DBF::FoxproColumn" do
|
57
|
-
|
43
|
+
table.columns.all? {|column| column.is_a?(DBF::Column::Foxpro)}.should be_true
|
58
44
|
end
|
59
45
|
end
|
60
46
|
|
61
47
|
describe DBF, "of type 03 (dBase III without memo file)" do
|
62
|
-
|
63
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_03.dbf"
|
64
|
-
end
|
48
|
+
let(:table) { DBF::Table.new "#{DB_PATH}/dbase_03.dbf" }
|
65
49
|
|
66
50
|
it_should_behave_like "DBF"
|
67
51
|
|
68
52
|
it "should report the correct version number" do
|
69
|
-
|
53
|
+
table.version.should == "03"
|
70
54
|
end
|
71
55
|
|
72
56
|
it "should report the correct version description" do
|
73
|
-
|
57
|
+
table.version_description.should == "dBase III without memo file"
|
74
58
|
end
|
75
59
|
|
76
60
|
it "should determine the number of records" do
|
77
|
-
|
61
|
+
table.record_count.should == 14
|
78
62
|
end
|
79
63
|
end
|
80
64
|
|
81
65
|
describe DBF, "of type 30 (Visual FoxPro)" do
|
82
|
-
|
83
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_30.dbf"
|
84
|
-
end
|
66
|
+
let(:table) { DBF::Table.new "#{DB_PATH}/dbase_30.dbf" }
|
85
67
|
|
86
68
|
it_should_behave_like "DBF"
|
87
69
|
|
88
70
|
it "should report the correct version number" do
|
89
|
-
|
71
|
+
table.version.should == "30"
|
90
72
|
end
|
91
73
|
|
92
74
|
it "should report the correct version description" do
|
93
|
-
|
75
|
+
table.version_description.should == "Visual FoxPro"
|
94
76
|
end
|
95
77
|
|
96
78
|
it "should determine the number of records" do
|
97
|
-
|
79
|
+
table.record_count.should == 34
|
98
80
|
end
|
99
81
|
end
|
100
82
|
|
101
83
|
describe DBF, "of type 31 (Visual FoxPro with AutoIncrement field)" do
|
102
|
-
|
103
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_31.dbf"
|
104
|
-
end
|
84
|
+
let(:table) { DBF::Table.new "#{DB_PATH}/dbase_31.dbf" }
|
105
85
|
|
106
86
|
it_should_behave_like "DBF"
|
107
87
|
|
108
88
|
it "should have a dBase version of 31" do
|
109
|
-
|
89
|
+
table.version.should == "31"
|
110
90
|
end
|
111
91
|
|
112
92
|
it "should report the correct version description" do
|
113
|
-
|
93
|
+
table.version_description.should == "Visual FoxPro with AutoIncrement field"
|
114
94
|
end
|
115
95
|
|
116
96
|
it "should determine the number of records" do
|
117
|
-
|
97
|
+
table.record_count.should == 77
|
118
98
|
end
|
119
99
|
end
|
120
100
|
|
121
101
|
describe DBF, "of type 83 (dBase III with memo file)" do
|
122
|
-
|
123
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
124
|
-
end
|
102
|
+
let(:table) { DBF::Table.new "#{DB_PATH}/dbase_83.dbf" }
|
125
103
|
|
126
104
|
it_should_behave_like "DBF"
|
127
105
|
|
128
106
|
it "should report the correct version number" do
|
129
|
-
|
107
|
+
table.version.should == "83"
|
130
108
|
end
|
131
109
|
|
132
110
|
it "should report the correct version description" do
|
133
|
-
|
111
|
+
table.version_description.should == "dBase III with memo file"
|
134
112
|
end
|
135
113
|
|
136
114
|
it "should determine the number of records" do
|
137
|
-
|
115
|
+
table.record_count.should == 67
|
138
116
|
end
|
139
117
|
end
|
140
118
|
|
141
119
|
describe DBF, "of type 8b (dBase IV with memo file)" do
|
142
|
-
|
143
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_8b.dbf"
|
144
|
-
end
|
120
|
+
let(:table) { DBF::Table.new "#{DB_PATH}/dbase_8b.dbf" }
|
145
121
|
|
146
122
|
it_should_behave_like "DBF"
|
147
123
|
|
148
124
|
it "should report the correct version number" do
|
149
|
-
|
125
|
+
table.version.should == "8b"
|
150
126
|
end
|
151
127
|
|
152
128
|
it "should report the correct version description" do
|
153
|
-
|
129
|
+
table.version_description.should == "dBase IV with memo file"
|
154
130
|
end
|
155
131
|
|
156
132
|
it "should determine the number of records" do
|
157
|
-
|
133
|
+
table.record_count.should == 10
|
158
134
|
end
|
159
135
|
end
|
160
136
|
|
161
137
|
describe DBF, "of type f5 (FoxPro with memo file)" do
|
162
|
-
|
163
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_f5.dbf"
|
164
|
-
end
|
138
|
+
let(:table) { DBF::Table.new "#{DB_PATH}/dbase_f5.dbf" }
|
165
139
|
|
166
140
|
it_should_behave_like "DBF"
|
167
141
|
it_should_behave_like "Foxpro DBF"
|
168
142
|
|
169
143
|
it "should report the correct version number" do
|
170
|
-
|
144
|
+
table.version.should == "f5"
|
171
145
|
end
|
172
146
|
|
173
147
|
it "should report the correct version description" do
|
174
|
-
|
148
|
+
table.version_description.should == "FoxPro with memo file"
|
175
149
|
end
|
176
150
|
|
177
151
|
it "should determine the number of records" do
|
178
|
-
|
152
|
+
table.record_count.should == 975
|
179
153
|
end
|
180
|
-
end
|
154
|
+
end
|
data/spec/dbf/record_spec.rb
CHANGED
@@ -14,31 +14,40 @@ describe DBF::Record do
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
describe '#==' do
|
18
|
-
|
17
|
+
describe '#==' do
|
18
|
+
let :record do
|
19
19
|
table = DBF::Table.new "#{DB_PATH}/dbase_8b.dbf"
|
20
|
-
|
20
|
+
table.record(9)
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'should be false if other does not have attributes' do
|
24
|
-
(
|
24
|
+
(record == mock('other')).should be_false
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'should be true if other attributes match' do
|
28
28
|
attributes = {:x => 1, :y => 2}
|
29
|
-
|
29
|
+
record.stub!(:attributes).and_return(attributes)
|
30
30
|
other = mock('object', :attributes => attributes)
|
31
|
-
(
|
31
|
+
(record == other).should be_true
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
describe 'column accessors' do
|
36
36
|
let(:table) { DBF::Table.new "#{DB_PATH}/dbase_8b.dbf"}
|
37
|
+
let(:record) { table.find(0) }
|
37
38
|
|
38
|
-
it 'should
|
39
|
-
record = table.find(0)
|
39
|
+
it 'should have dynamic accessors for the columns' do
|
40
40
|
record.should respond_to(:character)
|
41
41
|
record.character.should == 'One'
|
42
|
+
record.float.should == 1.23456789012346
|
43
|
+
record.logical.should == true
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should not define accessor methods on the base class' do
|
47
|
+
second_table = DBF::Table.new "#{DB_PATH}/dbase_03.dbf"
|
48
|
+
second_record = second_table.find(0)
|
49
|
+
record.character.should == 'One'
|
50
|
+
expect { second_record.character }.to raise_error(NoMethodError)
|
42
51
|
end
|
43
52
|
end
|
44
53
|
|
data/spec/dbf/table_spec.rb
CHANGED
@@ -6,20 +6,20 @@ describe DBF::Table do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
describe '#initialize' do
|
9
|
-
it '
|
9
|
+
it 'accepts a DBF filename' do
|
10
10
|
expect { DBF::Table.new "#{DB_PATH}/dbase_83.dbf" }.to_not raise_error
|
11
11
|
end
|
12
12
|
|
13
|
-
it '
|
13
|
+
it 'accepts a DBF and Memo filename' do
|
14
14
|
expect { DBF::Table.new "#{DB_PATH}/dbase_83.dbf", "#{DB_PATH}/dbase_83.dbt" }.to_not raise_error
|
15
15
|
end
|
16
16
|
|
17
|
-
it '
|
17
|
+
it 'accepts an io-like data object' do
|
18
18
|
data = StringIO.new File.read("#{DB_PATH}/dbase_83.dbf")
|
19
19
|
expect { DBF::Table.new data }.to_not raise_error
|
20
20
|
end
|
21
21
|
|
22
|
-
it '
|
22
|
+
it 'accepts an io-like data and memo object' do
|
23
23
|
data = StringIO.new File.read("#{DB_PATH}/dbase_83.dbf")
|
24
24
|
memo = StringIO.new File.read("#{DB_PATH}/dbase_83.dbt")
|
25
25
|
expect { DBF::Table.new data, memo }.to_not raise_error
|
@@ -27,17 +27,16 @@ describe DBF::Table do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
context "when closed" do
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
it "should close the data file" do
|
36
|
-
@table.instance_eval { @data }.should be_closed
|
30
|
+
it "closes the data and memo files" do
|
31
|
+
table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
32
|
+
table.close
|
33
|
+
table.should be_closed
|
37
34
|
end
|
38
35
|
|
39
|
-
it "
|
40
|
-
|
36
|
+
it "closes the data" do
|
37
|
+
table = DBF::Table.new "#{DB_PATH}/dbase_30.dbf"
|
38
|
+
table.close
|
39
|
+
table.should be_closed
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
@@ -77,143 +76,122 @@ describe DBF::Table do
|
|
77
76
|
end
|
78
77
|
|
79
78
|
describe "#record" do
|
80
|
-
before do
|
81
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
82
|
-
end
|
83
|
-
|
84
79
|
it "return nil for deleted records" do
|
85
|
-
|
86
|
-
|
80
|
+
table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
81
|
+
table.stub!(:deleted_record?).and_return(true)
|
82
|
+
table.record(5).should be_nil
|
87
83
|
end
|
88
84
|
end
|
89
85
|
|
90
86
|
describe "#current_record" do
|
91
|
-
before do
|
92
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
93
|
-
end
|
94
|
-
|
95
87
|
it "should return nil for deleted records" do
|
96
|
-
|
97
|
-
|
88
|
+
table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
89
|
+
table.stub!(:deleted_record?).and_return(true)
|
90
|
+
table.record(0).should be_nil
|
98
91
|
end
|
99
92
|
end
|
100
93
|
|
101
94
|
describe "#find" do
|
95
|
+
let(:table) { DBF::Table.new "#{DB_PATH}/dbase_83.dbf" }
|
96
|
+
|
102
97
|
describe "with index" do
|
103
|
-
before do
|
104
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
105
|
-
end
|
106
|
-
|
107
98
|
it "should return the correct record" do
|
108
|
-
|
99
|
+
table.find(5).should == table.record(5)
|
109
100
|
end
|
110
101
|
end
|
111
102
|
|
112
|
-
describe 'with array of indexes' do
|
113
|
-
before do
|
114
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
115
|
-
end
|
116
|
-
|
103
|
+
describe 'with array of indexes' do
|
117
104
|
it "should return the correct records" do
|
118
|
-
|
105
|
+
table.find([1, 5, 10]).should == [table.record(1), table.record(5), table.record(10)]
|
119
106
|
end
|
120
107
|
end
|
121
108
|
|
122
109
|
describe "with :all" do
|
123
|
-
before do
|
124
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
125
|
-
end
|
126
|
-
|
127
110
|
it "should accept a block" do
|
128
111
|
records = []
|
129
|
-
|
112
|
+
table.find(:all, :weight => 0.0) do |record|
|
130
113
|
records << record
|
131
114
|
end
|
132
|
-
records.should ==
|
115
|
+
records.should == table.find(:all, :weight => 0.0)
|
133
116
|
end
|
134
117
|
|
135
118
|
it "should return all records if options are empty" do
|
136
|
-
|
119
|
+
table.find(:all).should == table.to_a
|
137
120
|
end
|
138
121
|
|
139
122
|
it "should return matching records when used with options" do
|
140
|
-
|
123
|
+
table.find(:all, "WEIGHT" => 0.0).should == table.select {|r| r["weight"] == 0.0}
|
141
124
|
end
|
142
125
|
|
143
126
|
it "should AND multiple search terms" do
|
144
|
-
|
127
|
+
table.find(:all, "ID" => 30, "IMAGE" => "graphics/00000001/TBC01.jpg").should == []
|
145
128
|
end
|
146
129
|
|
147
130
|
it "should match original column names" do
|
148
|
-
|
131
|
+
table.find(:all, "WEIGHT" => 0.0).should_not be_empty
|
149
132
|
end
|
150
133
|
|
151
134
|
it "should match symbolized column names" do
|
152
|
-
|
135
|
+
table.find(:all, :WEIGHT => 0.0).should_not be_empty
|
153
136
|
end
|
154
137
|
|
155
138
|
it "should match downcased column names" do
|
156
|
-
|
139
|
+
table.find(:all, "weight" => 0.0).should_not be_empty
|
157
140
|
end
|
158
141
|
|
159
142
|
it "should match symbolized downcased column names" do
|
160
|
-
|
143
|
+
table.find(:all, :weight => 0.0).should_not be_empty
|
161
144
|
end
|
162
145
|
end
|
163
146
|
|
164
147
|
describe "with :first" do
|
165
|
-
before do
|
166
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
167
|
-
end
|
168
|
-
|
169
148
|
it "should return the first record if options are empty" do
|
170
|
-
|
149
|
+
table.find(:first).should == table.record(0)
|
171
150
|
end
|
172
151
|
|
173
152
|
it "should return the first matching record when used with options" do
|
174
|
-
|
153
|
+
table.find(:first, "CODE" => "C").should == table.record(5)
|
175
154
|
end
|
176
155
|
|
177
156
|
it "should AND multiple search terms" do
|
178
|
-
|
157
|
+
table.find(:first, "ID" => 30, "IMAGE" => "graphics/00000001/TBC01.jpg").should be_nil
|
179
158
|
end
|
180
159
|
end
|
181
160
|
end
|
182
161
|
|
183
162
|
describe "filename" do
|
184
|
-
before do
|
185
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_03.dbf"
|
186
|
-
end
|
187
|
-
|
188
163
|
it 'should be dbase_03.dbf' do
|
189
|
-
|
164
|
+
table = DBF::Table.new "#{DB_PATH}/dbase_03.dbf"
|
165
|
+
table.filename.should == "dbase_03.dbf"
|
190
166
|
end
|
191
167
|
end
|
192
168
|
|
193
169
|
describe 'has_memo_file?' do
|
194
170
|
describe 'without a memo file' do
|
195
|
-
|
196
|
-
|
171
|
+
it 'returns false' do
|
172
|
+
table = DBF::Table.new "#{DB_PATH}/dbase_03.dbf"
|
173
|
+
table.has_memo_file?.should be_false
|
174
|
+
end
|
197
175
|
end
|
198
176
|
|
199
177
|
describe 'with a memo file' do
|
200
|
-
|
201
|
-
|
178
|
+
it 'returns true' do
|
179
|
+
table = DBF::Table.new "#{DB_PATH}/dbase_30.dbf"
|
180
|
+
table.has_memo_file?.should be_true
|
181
|
+
end
|
202
182
|
end
|
203
183
|
end
|
204
184
|
|
205
185
|
describe 'columns' do
|
206
|
-
|
207
|
-
@table = DBF::Table.new "#{DB_PATH}/dbase_03.dbf"
|
208
|
-
end
|
186
|
+
let(:table) { DBF::Table.new "#{DB_PATH}/dbase_03.dbf" }
|
209
187
|
|
210
188
|
it 'should have correct size' do
|
211
|
-
|
189
|
+
table.columns.size.should == 31
|
212
190
|
end
|
213
191
|
|
214
192
|
it 'should have correct names' do
|
215
|
-
|
216
|
-
|
193
|
+
table.columns.first.name.should == 'Point_ID'
|
194
|
+
table.columns[29].name.should == 'Easting'
|
217
195
|
end
|
218
196
|
end
|
219
197
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dbf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -78,18 +78,16 @@ files:
|
|
78
78
|
- README.md
|
79
79
|
- bin/dbf
|
80
80
|
- docs/supported_types.markdown
|
81
|
-
- lib/dbf/attributes.rb
|
82
81
|
- lib/dbf/column/base.rb
|
83
82
|
- lib/dbf/column/dbase.rb
|
84
83
|
- lib/dbf/column/foxpro.rb
|
85
|
-
- lib/dbf/encodings.yml
|
86
84
|
- lib/dbf/memo/base.rb
|
87
85
|
- lib/dbf/memo/dbase3.rb
|
88
86
|
- lib/dbf/memo/dbase4.rb
|
89
87
|
- lib/dbf/memo/foxpro.rb
|
90
88
|
- lib/dbf/record.rb
|
89
|
+
- lib/dbf/table/encodings.rb
|
91
90
|
- lib/dbf/table.rb
|
92
|
-
- lib/dbf/util.rb
|
93
91
|
- lib/dbf/version.rb
|
94
92
|
- lib/dbf.rb
|
95
93
|
- spec/dbf/column_spec.rb
|
data/lib/dbf/attributes.rb
DELETED
data/lib/dbf/encodings.yml
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
# inspired by http://trac.osgeo.org/gdal/ticket/2864
|
2
|
-
|
3
|
-
"01": "cp437" # U.S. MS–DOS
|
4
|
-
"02": "cp850" # International MS–DOS
|
5
|
-
"03": "cp1252" # Windows ANSI
|
6
|
-
"08": "cp865" # Danish OEM
|
7
|
-
"09": "cp437" # Dutch OEM
|
8
|
-
"0a": "cp850" # Dutch OEM*
|
9
|
-
"0b": "cp437" # Finnish OEM
|
10
|
-
"0d": "cp437" # French OEM
|
11
|
-
"0e": "cp850" # French OEM*
|
12
|
-
"0f": "cp437" # German OEM
|
13
|
-
"10": "cp850" # German OEM*
|
14
|
-
"11": "cp437" # Italian OEM
|
15
|
-
"12": "cp850" # Italian OEM*
|
16
|
-
"13": "cp932" # Japanese Shift-JIS
|
17
|
-
"14": "cp850" # Spanish OEM*
|
18
|
-
"15": "cp437" # Swedish OEM
|
19
|
-
"16": "cp850" # Swedish OEM*
|
20
|
-
"17": "cp865" # Norwegian OEM
|
21
|
-
"18": "cp437" # Spanish OEM
|
22
|
-
"19": "cp437" # English OEM (Britain)
|
23
|
-
"1a": "cp850" # English OEM (Britain)*
|
24
|
-
"1b": "cp437" # English OEM (U.S.)
|
25
|
-
"1c": "cp863" # French OEM (Canada)
|
26
|
-
"1d": "cp850" # French OEM*
|
27
|
-
"1f": "cp852" # Czech OEM
|
28
|
-
"22": "cp852" # Hungarian OEM
|
29
|
-
"23": "cp852" # Polish OEM
|
30
|
-
"24": "cp860" # Portuguese OEM
|
31
|
-
"25": "cp850" # Portuguese OEM*
|
32
|
-
"26": "cp866" # Russian OEM
|
33
|
-
"37": "cp850" # English OEM (U.S.)*
|
34
|
-
"40": "cp852" # Romanian OEM
|
35
|
-
"4d": "cp936" # Chinese GBK (PRC)
|
36
|
-
"4e": "cp949" # Korean (ANSI/OEM)
|
37
|
-
"4f": "cp950" # Chinese Big5 (Taiwan)
|
38
|
-
"50": "cp874" # Thai (ANSI/OEM)
|
39
|
-
"57": "cp1252" # ANSI
|
40
|
-
"58": "cp1252" # Western European ANSI
|
41
|
-
"59": "cp1252" # Spanish ANSI
|
42
|
-
"64": "cp852" # Eastern European MS–DOS
|
43
|
-
"65": "cp866" # Russian MS–DOS
|
44
|
-
"66": "cp865" # Nordic MS–DOS
|
45
|
-
"67": "cp861" # Icelandic MS–DOS
|
46
|
-
"6a": "cp737" # Greek MS–DOS (437G)
|
47
|
-
"6b": "cp857" # Turkish MS–DOS
|
48
|
-
"6c": "cp863" # French–Canadian MS–DOS
|
49
|
-
"78": "cp950" # Taiwan Big 5
|
50
|
-
"79": "cp949" # Hangul (Wansung)
|
51
|
-
"7a": "cp936" # PRC GBK
|
52
|
-
"7b": "cp932" # Japanese Shift-JIS
|
53
|
-
"7c": "cp874" # Thai Windows/MS–DOS
|
54
|
-
"86": "cp737" # Greek OEM
|
55
|
-
"87": "cp852" # Slovenian OEM
|
56
|
-
"88": "cp857" # Turkish OEM
|
57
|
-
"c8": "cp1250" # Eastern European Windows
|
58
|
-
"c9": "cp1251" # Russian Windows
|
59
|
-
"ca": "cp1254" # Turkish Windows
|
60
|
-
"cb": "cp1253" # Greek Windows
|
61
|
-
"cc": "cp1257" # Baltic Windows
|