dbf 1.7.8 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|