1181834Sroberto 2280849Scy/** 3280849Scy * \file nested.c 4181834Sroberto * 5280849Scy * Handle options with arguments that contain nested values. 6280849Scy * 7280849Scy * @addtogroup autoopts 8280849Scy * @{ 9181834Sroberto */ 10181834Sroberto/* 11280849Scy * Automated Options Nested Values module. 12181834Sroberto * 13280849Scy * This file is part of AutoOpts, a companion to AutoGen. 14280849Scy * AutoOpts is free software. 15285169Scy * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 16181834Sroberto * 17280849Scy * AutoOpts is available under any one of two licenses. The license 18280849Scy * in use must be one of these two and the choice is under the control 19280849Scy * of the user of the license. 20181834Sroberto * 21280849Scy * The GNU Lesser General Public License, version 3 or later 22280849Scy * See the files "COPYING.lgplv3" and "COPYING.gplv3" 23181834Sroberto * 24280849Scy * The Modified Berkeley Software Distribution License 25280849Scy * See the file "COPYING.mbsd" 26181834Sroberto * 27280849Scy * These files have the following sha256 sums: 28181834Sroberto * 29280849Scy * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 30280849Scy * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 31280849Scy * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 32181834Sroberto */ 33280849Scy 34280849Scytypedef struct { 35280849Scy int xml_ch; 36280849Scy int xml_len; 37280849Scy char xml_txt[8]; 38280849Scy} xml_xlate_t; 39280849Scy 40280849Scystatic xml_xlate_t const xml_xlate[] = { 41280849Scy { '&', 4, "amp;" }, 42280849Scy { '<', 3, "lt;" }, 43280849Scy { '>', 3, "gt;" }, 44280849Scy { '"', 5, "quot;" }, 45280849Scy { '\'',5, "apos;" } 46280849Scy}; 47280849Scy 48280849Scy#ifndef ENOMSG 49280849Scy#define ENOMSG ENOENT 50280849Scy#endif 51280849Scy 52181834Sroberto/* = = = START-STATIC-FORWARD = = = */ 53181834Srobertostatic void 54280849Scyremove_continuation(char * src); 55181834Sroberto 56285169Scystatic char const * 57285169Scyscan_q_str(char const * pzTxt); 58181834Sroberto 59280849Scystatic tOptionValue * 60280849Scyadd_string(void ** pp, char const * name, size_t nm_len, 61280849Scy char const * val, size_t d_len); 62181834Sroberto 63280849Scystatic tOptionValue * 64280849Scyadd_bool(void ** pp, char const * name, size_t nm_len, 65280849Scy char const * val, size_t d_len); 66181834Sroberto 67285169Scystatic tOptionValue * 68280849Scyadd_number(void ** pp, char const * name, size_t nm_len, 69280849Scy char const * val, size_t d_len); 70181834Sroberto 71285169Scystatic tOptionValue * 72280849Scyadd_nested(void ** pp, char const * name, size_t nm_len, 73280849Scy char * val, size_t d_len); 74181834Sroberto 75280849Scystatic char const * 76280849Scyscan_name(char const * name, tOptionValue * res); 77181834Sroberto 78280849Scystatic char const * 79280849Scyunnamed_xml(char const * txt); 80181834Sroberto 81280849Scystatic char const * 82280849Scyscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val); 83181834Sroberto 84280849Scystatic char const * 85280849Scyfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len); 86280849Scy 87280849Scystatic char const * 88280849Scyscan_xml(char const * xml_name, tOptionValue * res_val); 89280849Scy 90181834Srobertostatic void 91280849Scysort_list(tArgList * arg_list); 92181834Sroberto/* = = = END-STATIC-FORWARD = = = */ 93181834Sroberto 94280849Scy/** 95280849Scy * Backslashes are used for line continuations. We keep the newline 96280849Scy * characters, but trim out the backslash: 97181834Sroberto */ 98181834Srobertostatic void 99280849Scyremove_continuation(char * src) 100181834Sroberto{ 101285169Scy char * pzD; 102181834Sroberto 103280849Scy do { 104280849Scy while (*src == NL) src++; 105280849Scy pzD = strchr(src, NL); 106280849Scy if (pzD == NULL) 107280849Scy return; 108181834Sroberto 109280849Scy /* 110280849Scy * pzD has skipped at least one non-newline character and now 111280849Scy * points to a newline character. It now becomes the source and 112280849Scy * pzD goes to the previous character. 113280849Scy */ 114280849Scy src = pzD--; 115280849Scy if (*pzD != '\\') 116280849Scy pzD++; 117280849Scy } while (pzD == src); 118280849Scy 119280849Scy /* 120280849Scy * Start shifting text. 121280849Scy */ 122181834Sroberto for (;;) { 123280849Scy char ch = ((*pzD++) = *(src++)); 124181834Sroberto switch (ch) { 125181834Sroberto case NUL: return; 126280849Scy case '\\': 127280849Scy if (*src == NL) 128280849Scy --pzD; /* rewrite on next iteration */ 129181834Sroberto } 130181834Sroberto } 131181834Sroberto} 132181834Sroberto 133280849Scy/** 134181834Sroberto * Find the end of a quoted string, skipping escaped quote characters. 135181834Sroberto */ 136285169Scystatic char const * 137285169Scyscan_q_str(char const * pzTxt) 138181834Sroberto{ 139181834Sroberto char q = *(pzTxt++); /* remember the type of quote */ 140181834Sroberto 141181834Sroberto for (;;) { 142181834Sroberto char ch = *(pzTxt++); 143181834Sroberto if (ch == NUL) 144181834Sroberto return pzTxt-1; 145181834Sroberto 146181834Sroberto if (ch == q) 147181834Sroberto return pzTxt; 148181834Sroberto 149181834Sroberto if (ch == '\\') { 150181834Sroberto ch = *(pzTxt++); 151181834Sroberto /* 152181834Sroberto * IF the next character is NUL, drop the backslash, too. 153181834Sroberto */ 154181834Sroberto if (ch == NUL) 155181834Sroberto return pzTxt - 2; 156181834Sroberto 157181834Sroberto /* 158181834Sroberto * IF the quote character or the escape character were escaped, 159181834Sroberto * then skip both, as long as the string does not end. 160181834Sroberto */ 161181834Sroberto if ((ch == q) || (ch == '\\')) { 162181834Sroberto if (*(pzTxt++) == NUL) 163181834Sroberto return pzTxt-1; 164181834Sroberto } 165181834Sroberto } 166181834Sroberto } 167181834Sroberto} 168181834Sroberto 169181834Sroberto 170280849Scy/** 171280849Scy * Associate a name with either a string or no value. 172181834Sroberto * 173280849Scy * @param[in,out] pp argument list to add to 174280849Scy * @param[in] name the name of the "suboption" 175280849Scy * @param[in] nm_len the length of the name 176280849Scy * @param[in] val the string value for the suboption 177280849Scy * @param[in] d_len the length of the value 178280849Scy * 179280849Scy * @returns the new value structure 180181834Sroberto */ 181280849Scystatic tOptionValue * 182280849Scyadd_string(void ** pp, char const * name, size_t nm_len, 183280849Scy char const * val, size_t d_len) 184181834Sroberto{ 185285169Scy tOptionValue * pNV; 186280849Scy size_t sz = nm_len + d_len + sizeof(*pNV); 187181834Sroberto 188280849Scy pNV = AGALOC(sz, "option name/str value pair"); 189181834Sroberto 190280849Scy if (val == NULL) { 191181834Sroberto pNV->valType = OPARG_TYPE_NONE; 192181834Sroberto pNV->pzName = pNV->v.strVal; 193181834Sroberto 194181834Sroberto } else { 195181834Sroberto pNV->valType = OPARG_TYPE_STRING; 196280849Scy if (d_len > 0) { 197280849Scy char const * src = val; 198280849Scy char * pzDst = pNV->v.strVal; 199280849Scy int ct = (int)d_len; 200280849Scy do { 201280849Scy int ch = *(src++) & 0xFF; 202280849Scy if (ch == NUL) goto data_copy_done; 203280849Scy if (ch == '&') 204280849Scy ch = get_special_char(&src, &ct); 205280849Scy *(pzDst++) = (char)ch; 206280849Scy } while (--ct > 0); 207280849Scy data_copy_done: 208280849Scy *pzDst = NUL; 209280849Scy 210280849Scy } else { 211280849Scy pNV->v.strVal[0] = NUL; 212280849Scy } 213280849Scy 214280849Scy pNV->pzName = pNV->v.strVal + d_len + 1; 215181834Sroberto } 216181834Sroberto 217280849Scy memcpy(pNV->pzName, name, nm_len); 218280849Scy pNV->pzName[ nm_len ] = NUL; 219280849Scy addArgListEntry(pp, pNV); 220181834Sroberto return pNV; 221181834Sroberto} 222181834Sroberto 223280849Scy/** 224280849Scy * Associate a name with a boolean value 225181834Sroberto * 226280849Scy * @param[in,out] pp argument list to add to 227280849Scy * @param[in] name the name of the "suboption" 228280849Scy * @param[in] nm_len the length of the name 229280849Scy * @param[in] val the boolean value for the suboption 230280849Scy * @param[in] d_len the length of the value 231280849Scy * 232280849Scy * @returns the new value structure 233181834Sroberto */ 234280849Scystatic tOptionValue * 235280849Scyadd_bool(void ** pp, char const * name, size_t nm_len, 236280849Scy char const * val, size_t d_len) 237181834Sroberto{ 238280849Scy size_t sz = nm_len + sizeof(tOptionValue) + 1; 239280849Scy tOptionValue * new_val = AGALOC(sz, "bool val"); 240181834Sroberto 241280849Scy /* 242280849Scy * Scan over whitespace is constrained by "d_len" 243280849Scy */ 244280849Scy while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 245280849Scy d_len--; val++; 246181834Sroberto } 247181834Sroberto 248280849Scy if (d_len == 0) 249280849Scy new_val->v.boolVal = 0; 250280849Scy 251280849Scy else if (IS_DEC_DIGIT_CHAR(*val)) 252280849Scy new_val->v.boolVal = (unsigned)atoi(val); 253280849Scy 254280849Scy else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val); 255280849Scy 256280849Scy new_val->valType = OPARG_TYPE_BOOLEAN; 257285169Scy new_val->pzName = (char *)(new_val + 1); 258280849Scy memcpy(new_val->pzName, name, nm_len); 259280849Scy new_val->pzName[ nm_len ] = NUL; 260280849Scy addArgListEntry(pp, new_val); 261280849Scy return new_val; 262181834Sroberto} 263181834Sroberto 264280849Scy/** 265280849Scy * Associate a name with strtol() value, defaulting to zero. 266181834Sroberto * 267280849Scy * @param[in,out] pp argument list to add to 268280849Scy * @param[in] name the name of the "suboption" 269280849Scy * @param[in] nm_len the length of the name 270280849Scy * @param[in] val the numeric value for the suboption 271280849Scy * @param[in] d_len the length of the value 272280849Scy * 273280849Scy * @returns the new value structure 274181834Sroberto */ 275285169Scystatic tOptionValue * 276280849Scyadd_number(void ** pp, char const * name, size_t nm_len, 277280849Scy char const * val, size_t d_len) 278181834Sroberto{ 279280849Scy size_t sz = nm_len + sizeof(tOptionValue) + 1; 280280849Scy tOptionValue * new_val = AGALOC(sz, "int val"); 281181834Sroberto 282280849Scy /* 283280849Scy * Scan over whitespace is constrained by "d_len" 284280849Scy */ 285280849Scy while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 286280849Scy d_len--; val++; 287181834Sroberto } 288280849Scy if (d_len == 0) 289280849Scy new_val->v.longVal = 0; 290181834Sroberto else 291280849Scy new_val->v.longVal = strtol(val, 0, 0); 292181834Sroberto 293280849Scy new_val->valType = OPARG_TYPE_NUMERIC; 294285169Scy new_val->pzName = (char *)(new_val + 1); 295280849Scy memcpy(new_val->pzName, name, nm_len); 296280849Scy new_val->pzName[ nm_len ] = NUL; 297280849Scy addArgListEntry(pp, new_val); 298280849Scy return new_val; 299181834Sroberto} 300181834Sroberto 301280849Scy/** 302280849Scy * Associate a name with a nested/hierarchical value. 303181834Sroberto * 304280849Scy * @param[in,out] pp argument list to add to 305280849Scy * @param[in] name the name of the "suboption" 306280849Scy * @param[in] nm_len the length of the name 307280849Scy * @param[in] val the nested values for the suboption 308280849Scy * @param[in] d_len the length of the value 309280849Scy * 310280849Scy * @returns the new value structure 311181834Sroberto */ 312285169Scystatic tOptionValue * 313280849Scyadd_nested(void ** pp, char const * name, size_t nm_len, 314280849Scy char * val, size_t d_len) 315181834Sroberto{ 316285169Scy tOptionValue * new_val; 317181834Sroberto 318280849Scy if (d_len == 0) { 319280849Scy size_t sz = nm_len + sizeof(*new_val) + 1; 320280849Scy new_val = AGALOC(sz, "empty nest"); 321280849Scy new_val->v.nestVal = NULL; 322280849Scy new_val->valType = OPARG_TYPE_HIERARCHY; 323285169Scy new_val->pzName = (char *)(new_val + 1); 324280849Scy memcpy(new_val->pzName, name, nm_len); 325280849Scy new_val->pzName[ nm_len ] = NUL; 326181834Sroberto 327181834Sroberto } else { 328280849Scy new_val = optionLoadNested(val, name, nm_len); 329181834Sroberto } 330181834Sroberto 331280849Scy if (new_val != NULL) 332280849Scy addArgListEntry(pp, new_val); 333181834Sroberto 334280849Scy return new_val; 335181834Sroberto} 336181834Sroberto 337280849Scy/** 338181834Sroberto * We have an entry that starts with a name. Find the end of it, cook it 339181834Sroberto * (if called for) and create the name/value association. 340181834Sroberto */ 341280849Scystatic char const * 342280849Scyscan_name(char const * name, tOptionValue * res) 343181834Sroberto{ 344285169Scy tOptionValue * new_val; 345285169Scy char const * pzScan = name+1; /* we know first char is a name char */ 346285169Scy char const * pzVal; 347285169Scy size_t nm_len = 1; 348285169Scy size_t d_len = 0; 349181834Sroberto 350280849Scy /* 351280849Scy * Scan over characters that name a value. These names may not end 352280849Scy * with a colon, but they may contain colons. 353280849Scy */ 354280849Scy pzScan = SPN_VALUE_NAME_CHARS(name + 1); 355280849Scy if (pzScan[-1] == ':') 356280849Scy pzScan--; 357280849Scy nm_len = (size_t)(pzScan - name); 358181834Sroberto 359280849Scy pzScan = SPN_HORIZ_WHITE_CHARS(pzScan); 360181834Sroberto 361280849Scy re_switch: 362280849Scy 363181834Sroberto switch (*pzScan) { 364181834Sroberto case '=': 365181834Sroberto case ':': 366280849Scy pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1); 367280849Scy if ((*pzScan == '=') || (*pzScan == ':')) 368280849Scy goto default_char; 369280849Scy goto re_switch; 370181834Sroberto 371280849Scy case NL: 372181834Sroberto case ',': 373181834Sroberto pzScan++; 374181834Sroberto /* FALLTHROUGH */ 375181834Sroberto 376181834Sroberto case NUL: 377280849Scy add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0); 378181834Sroberto break; 379181834Sroberto 380181834Sroberto case '"': 381181834Sroberto case '\'': 382181834Sroberto pzVal = pzScan; 383280849Scy pzScan = scan_q_str(pzScan); 384280849Scy d_len = (size_t)(pzScan - pzVal); 385280849Scy new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal, 386280849Scy d_len); 387280849Scy if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED)) 388280849Scy ao_string_cook(new_val->v.strVal, NULL); 389181834Sroberto break; 390181834Sroberto 391181834Sroberto default: 392181834Sroberto default_char: 393181834Sroberto /* 394181834Sroberto * We have found some strange text value. It ends with a newline 395181834Sroberto * or a comma. 396181834Sroberto */ 397181834Sroberto pzVal = pzScan; 398181834Sroberto for (;;) { 399181834Sroberto char ch = *(pzScan++); 400181834Sroberto switch (ch) { 401181834Sroberto case NUL: 402181834Sroberto pzScan--; 403280849Scy d_len = (size_t)(pzScan - pzVal); 404181834Sroberto goto string_done; 405181834Sroberto /* FALLTHROUGH */ 406181834Sroberto 407280849Scy case NL: 408181834Sroberto if ( (pzScan > pzVal + 2) 409181834Sroberto && (pzScan[-2] == '\\') 410181834Sroberto && (pzScan[ 0] != NUL)) 411181834Sroberto continue; 412181834Sroberto /* FALLTHROUGH */ 413181834Sroberto 414181834Sroberto case ',': 415280849Scy d_len = (size_t)(pzScan - pzVal) - 1; 416181834Sroberto string_done: 417280849Scy new_val = add_string(&(res->v.nestVal), name, nm_len, 418280849Scy pzVal, d_len); 419280849Scy if (new_val != NULL) 420280849Scy remove_continuation(new_val->v.strVal); 421181834Sroberto goto leave_scan_name; 422181834Sroberto } 423181834Sroberto } 424181834Sroberto break; 425181834Sroberto } leave_scan_name:; 426181834Sroberto 427181834Sroberto return pzScan; 428181834Sroberto} 429181834Sroberto 430280849Scy/** 431280849Scy * Some xml element that does not start with a name. 432280849Scy * The next character must be either '!' (introducing a comment), 433280849Scy * or '?' (introducing an XML meta-marker of some sort). 434280849Scy * We ignore these and indicate an error (NULL result) otherwise. 435181834Sroberto * 436280849Scy * @param[in] txt the text within an xml bracket 437280849Scy * @returns the address of the character after the closing marker, or NULL. 438181834Sroberto */ 439280849Scystatic char const * 440280849Scyunnamed_xml(char const * txt) 441181834Sroberto{ 442280849Scy switch (*txt) { 443280849Scy default: 444280849Scy txt = NULL; 445280849Scy break; 446181834Sroberto 447280849Scy case '!': 448280849Scy txt = strstr(txt, "-->"); 449280849Scy if (txt != NULL) 450280849Scy txt += 3; 451280849Scy break; 452181834Sroberto 453280849Scy case '?': 454280849Scy txt = strchr(txt, '>'); 455280849Scy if (txt != NULL) 456280849Scy txt++; 457280849Scy break; 458181834Sroberto } 459280849Scy return txt; 460280849Scy} 461181834Sroberto 462280849Scy/** 463280849Scy * Scan off the xml element name, and the rest of the header, too. 464280849Scy * Set the value type to NONE if it ends with "/>". 465280849Scy * 466280849Scy * @param[in] name the first name character (alphabetic) 467280849Scy * @param[out] nm_len the length of the name 468280849Scy * @param[out] val set valType field to STRING or NONE. 469280849Scy * 470280849Scy * @returns the scan resumption point, or NULL on error 471280849Scy */ 472280849Scystatic char const * 473280849Scyscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val) 474280849Scy{ 475280849Scy char const * scan = SPN_VALUE_NAME_CHARS(name + 1); 476280849Scy *nm_len = (size_t)(scan - name); 477280849Scy if (*nm_len > 64) 478181834Sroberto return NULL; 479280849Scy val->valType = OPARG_TYPE_STRING; 480181834Sroberto 481280849Scy if (IS_WHITESPACE_CHAR(*scan)) { 482280849Scy /* 483280849Scy * There are attributes following the name. Parse 'em. 484280849Scy */ 485280849Scy scan = SPN_WHITESPACE_CHARS(scan); 486280849Scy scan = parse_attrs(NULL, scan, &option_load_mode, val); 487280849Scy if (scan == NULL) 488280849Scy return NULL; /* oops */ 489280849Scy } 490181834Sroberto 491280849Scy if (! IS_END_XML_TOKEN_CHAR(*scan)) 492280849Scy return NULL; /* oops */ 493181834Sroberto 494280849Scy if (*scan == '/') { 495280849Scy /* 496280849Scy * Single element XML entries get inserted as an empty string. 497280849Scy */ 498280849Scy if (*++scan != '>') 499181834Sroberto return NULL; 500280849Scy val->valType = OPARG_TYPE_NONE; 501280849Scy } 502280849Scy return scan+1; 503280849Scy} 504181834Sroberto 505280849Scy/** 506280849Scy * We've found a closing '>' without a preceding '/', thus we must search 507280849Scy * the text for '<name/>' where "name" is the name of the XML element. 508280849Scy * 509280849Scy * @param[in] name the start of the name in the element header 510280849Scy * @param[in] nm_len the length of that name 511280849Scy * @param[out] len the length of the value (string between header and 512280849Scy * the trailer/tail. 513280849Scy * @returns the character after the trailer, or NULL if not found. 514280849Scy */ 515280849Scystatic char const * 516280849Scyfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len) 517280849Scy{ 518280849Scy char z[72] = "</"; 519280849Scy char * dst = z + 2; 520181834Sroberto 521280849Scy do { 522280849Scy *(dst++) = *(src++); 523280849Scy } while (--nm_len > 0); /* nm_len is known to be 64 or less */ 524280849Scy *(dst++) = '>'; 525280849Scy *dst = NUL; 526280849Scy 527280849Scy { 528280849Scy char const * res = strstr(val, z); 529280849Scy 530280849Scy if (res != NULL) { 531280849Scy char const * end = (option_load_mode != OPTION_LOAD_KEEP) 532280849Scy ? SPN_WHITESPACE_BACK(val, res) 533280849Scy : res; 534280849Scy *len = (size_t)(end - val); /* includes trailing white space */ 535280849Scy res = SPN_WHITESPACE_CHARS(res + (dst - z)); 536280849Scy } 537280849Scy return res; 538181834Sroberto } 539280849Scy} 540181834Sroberto 541280849Scy/** 542280849Scy * We've found a '<' character. We ignore this if it is a comment or a 543280849Scy * directive. If it is something else, then whatever it is we are looking 544280849Scy * at is bogus. Returning NULL stops processing. 545280849Scy * 546280849Scy * @param[in] xml_name the name of an xml bracket (usually) 547280849Scy * @param[in,out] res_val the option data derived from the XML element 548280849Scy * 549280849Scy * @returns the place to resume scanning input 550280849Scy */ 551280849Scystatic char const * 552280849Scyscan_xml(char const * xml_name, tOptionValue * res_val) 553280849Scy{ 554280849Scy size_t nm_len, v_len; 555280849Scy char const * scan; 556280849Scy char const * val_str; 557280849Scy tOptionValue valu; 558280849Scy tOptionLoadMode save_mode = option_load_mode; 559181834Sroberto 560280849Scy if (! IS_VAR_FIRST_CHAR(*++xml_name)) 561280849Scy return unnamed_xml(xml_name); 562181834Sroberto 563280849Scy /* 564280849Scy * "scan_xml_name()" may change "option_load_mode". 565280849Scy */ 566280849Scy val_str = scan_xml_name(xml_name, &nm_len, &valu); 567280849Scy if (val_str == NULL) 568280849Scy goto bail_scan_xml; 569181834Sroberto 570280849Scy if (valu.valType == OPARG_TYPE_NONE) 571280849Scy scan = val_str; 572280849Scy else { 573280849Scy if (option_load_mode != OPTION_LOAD_KEEP) 574280849Scy val_str = SPN_WHITESPACE_CHARS(val_str); 575280849Scy scan = find_end_xml(xml_name, nm_len, val_str, &v_len); 576280849Scy if (scan == NULL) 577280849Scy goto bail_scan_xml; 578181834Sroberto } 579181834Sroberto 580280849Scy /* 581280849Scy * "scan" now points to where the scan is to resume after returning. 582280849Scy * It either points after "/>" at the end of the XML element header, 583280849Scy * or it points after the "</name>" tail based on the name in the header. 584280849Scy */ 585280849Scy 586181834Sroberto switch (valu.valType) { 587181834Sroberto case OPARG_TYPE_NONE: 588280849Scy add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0); 589181834Sroberto break; 590181834Sroberto 591181834Sroberto case OPARG_TYPE_STRING: 592280849Scy { 593280849Scy tOptionValue * new_val = add_string( 594280849Scy &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 595181834Sroberto 596280849Scy if (option_load_mode != OPTION_LOAD_KEEP) 597280849Scy munge_str(new_val->v.strVal, option_load_mode); 598280849Scy 599181834Sroberto break; 600280849Scy } 601181834Sroberto 602181834Sroberto case OPARG_TYPE_BOOLEAN: 603280849Scy add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 604181834Sroberto break; 605181834Sroberto 606181834Sroberto case OPARG_TYPE_NUMERIC: 607280849Scy add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 608181834Sroberto break; 609181834Sroberto 610181834Sroberto case OPARG_TYPE_HIERARCHY: 611181834Sroberto { 612280849Scy char * pz = AGALOC(v_len+1, "h scan"); 613280849Scy memcpy(pz, val_str, v_len); 614280849Scy pz[v_len] = NUL; 615280849Scy add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len); 616181834Sroberto AGFREE(pz); 617181834Sroberto break; 618181834Sroberto } 619181834Sroberto 620181834Sroberto case OPARG_TYPE_ENUMERATION: 621181834Sroberto case OPARG_TYPE_MEMBERSHIP: 622181834Sroberto default: 623181834Sroberto break; 624181834Sroberto } 625181834Sroberto 626181834Sroberto option_load_mode = save_mode; 627280849Scy return scan; 628280849Scy 629280849Scybail_scan_xml: 630280849Scy option_load_mode = save_mode; 631280849Scy return NULL; 632181834Sroberto} 633181834Sroberto 634181834Sroberto 635280849Scy/** 636181834Sroberto * Deallocate a list of option arguments. This must have been gotten from 637181834Sroberto * a hierarchical option argument, not a stacked list of strings. It is 638181834Sroberto * an internal call, so it is not validated. The caller is responsible for 639181834Sroberto * knowing what they are doing. 640181834Sroberto */ 641280849ScyLOCAL void 642280849Scyunload_arg_list(tArgList * arg_list) 643181834Sroberto{ 644280849Scy int ct = arg_list->useCt; 645280849Scy char const ** pnew_val = arg_list->apzArgs; 646181834Sroberto 647181834Sroberto while (ct-- > 0) { 648285169Scy tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++)); 649280849Scy if (new_val->valType == OPARG_TYPE_HIERARCHY) 650280849Scy unload_arg_list(new_val->v.nestVal); 651280849Scy AGFREE(new_val); 652181834Sroberto } 653181834Sroberto 654285169Scy AGFREE(arg_list); 655181834Sroberto} 656181834Sroberto 657181834Sroberto/*=export_func optionUnloadNested 658181834Sroberto * 659181834Sroberto * what: Deallocate the memory for a nested value 660181834Sroberto * arg: + tOptionValue const * + pOptVal + the hierarchical value + 661181834Sroberto * 662181834Sroberto * doc: 663181834Sroberto * A nested value needs to be deallocated. The pointer passed in should 664181834Sroberto * have been gotten from a call to @code{configFileLoad()} (See 665181834Sroberto * @pxref{libopts-configFileLoad}). 666181834Sroberto=*/ 667181834Srobertovoid 668280849ScyoptionUnloadNested(tOptionValue const * opt_val) 669181834Sroberto{ 670280849Scy if (opt_val == NULL) return; 671280849Scy if (opt_val->valType != OPARG_TYPE_HIERARCHY) { 672181834Sroberto errno = EINVAL; 673181834Sroberto return; 674181834Sroberto } 675181834Sroberto 676280849Scy unload_arg_list(opt_val->v.nestVal); 677181834Sroberto 678285169Scy AGFREE(opt_val); 679181834Sroberto} 680181834Sroberto 681280849Scy/** 682181834Sroberto * This is a _stable_ sort. The entries are sorted alphabetically, 683181834Sroberto * but within entries of the same name the ordering is unchanged. 684181834Sroberto * Typically, we also hope the input is sorted. 685181834Sroberto */ 686181834Srobertostatic void 687280849Scysort_list(tArgList * arg_list) 688181834Sroberto{ 689181834Sroberto int ix; 690280849Scy int lm = arg_list->useCt; 691181834Sroberto 692181834Sroberto /* 693181834Sroberto * This loop iterates "useCt" - 1 times. 694181834Sroberto */ 695181834Sroberto for (ix = 0; ++ix < lm;) { 696181834Sroberto int iy = ix-1; 697285169Scy tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]); 698285169Scy tOptionValue * old_v = C(tOptionValue *, arg_list->apzArgs[iy]); 699181834Sroberto 700181834Sroberto /* 701181834Sroberto * For as long as the new entry precedes the "old" entry, 702181834Sroberto * move the old pointer. Stop before trying to extract the 703181834Sroberto * "-1" entry. 704181834Sroberto */ 705280849Scy while (strcmp(old_v->pzName, new_v->pzName) > 0) { 706285169Scy arg_list->apzArgs[iy+1] = VOIDP(old_v); 707285169Scy old_v = (tOptionValue *)VOIDP(arg_list->apzArgs[--iy]); 708181834Sroberto if (iy < 0) 709181834Sroberto break; 710181834Sroberto } 711181834Sroberto 712181834Sroberto /* 713181834Sroberto * Always store the pointer. Sometimes it is redundant, 714181834Sroberto * but the redundancy is cheaper than a test and branch sequence. 715181834Sroberto */ 716285169Scy arg_list->apzArgs[iy+1] = VOIDP(new_v); 717181834Sroberto } 718181834Sroberto} 719181834Sroberto 720280849Scy/*= 721181834Sroberto * private: 722181834Sroberto * 723181834Sroberto * what: parse a hierarchical option argument 724280849Scy * arg: + char const * + pzTxt + the text to scan + 725280849Scy * arg: + char const * + pzName + the name for the text + 726280849Scy * arg: + size_t + nm_len + the length of "name" + 727181834Sroberto * 728285169Scy * ret_type: tOptionValue * 729181834Sroberto * ret_desc: An allocated, compound value structure 730181834Sroberto * 731181834Sroberto * doc: 732181834Sroberto * A block of text represents a series of values. It may be an 733181834Sroberto * entire configuration file, or it may be an argument to an 734181834Sroberto * option that takes a hierarchical value. 735280849Scy * 736280849Scy * If NULL is returned, errno will be set: 737280849Scy * @itemize @bullet 738280849Scy * @item 739280849Scy * @code{EINVAL} the input text was NULL. 740280849Scy * @item 741280849Scy * @code{ENOMEM} the storage structures could not be allocated 742280849Scy * @item 743280849Scy * @code{ENOMSG} no configuration values were found 744280849Scy * @end itemize 745280849Scy=*/ 746280849ScyLOCAL tOptionValue * 747280849ScyoptionLoadNested(char const * text, char const * name, size_t nm_len) 748181834Sroberto{ 749285169Scy tOptionValue * res_val; 750181834Sroberto 751181834Sroberto /* 752181834Sroberto * Make sure we have some data and we have space to put what we find. 753181834Sroberto */ 754280849Scy if (text == NULL) { 755181834Sroberto errno = EINVAL; 756181834Sroberto return NULL; 757181834Sroberto } 758280849Scy text = SPN_WHITESPACE_CHARS(text); 759280849Scy if (*text == NUL) { 760280849Scy errno = ENOMSG; 761181834Sroberto return NULL; 762181834Sroberto } 763280849Scy res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args"); 764280849Scy res_val->valType = OPARG_TYPE_HIERARCHY; 765285169Scy res_val->pzName = (char *)(res_val + 1); 766280849Scy memcpy(res_val->pzName, name, nm_len); 767280849Scy res_val->pzName[nm_len] = NUL; 768181834Sroberto 769280849Scy { 770280849Scy tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l"); 771280849Scy 772280849Scy res_val->v.nestVal = arg_list; 773280849Scy arg_list->useCt = 0; 774280849Scy arg_list->allocCt = MIN_ARG_ALLOC_CT; 775181834Sroberto } 776181834Sroberto 777181834Sroberto /* 778181834Sroberto * Scan until we hit a NUL. 779181834Sroberto */ 780181834Sroberto do { 781280849Scy text = SPN_WHITESPACE_CHARS(text); 782280849Scy if (IS_VAR_FIRST_CHAR(*text)) 783280849Scy text = scan_name(text, res_val); 784280849Scy 785280849Scy else switch (*text) { 786181834Sroberto case NUL: goto scan_done; 787280849Scy case '<': text = scan_xml(text, res_val); 788280849Scy if (text == NULL) goto woops; 789280849Scy if (*text == ',') text++; break; 790280849Scy case '#': text = strchr(text, NL); break; 791181834Sroberto default: goto woops; 792181834Sroberto } 793280849Scy } while (text != NULL); scan_done:; 794181834Sroberto 795280849Scy { 796280849Scy tArgList * al = res_val->v.nestVal; 797280849Scy if (al->useCt == 0) { 798280849Scy errno = ENOMSG; 799280849Scy goto woops; 800280849Scy } 801280849Scy if (al->useCt > 1) 802280849Scy sort_list(al); 803181834Sroberto } 804181834Sroberto 805280849Scy return res_val; 806280849Scy 807181834Sroberto woops: 808280849Scy AGFREE(res_val->v.nestVal); 809280849Scy AGFREE(res_val); 810181834Sroberto return NULL; 811181834Sroberto} 812181834Sroberto 813181834Sroberto/*=export_func optionNestedVal 814181834Sroberto * private: 815181834Sroberto * 816181834Sroberto * what: parse a hierarchical option argument 817285169Scy * arg: + tOptions * + opts + program options descriptor + 818285169Scy * arg: + tOptDesc * + od + the descriptor for this arg + 819181834Sroberto * 820181834Sroberto * doc: 821181834Sroberto * Nested value was found on the command line 822181834Sroberto=*/ 823181834Srobertovoid 824280849ScyoptionNestedVal(tOptions * opts, tOptDesc * od) 825181834Sroberto{ 826280849Scy if (opts < OPTPROC_EMIT_LIMIT) 827280849Scy return; 828181834Sroberto 829280849Scy if (od->fOptState & OPTST_RESET) { 830280849Scy tArgList * arg_list = od->optCookie; 831280849Scy int ct; 832280849Scy char const ** av; 833280849Scy 834280849Scy if (arg_list == NULL) 835280849Scy return; 836280849Scy ct = arg_list->useCt; 837280849Scy av = arg_list->apzArgs; 838280849Scy 839280849Scy while (--ct >= 0) { 840285169Scy void * p = VOIDP(*(av++)); 841280849Scy optionUnloadNested((tOptionValue const *)p); 842280849Scy } 843280849Scy 844280849Scy AGFREE(od->optCookie); 845280849Scy 846280849Scy } else { 847280849Scy tOptionValue * opt_val = optionLoadNested( 848280849Scy od->optArg.argString, od->pz_Name, strlen(od->pz_Name)); 849280849Scy 850280849Scy if (opt_val != NULL) 851285169Scy addArgListEntry(&(od->optCookie), VOIDP(opt_val)); 852280849Scy } 853181834Sroberto} 854280849Scy 855280849Scy/** 856280849Scy * get_special_char 857280849Scy */ 858280849ScyLOCAL int 859280849Scyget_special_char(char const ** ppz, int * ct) 860280849Scy{ 861280849Scy char const * pz = *ppz; 862294554Sdelphij char * rz; 863280849Scy 864280849Scy if (*ct < 3) 865280849Scy return '&'; 866280849Scy 867280849Scy if (*pz == '#') { 868280849Scy int base = 10; 869280849Scy int retch; 870280849Scy 871280849Scy pz++; 872280849Scy if (*pz == 'x') { 873280849Scy base = 16; 874280849Scy pz++; 875280849Scy } 876294554Sdelphij retch = (int)strtoul(pz, &rz, base); 877294554Sdelphij pz = rz; 878280849Scy if (*pz != ';') 879280849Scy return '&'; 880280849Scy base = (int)(++pz - *ppz); 881280849Scy if (base > *ct) 882280849Scy return '&'; 883280849Scy 884280849Scy *ct -= base; 885280849Scy *ppz = pz; 886280849Scy return retch; 887280849Scy } 888280849Scy 889280849Scy { 890280849Scy int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 891280849Scy xml_xlate_t const * xlatp = xml_xlate; 892280849Scy 893280849Scy for (;;) { 894280849Scy if ( (*ct >= xlatp->xml_len) 895280849Scy && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) { 896280849Scy *ppz += xlatp->xml_len; 897280849Scy *ct -= xlatp->xml_len; 898280849Scy return xlatp->xml_ch; 899280849Scy } 900280849Scy 901280849Scy if (--ctr <= 0) 902280849Scy break; 903280849Scy xlatp++; 904280849Scy } 905280849Scy } 906280849Scy return '&'; 907280849Scy} 908280849Scy 909280849Scy/** 910280849Scy * emit_special_char 911280849Scy */ 912280849ScyLOCAL void 913280849Scyemit_special_char(FILE * fp, int ch) 914280849Scy{ 915280849Scy int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 916280849Scy xml_xlate_t const * xlatp = xml_xlate; 917280849Scy 918280849Scy putc('&', fp); 919280849Scy for (;;) { 920280849Scy if (ch == xlatp->xml_ch) { 921280849Scy fputs(xlatp->xml_txt, fp); 922280849Scy return; 923280849Scy } 924280849Scy if (--ctr <= 0) 925280849Scy break; 926280849Scy xlatp++; 927280849Scy } 928280849Scy fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF)); 929280849Scy} 930280849Scy 931280849Scy/** @} 932280849Scy * 933181834Sroberto * Local Variables: 934181834Sroberto * mode: C 935181834Sroberto * c-file-style: "stroustrup" 936181834Sroberto * indent-tabs-mode: nil 937181834Sroberto * End: 938181834Sroberto * end of autoopts/nested.c */ 939