1 : /******************************************************************************
2 : * $Id: ogrbnaparser.cpp 20996 2010-10-28 18:38:15Z rouault $
3 : *
4 : * Project: BNA Parser
5 : * Purpose: Parse a BNA record
6 : * Author: Even Rouault, even dot rouault at mines dash paris dot org
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2007, Even Rouault
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "ogrbnaparser.h"
31 :
32 : #include "cpl_conv.h"
33 : #include "cpl_string.h"
34 :
35 884 : void BNA_FreeRecord(BNARecord* record)
36 : {
37 884 : if (record)
38 : {
39 : int i;
40 4420 : for(i=0;i<NB_MAX_BNA_IDS;i++)
41 : {
42 3536 : if (record->ids[i]) VSIFree(record->ids[i]);
43 3536 : record->ids[i] = NULL;
44 : }
45 884 : CPLFree(record->tabCoords);
46 884 : record->tabCoords = NULL;
47 884 : CPLFree(record);
48 : }
49 884 : }
50 :
51 0 : const char* BNA_FeatureTypeToStr(BNAFeatureType featureType)
52 : {
53 0 : switch (featureType)
54 : {
55 : case BNA_POINT:
56 0 : return "point";
57 : case BNA_POLYGON:
58 0 : return "polygon";
59 : case BNA_POLYLINE:
60 0 : return "polyline";
61 : case BNA_ELLIPSE:
62 0 : return "ellipse";
63 : default:
64 0 : return "unknown";
65 : }
66 : }
67 :
68 0 : void BNA_Display(BNARecord* record)
69 : {
70 : int i;
71 : fprintf(stderr, "\"%s\", \"%s\", \"%s\", %s\n",
72 0 : record->ids[0] ? record->ids[0] : "",
73 0 : record->ids[1] ? record->ids[1] : "",
74 0 : record->ids[2] ? record->ids[2] : "",
75 0 : BNA_FeatureTypeToStr(record->featureType));
76 0 : for(i=0;i<record->nCoords;i++)
77 0 : fprintf(stderr, "%f, %f\n", record->tabCoords[i][0], record->tabCoords[i][1]);
78 0 : }
79 :
80 : /*
81 : For a description of the format, see http://www.softwright.com/faq/support/boundary_file_bna_format.html
82 : and http://64.145.236.125/forum/topic.asp?topic_id=1930&forum_id=1&Topic_Title=how+to+edit+*.bna+files%3F&forum_title=Surfer+Support&M=False
83 : */
84 :
85 : /* The following lines are a valid BNA file (for this parser) :
86 : "PID1","SID1",1
87 : -0,0
88 : "PID2","SID2",-2, -1e-5,-1e2 ,-2,-2
89 : "PID3""a","SID3",4
90 : 5,5
91 : 6,5
92 : 6,6
93 : 5,5
94 : "PID4","SID4",2
95 : 10,10
96 : 5,4
97 :
98 : "PID4","SID4",2
99 :
100 : 10,10
101 : 5,4
102 : */
103 :
104 : /* The following lines are a valid BNA file (for this parser) :
105 : (small extract from ftp://ftp.ciesin.org/pub/census/usa/tiger/ct/bnablk/b09001.zip)
106 : "090010000.00099A","099A","090010000.00099A","blk",21
107 : -73.049337, 41.125000 -73.093761, 41.157780 -73.107900, 41.168300
108 : -73.106878, 41.166459 -73.095800, 41.146500 -73.114553, 41.146331
109 : -73.138922, 41.147993 -73.166200, 41.154200 -73.198497, 41.085988
110 : -73.232241, 41.029986 -73.229548, 41.030507 -73.183922, 41.041955
111 : -73.178678, 41.043239 -73.177951, 41.043417 -73.147888, 41.050781
112 : -73.118658, 41.057942 -73.052399, 41.074174 -73.024976, 41.080892
113 : -73.000000, 41.087010 -73.035597, 41.114420 -73.049337, 41.125000
114 : */
115 :
116 : /* We are (and must be) a bit tolerant : BNA files format has several variations */
117 : /* and most don't follow strictly the 'specification' */
118 : /* Extra spaces, tabulations or line feed are accepted and ignored */
119 : /* We allow one line format and several line format in the same file */
120 : /* We allow from NB_MIN_BNA_IDS to NB_MAX_BNA_IDS ids */
121 : /* We allow that couples of coordinates on the same line may be separated only by spaces */
122 : /* (instead of being separated by a comma) */
123 :
124 : #define STRING_NOT_TERMINATED "string not terminated when end of line occured"
125 : #define MISSING_FIELDS "missing fields"
126 : #define BAD_INTEGER_NUMBER_FORMAT "bad integer number format"
127 : #define BAD_FLOAT_NUMBER_FORMAT "bad float number format"
128 : #define PRIMARY_ID_MANDATORY "primary ID can't be empty or missing"
129 : #define STRING_EXPECTED "string expected"
130 : #define NUMBER_EXPECTED "number expected"
131 : #define INTEGER_NUMBER_EXPECTED "integer number expected"
132 : #define FLOAT_NUMBER_EXPECTED "float number expected"
133 : #define INVALID_GEOMETRY_TYPE "invalid geometry type"
134 : #define TOO_LONG_ID "too long id (> 256 characters)"
135 : #define MAX_BNA_IDS_REACHED "maximum number of IDs reached"
136 : #define NOT_ENOUGH_MEMORY "not enough memory for request number of coordinates"
137 : #define LINE_TOO_LONG "line too long"
138 :
139 : #define TMP_BUFFER_SIZE 256
140 : #define LINE_BUFFER_SIZE 1024
141 :
142 : enum
143 : {
144 : BNA_LINE_OK,
145 : BNA_LINE_EOF,
146 : BNA_LINE_TOO_LONG
147 : };
148 :
149 6098 : static int BNA_GetLine(char szLineBuffer[LINE_BUFFER_SIZE+1], VSILFILE* f)
150 : {
151 6098 : char* ptrCurLine = szLineBuffer;
152 6098 : int nRead = VSIFReadL(szLineBuffer, 1, LINE_BUFFER_SIZE, f);
153 6098 : szLineBuffer[nRead] = 0;
154 6098 : if (nRead == 0)
155 : {
156 : /* EOF */
157 146 : return BNA_LINE_EOF;
158 : }
159 :
160 5952 : int bFoundEOL = FALSE;
161 138964 : while (*ptrCurLine)
162 : {
163 133012 : if (*ptrCurLine == 0x0d || *ptrCurLine == 0x0a)
164 : {
165 5952 : bFoundEOL = TRUE;
166 5952 : break;
167 : }
168 127060 : ptrCurLine ++;
169 : }
170 5952 : if (!bFoundEOL)
171 : {
172 0 : if (nRead < LINE_BUFFER_SIZE)
173 0 : return BNA_LINE_OK;
174 : else
175 0 : return BNA_LINE_TOO_LONG;
176 : }
177 :
178 5952 : if (*ptrCurLine == 0x0d)
179 : {
180 4448 : if (ptrCurLine == szLineBuffer + LINE_BUFFER_SIZE - 1)
181 : {
182 : char c;
183 0 : nRead = VSIFReadL(&c, 1, 1, f);
184 0 : if (nRead == 1)
185 : {
186 0 : if (c == 0x0a)
187 : {
188 : /* Do nothing */
189 : }
190 : else
191 : {
192 0 : VSIFSeekL(f, VSIFTellL(f) - 1, SEEK_SET);
193 : }
194 : }
195 : }
196 4448 : else if (ptrCurLine[1] == 0x0a)
197 : {
198 4448 : VSIFSeekL(f, VSIFTellL(f) + ptrCurLine + 2 - (szLineBuffer + nRead), SEEK_SET);
199 : }
200 : else
201 : {
202 0 : VSIFSeekL(f, VSIFTellL(f) + ptrCurLine + 1 - (szLineBuffer + nRead), SEEK_SET);
203 : }
204 : }
205 : else /* *ptrCurLine == 0x0a */
206 : {
207 1504 : VSIFSeekL(f, VSIFTellL(f) + ptrCurLine + 1 - (szLineBuffer + nRead), SEEK_SET);
208 : }
209 5952 : *ptrCurLine = 0;
210 :
211 5952 : return BNA_LINE_OK;
212 : }
213 :
214 :
215 884 : BNARecord* BNA_GetNextRecord(VSILFILE* f,
216 : int* ok,
217 : int* curLine,
218 : int verbose,
219 : BNAFeatureType interestFeatureType)
220 : {
221 : BNARecord* record;
222 : char c;
223 884 : int inQuotes = FALSE;
224 884 : int numField = 0;
225 884 : char* ptrBeginningOfNumber = NULL;
226 884 : int exponentFound = 0;
227 884 : int exponentSignFound = 0;
228 884 : int dotFound = 0;
229 884 : int numChar = 0;
230 884 : const char* detailedErrorMsg = NULL;
231 884 : BNAFeatureType currentFeatureType = (BNAFeatureType) -1;
232 884 : int nbExtraId = 0;
233 : char tmpBuffer[NB_MAX_BNA_IDS][TMP_BUFFER_SIZE+1];
234 884 : int tmpBufferLength[NB_MAX_BNA_IDS] = {0, 0, 0};
235 : char szLineBuffer[LINE_BUFFER_SIZE + 1];
236 :
237 884 : record = (BNARecord*)CPLMalloc(sizeof(BNARecord));
238 884 : memset(record, 0, sizeof(BNARecord));
239 :
240 5214 : while (TRUE)
241 : {
242 6098 : numChar = 0;
243 6098 : (*curLine)++;
244 :
245 6098 : int retGetLine = BNA_GetLine(szLineBuffer, f);
246 6098 : if (retGetLine == BNA_LINE_TOO_LONG)
247 : {
248 0 : detailedErrorMsg = LINE_TOO_LONG;
249 0 : goto error;
250 : }
251 6098 : else if (retGetLine == BNA_LINE_EOF)
252 : {
253 : break;
254 : }
255 :
256 5952 : char* ptrCurLine = szLineBuffer;
257 5952 : const char* ptrBeginLine = szLineBuffer;
258 :
259 5952 : if (*ptrCurLine == 0)
260 0 : continue;
261 :
262 127060 : while(1)
263 : {
264 133012 : numChar = ptrCurLine - ptrBeginLine;
265 133012 : c = *ptrCurLine;
266 133012 : if (c == 0) c = 10;
267 133012 : if (inQuotes)
268 : {
269 7400 : if (c == 10)
270 : {
271 0 : detailedErrorMsg = STRING_NOT_TERMINATED;
272 0 : goto error;
273 : }
274 7400 : else if (c == '"' && ptrCurLine[1] == '"')
275 : {
276 0 : if (tmpBufferLength[numField] == TMP_BUFFER_SIZE)
277 : {
278 0 : detailedErrorMsg = TOO_LONG_ID;
279 0 : goto error;
280 : }
281 0 : tmpBuffer[numField][tmpBufferLength[numField]++] = c;
282 :
283 0 : ptrCurLine++;
284 : }
285 7400 : else if (c == '"')
286 : {
287 1480 : inQuotes = FALSE;
288 : }
289 : else
290 : {
291 5920 : if (tmpBufferLength[numField] == TMP_BUFFER_SIZE)
292 : {
293 0 : detailedErrorMsg = TOO_LONG_ID;
294 0 : goto error;
295 : }
296 5920 : tmpBuffer[numField][tmpBufferLength[numField]++] = c;
297 : }
298 : }
299 125612 : else if (c == ' ' || c == '\t')
300 : {
301 2324 : if (numField > NB_MIN_BNA_IDS + nbExtraId && ptrBeginningOfNumber != NULL)
302 : {
303 0 : do
304 : {
305 2324 : ptrCurLine++;
306 2324 : numChar = ptrCurLine - ptrBeginLine;
307 2324 : c = *ptrCurLine;
308 2324 : if (!(c == ' ' || c == '\t'))
309 2324 : break;
310 : } while(c);
311 2324 : if (c == 0) c = 10;
312 :
313 2324 : if (interestFeatureType == BNA_READ_ALL ||
314 : interestFeatureType == currentFeatureType)
315 : {
316 664 : char* pszComma = strchr(ptrBeginningOfNumber, ',');
317 664 : if (pszComma)
318 664 : *pszComma = '\0';
319 664 : record->tabCoords[(numField - nbExtraId - NB_MIN_BNA_IDS - 1) / 2]
320 664 : [1 - ((numField - nbExtraId) % 2)] =
321 664 : CPLAtof(ptrBeginningOfNumber);
322 664 : if (pszComma)
323 664 : *pszComma = ',';
324 : }
325 2324 : if (numField == NB_MIN_BNA_IDS + 1 + nbExtraId + 2 * record->nCoords - 1)
326 : {
327 0 : if (c != 10)
328 : {
329 0 : if (verbose)
330 : {
331 : CPLError(CE_Warning, CPLE_AppDefined,
332 : "At line %d, at char %d, extra data will be ignored!\n",
333 0 : *curLine, numChar+1);
334 : }
335 : }
336 0 : *ok = 1;
337 0 : return record;
338 : }
339 :
340 2324 : ptrBeginningOfNumber = NULL;
341 2324 : exponentFound = 0;
342 2324 : exponentSignFound = 0;
343 2324 : dotFound = 0;
344 2324 : numField++;
345 :
346 2324 : if (c == 10)
347 0 : break;
348 :
349 2324 : if (c != ',')
350 : {
351 : /* don't increment ptrCurLine */
352 2324 : continue;
353 : }
354 : }
355 : else
356 : {
357 : /* ignore */
358 : }
359 : }
360 132842 : else if (c == 10 || c == ',')
361 : {
362 : /* Eat a comma placed at end of line */
363 15506 : if (c == ',')
364 : {
365 9554 : const char* ptr = ptrCurLine+1;
366 19108 : while(*ptr)
367 : {
368 9554 : if (*ptr != ' ' && *ptr != '\t')
369 9554 : break;
370 0 : ptr++;
371 : }
372 9554 : if (*ptr == 0)
373 : {
374 0 : c = 10;
375 : }
376 : }
377 :
378 15506 : if (numField == 0)
379 : {
380 : /* Maybe not so mandatory.. Atlas MapMaker(TM) exports BNA files with empty primaryID */
381 : /*
382 : if (record->primaryID == NULL || *(record->primaryID) == 0)
383 : {
384 : detailedErrorMsg = PRIMARY_ID_MANDATORY;
385 : goto error;
386 : }
387 : */
388 : }
389 14768 : else if (numField == NB_MIN_BNA_IDS + nbExtraId)
390 : {
391 : int nCoords;
392 738 : if (ptrBeginningOfNumber == NULL)
393 : {
394 0 : detailedErrorMsg = INTEGER_NUMBER_EXPECTED;
395 0 : goto error;
396 : }
397 738 : nCoords = atoi(ptrBeginningOfNumber);
398 738 : if (nCoords == 0 || nCoords == -1)
399 : {
400 0 : detailedErrorMsg = INVALID_GEOMETRY_TYPE;
401 0 : goto error;
402 : }
403 738 : else if (nCoords == 1)
404 : {
405 144 : currentFeatureType = record->featureType = BNA_POINT;
406 144 : record->nCoords = 1;
407 : }
408 594 : else if (nCoords == 2)
409 : {
410 124 : currentFeatureType = record->featureType = BNA_ELLIPSE;
411 124 : record->nCoords = 2;
412 : }
413 470 : else if (nCoords > 0)
414 : {
415 410 : currentFeatureType = record->featureType = BNA_POLYGON;
416 410 : record->nCoords = nCoords;
417 : }
418 : else
419 : {
420 60 : currentFeatureType = record->featureType = BNA_POLYLINE;
421 60 : record->nCoords = -nCoords;
422 : }
423 :
424 738 : record->nIDs = NB_MIN_BNA_IDS + nbExtraId;
425 :
426 738 : if (interestFeatureType == BNA_READ_ALL ||
427 : interestFeatureType == currentFeatureType)
428 : {
429 : int i;
430 1390 : for(i=0;i<NB_MAX_BNA_IDS;i++)
431 : {
432 1112 : if (tmpBufferLength[i] && tmpBuffer[i][0])
433 : {
434 558 : record->ids[i] = (char*)CPLMalloc(tmpBufferLength[i] + 1);
435 558 : tmpBuffer[i][tmpBufferLength[i]] = 0;
436 558 : memcpy(record->ids[i], tmpBuffer[i], tmpBufferLength[i] + 1);
437 : }
438 : }
439 :
440 : record->tabCoords =
441 278 : (double(*)[2])VSIMalloc(record->nCoords * 2 * sizeof(double));
442 278 : if (record->tabCoords == NULL)
443 : {
444 0 : detailedErrorMsg = NOT_ENOUGH_MEMORY;
445 0 : goto error;
446 : }
447 : }
448 : }
449 14030 : else if (numField > NB_MIN_BNA_IDS + nbExtraId)
450 : {
451 13288 : if (ptrBeginningOfNumber == NULL)
452 : {
453 0 : detailedErrorMsg = FLOAT_NUMBER_EXPECTED;
454 0 : goto error;
455 : }
456 13288 : if (interestFeatureType == BNA_READ_ALL ||
457 : interestFeatureType == currentFeatureType)
458 : {
459 5756 : char* pszComma = strchr(ptrBeginningOfNumber, ',');
460 5756 : if (pszComma)
461 3234 : *pszComma = '\0';
462 5756 : record->tabCoords[(numField - nbExtraId - NB_MIN_BNA_IDS - 1) / 2]
463 5756 : [1 - ((numField - nbExtraId) % 2)] =
464 5756 : CPLAtof(ptrBeginningOfNumber);
465 5756 : if (pszComma)
466 3234 : *pszComma = ',';
467 : }
468 13288 : if (numField == NB_MIN_BNA_IDS + 1 + nbExtraId + 2 * record->nCoords - 1)
469 : {
470 738 : if (c != 10)
471 : {
472 0 : if (verbose)
473 : {
474 : CPLError(CE_Warning, CPLE_AppDefined,
475 : "At line %d, at char %d, extra data will be ignored!\n",
476 0 : *curLine, numChar+1);
477 : }
478 : }
479 738 : *ok = 1;
480 738 : return record;
481 : }
482 : }
483 :
484 14768 : ptrBeginningOfNumber = NULL;
485 14768 : exponentFound = 0;
486 14768 : exponentSignFound = 0;
487 14768 : dotFound = 0;
488 14768 : numField++;
489 :
490 14768 : if (c == 10)
491 5214 : break;
492 : }
493 107782 : else if (c == '"')
494 : {
495 1480 : if (numField < NB_MIN_BNA_IDS)
496 : {
497 1476 : inQuotes = TRUE;
498 : }
499 8 : else if (numField >= NB_MIN_BNA_IDS && currentFeatureType == -1)
500 : {
501 4 : if (ptrBeginningOfNumber == NULL)
502 : {
503 4 : if (nbExtraId == NB_MAX_BNA_IDS - NB_MIN_BNA_IDS)
504 : {
505 0 : detailedErrorMsg = MAX_BNA_IDS_REACHED;
506 0 : goto error;
507 : }
508 4 : nbExtraId ++;
509 4 : inQuotes = TRUE;
510 : }
511 : else
512 : {
513 0 : detailedErrorMsg = BAD_INTEGER_NUMBER_FORMAT;
514 0 : goto error;
515 : }
516 : }
517 : else
518 : {
519 0 : detailedErrorMsg = NUMBER_EXPECTED;
520 0 : goto error;
521 : }
522 : }
523 : else
524 : {
525 106302 : if (numField < NB_MIN_BNA_IDS || (numField == NB_MIN_BNA_IDS + nbExtraId - 1))
526 : {
527 0 : detailedErrorMsg = STRING_EXPECTED;
528 0 : goto error;
529 : }
530 106302 : else if (numField == NB_MIN_BNA_IDS + nbExtraId)
531 : {
532 1074 : if (c >= '0' && c <= '9')
533 : {
534 : }
535 120 : else if (c == '+' || c == '-')
536 : {
537 60 : if (ptrBeginningOfNumber != NULL)
538 : {
539 0 : detailedErrorMsg = BAD_INTEGER_NUMBER_FORMAT;
540 0 : goto error;
541 : }
542 : }
543 : else
544 : {
545 0 : detailedErrorMsg = BAD_INTEGER_NUMBER_FORMAT;
546 0 : goto error;
547 : }
548 1074 : if (ptrBeginningOfNumber == NULL)
549 738 : ptrBeginningOfNumber = ptrCurLine;
550 : }
551 : else
552 : {
553 105228 : if (c >= '0' && c <= '9')
554 : {
555 : }
556 14396 : else if (c == '.')
557 : {
558 14396 : if (dotFound || exponentFound)
559 : {
560 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
561 0 : goto error;
562 : }
563 14396 : dotFound = 1;
564 : }
565 0 : else if (c == '+' || c == '-')
566 : {
567 0 : if (ptrBeginningOfNumber == NULL)
568 : {
569 : }
570 0 : else if (exponentFound)
571 : {
572 0 : if (exponentSignFound == 0 && ptrCurLine > ptrBeginLine &&
573 0 : (ptrCurLine[-1] == 'e' || ptrCurLine[-1] == 'E' ||
574 0 : ptrCurLine[-1] == 'd' || ptrCurLine[-1] == 'D'))
575 : {
576 0 : exponentSignFound = 1;
577 : }
578 : else
579 : {
580 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
581 0 : goto error;
582 : }
583 : }
584 : else
585 : {
586 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
587 0 : goto error;
588 : }
589 : }
590 0 : else if (c == 'e' || c == 'E' || c == 'd' || c == 'D')
591 : {
592 0 : if (ptrBeginningOfNumber == NULL ||
593 0 : !(ptrCurLine[-1] >= '0' && ptrCurLine[-1] <= '9') ||
594 : exponentFound == 1)
595 : {
596 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
597 0 : goto error;
598 : }
599 0 : exponentFound = 1;
600 : }
601 : else
602 : {
603 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
604 0 : goto error;
605 : }
606 105228 : if (ptrBeginningOfNumber == NULL)
607 15612 : ptrBeginningOfNumber = ptrCurLine;
608 : }
609 : }
610 124736 : ptrCurLine++;
611 : }
612 : }
613 :
614 146 : if (numField == 0)
615 : {
616 : /* End of file */
617 146 : *ok = 1;
618 146 : BNA_FreeRecord(record);
619 146 : return NULL;
620 : }
621 : else
622 : {
623 0 : detailedErrorMsg = MISSING_FIELDS;
624 : goto error;
625 : }
626 : error:
627 0 : if (verbose)
628 : {
629 0 : if (detailedErrorMsg)
630 : {
631 : CPLError(CE_Failure, CPLE_AppDefined,
632 : "Parsing failed at line %d, at char %d : %s!\n",
633 0 : *curLine, numChar+1, detailedErrorMsg);
634 : }
635 : else
636 : {
637 : CPLError(CE_Failure, CPLE_AppDefined,
638 : "Parsing failed at line %d, at char %d!\n",
639 0 : *curLine, numChar+1);
640 : }
641 : }
642 0 : BNA_FreeRecord(record);
643 0 : return NULL;
644 : }
|