gedcom_ruby 0.3.1 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 42866a57868acd57302baaa15c34da3ee628efae
4
- data.tar.gz: 1d9ea35ed03626c8c246bf2e7af1e12638a90566
3
+ metadata.gz: 652c0329a3da725d78fc30443b0e062f3f833d8e
4
+ data.tar.gz: fa7b0f98c14928554d1b8bb8b74dbd2a2d1be230
5
5
  SHA512:
6
- metadata.gz: 29e7b96122f3964984543612057151c56d639895b440cab466cf4f0dde6e9861c25ac1b3ed6228008fbae652db9af5bfb080b1073bfb8a3a28e5ebabb566a3eb
7
- data.tar.gz: 40fb3f8ac4fae584f9cc23c655c39bc24095a6c0578533db3d5f1b2dce516b7d005f031b10b69f48cff1e5ddb70fa2612e6798c532ec43005ca8213fdd5f61d9
6
+ metadata.gz: 2f42a68770f124ee2b8937355b99adcba49625a57e8e5a042ba31284b153163902ae7373112537f6eb73d4555a7b70704e6a200403627d0ba60a881d5fce0cb9
7
+ data.tar.gz: 6fa98503efb3513e5a931ff360cef94ea25dc53dde29b6644519d0de8e01bd71bd97eb64008c74c6da2715ffcb83eb91d5ce08c7711a2ebc8889b10ec15cd740
@@ -0,0 +1,245 @@
1
+ # -------------------------------------------------------------------------
2
+ # gedcom_date.rb -- module definition for GEDCOM date handler
3
+ # Copyright (C) 2008 Phillip Davies (binary011010@verizon.net)
4
+ # -------------------------------------------------------------------------
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+ # -------------------------------------------------------------------------
19
+ #
20
+ require 'gedcom_ruby/date_parser'
21
+ module GEDCOM
22
+
23
+ class DatePart < GEDCOM_DATE_PARSER::GEDDate
24
+
25
+ # Flags
26
+ NONE = GEDCOM_DATE_PARSER::GFNONE
27
+ PHRASE = GEDCOM_DATE_PARSER::GFPHRASE
28
+ NONSTANDARD = GEDCOM_DATE_PARSER::GFNONSTANDARD
29
+ NOFLAG = GEDCOM_DATE_PARSER::GFNOFLAG
30
+ NODAY = GEDCOM_DATE_PARSER::GFNODAY
31
+ NOMONTH = GEDCOM_DATE_PARSER::GFNOMONTH
32
+ NOYEAR = GEDCOM_DATE_PARSER::GFNOYEAR
33
+ YEARSPAN = GEDCOM_DATE_PARSER::GFYEARSPAN
34
+
35
+ def initialize(type=GEDCOM_DATE_PARSER::GCTGREGORIAN, flags=NONE, data=nil)
36
+ super( type, flags, data )
37
+ end
38
+
39
+ def calendar
40
+ @type
41
+ end
42
+
43
+ def compliance
44
+ @flags
45
+ end
46
+
47
+ def phrase
48
+ raise DateFormatException if( @flags != PHRASE )
49
+ @data
50
+ end
51
+
52
+ def has_day?
53
+ return false if ( @flags == PHRASE )
54
+ return ((@data.flags & NODAY) != 0 ? false : true)
55
+ end
56
+
57
+ def has_month?
58
+ return false if ( @flags == PHRASE )
59
+ return ((@data.flags & NOMONTH) != 0 ? false : true)
60
+ end
61
+
62
+ def has_year?
63
+ return false if ( @flags == PHRASE )
64
+ return ((@data.flags & NOYEAR) != 0 ? false : true)
65
+ end
66
+
67
+ def has_year_span?
68
+ return false if ( @flags == PHRASE )
69
+ return ((@data.flags & YEARSPAN) != 0 ? true : false)
70
+ end
71
+
72
+ def day
73
+ raise DateFormatException, "date has no day" if (@flags == PHRASE || (@data.flags & NODAY) != 0)
74
+ @data.day
75
+ end
76
+
77
+ def month
78
+ raise DateFormatException, "date has no month" if (@flags == PHRASE || (@data.flags & NOMONTH) != 0)
79
+ @data.month
80
+ end
81
+
82
+ def year
83
+ raise DateFormatException, "date has no year" if (@flags == PHRASE || (@data.flags & NOYEAR) != 0)
84
+ @data.year
85
+ end
86
+
87
+ def to_year
88
+ raise DateFormatException, "date has no year span" if (@flags == PHRASE || (@data.flags & YEARSPAN) == 0)
89
+ @data.year2
90
+ end
91
+
92
+ def epoch
93
+ raise DateFormatException, "only gregorian dates have epoch" if ( @flags == PHRASE || @type != GEDCOM_DATE_PARSER::GCTGREGORIAN )
94
+ return (( @data.adbc == GEDCOM_DATE_PARSER::GEDADBCBC ) ? "BC" : "AD" )
95
+ end
96
+
97
+ def to_s
98
+ GEDCOM_DATE_PARSER::DateParser.build_gedcom_date_part_string( self )
99
+ end
100
+
101
+ def <=>( dp )
102
+ return -1 if has_year? and !dp.has_year?
103
+ return 1 if !has_year? and dp.has_year?
104
+
105
+ if has_year? and dp.has_year?
106
+ rc = ( year <=> dp.year )
107
+ return rc unless rc == 0
108
+ end
109
+
110
+ return -1 if dp.has_month? and !dp.has_month?
111
+ return 1 if !dp.has_month? and dp.has_month?
112
+
113
+ if has_month? and dp.has_month?
114
+ rc = ( month <=> dp.month )
115
+ return rc unless rc == 0
116
+ end
117
+
118
+ return -1 if dp.has_day? and !dp.has_day?
119
+ return 1 if !dp.has_day? and dp.has_day?
120
+
121
+ if has_day? and dp.has_day?
122
+ rc = ( day <=> dp.day )
123
+ return rc unless rc == 0
124
+ end
125
+
126
+ return 0
127
+ end
128
+ end #/ DatePart
129
+
130
+ class Date < GEDCOM_DATE_PARSER::GEDDateValue
131
+ # Calendar types
132
+ NONE = GEDCOM_DATE_PARSER::GCNONE
133
+ ABOUT = GEDCOM_DATE_PARSER::GCABOUT
134
+ CALCULATED = GEDCOM_DATE_PARSER::GCCALCULATED
135
+ ESTIMATED = GEDCOM_DATE_PARSER::GCESTIMATED
136
+ BEFORE = GEDCOM_DATE_PARSER::GCBEFORE
137
+ AFTER = GEDCOM_DATE_PARSER::GCAFTER
138
+ BETWEEN = GEDCOM_DATE_PARSER::GCBETWEEN
139
+ FROM = GEDCOM_DATE_PARSER::GCFROM
140
+ TO = GEDCOM_DATE_PARSER::GCTO
141
+ FROMTO = GEDCOM_DATE_PARSER::GCFROMTO
142
+ INTERPRETED = GEDCOM_DATE_PARSER::GCINTERPRETED
143
+ CHILD = GEDCOM_DATE_PARSER::GCCHILD
144
+ CLEARED = GEDCOM_DATE_PARSER::GCCLEARED
145
+ COMPLETED = GEDCOM_DATE_PARSER::GCCOMPLETED
146
+ INFANT = GEDCOM_DATE_PARSER::GCINFANT
147
+ PRE1970 = GEDCOM_DATE_PARSER::GCPRE1970
148
+ QUALIFIED = GEDCOM_DATE_PARSER::GCQUALIFIED
149
+ STILLBORN = GEDCOM_DATE_PARSER::GCSTILLBORN
150
+ SUBMITTED = GEDCOM_DATE_PARSER::GCSUBMITTED
151
+ UNCLEARED = GEDCOM_DATE_PARSER::GCUNCLEARED
152
+ BIC = GEDCOM_DATE_PARSER::GCBIC
153
+ DNS = GEDCOM_DATE_PARSER::GCDNS
154
+ DNSCAN = GEDCOM_DATE_PARSER::GCDNSCAN
155
+ DEAD = GEDCOM_DATE_PARSER::GCDEAD
156
+
157
+ def Date.safe_new( parm )
158
+ Date.new( parm ) { |errmsg| }
159
+ end
160
+
161
+ def initialize ( date_str, calendar=DateType::DEFAULT )
162
+ begin
163
+ @date1 = DatePart.new
164
+ @date2 = DatePart.new
165
+ super(GEDCOM_DATE_PARSER::DateParser::GEDFNONE, @date1, @date2)
166
+ GEDCOM_DATE_PARSER::DateParser.parse_gedcom_date( date_str, self, calendar )
167
+ rescue GEDCOM_DATE_PARSER::DateParseException
168
+ err_msg = "format error at '"
169
+ if (@date1 && (@date1.flags & DatePart::NONSTANDARD))
170
+ err_msg += @date1.data.to_s
171
+ elsif (@date2)
172
+ err_msg += @date2.data.to_s
173
+ end
174
+ err_msg += "'"
175
+ if (block_given?)
176
+ yield( err_msg )
177
+ else
178
+ raise DateFormatException, err_msg
179
+ end
180
+ end
181
+ end
182
+
183
+ def format
184
+ @flags
185
+ end
186
+
187
+ def first
188
+ @date1
189
+ end
190
+
191
+ def last
192
+ @date2
193
+ end
194
+
195
+ def to_s
196
+ GEDCOM_DATE_PARSER::DateParser.build_gedcom_date_string( self )
197
+ end
198
+
199
+ def is_date?
200
+ (@flags & (NONE | ABOUT | CALCULATED | ESTIMATED | BEFORE | AFTER | BETWEEN \
201
+ | FROM | TO | FROMTO | INTERPRETED)) != 0 ? false : true
202
+ end
203
+
204
+ def is_range?
205
+ (@flags & (BETWEEN | FROMTO)) != 0 ? true : false
206
+ end
207
+
208
+ def <=>( d )
209
+ if is_date? and d.is_date?
210
+ rc = ( first <=> d.first )
211
+ return rc unless rc == 0
212
+
213
+ if is_range? and d.is_range?
214
+ return ( last <=> d.last )
215
+ elsif is_range?
216
+ return 1
217
+ elsif d.is_range?
218
+ return -1
219
+ end
220
+
221
+ return 0
222
+ elsif is_date?
223
+ return -1
224
+ elsif d.is_date?
225
+ return 1
226
+ end
227
+
228
+ return format <=> d.format
229
+ end
230
+ end
231
+
232
+ class DateType
233
+ GREGORIAN = GEDCOM_DATE_PARSER::GCTGREGORIAN
234
+ JULIAN = GEDCOM_DATE_PARSER::GCTJULIAN
235
+ HEBREW = GEDCOM_DATE_PARSER::GCTHEBREW
236
+ FRENCH = GEDCOM_DATE_PARSER::GCTFRENCH
237
+ FUTURE = GEDCOM_DATE_PARSER::GCTFUTURE
238
+ UNKNOWN = GEDCOM_DATE_PARSER::GCTUNKNOWN
239
+ DEFAULT = GEDCOM_DATE_PARSER::GCTDEFAULT
240
+ end
241
+
242
+ class DateFormatException < Exception
243
+
244
+ end
245
+ end
@@ -0,0 +1,952 @@
1
+ # -------------------------------------------------------------------------
2
+ # gedcom_date_parser.rb -- module definition for GEDCOM date parser
3
+ # Copyright (C) 2008 Phillip Davies (binary011010@verizon.net)
4
+ # -------------------------------------------------------------------------
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
+ # -------------------------------------------------------------------------
19
+ #
20
+ module GEDCOM_DATE_PARSER
21
+ # Token Constants
22
+ # General Tokens
23
+ TKERROR = -2
24
+ TKEOF = -1
25
+ TKNONE = 0
26
+
27
+ TKNUMBER = 1
28
+ TKMONTH = 2
29
+ TKAPPROXIMATED = 3
30
+ TKRANGE = 4
31
+ TKPERIOD = 5
32
+ TKINTERPRETED = 6
33
+ TKLPAREN = 7
34
+ TKRPAREN = 8
35
+ TKBC = 9
36
+ TKAND = 10
37
+ TKTO = 11
38
+ TKSLASH = 12
39
+ TKSTATUS = 13
40
+ TKOTHER = 14
41
+
42
+ # Specific Tokens
43
+ TKJANUARY = 1
44
+ TKFEBRUARY = 2
45
+ TKMARCH = 3
46
+ TKAPRIL = 4
47
+ TKMAY = 5
48
+ TKJUNE = 6
49
+ TKJULY = 7
50
+ TKAUGUST = 8
51
+ TKSEPTEMBER = 9
52
+ TKOCTOBER = 10
53
+ TKNOVEMBER = 11
54
+ TKDECEMBER = 12
55
+
56
+ TKVENDEMIAIRE = 13
57
+ TKBRUMAIRE = 14
58
+ TKFRIMAIRE = 15
59
+ TKNIVOSE = 16
60
+ TKPLUVIOSE = 17
61
+ TKVENTOSE = 18
62
+ TKGERMINAL = 19
63
+ TKFLOREAL = 20
64
+ TKPRAIRIAL = 21
65
+ TKMESSIDOR = 22
66
+ TKTHERMIDOR = 23
67
+ TKFRUCTIDOR = 24
68
+ TKJOUR_COMP = 25
69
+ TKJOUR = 26
70
+ TKCOMP = 27
71
+
72
+ TKTISHRI = 28
73
+ TKCHESHVAN = 29
74
+ TKKISLEV = 30
75
+ TKTEVET = 31
76
+ TKSHEVAT = 32
77
+ TKADAR = 33
78
+ TKADAR_SHENI = 34
79
+ TKNISAN = 35
80
+ TKIYAR = 36
81
+ TKSIVAN = 37
82
+ TKTAMMUZ = 38
83
+ TKAV = 39
84
+ TKELUL = 40
85
+ TKSHENI = 41
86
+
87
+ TKABOUT = 80
88
+ TKCALCULATED = 81
89
+ TKESTIMATED = 82
90
+ TKBEFORE = 83
91
+ TKAFTER = 84
92
+ TKBETWEEN = 85
93
+ TKFROM = 86
94
+
95
+ TKCHILD = 87
96
+ TKCLEARED = 88
97
+ TKCOMPLETED = 89
98
+ TKINFANT = 90
99
+ TKPRE1970 = 91
100
+ TKQUALIFIED = 92
101
+ TKSTILLBORN = 93
102
+ TKSUBMITTED = 94
103
+ TKUNCLEARED = 95
104
+ TKBIC = 96 #Born In the Covenant
105
+ TKDNS = 97 #Do Not Submit
106
+ TKDNSCAN = 98 #Do Not Submit / Cancelled
107
+ TKDEAD = 99
108
+
109
+ #states
110
+ ST_DV_ERROR = -1
111
+ ST_DV_START = 1
112
+ ST_DV_DATE = 2
113
+ ST_DV_DATE_APPROX = 3
114
+ ST_DV_DATE_RANGE = 4
115
+ ST_DV_TO = 5
116
+ ST_DV_DATE_PERIOD = 6
117
+ ST_DV_DATE_INTERP = 7
118
+ ST_DV_DATE_PHRASE = 8
119
+ ST_DV_AND = 9
120
+ ST_DV_STATUS = 10
121
+ ST_DV_END = 11
122
+
123
+ ST_DT_ERROR = -1
124
+ ST_DT_START = 1
125
+ ST_DT_NUMBER = 2
126
+ ST_DT_MONTH = 3
127
+ ST_DT_SLASH = 4
128
+ ST_DT_BC = 5
129
+ ST_DT_END = 6
130
+
131
+
132
+ # After parsing, all flags should be available as booleans with accessors
133
+ GCTGREGORIAN = 0
134
+ GCTJULIAN = 1
135
+ GCTHEBREW = 2
136
+ GCTFRENCH = 3
137
+ GCTFUTURE = 4
138
+ GCTUNKNOWN = 99
139
+
140
+ GCTDEFAULT = GCTGREGORIAN
141
+
142
+ # date constants
143
+
144
+ GCNONE = 0
145
+
146
+ # approximated date constants
147
+
148
+ GCABOUT = 1
149
+ GCCALCULATED = 2
150
+ GCESTIMATED = 3
151
+
152
+ # date range constants
153
+
154
+ GCBEFORE = 4
155
+ GCAFTER = 5
156
+ GCBETWEEN = 6
157
+
158
+ # date period constants
159
+
160
+ GCFROM = 7
161
+ GCTO = 8
162
+ GCFROMTO = 9
163
+
164
+ # other date constants
165
+
166
+ GCINTERPRETED = 10
167
+
168
+ # LDS ordinance constants
169
+
170
+ GCCHILD = 11
171
+ GCCLEARED = 12
172
+ GCCOMPLETED = 13
173
+ GCINFANT = 14
174
+ GCPRE1970 = 15
175
+ GCQUALIFIED = 16
176
+ GCSTILLBORN = 17
177
+ GCSUBMITTED = 18
178
+ GCUNCLEARED = 19
179
+ GCBIC = 20 # Born In the Covenant
180
+ GCDNS = 21 # Do Not Submit
181
+ GCDNSCAN = 22 # Do Not Submit / Cancelled
182
+ GCDEAD = 23
183
+
184
+ # date flags
185
+
186
+ GFNONE = 0
187
+ GFPHRASE = 1
188
+ GFNONSTANDARD = 2
189
+
190
+ # date bit flags
191
+
192
+ GFNOFLAG = 0
193
+ GFNODAY = 1
194
+ GFNOMONTH = 2
195
+ GFNOYEAR = 4
196
+ GFYEARSPAN = 8
197
+
198
+ # data type constants
199
+
200
+ GCMAXPHRASEBUFFERSIZE = 35
201
+
202
+ # BC / AD
203
+ GEDADBCBC = 0
204
+ GEDADBCAD = 1
205
+
206
+ Default_Months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
207
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]
208
+
209
+ Hebrew_Months = [ "Tishri", "Cheshvan", "Kislev", "Tevet", "Shevat", "Adar",
210
+ "Adar Sheni", "Nisan", "Iyar", "Sivan", "Tammuz", "Av",
211
+ "Elul", "Sheni" ]
212
+
213
+ French_Months = [ "Vend", "Brum", "Frim", "Niv", "Pluv", "Vent", "Germ", "Flor",
214
+ "Prair", "Mess", "Therm", "Fruct", "J. Comp", "Jour", "Comp" ]
215
+
216
+ class Token
217
+ attr_accessor :lexeme, :general, :specific
218
+ def initialize(lex, gen, spec)
219
+ @lexeme = lex
220
+ @general = gen
221
+ @specific = spec
222
+ end
223
+ end
224
+ TokenTable = []
225
+ TokenTable << Token.new("(", TKLPAREN, 0 )
226
+ TokenTable << Token.new(")", TKRPAREN, 0 )
227
+ TokenTable << Token.new("-", TKSLASH, 0 )
228
+ TokenTable << Token.new("/", TKSLASH, 0 )
229
+ TokenTable << Token.new("AAV", TKMONTH, TKAV )
230
+ TokenTable << Token.new("ABOUT", TKAPPROXIMATED, TKABOUT )
231
+ TokenTable << Token.new("ABT", TKAPPROXIMATED, TKABOUT )
232
+ TokenTable << Token.new("ADAR", TKMONTH, TKADAR )
233
+ TokenTable << Token.new("ADR", TKMONTH, TKADAR )
234
+ TokenTable << Token.new("AFTER", TKRANGE, TKAFTER )
235
+ TokenTable << Token.new("AND", TKAND, 0 )
236
+ TokenTable << Token.new("APRIL", TKMONTH, TKAPRIL )
237
+ TokenTable << Token.new("AUGUST", TKMONTH, TKAUGUST )
238
+ TokenTable << Token.new("AV", TKMONTH, TKAV )
239
+ TokenTable << Token.new("BC", TKBC, 0 )
240
+ TokenTable << Token.new("BEFORE", TKRANGE, TKBEFORE )
241
+ TokenTable << Token.new("BETWEEN", TKRANGE, TKBETWEEN )
242
+ TokenTable << Token.new("BIC", TKSTATUS, TKBIC )
243
+ TokenTable << Token.new("BRUMAIRE", TKMONTH, TKBRUMAIRE )
244
+ TokenTable << Token.new("CALCULATED", TKAPPROXIMATED, TKCALCULATED )
245
+ TokenTable << Token.new("CHESHVAN", TKMONTH, TKCHESHVAN )
246
+ TokenTable << Token.new("CHILD", TKSTATUS, TKCHILD )
247
+ TokenTable << Token.new("CLEARED", TKSTATUS, TKCLEARED )
248
+ TokenTable << Token.new("COMPLETED", TKSTATUS, TKCOMPLETED )
249
+ TokenTable << Token.new("COMPLIMENTAIRS", TKMONTH, TKCOMP )
250
+ TokenTable << Token.new("CSH", TKMONTH, TKCHESHVAN )
251
+ TokenTable << Token.new("DEAD", TKSTATUS, TKDEAD )
252
+ TokenTable << Token.new("DECEMBER", TKMONTH, TKDECEMBER )
253
+ TokenTable << Token.new("DNS", TKSTATUS, TKDNS )
254
+ TokenTable << Token.new("DNSCAN", TKSTATUS, TKDNSCAN )
255
+ TokenTable << Token.new("ELL", TKMONTH, TKELUL )
256
+ TokenTable << Token.new("ELUL", TKMONTH, TKELUL )
257
+ TokenTable << Token.new("ESTIMATED", TKAPPROXIMATED, TKESTIMATED )
258
+ TokenTable << Token.new("FEBRUARY", TKMONTH, TKFEBRUARY )
259
+ TokenTable << Token.new("FLOREAL", TKMONTH, TKFLOREAL )
260
+ TokenTable << Token.new("FRIMAIRE", TKMONTH, TKFRIMAIRE )
261
+ TokenTable << Token.new("FROM", TKPERIOD, TKFROM )
262
+ TokenTable << Token.new("FRUCTIDOR", TKMONTH, TKFRUCTIDOR )
263
+ TokenTable << Token.new("GERMINAL", TKMONTH, TKGERMINAL )
264
+ TokenTable << Token.new("INFANT", TKSTATUS, TKINFANT )
265
+ TokenTable << Token.new("INTERPRETED", TKINTERPRETED, 0 )
266
+ TokenTable << Token.new("IYAR", TKMONTH, TKIYAR )
267
+ TokenTable << Token.new("IYR", TKMONTH, TKIYAR )
268
+ TokenTable << Token.new("JANUARY", TKMONTH, TKJANUARY )
269
+ TokenTable << Token.new("JOUR", TKMONTH, TKJOUR )
270
+ TokenTable << Token.new("JULY", TKMONTH, TKJULY )
271
+ TokenTable << Token.new("JUNE", TKMONTH, TKJUNE )
272
+ TokenTable << Token.new("KISLEV", TKMONTH, TKKISLEV )
273
+ TokenTable << Token.new("KSL", TKMONTH, TKKISLEV )
274
+ TokenTable << Token.new("MARCH", TKMONTH, TKMARCH )
275
+ TokenTable << Token.new("MAY", TKMONTH, TKMAY )
276
+ TokenTable << Token.new("MESSIDOR", TKMONTH, TKMESSIDOR )
277
+ TokenTable << Token.new("NISAN", TKMONTH, TKNISAN )
278
+ TokenTable << Token.new("NIVOSE", TKMONTH, TKNIVOSE )
279
+ TokenTable << Token.new("NOVEMBER", TKMONTH, TKNOVEMBER )
280
+ TokenTable << Token.new("NSN", TKMONTH, TKNISAN )
281
+ TokenTable << Token.new("OCTOBER", TKMONTH, TKOCTOBER )
282
+ TokenTable << Token.new("PLUVIOSE", TKMONTH, TKPLUVIOSE )
283
+ TokenTable << Token.new("PRAIRIAL", TKMONTH, TKPRAIRIAL )
284
+ TokenTable << Token.new("PRE1970", TKSTATUS, TKPRE1970 )
285
+ TokenTable << Token.new("QUALIFIED", TKSTATUS, TKQUALIFIED )
286
+ TokenTable << Token.new("SEPTEMBER", TKMONTH, TKSEPTEMBER )
287
+ TokenTable << Token.new("SHENI", TKMONTH, TKSHENI )
288
+ TokenTable << Token.new("SHEVAT", TKMONTH, TKSHEVAT )
289
+ TokenTable << Token.new("SHV", TKMONTH, TKSHEVAT )
290
+ TokenTable << Token.new("SIVAN", TKMONTH, TKSIVAN )
291
+ TokenTable << Token.new("STILLBORN", TKSTATUS, TKSTILLBORN )
292
+ TokenTable << Token.new("SUBMITTED", TKSTATUS, TKSUBMITTED )
293
+ TokenTable << Token.new("SVN", TKMONTH, TKSIVAN )
294
+ TokenTable << Token.new("TAMMUZ", TKMONTH, TKTAMMUZ )
295
+ TokenTable << Token.new("TEVET", TKMONTH, TKTEVET )
296
+ TokenTable << Token.new("THERMIDOR", TKMONTH, TKTHERMIDOR )
297
+ TokenTable << Token.new("TISHRI", TKMONTH, TKTISHRI )
298
+ TokenTable << Token.new("TMZ", TKMONTH, TKTAMMUZ )
299
+ TokenTable << Token.new("TO", TKTO, 0 )
300
+ TokenTable << Token.new("TSH", TKMONTH, TKTISHRI )
301
+ TokenTable << Token.new("TVT", TKMONTH, TKTEVET )
302
+ TokenTable << Token.new("UNCLEARED", TKSTATUS, TKUNCLEARED )
303
+ TokenTable << Token.new("VENDEMIAIRE", TKMONTH, TKVENDEMIAIRE )
304
+ TokenTable << Token.new("VENTOSE", TKMONTH, TKVENTOSE )
305
+ TokenTable << Token.new(0, 0, 0 )
306
+
307
+
308
+ class GEDStateEntry
309
+ attr_accessor :state, :input, :nextState, :action
310
+ def initialize(st, ip, ns, a)
311
+ @state = st
312
+ @input = ip
313
+ @nextState = ns
314
+ @action = a
315
+ end
316
+ end
317
+
318
+ DateValueStateTable = []
319
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKNUMBER, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
320
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKMONTH, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
321
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKAPPROXIMATED, ST_DV_DATE_APPROX, 1 ) # 1: set the approx type
322
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKRANGE, ST_DV_DATE_RANGE, 2 ) # 2: set the range type
323
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKTO, ST_DV_TO, 3 ) # 3: set the period type
324
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKPERIOD, ST_DV_DATE_PERIOD, 3 ) # 3: set the period type
325
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKINTERPRETED, ST_DV_DATE_INTERP, 4 ) # 4: set interpreted
326
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKLPAREN, ST_DV_DATE_PHRASE, 5 ) # 5: get remaining buffer as phrase
327
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKSTATUS, ST_DV_STATUS, 10 ) # 10: set status
328
+ DateValueStateTable << GEDStateEntry.new( ST_DV_START, TKEOF, ST_DV_END, 6 ) # 6: if 'between' and not second date read, error, else terminate
329
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE, TKLPAREN, ST_DV_DATE_PHRASE, 7 ) # 7: if 'interpreted', get remaining buffer as phrase
330
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE, TKAND, ST_DV_AND, 8 ) # 8: if 'between', prepare to read next date
331
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE, TKTO, ST_DV_TO, 9 ) # 9: if 'from', set FROMTO, prepare to read next date
332
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE, TKEOF, ST_DV_END, 6 ) # 6: if 'between' and not second date read, error, else terminate
333
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE_APPROX, TKNUMBER, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
334
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE_APPROX, TKMONTH, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
335
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE_RANGE, TKNUMBER, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
336
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE_RANGE, TKMONTH, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
337
+ DateValueStateTable << GEDStateEntry.new( ST_DV_TO, TKNUMBER, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
338
+ DateValueStateTable << GEDStateEntry.new( ST_DV_TO, TKMONTH, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
339
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE_PERIOD, TKNUMBER, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
340
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE_PERIOD, TKMONTH, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
341
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE_INTERP, TKNUMBER, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
342
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE_INTERP, TKMONTH, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
343
+ DateValueStateTable << GEDStateEntry.new( ST_DV_DATE_PHRASE, TKEOF, ST_DV_END, 6 ) # 6: if 'between' and not second date read, error, else terminate
344
+ DateValueStateTable << GEDStateEntry.new( ST_DV_AND, TKNUMBER, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
345
+ DateValueStateTable << GEDStateEntry.new( ST_DV_AND, TKMONTH, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
346
+ DateValueStateTable << GEDStateEntry.new( ST_DV_TO, TKNUMBER, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
347
+ DateValueStateTable << GEDStateEntry.new( ST_DV_TO, TKMONTH, ST_DV_DATE, 0 ) # 0: inc dates read, parse a date
348
+ DateValueStateTable << GEDStateEntry.new( ST_DV_STATUS, TKEOF, ST_DV_END, 6 )
349
+ DateValueStateTable << GEDStateEntry.new( 0, 0, 0, 0 )
350
+
351
+ DateStateTable = []
352
+ DateStateTable << GEDStateEntry.new( ST_DT_START, TKNUMBER, ST_DT_NUMBER, 0 ) # 0: store number, set NUMBER
353
+ DateStateTable << GEDStateEntry.new( ST_DT_START, TKMONTH, ST_DT_MONTH, 1 ) # 1: if MONTH, then error, else set number to be day, set month, set MONTH
354
+ DateStateTable << GEDStateEntry.new( ST_DT_NUMBER, TKMONTH, ST_DT_MONTH, 1 ) # 1: if MONTH, then error, else set number to be day, set month, set MONTH
355
+ DateStateTable << GEDStateEntry.new( ST_DT_NUMBER, TKSLASH, ST_DT_SLASH, 2 ) # 2: if SLASH, then error, else set SLASH, set number to be year
356
+ DateStateTable << GEDStateEntry.new( ST_DT_NUMBER, TKBC, ST_DT_BC, 3 ) # 3: if not SLASH set number to be year, set bc
357
+ DateStateTable << GEDStateEntry.new( ST_DT_NUMBER, TKEOF, ST_DT_END, 4 ) # 4: if not SLASH set number to be year, terminate
358
+ DateStateTable << GEDStateEntry.new( ST_DT_NUMBER, TKTO, ST_DT_END, 4 ) # 4: if TO set number to be year, terminate
359
+ DateStateTable << GEDStateEntry.new( ST_DT_NUMBER, TKAND, ST_DT_END, 4 ) # 4: if AND set number to be year, terminate
360
+ DateStateTable << GEDStateEntry.new( ST_DT_MONTH, TKNUMBER, ST_DT_NUMBER, 5 ) # 5: if NUMBER, set number to be day. set number to be year, store number, set NUMBER
361
+ DateStateTable << GEDStateEntry.new( ST_DT_MONTH, TKEOF, ST_DT_END, 6 ) # 6: terminate
362
+ DateStateTable << GEDStateEntry.new( ST_DT_MONTH, TKTO, ST_DT_END, 6 ) # 6: if TO, terminate
363
+ DateStateTable << GEDStateEntry.new( ST_DT_MONTH, TKAND, ST_DT_END, 6 ) # 6: if AND, terminate
364
+ DateStateTable << GEDStateEntry.new( ST_DT_SLASH, TKNUMBER, ST_DT_NUMBER, 7 ) # 7: set number to be year2
365
+ DateStateTable << GEDStateEntry.new( ST_DT_BC, TKEOF, ST_DT_END, 6 ) # 6: terminate
366
+ DateStateTable << GEDStateEntry.new( 0, 0, 0, 0 )
367
+
368
+ class GEDParserState
369
+ attr_accessor :buffer, :lastGeneralToken, :lastSpecificToken, :pos
370
+ def initialize( buf, lgt, lst, p )
371
+ @buffer = buf
372
+ @lastGeneralToken = lgt
373
+ @lastSpecificToken = lst
374
+ @pos = p
375
+ end
376
+ end
377
+
378
+ # Gregorian Date Class
379
+ class GEDDateGreg
380
+ attr_accessor :flags, :day, :month, :year, :year2, :adbc
381
+ def initialize(flg, d, m, y, y2, adbc)
382
+ @flags = flg
383
+ @day = d
384
+ @month = m
385
+ @year = y
386
+ @year2 = y2
387
+ @adbc = adbc
388
+ end
389
+ end
390
+
391
+ # General Date Class
392
+ class GEDDateGeneral
393
+ attr_accessor :flags, :day, :month, :year
394
+ def initialize(flg, d, m, y)
395
+ @flags = flg
396
+ @day = d
397
+ @month = m
398
+ @year = y
399
+ end
400
+ end
401
+
402
+ class GEDDate
403
+ attr_accessor :type, :flags, :data
404
+ def initialize(type, flags, data)
405
+ @type = type
406
+ @flags = flags
407
+ @data = data # Data should be either a string, Gregorian date or General Date
408
+ end
409
+ end
410
+
411
+ class GEDDateValue # This should be the end result of our parsing
412
+ attr_accessor :flags, :date1, :date2
413
+ def initialize(flags, d1, d2)
414
+ @flags = flags
415
+ @date1 = d1
416
+ @date2 = d2
417
+ end
418
+ end
419
+
420
+ class DateParser
421
+ GEDFNONE = 0
422
+ GEDFBETWEEN = 1
423
+ GEDFFROM = 2
424
+ GEDFINTERP = 4
425
+ GEDFNUMBER = 8
426
+ GEDFMONTH = 16
427
+ GEDFSLASH = 32
428
+
429
+ def self.get_token( parser )
430
+ # Get a single token from this parser state (class method)
431
+ # Inputs: parser - parser state (GEDParserState)
432
+ # Outputs: general - general token
433
+ # specific - specific token
434
+ startPos = parser.pos
435
+
436
+ # if we've got a token saved in the parser, return it
437
+ if ( parser.lastGeneralToken != TKNONE )
438
+ general = parser.lastGeneralToken
439
+ specific = parser.lastSpecificToken
440
+ parser.lastGeneralToken = TKNONE
441
+ parser.lastSpecificToken = TKNONE
442
+ return general, specific
443
+ end
444
+
445
+ #eat leading white-space
446
+ parser.pos+=1 while ( parser.buffer[ parser.pos, 1 ]==" " )
447
+
448
+ #if the buffer is empty, return TKEOF
449
+ if ( parser.buffer[ parser.pos, 1 ] == nil || parser.buffer[parser.pos, 1] == "")
450
+ specific = TKNONE
451
+ general = TKEOF
452
+ return general, specific
453
+ end
454
+
455
+ lexeme = ""
456
+ # if it's a number, parse it out and return it
457
+ if ( parser.buffer[ parser.pos, 1 ] =~ /[0-9]/ )
458
+ while ( parser.buffer[ parser.pos, 1 ] =~ /[0-9]/)
459
+ lexeme << parser.buffer[ parser.pos, 1 ]
460
+ parser.pos+=1
461
+ end
462
+ specific = lexeme.to_i
463
+ general = TKNUMBER
464
+ return general, specific
465
+ end
466
+
467
+ currentToken = 0
468
+ lexPos = 0
469
+ # if it is not a number, incrementally look at each token in the table
470
+ while ( TokenTable[ currentToken ].lexeme != 0 )
471
+ lexeme << parser.buffer[ parser.pos, 1 ].upcase
472
+ lexPos+=1
473
+ parser.pos+=1
474
+
475
+ if( lexeme[ lexPos-1, 1 ] != TokenTable[ currentToken ].lexeme[ lexPos-1, 1 ] )
476
+ currentToken+=1 while( ( TokenTable[ currentToken ].lexeme != 0 ) &&
477
+ ( (TokenTable[ currentToken ].lexeme[0, lexPos] <=> lexeme[0, lexPos] ) < 0 ) )
478
+
479
+ #if the lexeme does not appear in the table, exit with an error
480
+ break if ( TokenTable[ currentToken ].lexeme == 0 || \
481
+ (TokenTable[ currentToken ].lexeme[0, lexPos] <=> lexeme[0, lexPos] ) != 0 )
482
+
483
+ end
484
+
485
+ #if the lexeme terminates, return the value of the current token
486
+ if( ( ( lexeme[0,1] =~ /[a-zA-Z]/) && ( parser.buffer[ parser.pos, 1 ] !~ /[0-9a-zA-Z]/) ) ||
487
+ ( ( lexeme[0,1] !~ /[a-zA-Z]/ ) && ( lexPos >= TokenTable[ currentToken ].lexeme.length ) ) )
488
+ specific = TokenTable[ currentToken ].specific
489
+ general = TokenTable[ currentToken ].general
490
+ return general, specific
491
+ end
492
+
493
+ #if the current token terminates before the lexeme, then we have an error
494
+ break if ( TokenTable[ currentToken ].lexeme[ lexPos, 1 ] == nil )
495
+
496
+ end
497
+
498
+ parser.pos = startPos
499
+
500
+ specific = TKNONE
501
+ general = TKERROR
502
+
503
+ return general, specific
504
+ end
505
+
506
+ def self.put_token( parser, general, specific )
507
+ # Update the parser state (class method)
508
+ # Inputs: parser - parser state (GEDParserState)
509
+ # general - general token
510
+ # specific - specific token
511
+ # Outputs: None
512
+ parser.lastGeneralToken = general
513
+ parser.lastSpecificToken = specific
514
+ end
515
+
516
+ def self.get_date_text( date )
517
+ # Stringify the GEDCOM Date (class method)
518
+ # Inputs: date - Date Part (GEDDate)
519
+ # Outputs: buffer - Output string
520
+ buffer = ""
521
+
522
+ if ( (date.flags & (GFPHRASE | GFNONSTANDARD)) != 0)
523
+ buffer += date.data
524
+ return buffer
525
+ end
526
+
527
+ case ( date.type )
528
+ when GCTHEBREW
529
+ months = Hebrew_Months
530
+ when GCTFRENCH
531
+ months = French_Months
532
+ else
533
+ months = Default_Months
534
+ end
535
+
536
+ return buffer if not (date.data)
537
+
538
+ if ( date.data.flags && (( date.data.flags & GFNODAY ) == 0) )
539
+ buffer += date.data.day.to_s
540
+ buffer += " " if ( (( date.data.flags & GFNOMONTH ) == 0) || (( date.data.flags & GFNOYEAR ) == 0) )
541
+ end
542
+
543
+ if ( date.data.flags && (( date.data.flags & GFNOMONTH ) == 0) )
544
+ buffer += months[ date.data.month - 1 ]
545
+ buffer += " " if( ( date.data.flags & GFNOYEAR ) == 0 )
546
+ end
547
+
548
+ if ( date.data.flags && (( date.data.flags & GFNOYEAR ) == 0) )
549
+ buffer += date.data.year.to_s
550
+ if ( ( date.data.flags & GFYEARSPAN ) != 0 )
551
+ buffer += "-"
552
+ buffer += date.data.year2.to_s
553
+ end
554
+ end
555
+
556
+ buffer += " BC" if ( (date.type == GCTGREGORIAN) && (date.data.adbc != GEDADBCAD) )
557
+ buffer
558
+ end
559
+
560
+ def self.validate_month_for_type( month, calType )
561
+ # Make sure this is a valid month for this calendar type (class method)
562
+ # Inputs: parser - parser state
563
+ # Outputs: general - general token
564
+ # specific - specific token
565
+ case calType
566
+ when GCTGREGORIAN || GCTJULIAN
567
+ return ( month - TKJANUARY + 1 ) if( month >= TKJANUARY && month <= TKDECEMBER )
568
+
569
+ when GCTHEBREW
570
+ return ( month - TKTISHRI + 1 ) if( month >= TKTISHRI && month <= TKELUL )
571
+
572
+ when GCTFRENCH
573
+ return ( month - TKVENDEMIAIRE + 1 )if( month >= TKVENDEMIAIRE && month <= TKJOUR_COMP )
574
+ end
575
+ return -1
576
+ end
577
+
578
+ def self.parse_date_part( parser, datePart, type )
579
+ # Parse out a date part (class method)
580
+ # Inputs: parser - parser state
581
+ # datePart - date part (GEDDate)
582
+ # type - calendar type
583
+ # Outputs: None (updated date part)
584
+ state = ST_DT_START
585
+ flags = GEDFNONE
586
+
587
+ # Initialize the datePart, in case it contains old data
588
+ datePart.type = type
589
+ datePart.flags = GFNONE
590
+ if (type == GCTGREGORIAN)
591
+ datePart.data = GEDDateGreg.new(flags, 0, 0, 0, 0, GEDADBCAD)
592
+ else
593
+ datePart.data = GEDDateGeneral.new(flags, 0, 0, 0)
594
+ end
595
+ number = 0
596
+
597
+ while ( ( state != ST_DT_END ) && ( state != ST_DT_ERROR ) )
598
+ general, specific = get_token( parser )
599
+ raise DateParseException, "error parsing datepart, pre-transition" if (general == TKERROR)
600
+ transitionFound = 0
601
+
602
+ case ( general )
603
+ when TKNUMBER
604
+ when TKMONTH
605
+ when TKSLASH
606
+ when TKBC
607
+ when TKEOF
608
+ when TKERROR
609
+ when TKTO, TKAND
610
+ put_token( parser, general, specific)
611
+ else
612
+ put_token( parser, general, specific )
613
+ general = TKEOF
614
+ specific = TKNONE
615
+ break
616
+ end
617
+
618
+ DateStateTable.each do |dateState|
619
+ break if dateState.state < 1
620
+
621
+ if( ( dateState.state == state ) && ( dateState.input == general ) )
622
+ state = dateState.nextState
623
+ transitionFound = 1
624
+
625
+ case dateState.action
626
+ # 0: store number, set NUMBER
627
+ when 0
628
+ number = specific
629
+ flags |= GEDFNUMBER
630
+
631
+ # 1: if MONTH, then error, else set number to be day, set month, set MONTH
632
+ when 1
633
+ if ( type == GCTFRENCH )
634
+ # if the token is "JOUR", make sure they also typed at least
635
+ # part of "COMPLIMENTAIRES"
636
+
637
+ case specific
638
+ when TKJOUR
639
+ general, specific = get_token( parser )
640
+ raise DateParseException, "error parsing datepart, post-JOUR (french calendar)" if (general == TKERROR)
641
+ if ( general != TKMONTH && specific != TKCOMP )
642
+ state = ST_DT_ERROR
643
+ put_token( parser, general, specific )
644
+ end #fall through
645
+
646
+ when TKCOMP
647
+ specific = TKJOUR_COMP
648
+ end
649
+ elsif ( type == GCTHEBREW )
650
+ # if the token is "ADAR", see if it is followed by "SHENI",
651
+ # and if it is, change the month to "ADAR SHENI"
652
+
653
+ if( specific == TKADAR )
654
+ general, specific = get_token( parser )
655
+ raise DateParseException, "error parsing datepart, post-ADAR" if (general == TKERROR)
656
+ if( general == TKMONTH && specific == TKSHENI )
657
+ specific = TKADAR_SHENI
658
+ else
659
+ put_token( parser, general, specific )
660
+ end
661
+ end
662
+ end
663
+
664
+ if ( ( flags & GEDFMONTH ) != 0 )
665
+ state = ST_DT_ERROR
666
+ else
667
+ month = validate_month_for_type( specific, type )
668
+ if ( month < 1 )
669
+ state = ST_DT_ERROR
670
+ else
671
+ datePart.data.day = number
672
+ datePart.data.month = month
673
+ end
674
+ flags |= GEDFMONTH
675
+ number = 0
676
+ end
677
+
678
+ # 2: if SLASH, then error, else set SLASH, set number to be year
679
+ when 2
680
+ if ( ( ( flags & GEDFSLASH ) != 0 ) || ( type != GCTGREGORIAN ) )
681
+ state = ST_DT_ERROR
682
+ else
683
+ datePart.data.year = number if ( number > 0 )
684
+
685
+ datePart.data.flags |= GFYEARSPAN
686
+ number = 0
687
+ flags |= GEDFSLASH
688
+ end
689
+
690
+ # 3: if not SLASH set number to be year, set bc
691
+ # 4: if not SLASH set number to be year, terminate
692
+ # 6: terminate
693
+ when 3, 4, 6
694
+ if (dateState.action == 3)
695
+ if( type != GCTGREGORIAN )
696
+ state = ST_DT_ERROR
697
+ break
698
+ end
699
+ datePart.data.adbc = GEDADBCBC
700
+ end
701
+
702
+ if (dateState.action == 3 || dateState.action == 4)
703
+ if( ( number > 0 ) && ( ( flags & GEDFSLASH ) == 0 ) )
704
+ datePart.data.year = number
705
+ number = 0
706
+ end
707
+ end
708
+
709
+
710
+ datePart.data.flags |= GFNODAY if( datePart.data.day < 1 )
711
+
712
+ datePart.data.flags |= GFNOMONTH if( datePart.data.month < 1 )
713
+
714
+ datePart.data.flags |= GFNOYEAR if( datePart.data.year < 1 )
715
+
716
+
717
+ # 5: if NUMBER, set number to be day. set number to be year, store number, set NUMBER
718
+ when 5
719
+ datePart.data.day = number if( ( number > 0 ) && ( ( flags & GEDFNUMBER ) != 0 ) )
720
+
721
+ datePart.data.year = specific
722
+
723
+ number = 0
724
+ flags |= GEDFNUMBER
725
+
726
+ # 7: set number to be year2 (Gregorian Calendar)
727
+ when 7
728
+ datePart.data.year2 = ( specific % 100 )
729
+ number = 0
730
+ end
731
+
732
+ break
733
+ end
734
+ end
735
+
736
+ state = ST_DT_ERROR if( transitionFound == 0 )
737
+ end
738
+
739
+ raise DateParseException, "error parsing datepart, general" if( state == ST_DT_ERROR )
740
+
741
+ end
742
+
743
+
744
+ def self.parse_gedcom_date( dateString, date, type = GCTDEFAULT )
745
+ # Parse out a GEDCOM date (class method)
746
+ # Inputs: dateString - String containing GEDCOM date
747
+ # date - date (GEDDateValue)
748
+ # type - calendar type
749
+ # Outputs: None (updated date)
750
+
751
+ parser = GEDParserState.new( "", 0, 0, 0 )
752
+ parser.buffer = dateString
753
+
754
+ # New date 1 if it's nil
755
+ date.date1 = GEDDate.new( type, GFNONE, nil ) if not date.date1
756
+ datePart = date.date1
757
+
758
+ state = ST_DV_START
759
+ flags = GEDFNONE
760
+ datesRead = 0
761
+
762
+ while ( ( state != ST_DV_END ) && ( state != ST_DV_ERROR ) )
763
+ savePos = parser.pos
764
+ general, specific = get_token( parser )
765
+ raise DateParseException, "error parsing date" if (general == TKERROR)
766
+ transitionFound = 0
767
+
768
+ DateValueStateTable.each do |dateValueState|
769
+ break if dateValueState.state < 1
770
+
771
+ if( ( dateValueState.state == state ) && ( dateValueState.input == general ) )
772
+
773
+ transitionFound = 1
774
+ state = dateValueState.nextState
775
+
776
+ case ( dateValueState.action )
777
+ # 0: inc dates read, parse a date
778
+ when 0
779
+ put_token( parser, general, specific )
780
+ begin
781
+ if (datesRead != 0)
782
+ # New date 2 if it's nil
783
+ date.date2 = GEDDate.new( type, GFNONE, nil ) if not date.date2
784
+ datePart = date.date2
785
+ end
786
+ parse_date_part( parser, datePart, type )
787
+ datesRead+=1
788
+ rescue
789
+ state = ST_DV_ERROR
790
+ end
791
+
792
+ # 1: set the approx type
793
+ when 1
794
+ date.flags = case specific
795
+ when TKABOUT then GCABOUT
796
+ when TKCALCULATED then GCCALCULATED
797
+ when TKESTIMATED then GCESTIMATED
798
+ end
799
+
800
+ # 2: set the range type
801
+ when 2
802
+ date.flags = case specific
803
+ when TKBEFORE then GCBEFORE
804
+ when TKAFTER then GCAFTER
805
+ when TKBETWEEN
806
+ flags |= GEDFBETWEEN
807
+ GCBETWEEN
808
+ end
809
+
810
+ # 3: set the period type
811
+ when 3
812
+ if general == TKTO
813
+ date.flags = GCTO
814
+ elsif specific == TKFROM
815
+ date.flags = GCFROM
816
+ flags |= GEDFFROM
817
+ end
818
+
819
+ # 4: set interpreted
820
+ when 4
821
+ date.flags = GCINTERPRETED
822
+ flags |= GEDFINTERP
823
+
824
+ # 5: get remaining buffer as phrase
825
+ # 7: if 'interpreted', get remaining buffer as phrase
826
+ when 5, 7
827
+ # This is kind of a sucky way to handle this, but the shared functionality
828
+ # between action 5 and 7 doesn't seem like enough to warrant breaking out
829
+ # into it's own method.
830
+ if dateValueState.action == 7 && ( flags & GEDFINTERP ) == 0
831
+ state = ST_DV_ERROR
832
+ break
833
+ end
834
+
835
+ # Strip off trailing whitespace and closing parenthesis
836
+ buffer = parser.buffer.slice( parser.pos, parser.buffer.length ).rstrip.split( ')' )[0]
837
+ datePart.data = buffer
838
+ datePart.flags = GFPHRASE
839
+ parser.pos = parser.buffer.length
840
+
841
+ # 6: if 'between' and not second date read, error, else terminate
842
+ when 6
843
+ state = ST_DV_ERROR if( ( ( flags & GEDFBETWEEN ) != 0 ) && datesRead < 2 )
844
+
845
+ # else -- nextState is ST_DV_END, so we're done!
846
+
847
+ # 7: see above 5
848
+
849
+ # 8: if 'between', prepare to read next date
850
+ when 8
851
+ state = ST_DV_ERROR if( ( flags & GEDFBETWEEN ) == 0 )
852
+
853
+ # 9: if 'from', set FROMTO, prepare to read next date
854
+ when 9
855
+ if( ( flags & GEDFFROM ) == 0 )
856
+ state = ST_DV_ERROR
857
+ else
858
+ date.flags = GCFROMTO
859
+ end
860
+
861
+ # 10: set status
862
+ when 10
863
+ date.flags = case specific
864
+ when TKCHILD then GCCHILD
865
+ when TKCLEARED then GCCLEARED
866
+ when TKCOMPLETED then GCCOMPLETED
867
+ when TKINFANT then GCINFANT
868
+ when TKPRE1970 then GCPRE1970
869
+ when TKQUALIFIED then GCQUALIFIED
870
+ when TKSTILLBORN then GCSTILLBORN
871
+ when TKSUBMITTED then GCSUBMITTED
872
+ when TKUNCLEARED then GCUNCLEARED
873
+ when TKBIC then GCBIC
874
+ when TKDNS then GCDNS
875
+ when TKDNSCAN then GCDNSCAN
876
+ when TKDEAD then GCDEAD
877
+ end
878
+ end
879
+ break # ... Out of the DateValueStateTable.each block
880
+ end
881
+ end
882
+
883
+ state = ST_DV_ERROR if( transitionFound == 0 )
884
+ end
885
+
886
+ if( state == ST_DV_ERROR )
887
+ parser.pos = savePos
888
+ datePart.flags = GFNONSTANDARD
889
+ datePart.data = parser.buffer.slice( parser.pos, parser.buffer.length )
890
+ raise DateParseException, "error parsing date, general"
891
+ end
892
+ end
893
+
894
+ def self.build_gedcom_date_string( date )
895
+ # Stringify a GEDCOM date (class method)
896
+ # Inputs: date - date (GEDDateValue)
897
+ # Outputs: buffer - output string
898
+ buffer = ""
899
+
900
+ case ( date.flags )
901
+ when GCABOUT then buffer += "abt "
902
+ when GCCALCULATED then buffer += "cal "
903
+ when GCESTIMATED then buffer += "est "
904
+ when GCBEFORE then buffer += "bef "
905
+ when GCAFTER then buffer += "aft "
906
+ when GCBETWEEN then buffer += "bet "
907
+ when GCFROM then
908
+ when GCFROMTO then buffer += "from "
909
+ when GCTO then buffer += "to "
910
+ when GCINTERPRETED then buffer += "int "
911
+
912
+ when GCCHILD then buffer += "child"; return
913
+ when GCCLEARED then buffer += "cleared"; return
914
+ when GCCOMPLETED then buffer += "completed"; return
915
+ when GCINFANT then buffer += "infant"; return
916
+ when GCPRE1970 then buffer += "pre-1970"; return
917
+ when GCQUALIFIED then buffer += "qualified"; return
918
+ when GCSTILLBORN then buffer += "stillborn"; return
919
+ when GCSUBMITTED then buffer += "submitted"; return
920
+ when GCUNCLEARED then buffer += "uncleared"; return
921
+ when GCBIC then buffer += "BIC"; return
922
+ when GCDNS then buffer += "DNS"; return
923
+ when GCDNSCAN then buffer += "DNSCAN"; return
924
+ when GCDEAD then buffer += "dead"; return
925
+ end
926
+
927
+ buffer += get_date_text( date.date1 ) if (date.date1)
928
+
929
+ case ( date.flags )
930
+ when GCBETWEEN then buffer += " and "
931
+ when GCFROMTO then buffer += " to "
932
+ end
933
+
934
+ buffer += get_date_text( date.date2 ) if (date.date2)
935
+ buffer
936
+ end
937
+
938
+ def self.build_gedcom_date_part_string( date )
939
+ # Stringify a GEDCOM date part (class method)
940
+ # Inputs: date - date part (GEDDate)
941
+ # Outputs: buffer - output string
942
+ buffer = ""
943
+ buffer += get_date_text( date )
944
+ buffer
945
+ end
946
+
947
+ end
948
+
949
+ class DateParseException < Exception
950
+
951
+ end
952
+ end