field.c revision 1.21 1 /* $NetBSD: field.c,v 1.21 2002/08/09 14:15:12 blymn Exp $ */
2 /*-
3 * Copyright (c) 1998-1999 Brett Lymn
4 * (blymn (at) baea.com.au, brett_lymn (at) yahoo.com.au)
5 * All rights reserved.
6 *
7 * This code has been donated to The NetBSD Foundation by the Author.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 *
29 */
30
31 #include <stdlib.h>
32 #include <strings.h>
33 #include <stdarg.h>
34 #include <form.h>
35 #include "internals.h"
36
37 extern FORM _formi_default_form;
38
39 FIELD _formi_default_field = {
40 0, /* rows in the field */
41 0, /* columns in the field */
42 0, /* dynamic rows */
43 0, /* dynamic columns */
44 0, /* maximum growth */
45 0, /* starting row in the form subwindow */
46 0, /* starting column in the form subwindow */
47 0, /* number of off screen rows */
48 0, /* index of this field in form fields array. */
49 0, /* number of buffers associated with this field */
50 FALSE, /* set to true if buffer 0 has changed. */
51 NO_JUSTIFICATION, /* justification style of the field */
52 FALSE, /* set to true if field is in overlay mode */
53 0, /* starting char in string (horiz scroll) */
54 0, /* starting line in field (vert scroll) */
55 0, /* number of rows actually used in field */
56 0, /* actual pos of cursor in row, not same as x pos due to tabs */
57 0, /* x pos of cursor in field */
58 0, /* y pos of cursor in field */
59 0, /* start of a new page on the form if 1 */
60 0, /* number of the page this field is on */
61 A_NORMAL, /* character attributes for the foreground */
62 A_NORMAL, /* character attributes for the background */
63 ' ', /* padding character */
64 DEFAULT_FORM_OPTS, /* options for the field */
65 NULL, /* the form this field is bound to, if any */
66 NULL, /* field above this one */
67 NULL, /* field below this one */
68 NULL, /* field to the left of this one */
69 NULL, /* field to the right of this one */
70 NULL, /* user defined pointer. */
71 NULL, /* used if fields are linked */
72 NULL, /* type struct for the field */
73 {NULL, NULL}, /* circle queue glue for sorting fields */
74 NULL, /* args for field type. */
75 0, /* number of allocated slots in lines array */
76 NULL, /* pointer to the array of lines structures. */
77 NULL, /* array of buffers for the field */
78 };
79
80 /* internal function prototypes */
81 static int
82 field_buffer_init(FIELD *field, int buffer, size_t len);
83 static FIELD *
84 _formi_create_field(FIELD *, int, int, int, int, int, int);
85
86
87 /*
88 * Set the userptr for the field
89 */
90 int
91 set_field_userptr(FIELD *field, void *ptr)
92 {
93 FIELD *fp = (field == NULL) ? &_formi_default_field : field;
94
95 fp->userptr = ptr;
96
97 return E_OK;
98 }
99
100 /*
101 * Return the userptr for the field.
102 */
103
104 void *
105 field_userptr(FIELD *field)
106 {
107 if (field == NULL)
108 return _formi_default_field.userptr;
109 else
110 return field->userptr;
111 }
112
113 /*
114 * Set the options for the designated field.
115 */
116 int
117 set_field_opts(FIELD *field, Form_Options options)
118 {
119 int i;
120
121 FIELD *fp = (field == NULL) ? &_formi_default_field : field;
122
123 /* not allowed to set opts if the field is the current one */
124 if ((field != NULL) && (field->parent != NULL) &&
125 (field->parent->cur_field == field->index))
126 return E_CURRENT;
127
128 if ((options & O_STATIC) == O_STATIC) {
129 for (i = 0; i < fp->nbuf; i++) {
130 if (fp->buffers[i].length > fp->cols)
131 fp->buffers[i].string[fp->cols] = '\0';
132 }
133 }
134
135 fp->opts = options;
136
137 /* if appropriate, redraw the field */
138 if ((field != NULL) && (field->parent != NULL)
139 && (field->parent->posted == 1)) {
140 _formi_redraw_field(field->parent, field->index);
141 pos_form_cursor(field->parent);
142 wrefresh(field->parent->scrwin);
143 }
144
145 return E_OK;
146 }
147
148 /*
149 * Turn on the passed field options.
150 */
151 int
152 field_opts_on(FIELD *field, Form_Options options)
153 {
154 int i;
155
156 FIELD *fp = (field == NULL) ? &_formi_default_field : field;
157
158 /* not allowed to set opts if the field is the current one */
159 if ((field != NULL) && (field->parent != NULL) &&
160 (field->parent->cur_field == field->index))
161 return E_CURRENT;
162
163 if ((options & O_STATIC) == O_STATIC) {
164 for (i = 0; i < fp->nbuf; i++) {
165 if (fp->buffers[i].length > fp->cols)
166 fp->buffers[i].string[fp->cols] = '\0';
167 }
168 }
169
170 fp->opts |= options;
171
172 /* if appropriate, redraw the field */
173 if ((field != NULL) && (field->parent != NULL)
174 && (field->parent->posted == 1)) {
175 _formi_redraw_field(field->parent, field->index);
176 pos_form_cursor(field->parent);
177 wrefresh(field->parent->scrwin);
178 }
179
180 return E_OK;
181 }
182
183 /*
184 * Turn off the passed field options.
185 */
186 int
187 field_opts_off(FIELD *field, Form_Options options)
188 {
189 FIELD *fp = (field == NULL) ? &_formi_default_field : field;
190
191 /* not allowed to set opts if the field is the current one */
192 if ((field != NULL) && (field->parent != NULL) &&
193 (field->parent->cur_field == field->index))
194 return E_CURRENT;
195
196 fp->opts &= ~options;
197
198 /* if appropriate, redraw the field */
199 if ((field != NULL) && (field->parent != NULL)
200 && (field->parent->posted == 1)) {
201 _formi_redraw_field(field->parent, field->index);
202 pos_form_cursor(field->parent);
203 wrefresh(field->parent->scrwin);
204 }
205
206 return E_OK;
207 }
208
209 /*
210 * Return the field options associated with the passed field.
211 */
212 Form_Options
213 field_opts(FIELD *field)
214 {
215 if (field == NULL)
216 return _formi_default_field.opts;
217 else
218 return field->opts;
219 }
220
221 /*
222 * Set the justification for the passed field.
223 */
224 int
225 set_field_just(FIELD *field, int justification)
226 {
227 FIELD *fp = (field == NULL) ? &_formi_default_field : field;
228
229 /*
230 * not allowed to set justification if the field is
231 * the current one
232 */
233 if ((field != NULL) && (field->parent != NULL) &&
234 (field->parent->cur_field == field->index))
235 return E_CURRENT;
236
237 if ((justification < MIN_JUST_STYLE) /* check justification valid */
238 || (justification > MAX_JUST_STYLE))
239 return E_BAD_ARGUMENT;
240
241 /* only allow justification on static, single row fields */
242 if (((fp->opts & O_STATIC) != O_STATIC) ||
243 ((fp->rows + fp->nrows) > 1))
244 return E_BAD_ARGUMENT;
245
246 fp->justification = justification;
247
248 _formi_init_field_xpos(fp);
249
250 return E_OK;
251 }
252
253 /*
254 * Return the justification style of the field passed.
255 */
256 int
257 field_just(FIELD *field)
258 {
259 if (field == NULL)
260 return _formi_default_field.justification;
261 else
262 return field->justification;
263 }
264
265 /*
266 * Return information about the field passed.
267 */
268 int
269 field_info(FIELD *field, int *rows, int *cols, int *frow, int *fcol,
270 int *nrow, int *nbuf)
271 {
272 if (field == NULL)
273 return E_BAD_ARGUMENT;
274
275 *rows = field->rows;
276 *cols = field->cols;
277 *frow = field->form_row;
278 *fcol = field->form_col;
279 *nrow = field->nrows;
280 *nbuf = field->nbuf;
281
282 return E_OK;
283 }
284
285 /*
286 * Report the dynamic field information.
287 */
288 int
289 dynamic_field_info(FIELD *field, int *drows, int *dcols, int *max)
290 {
291 if (field == NULL)
292 return E_BAD_ARGUMENT;
293
294 if ((field->opts & O_STATIC) == O_STATIC) {
295 *drows = field->rows;
296 *dcols = field->cols;
297 } else {
298 *drows = field->drows;
299 *dcols = field->dcols;
300 }
301
302 *max = field->max;
303
304 return E_OK;
305 }
306
307 /*
308 * Init all the field variables, perform wrapping and other tasks
309 * after the field buffer is set.
310 */
311 static int
312 field_buffer_init(FIELD *field, int buffer, size_t len)
313 {
314 int status;
315
316 if (buffer == 0) {
317 field->start_char = 0;
318 field->start_line = 0;
319 field->row_xpos = 0;
320 field->cursor_xpos = 0;
321 field->cursor_ypos = 0;
322 field->row_count = 1; /* must be at least one row */
323 field->lines[0].start = 0;
324 field->lines[0].end = (len > 0)? (len - 1) : 0;
325 field->lines[0].length =
326 _formi_tab_expanded_length(field->buffers[0].string,
327 0, field->lines[0].end);
328
329 /* we have to hope the wrap works - if it does not then the
330 buffer is pretty much borked */
331 status = _formi_wrap_field(field, 0);
332 if (status != E_OK)
333 return status;
334
335 /*
336 * calculate the tabs for a single row field, the
337 * multiline case is handled when the wrap is done.
338 */
339 if (field->row_count == 1)
340 _formi_calculate_tabs(field, 0);
341
342 /* redraw the field to reflect the new contents. If the field
343 * is attached....
344 */
345 if ((field->parent != NULL) && (field->parent->posted == 1)) {
346 _formi_redraw_field(field->parent, field->index);
347 /* make sure cursor goes back to current field */
348 pos_form_cursor(field->parent);
349 }
350 }
351
352 return E_OK;
353 }
354
355
356 /*
357 * Set the field buffer to the string that results from processing
358 * the given format (fmt) using sprintf.
359 */
360 int
361 set_field_printf(FIELD *field, int buffer, char *fmt, ...)
362 {
363 int len;
364 va_list args;
365
366 if (field == NULL)
367 return E_BAD_ARGUMENT;
368
369 if (buffer >= field->nbuf)
370 return E_BAD_ARGUMENT;
371
372 va_start(args, fmt);
373 /* check for buffer already existing, free the storage */
374 if (field->buffers[buffer].allocated != 0)
375 free(field->buffers[buffer].string);
376
377 len = vasprintf(&field->buffers[buffer].string, fmt, args);
378 va_end(args);
379 if (len < 0)
380 return E_SYSTEM_ERROR;
381
382 field->buffers[buffer].length = len;
383 field->buffers[buffer].allocated = len + 1;
384 if (((field->opts & O_STATIC) == O_STATIC) && (len > field->cols)
385 && ((field->rows + field->nrows) == 1))
386 len = field->cols;
387
388 field->buffers[buffer].string[len] = '\0';
389 return field_buffer_init(field, buffer, (unsigned int) len);
390 }
391
392 /*
393 * Set the value of the field buffer to the value given.
394 */
395
396 int
397 set_field_buffer(FIELD *field, int buffer, char *value)
398 {
399 size_t len;
400 int status;
401
402 if (field == NULL)
403 return E_BAD_ARGUMENT;
404
405 if (buffer >= field->nbuf) /* make sure buffer is valid */
406 return E_BAD_ARGUMENT;
407
408 len = strlen(value);
409 if (((field->opts & O_STATIC) == O_STATIC) && (len > field->cols)
410 && ((field->rows + field->nrows) == 1))
411 len = field->cols;
412
413 #ifdef DEBUG
414 if (_formi_create_dbg_file() != E_OK)
415 return E_SYSTEM_ERROR;
416
417 fprintf(dbg,
418 "set_field_buffer: entry: len = %d, value = %s, buffer=%d\n",
419 len, value, buffer);
420 fprintf(dbg, "set_field_buffer: entry: string = ");
421 if (field->buffers[buffer].string != NULL)
422 fprintf(dbg, "%s, len = %d\n", field->buffers[buffer].string,
423 field->buffers[buffer].length);
424 else
425 fprintf(dbg, "(null), len = 0\n");
426 fprintf(dbg, "set_field_buffer: entry: lines.len = %d\n",
427 field->lines[0].length);
428 #endif
429
430 if ((field->buffers[buffer].string =
431 (char *) realloc(field->buffers[buffer].string, len + 1)) == NULL)
432 return E_SYSTEM_ERROR;
433
434 strlcpy(field->buffers[buffer].string, value, len + 1);
435 field->buffers[buffer].length = len;
436 field->buffers[buffer].allocated = len + 1;
437 status = field_buffer_init(field, buffer, len);
438
439 #ifdef DEBUG
440 fprintf(dbg, "set_field_buffer: exit: len = %d, value = %s\n",
441 len, value);
442 fprintf(dbg, "set_field_buffer: exit: string = %s, len = %d\n",
443 field->buffers[buffer].string, field->buffers[buffer].length);
444 fprintf(dbg, "set_field_buffer: exit: lines.len = %d\n",
445 field->lines[0].length);
446 #endif
447
448 return status;
449 }
450
451 /*
452 * Return the requested field buffer to the caller.
453 */
454 char *
455 field_buffer(FIELD *field, int buffer)
456 {
457
458 if (field == NULL)
459 return NULL;
460
461 if (buffer >= field->nbuf)
462 return NULL;
463
464 return field->buffers[buffer].string;
465 }
466
467 /*
468 * Set the buffer 0 field status.
469 */
470 int
471 set_field_status(FIELD *field, int status)
472 {
473
474 if (field == NULL)
475 return E_BAD_ARGUMENT;
476
477 if (status != FALSE)
478 field->buf0_status = TRUE;
479 else
480 field->buf0_status = FALSE;
481
482 return E_OK;
483 }
484
485 /*
486 * Return the buffer 0 status flag for the given field.
487 */
488 int
489 field_status(FIELD *field)
490 {
491
492 if (field == NULL) /* the default buffer 0 never changes :-) */
493 return FALSE;
494
495 return field->buf0_status;
496 }
497
498 /*
499 * Set the maximum growth for a dynamic field.
500 */
501 int
502 set_max_field(FIELD *fptr, int max)
503 {
504 FIELD *field = (fptr == NULL)? &_formi_default_field : fptr;
505
506 if ((field->opts & O_STATIC) == O_STATIC) /* check if field dynamic */
507 return E_BAD_ARGUMENT;
508
509 if (max < 0) /* negative numbers are bad.... */
510 return E_BAD_ARGUMENT;
511
512 field->max = max;
513 return E_OK;
514 }
515
516 /*
517 * Set the field foreground character attributes.
518 */
519 int
520 set_field_fore(FIELD *fptr, chtype attribute)
521 {
522 FIELD *field = (fptr == NULL)? &_formi_default_field : fptr;
523
524 field->fore = attribute;
525 return E_OK;
526 }
527
528 /*
529 * Return the foreground character attribute for the given field.
530 */
531 chtype
532 field_fore(FIELD *field)
533 {
534 if (field == NULL)
535 return _formi_default_field.fore;
536 else
537 return field->fore;
538 }
539
540 /*
541 * Set the background character attribute for the given field.
542 */
543 int
544 set_field_back(FIELD *field, chtype attribute)
545 {
546 if (field == NULL)
547 _formi_default_field.back = attribute;
548 else
549 field->back = attribute;
550
551 return E_OK;
552 }
553
554 /*
555 * Get the background character attribute for the given field.
556 */
557 chtype
558 field_back(FIELD *field)
559 {
560 if (field == NULL)
561 return _formi_default_field.back;
562 else
563 return field->back;
564 }
565
566 /*
567 * Set the pad character for the given field.
568 */
569 int
570 set_field_pad(FIELD *field, int pad)
571 {
572 if (field == NULL)
573 _formi_default_field.pad = pad;
574 else
575 field->pad = pad;
576
577 return E_OK;
578 }
579
580 /*
581 * Return the padding character for the given field.
582 */
583 int
584 field_pad(FIELD *field)
585 {
586 if (field == NULL)
587 return _formi_default_field.pad;
588 else
589 return field->pad;
590 }
591
592 /*
593 * Set the field initialisation function hook.
594 */
595 int
596 set_field_init(FORM *form, Form_Hook function)
597 {
598 if (form == NULL)
599 _formi_default_form.field_init = function;
600 else
601 form->field_init = function;
602
603 return E_OK;
604 }
605
606 /*
607 * Return the function hook for the field initialisation.
608 */
609 Form_Hook
610 field_init(FORM *form)
611 {
612 if (form == NULL)
613 return _formi_default_form.field_init;
614 else
615 return form->field_init;
616 }
617
618 /*
619 * Set the field termination function hook.
620 */
621 int
622 set_field_term(FORM *form, Form_Hook function)
623 {
624 if (form == NULL)
625 _formi_default_form.field_term = function;
626 else
627 form->field_term = function;
628
629 return E_OK;
630 }
631
632 /*
633 * Return the function hook defined for the field termination.
634 */
635 Form_Hook
636 field_term(FORM *form)
637 {
638 if (form == NULL)
639 return _formi_default_form.field_term;
640 else
641 return form->field_term;
642 }
643
644 /*
645 * Set the page flag on the given field to indicate it is the start of a
646 * new page.
647 */
648 int
649 set_new_page(FIELD *fptr, int page)
650 {
651 FIELD *field = (fptr == NULL)? &_formi_default_field : fptr;
652
653 if (field->parent != NULL) /* check if field is connected to a form */
654 return E_CONNECTED;
655
656 field->page_break = (page != FALSE);
657 return E_OK;
658 }
659
660 /*
661 * Return the page status for the given field. TRUE is returned if the
662 * field is the start of a new page.
663 */
664 int
665 new_page(FIELD *field)
666 {
667 if (field == NULL)
668 return _formi_default_field.page_break;
669 else
670 return field->page_break;
671 }
672
673 /*
674 * Return the index of the field in the form fields array.
675 */
676 int
677 field_index(FIELD *field)
678 {
679 if (field == NULL)
680 return E_BAD_ARGUMENT;
681
682 if (field->parent == NULL)
683 return E_NOT_CONNECTED;
684
685 return field->index;
686 }
687
688 /*
689 * Internal function that does most of the work to create a new field.
690 * The new field is initialised from the information in the prototype
691 * field passed.
692 * Returns NULL on error.
693 */
694 static FIELD *
695 _formi_create_field(FIELD *prototype, int rows, int cols, int frow,
696 int fcol, int nrows, int nbuf)
697 {
698 FIELD *new;
699
700 if ((rows <= 0) || (cols <= 0) || (frow < 0) || (fcol < 0) ||
701 (nrows < 0) || (nbuf < 0))
702 return NULL;
703
704 if ((new = (FIELD *)malloc(sizeof(FIELD))) == NULL) {
705 return NULL;
706 }
707
708 /* copy in the default field info */
709 bcopy(prototype, new, sizeof(FIELD));
710
711 new->nbuf = nbuf + 1;
712 new->rows = rows;
713 new->cols = cols;
714 new->form_row = frow;
715 new->form_col = fcol;
716 new->nrows = nrows;
717 new->link = new;
718 return new;
719 }
720
721 /*
722 * Create a new field structure.
723 */
724 FIELD *
725 new_field(int rows, int cols, int frow, int fcol, int nrows, int nbuf)
726 {
727 FIELD *new;
728 size_t buf_len;
729 int i;
730
731
732 if ((new = _formi_create_field(&_formi_default_field, rows, cols,
733 frow, fcol, nrows, nbuf)) == NULL)
734 return NULL;
735
736 buf_len = (nbuf + 1) * sizeof(FORM_STR);
737
738 if ((new->buffers = (FORM_STR *)malloc(buf_len)) == NULL) {
739 free(new);
740 return NULL;
741 }
742
743 /* Initialise the strings to a zero length string */
744 for (i = 0; i < nbuf + 1; i++) {
745 if ((new->buffers[i].string =
746 (char *) malloc(sizeof(char))) == NULL) {
747 free(new->buffers);
748 free(new);
749 return NULL;
750 }
751 new->buffers[i].string[0] = '\0';
752 new->buffers[i].length = 0;
753 new->buffers[i].allocated = 1;
754 }
755
756 if ((new->lines = (_FORMI_FIELD_LINES *)
757 malloc(sizeof(struct _formi_field_lines))) == NULL) {
758 free(new->buffers);
759 free(new);
760 return NULL;
761 }
762
763 new->lines_alloced = 1;
764 new->lines[0].length = 0;
765 new->lines[0].start = 0;
766 new->lines[0].end = 0;
767 new->lines[0].tabs = NULL;
768
769 return new;
770 }
771
772 /*
773 * Duplicate the given field, including it's buffers.
774 */
775 FIELD *
776 dup_field(FIELD *field, int frow, int fcol)
777 {
778 FIELD *new;
779 size_t row_len, buf_len;
780
781 if (field == NULL)
782 return NULL;
783
784 /* XXXX this right???? */
785 if ((new = _formi_create_field(field, (int) field->rows,
786 (int ) field->cols,
787 frow, fcol, (int) field->nrows,
788 field->nbuf - 1)) == NULL)
789 return NULL;
790
791 row_len = (field->rows + field->nrows + 1) * field->cols;
792 buf_len = (field->nbuf + 1) * row_len * sizeof(FORM_STR);
793
794 if ((new->buffers = (FORM_STR *)malloc(buf_len)) == NULL) {
795 free(new);
796 return NULL;
797 }
798
799 /* copy the buffers from the source field into the new copy */
800 bcopy(field->buffers, new->buffers, buf_len);
801
802 return new;
803 }
804
805 /*
806 * Create a new field at the specified location by duplicating the given
807 * field. The buffers are shared with the parent field.
808 */
809 FIELD *
810 link_field(FIELD *field, int frow, int fcol)
811 {
812 FIELD *new;
813
814 if (field == NULL)
815 return NULL;
816
817 if ((new = _formi_create_field(field, (int) field->rows,
818 (int) field->cols,
819 frow, fcol, (int) field->nrows,
820 field->nbuf - 1)) == NULL)
821 return NULL;
822
823 new->link = field->link;
824 field->link = new;
825
826 /* we are done. The buffer pointer was copied during the field
827 creation. */
828 return new;
829 }
830
831 /*
832 * Release all storage allocated to the field
833 */
834 int
835 free_field(FIELD *field)
836 {
837 FIELD *flink;
838 int i;
839 _formi_tab_t *ts, *nts;
840
841 if (field == NULL)
842 return E_BAD_ARGUMENT;
843
844 if (field->parent != NULL)
845 return E_CONNECTED;
846
847 if (field->link == field) { /* check if field linked */
848 /* no it is not - release the buffers */
849 free(field->buffers);
850 /* free the tab structures */
851 for (i = 0; i < field->row_count - 1; i++) {
852 if (field->lines[i].tabs != NULL) {
853 ts = field->lines[i].tabs;
854 while (ts != NULL) {
855 nts = ts->fwd;
856 free(ts);
857 ts = nts;
858 }
859 }
860 }
861 } else {
862 /* is linked, traverse the links to find the field referring
863 * to the one to be freed.
864 */
865 for (flink = field->link; flink != field; flink = flink->link);
866 flink->link = field->link;
867 }
868
869 free(field);
870 return E_OK;
871 }
872
873
874