5 * Handle options with arguments that contain nested values.
11 * Automated Options Nested Values module.
13 * This file is part of AutoOpts, a companion to AutoGen.
14 * AutoOpts is free software.
15 * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
17 * AutoOpts is available under any one of two licenses. The license
18 * in use must be one of these two and the choice is under the control
19 * of the user of the license.
21 * The GNU Lesser General Public License, version 3 or later
22 * See the files "COPYING.lgplv3" and "COPYING.gplv3"
24 * The Modified Berkeley Software Distribution License
25 * See the file "COPYING.mbsd"
27 * These files have the following sha256 sums:
29 * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3
30 * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3
31 * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd
40 static xml_xlate_t const xml_xlate[] = {
52 /* = = = START-STATIC-FORWARD = = = */
54 remove_continuation(char * src);
57 scan_q_str(char const * pzTxt);
60 add_string(void ** pp, char const * name, size_t nm_len,
61 char const * val, size_t d_len);
64 add_bool(void ** pp, char const * name, size_t nm_len,
65 char const * val, size_t d_len);
68 add_number(void ** pp, char const * name, size_t nm_len,
69 char const * val, size_t d_len);
72 add_nested(void ** pp, char const * name, size_t nm_len,
73 char * val, size_t d_len);
76 scan_name(char const * name, tOptionValue * res);
79 unnamed_xml(char const * txt);
82 scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val);
85 find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len);
88 scan_xml(char const * xml_name, tOptionValue * res_val);
91 sort_list(tArgList * arg_list);
92 /* = = = END-STATIC-FORWARD = = = */
95 * Backslashes are used for line continuations. We keep the newline
96 * characters, but trim out the backslash:
99 remove_continuation(char * src)
104 while (*src == NL) src++;
105 pzD = strchr(src, NL);
110 * pzD has skipped at least one non-newline character and now
111 * points to a newline character. It now becomes the source and
112 * pzD goes to the previous character.
117 } while (pzD == src);
120 * Start shifting text.
123 char ch = ((*pzD++) = *(src++));
128 --pzD; /* rewrite on next iteration */
134 * Find the end of a quoted string, skipping escaped quote characters.
137 scan_q_str(char const * pzTxt)
139 char q = *(pzTxt++); /* remember the type of quote */
142 char ch = *(pzTxt++);
152 * IF the next character is NUL, drop the backslash, too.
158 * IF the quote character or the escape character were escaped,
159 * then skip both, as long as the string does not end.
161 if ((ch == q) || (ch == '\\')) {
162 if (*(pzTxt++) == NUL)
171 * Associate a name with either a string or no value.
173 * @param[in,out] pp argument list to add to
174 * @param[in] name the name of the "suboption"
175 * @param[in] nm_len the length of the name
176 * @param[in] val the string value for the suboption
177 * @param[in] d_len the length of the value
179 * @returns the new value structure
181 static tOptionValue *
182 add_string(void ** pp, char const * name, size_t nm_len,
183 char const * val, size_t d_len)
186 size_t sz = nm_len + d_len + sizeof(*pNV);
188 pNV = AGALOC(sz, "option name/str value pair");
191 pNV->valType = OPARG_TYPE_NONE;
192 pNV->pzName = pNV->v.strVal;
195 pNV->valType = OPARG_TYPE_STRING;
197 char const * src = val;
198 char * pzDst = pNV->v.strVal;
201 int ch = *(src++) & 0xFF;
202 if (ch == NUL) goto data_copy_done;
204 ch = get_special_char(&src, &ct);
205 *(pzDst++) = (char)ch;
211 pNV->v.strVal[0] = NUL;
214 pNV->pzName = pNV->v.strVal + d_len + 1;
217 memcpy(pNV->pzName, name, nm_len);
218 pNV->pzName[ nm_len ] = NUL;
219 addArgListEntry(pp, pNV);
224 * Associate a name with a boolean value
226 * @param[in,out] pp argument list to add to
227 * @param[in] name the name of the "suboption"
228 * @param[in] nm_len the length of the name
229 * @param[in] val the boolean value for the suboption
230 * @param[in] d_len the length of the value
232 * @returns the new value structure
234 static tOptionValue *
235 add_bool(void ** pp, char const * name, size_t nm_len,
236 char const * val, size_t d_len)
238 size_t sz = nm_len + sizeof(tOptionValue) + 1;
239 tOptionValue * new_val = AGALOC(sz, "bool val");
242 * Scan over whitespace is constrained by "d_len"
244 while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
249 new_val->v.boolVal = 0;
251 else if (IS_DEC_DIGIT_CHAR(*val))
252 new_val->v.boolVal = (unsigned)atoi(val);
254 else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val);
256 new_val->valType = OPARG_TYPE_BOOLEAN;
257 new_val->pzName = (char *)(new_val + 1);
258 memcpy(new_val->pzName, name, nm_len);
259 new_val->pzName[ nm_len ] = NUL;
260 addArgListEntry(pp, new_val);
265 * Associate a name with strtol() value, defaulting to zero.
267 * @param[in,out] pp argument list to add to
268 * @param[in] name the name of the "suboption"
269 * @param[in] nm_len the length of the name
270 * @param[in] val the numeric value for the suboption
271 * @param[in] d_len the length of the value
273 * @returns the new value structure
275 static tOptionValue *
276 add_number(void ** pp, char const * name, size_t nm_len,
277 char const * val, size_t d_len)
279 size_t sz = nm_len + sizeof(tOptionValue) + 1;
280 tOptionValue * new_val = AGALOC(sz, "int val");
283 * Scan over whitespace is constrained by "d_len"
285 while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
289 new_val->v.longVal = 0;
291 new_val->v.longVal = strtol(val, 0, 0);
293 new_val->valType = OPARG_TYPE_NUMERIC;
294 new_val->pzName = (char *)(new_val + 1);
295 memcpy(new_val->pzName, name, nm_len);
296 new_val->pzName[ nm_len ] = NUL;
297 addArgListEntry(pp, new_val);
302 * Associate a name with a nested/hierarchical value.
304 * @param[in,out] pp argument list to add to
305 * @param[in] name the name of the "suboption"
306 * @param[in] nm_len the length of the name
307 * @param[in] val the nested values for the suboption
308 * @param[in] d_len the length of the value
310 * @returns the new value structure
312 static tOptionValue *
313 add_nested(void ** pp, char const * name, size_t nm_len,
314 char * val, size_t d_len)
316 tOptionValue * new_val;
319 size_t sz = nm_len + sizeof(*new_val) + 1;
320 new_val = AGALOC(sz, "empty nest");
321 new_val->v.nestVal = NULL;
322 new_val->valType = OPARG_TYPE_HIERARCHY;
323 new_val->pzName = (char *)(new_val + 1);
324 memcpy(new_val->pzName, name, nm_len);
325 new_val->pzName[ nm_len ] = NUL;
328 new_val = optionLoadNested(val, name, nm_len);
332 addArgListEntry(pp, new_val);
338 * We have an entry that starts with a name. Find the end of it, cook it
339 * (if called for) and create the name/value association.
342 scan_name(char const * name, tOptionValue * res)
344 tOptionValue * new_val;
345 char const * pzScan = name+1; /* we know first char is a name char */
351 * Scan over characters that name a value. These names may not end
352 * with a colon, but they may contain colons.
354 pzScan = SPN_VALUE_NAME_CHARS(name + 1);
355 if (pzScan[-1] == ':')
357 nm_len = (size_t)(pzScan - name);
359 pzScan = SPN_HORIZ_WHITE_CHARS(pzScan);
366 pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1);
367 if ((*pzScan == '=') || (*pzScan == ':'))
377 add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0);
383 pzScan = scan_q_str(pzScan);
384 d_len = (size_t)(pzScan - pzVal);
385 new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal,
387 if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
388 ao_string_cook(new_val->v.strVal, NULL);
394 * We have found some strange text value. It ends with a newline
399 char ch = *(pzScan++);
403 d_len = (size_t)(pzScan - pzVal);
408 if ( (pzScan > pzVal + 2)
409 && (pzScan[-2] == '\\')
410 && (pzScan[ 0] != NUL))
415 d_len = (size_t)(pzScan - pzVal) - 1;
417 new_val = add_string(&(res->v.nestVal), name, nm_len,
420 remove_continuation(new_val->v.strVal);
421 goto leave_scan_name;
431 * Some xml element that does not start with a name.
432 * The next character must be either '!' (introducing a comment),
433 * or '?' (introducing an XML meta-marker of some sort).
434 * We ignore these and indicate an error (NULL result) otherwise.
436 * @param[in] txt the text within an xml bracket
437 * @returns the address of the character after the closing marker, or NULL.
440 unnamed_xml(char const * txt)
448 txt = strstr(txt, "-->");
454 txt = strchr(txt, '>');
463 * Scan off the xml element name, and the rest of the header, too.
464 * Set the value type to NONE if it ends with "/>".
466 * @param[in] name the first name character (alphabetic)
467 * @param[out] nm_len the length of the name
468 * @param[out] val set valType field to STRING or NONE.
470 * @returns the scan resumption point, or NULL on error
473 scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val)
475 char const * scan = SPN_VALUE_NAME_CHARS(name + 1);
476 *nm_len = (size_t)(scan - name);
479 val->valType = OPARG_TYPE_STRING;
481 if (IS_WHITESPACE_CHAR(*scan)) {
483 * There are attributes following the name. Parse 'em.
485 scan = SPN_WHITESPACE_CHARS(scan);
486 scan = parse_attrs(NULL, scan, &option_load_mode, val);
488 return NULL; /* oops */
491 if (! IS_END_XML_TOKEN_CHAR(*scan))
492 return NULL; /* oops */
496 * Single element XML entries get inserted as an empty string.
500 val->valType = OPARG_TYPE_NONE;
506 * We've found a closing '>' without a preceding '/', thus we must search
507 * the text for '<name/>' where "name" is the name of the XML element.
509 * @param[in] name the start of the name in the element header
510 * @param[in] nm_len the length of that name
511 * @param[out] len the length of the value (string between header and
513 * @returns the character after the trailer, or NULL if not found.
516 find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len)
523 } while (--nm_len > 0); /* nm_len is known to be 64 or less */
528 char const * res = strstr(val, z);
531 char const * end = (option_load_mode != OPTION_LOAD_KEEP)
532 ? SPN_WHITESPACE_BACK(val, res)
534 *len = (size_t)(end - val); /* includes trailing white space */
535 res = SPN_WHITESPACE_CHARS(res + (dst - z));
542 * We've found a '<' character. We ignore this if it is a comment or a
543 * directive. If it is something else, then whatever it is we are looking
544 * at is bogus. Returning NULL stops processing.
546 * @param[in] xml_name the name of an xml bracket (usually)
547 * @param[in,out] res_val the option data derived from the XML element
549 * @returns the place to resume scanning input
552 scan_xml(char const * xml_name, tOptionValue * res_val)
554 size_t nm_len, v_len;
556 char const * val_str;
558 tOptionLoadMode save_mode = option_load_mode;
560 if (! IS_VAR_FIRST_CHAR(*++xml_name))
561 return unnamed_xml(xml_name);
564 * "scan_xml_name()" may change "option_load_mode".
566 val_str = scan_xml_name(xml_name, &nm_len, &valu);
570 if (valu.valType == OPARG_TYPE_NONE)
573 if (option_load_mode != OPTION_LOAD_KEEP)
574 val_str = SPN_WHITESPACE_CHARS(val_str);
575 scan = find_end_xml(xml_name, nm_len, val_str, &v_len);
581 * "scan" now points to where the scan is to resume after returning.
582 * It either points after "/>" at the end of the XML element header,
583 * or it points after the "</name>" tail based on the name in the header.
586 switch (valu.valType) {
587 case OPARG_TYPE_NONE:
588 add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0);
591 case OPARG_TYPE_STRING:
593 tOptionValue * new_val = add_string(
594 &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
596 if (option_load_mode != OPTION_LOAD_KEEP)
597 munge_str(new_val->v.strVal, option_load_mode);
602 case OPARG_TYPE_BOOLEAN:
603 add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
606 case OPARG_TYPE_NUMERIC:
607 add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
610 case OPARG_TYPE_HIERARCHY:
612 char * pz = AGALOC(v_len+1, "h scan");
613 memcpy(pz, val_str, v_len);
615 add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len);
620 case OPARG_TYPE_ENUMERATION:
621 case OPARG_TYPE_MEMBERSHIP:
626 option_load_mode = save_mode;
630 option_load_mode = save_mode;
636 * Deallocate a list of option arguments. This must have been gotten from
637 * a hierarchical option argument, not a stacked list of strings. It is
638 * an internal call, so it is not validated. The caller is responsible for
639 * knowing what they are doing.
642 unload_arg_list(tArgList * arg_list)
644 int ct = arg_list->useCt;
645 char const ** pnew_val = arg_list->apzArgs;
648 tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++));
649 if (new_val->valType == OPARG_TYPE_HIERARCHY)
650 unload_arg_list(new_val->v.nestVal);
657 /*=export_func optionUnloadNested
659 * what: Deallocate the memory for a nested value
660 * arg: + tOptionValue const * + pOptVal + the hierarchical value +
663 * A nested value needs to be deallocated. The pointer passed in should
664 * have been gotten from a call to @code{configFileLoad()} (See
665 * @pxref{libopts-configFileLoad}).
668 optionUnloadNested(tOptionValue const * opt_val)
670 if (opt_val == NULL) return;
671 if (opt_val->valType != OPARG_TYPE_HIERARCHY) {
676 unload_arg_list(opt_val->v.nestVal);
682 * This is a _stable_ sort. The entries are sorted alphabetically,
683 * but within entries of the same name the ordering is unchanged.
684 * Typically, we also hope the input is sorted.
687 sort_list(tArgList * arg_list)
690 int lm = arg_list->useCt;
693 * This loop iterates "useCt" - 1 times.
695 for (ix = 0; ++ix < lm;) {
697 tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]);
698 tOptionValue * old_v = C(tOptionValue *, arg_list->apzArgs[iy]);
701 * For as long as the new entry precedes the "old" entry,
702 * move the old pointer. Stop before trying to extract the
705 while (strcmp(old_v->pzName, new_v->pzName) > 0) {
706 arg_list->apzArgs[iy+1] = VOIDP(old_v);
707 old_v = (tOptionValue *)VOIDP(arg_list->apzArgs[--iy]);
713 * Always store the pointer. Sometimes it is redundant,
714 * but the redundancy is cheaper than a test and branch sequence.
716 arg_list->apzArgs[iy+1] = VOIDP(new_v);
723 * what: parse a hierarchical option argument
724 * arg: + char const * + pzTxt + the text to scan +
725 * arg: + char const * + pzName + the name for the text +
726 * arg: + size_t + nm_len + the length of "name" +
728 * ret_type: tOptionValue *
729 * ret_desc: An allocated, compound value structure
732 * A block of text represents a series of values. It may be an
733 * entire configuration file, or it may be an argument to an
734 * option that takes a hierarchical value.
736 * If NULL is returned, errno will be set:
739 * @code{EINVAL} the input text was NULL.
741 * @code{ENOMEM} the storage structures could not be allocated
743 * @code{ENOMSG} no configuration values were found
747 optionLoadNested(char const * text, char const * name, size_t nm_len)
749 tOptionValue * res_val;
752 * Make sure we have some data and we have space to put what we find.
758 text = SPN_WHITESPACE_CHARS(text);
763 res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args");
764 res_val->valType = OPARG_TYPE_HIERARCHY;
765 res_val->pzName = (char *)(res_val + 1);
766 memcpy(res_val->pzName, name, nm_len);
767 res_val->pzName[nm_len] = NUL;
770 tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l");
772 res_val->v.nestVal = arg_list;
774 arg_list->allocCt = MIN_ARG_ALLOC_CT;
778 * Scan until we hit a NUL.
781 text = SPN_WHITESPACE_CHARS(text);
782 if (IS_VAR_FIRST_CHAR(*text))
783 text = scan_name(text, res_val);
785 else switch (*text) {
786 case NUL: goto scan_done;
787 case '<': text = scan_xml(text, res_val);
788 if (text == NULL) goto woops;
789 if (*text == ',') text++; break;
790 case '#': text = strchr(text, NL); break;
793 } while (text != NULL); scan_done:;
796 tArgList * al = res_val->v.nestVal;
797 if (al->useCt == 0) {
808 AGFREE(res_val->v.nestVal);
813 /*=export_func optionNestedVal
816 * what: parse a hierarchical option argument
817 * arg: + tOptions * + opts + program options descriptor +
818 * arg: + tOptDesc * + od + the descriptor for this arg +
821 * Nested value was found on the command line
824 optionNestedVal(tOptions * opts, tOptDesc * od)
826 if (opts < OPTPROC_EMIT_LIMIT)
829 if (od->fOptState & OPTST_RESET) {
830 tArgList * arg_list = od->optCookie;
834 if (arg_list == NULL)
836 ct = arg_list->useCt;
837 av = arg_list->apzArgs;
840 void * p = VOIDP(*(av++));
841 optionUnloadNested((tOptionValue const *)p);
844 AGFREE(od->optCookie);
847 tOptionValue * opt_val = optionLoadNested(
848 od->optArg.argString, od->pz_Name, strlen(od->pz_Name));
851 addArgListEntry(&(od->optCookie), VOIDP(opt_val));
859 get_special_char(char const ** ppz, int * ct)
861 char const * pz = *ppz;
875 retch = (int)strtoul(pz, (char **)&pz, base);
878 base = (int)(++pz - *ppz);
888 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
889 xml_xlate_t const * xlatp = xml_xlate;
892 if ( (*ct >= xlatp->xml_len)
893 && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) {
894 *ppz += xlatp->xml_len;
895 *ct -= xlatp->xml_len;
896 return xlatp->xml_ch;
911 emit_special_char(FILE * fp, int ch)
913 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
914 xml_xlate_t const * xlatp = xml_xlate;
918 if (ch == xlatp->xml_ch) {
919 fputs(xlatp->xml_txt, fp);
926 fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF));
933 * c-file-style: "stroustrup"
934 * indent-tabs-mode: nil
936 * end of autoopts/nested.c */