]> gitweb.factorcode.org Git - factor.git/blob - extra/dbf/dbf.factor
factor: trim using lists
[factor.git] / extra / dbf / dbf.factor
1 ! Copyright (C) 2018 John Benediktsson
2 ! See http://factorcode.org/license.txt for BSD license
3
4 USING: accessors alien.c-types alien.endian assocs calendar
5 calendar.parser classes.struct combinators endian io
6 io.encodings.8-bit io.encodings.ascii io.encodings.binary
7 io.encodings.string io.files io.streams.byte-array kernel math
8 math.parser namespaces sequences ;
9
10 IN: dbf
11
12 SYMBOL: dbf-encoding
13
14 CONSTANT: dbf-encodings H{
15     { 0x00 ascii }
16     { 0x01 cp437 }
17     { 0x02 cp850 }
18     { 0x03 windows-1252 }
19     { 0x04 mac-roman }
20     { 0x08 cp865 }
21     { 0x09 cp437 }
22     { 0x0A cp850 }
23     { 0x0B cp437 }
24     { 0x0D cp437 }
25     { 0x0E cp850 }
26     { 0x0F cp437 }
27     { 0x10 cp850 }
28     { 0x11 cp437 }
29     { 0x12 cp850 }
30     { 0x13 "cp932" }
31     { 0x14 cp850 }
32     { 0x15 cp437 }
33     { 0x16 cp850 }
34     { 0x17 cp865 }
35     { 0x18 cp437 }
36     { 0x19 cp437 }
37     { 0x1A cp850 }
38     { 0x1B cp437 }
39     { 0x1C cp863 }
40     { 0x1D cp850 }
41     { 0x1F cp852 }
42     { 0x22 cp852 }
43     { 0x23 cp852 }
44     { 0x24 cp860 }
45     { 0x25 cp850 }
46     { 0x26 cp866 }
47     { 0x37 cp850 }
48     { 0x40 cp852 }
49     { 0x4D "cp936" }
50     { 0x4E "cp949" }
51     { 0x4F "cp950" }
52     { 0x50 "cp874" }
53     { 0x57 windows-1252 }
54     { 0x58 windows-1252 }
55     { 0x59 windows-1252 }
56     { 0x64 cp852 }
57     { 0x65 cp866 }
58     { 0x66 cp865 }
59     { 0x67 cp861 }
60     { 0x68 f }
61     { 0x69 f }
62     { 0x6a "cp737" }
63     { 0x6b cp857 }
64     { 0x6c cp863 }
65     { 0x78 "cp950" }
66     { 0x79 "cp949" }
67     { 0x7a "cp936" }
68     { 0x7b "cp932" }
69     { 0x7c "cp874" }
70     { 0x7d windows-1255 }
71     { 0x7e windows-1256 }
72     { 0x86 "cp737" }
73     { 0x87 cp852 }
74     { 0x88 cp857 }
75     { 0x96 "mac-cyrillic" }
76     { 0x97 "mac-latin2" }
77     { 0x98 "mac-greek" }
78     { 0xc8 windows-1250 }
79     { 0xc9 windows-1251 }
80     { 0xca windows-1254 }
81     { 0xcb windows-1253 }
82     { 0xcc windows-1250 }
83 }
84
85 CONSTANT: dbf-file-types H{
86     { 0x02 "FoxBASE" }
87     { 0x03 "FoxBASE+/dBase III plus, no memo" }
88     { 0x04 "dBase IV, no memo" }
89     { 0x05 "dBase V, no memo" }
90     { 0x07 "Visual Objects 1.x" }
91     { 0x30 "Visual FoxPro" }
92     { 0x31 "Visual FoxPro, autoincrement enabled" }
93     { 0x32 "Visual FoxPro with field type Varchar or Varbinary" }
94     { 0x43 "dBase IV SQL table files, no memo" }
95     { 0x63 "dBase IV SQL system files, no memo" }
96     { 0x7b "dBase IV, with memo" }
97     { 0x83 "FoxBASE+/dBase III PLUS, with memo" }
98     { 0x87 "Visual Objects 1.x, with memo" }
99     { 0x8B "dBase IV with memo" }
100     { 0x8E "dBase IV with SQL table" }
101     { 0xCB "dBase IV SQL table files, with memo" }
102     { 0xE5 "HiPer-Six format with SMT memo file" }
103     { 0xF5 "FoxPro 2.x (or earlier) with memo" }
104     { 0xFB "FoxBASE" }
105 }
106
107 STRUCT: dbf-file-header
108     { file-type uint8_t }
109     { year uint8_t }
110     { month uint8_t }
111     { day uint8_t }
112     { #records uint32_t }
113     { header-length uint16_t }
114     { record-length uint16_t }
115     { reserved1 uint16_t }
116     { incomplete-transaction uint8_t }
117     { encryption-flag uint8_t }
118     { free-record-thread uint32_t }
119     { reserved2 uint32_t }
120     { reserved3 uint32_t }
121     { mdx-flag uint8_t }
122     { language-driver uint8_t }
123     { reserved4 uint16_t } ;
124
125 : read-file-header ( -- file-header )
126     dbf-file-header read-struct ;
127
128 CONSTANT: dbf-field-flags H{
129     { 0x01 "System Column (not visible to user)" }
130     { 0x02 "Column can store null values" }
131     { 0x04 "Binary column (for CHAR and MEMO only)" }
132     { 0x06 "(0x02+0x04) When a field is NULL and binary (Integer, Currency, and Character/Memo fields)" }
133     { 0x0C "Column is autoincrementing" }
134 }
135
136 STRUCT: dbf-field-header
137     { name uint8_t[11] }
138     { type uint8_t }
139     { address uint32_t }
140     { length uint8_t }
141     { #decimals uint8_t }
142     { reserved1 uint16_t }
143     { workarea-id uint8_t }
144     { reserved2 uint8_t }
145     { reserved3 uint8_t }
146     { set-fields-flag uint8_t }
147     { reserved4 uint8_t[7] }
148     { index-field-flag uint8_t } ;
149
150 : read-field-headers ( -- field-headers )
151     [ read1 dup { CHAR: \r CHAR: \n f } member? not ] [
152         dbf-field-header heap-size 1 - read swap prefix
153         dbf-field-header memory>struct
154     ] produce nip ;
155
156 : check-field-header ( field-header -- field-header )
157     dup type>> {
158         { CHAR: I [ dup length>> 4 assert= ] }
159         { CHAR: L [ dup length>> 1 assert= ] }
160         { CHAR: O [ dup length>> 8 assert= ] }
161         { CHAR: Y [ dup length>> 8 assert= ] }
162         { CHAR: D [ dup length>> 8 assert= ] }
163         { CHAR: T [ dup length>> 8 assert= ] }
164         { CHAR: M [ dup length>> 10 assert= ] }
165         [ drop ]
166     } case ;
167
168 : check-record-length ( file-header field-headers -- )
169     [ record-length>> ] [ [ length>> ] map-sum ] bi* assert= ;
170
171 DEFER: parse-field
172
173 TUPLE: record deleted? values ;
174
175 : read-records ( field-headers -- records )
176     [ read1 dup { 0x1a f } member? not ]
177     [
178         CHAR: * = over [
179             [ length>> read ]
180             [ type>> parse-field ] bi
181         ] map record boa
182     ] produce 2nip ;
183
184 TUPLE: dbf file-header field-headers records ;
185
186 : load-dbf ( path -- dbf )
187     binary [
188         read-file-header
189         read-field-headers
190         over header-length>> seek-absolute seek-input
191         over language-driver>> dbf-encodings at dbf-encoding [
192             dup read-records dbf boa
193         ] with-variable
194     ] with-file-reader ;
195
196 : seek-record ( n file-header -- )
197     [ record-length>> * ] [ header-length>> ] bi +
198     seek-absolute seek-input ;
199
200 : parse-string ( byte-array -- string )
201     [ " \0" member? ] trim-tail dbf-encoding get decode ;
202
203 : parse-date ( byte-array -- date/f )
204     dup [ " \0" member? ] all? [ drop f ] [
205         binary [ read-ymd <date-gmt> ] with-byte-reader
206     ] if ;
207
208 : parse-float ( byte-array -- n )
209     [ "\r\n\t *" member? ] trim string>number ;
210
211 : parse-int ( byte-array -- n )
212     dup length 4 assert= le> ;
213
214 : parse-short ( byte-array -- n )
215     dup length 2 assert= le> ;
216
217 SYMBOL: unknown
218
219 ERROR: illegal-logical value ;
220
221 : parse-logical ( byte-array -- n )
222     first {
223         { [ dup "TtYy" member? ] [ drop t ] }
224         { [ dup "FfNn" member? ] [ drop f ] }
225         { [ dup "? " member? ] [ drop unknown ] }
226         [ illegal-logical ]
227     } cond ;
228
229 : parse-numeric ( byte-array -- n )
230     [ "\r\n\t *" member? ] trim
231     H{ { CHAR: , CHAR: . } } substitute string>number ;
232
233 : parse-double ( byte-array -- n )
234     dup length 8 assert= le> bits>double ;
235
236 : parse-currency ( byte-array -- n )
237     dup length 8 assert= le> 10000 / ;
238
239 : parse-timestamp ( byte-array -- timestamp )
240     [ -4713 1 1 <date> ] dip 4 cut [ le> ] bi@
241     [ days time+ ] [ milliseconds time+ ] bi* ;
242
243 ERROR: unsupported-field-type type ;
244
245 : parse-field ( byte-array type -- data )
246     {
247         { CHAR: \0 [ ] }
248         { CHAR: 2  [ parse-short ] }
249         { CHAR: 4  [ parse-int ] }
250         { CHAR: 8  [ parse-double ] }
251         { CHAR: C  [ parse-string ] }
252         { CHAR: D  [ parse-date ] }
253         { CHAR: F  [ parse-float ] }
254         { CHAR: I  [ parse-int ] }
255         { CHAR: L  [ parse-logical ] }
256         { CHAR: N  [ parse-numeric ] }
257         { CHAR: O  [ parse-double ] }
258         { CHAR: V  [ parse-string ] }
259         { CHAR: Y  [ parse-currency ] }
260         { CHAR: @  [ parse-timestamp ] }
261         ! { CHAR: +  [ parse-autoincrement ] }
262         ! { CHAR: M  [ parse-memo ] }
263         ! { CHAR: T  [ parse-datetime ] }
264         ! { CHAR: B  [ parse-double? ] } ! (only on dbversion in [0x30, 0x31, 0x32])
265         ! { CHAR: G  [ parse-general ] }
266         ! { CHAR: P  [ parse-picture ] }
267         ! { CHAR: Q  [ parse-varbinary ] }
268         [ unsupported-field-type ]
269     } case ;
270
271 : dbase3-memo ( n path -- data )
272     binary [
273         512 * seek-absolute seek-input
274         B{ } [
275             512 read
276             dup [ B{ 0 0x1a } member? ] find drop
277             [ head f ] [ t ] if* [ append ] dip
278         ] loop
279     ] with-file-reader ;
280
281 LE-STRUCT: db4-memo-header
282     { reserved uint } ! B{ 0xff 0xff 0x08 0x08 }
283     { length uint } ;
284
285 : dbase4-memo ( n path -- data )
286     binary [
287         512 * seek-absolute seek-input
288         db4-memo-header read-struct length>> read
289     ] with-file-reader ;
290
291 BE-STRUCT: vfp-file-header
292     { nextblock uint }
293     { reserved1 ushort }
294     { blocksize ushort }
295     { reserved2 uchar[504] } ;
296
297 BE-STRUCT: vfp-memo-header
298     { type uint }
299     { length uint } ;
300
301 CONSTANT: vfp-memo-types H{
302     { 0x0 "picture memo" }
303     { 0x1 "text memo" }
304     { 0x2 "object memo" }
305 }
306
307 : vfp-memo ( n path -- data )
308     binary [
309         vfp-file-header read-struct blocksize>> *
310         seek-absolute seek-input
311         vfp-memo-header read-struct length>> read
312     ] with-file-reader ;