1181834Sroberto 2290000Sglebius/** 3290000Sglebius * \file nested.c 4181834Sroberto * 5290000Sglebius * Handle options with arguments that contain nested values. 6290000Sglebius * 7290000Sglebius * @addtogroup autoopts 8290000Sglebius * @{ 9181834Sroberto */ 10181834Sroberto/* 11290000Sglebius * Automated Options Nested Values module. 12181834Sroberto * 13290000Sglebius * This file is part of AutoOpts, a companion to AutoGen. 14290000Sglebius * AutoOpts is free software. 15290000Sglebius * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 16181834Sroberto * 17290000Sglebius * AutoOpts is available under any one of two licenses. The license 18290000Sglebius * in use must be one of these two and the choice is under the control 19290000Sglebius * of the user of the license. 20181834Sroberto * 21290000Sglebius * The GNU Lesser General Public License, version 3 or later 22290000Sglebius * See the files "COPYING.lgplv3" and "COPYING.gplv3" 23181834Sroberto * 24290000Sglebius * The Modified Berkeley Software Distribution License 25290000Sglebius * See the file "COPYING.mbsd" 26181834Sroberto * 27290000Sglebius * These files have the following sha256 sums: 28181834Sroberto * 29290000Sglebius * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 30290000Sglebius * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 31290000Sglebius * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 32181834Sroberto */ 33290000Sglebius 34290000Sglebiustypedef struct { 35290000Sglebius int xml_ch; 36290000Sglebius int xml_len; 37290000Sglebius char xml_txt[8]; 38290000Sglebius} xml_xlate_t; 39290000Sglebius 40290000Sglebiusstatic xml_xlate_t const xml_xlate[] = { 41290000Sglebius { '&', 4, "amp;" }, 42290000Sglebius { '<', 3, "lt;" }, 43290000Sglebius { '>', 3, "gt;" }, 44290000Sglebius { '"', 5, "quot;" }, 45290000Sglebius { '\'',5, "apos;" } 46290000Sglebius}; 47290000Sglebius 48290000Sglebius#ifndef ENOMSG 49290000Sglebius#define ENOMSG ENOENT 50290000Sglebius#endif 51290000Sglebius 52181834Sroberto/* = = = START-STATIC-FORWARD = = = */ 53181834Srobertostatic void 54290000Sglebiusremove_continuation(char * src); 55181834Sroberto 56290000Sglebiusstatic char const * 57290000Sglebiusscan_q_str(char const * pzTxt); 58181834Sroberto 59290000Sglebiusstatic tOptionValue * 60290000Sglebiusadd_string(void ** pp, char const * name, size_t nm_len, 61290000Sglebius char const * val, size_t d_len); 62181834Sroberto 63290000Sglebiusstatic tOptionValue * 64290000Sglebiusadd_bool(void ** pp, char const * name, size_t nm_len, 65290000Sglebius char const * val, size_t d_len); 66181834Sroberto 67290000Sglebiusstatic tOptionValue * 68290000Sglebiusadd_number(void ** pp, char const * name, size_t nm_len, 69290000Sglebius char const * val, size_t d_len); 70181834Sroberto 71290000Sglebiusstatic tOptionValue * 72290000Sglebiusadd_nested(void ** pp, char const * name, size_t nm_len, 73290000Sglebius char * val, size_t d_len); 74181834Sroberto 75290000Sglebiusstatic char const * 76290000Sglebiusscan_name(char const * name, tOptionValue * res); 77181834Sroberto 78290000Sglebiusstatic char const * 79290000Sglebiusunnamed_xml(char const * txt); 80181834Sroberto 81290000Sglebiusstatic char const * 82290000Sglebiusscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val); 83181834Sroberto 84290000Sglebiusstatic char const * 85290000Sglebiusfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len); 86290000Sglebius 87290000Sglebiusstatic char const * 88290000Sglebiusscan_xml(char const * xml_name, tOptionValue * res_val); 89290000Sglebius 90181834Srobertostatic void 91290000Sglebiussort_list(tArgList * arg_list); 92181834Sroberto/* = = = END-STATIC-FORWARD = = = */ 93181834Sroberto 94290000Sglebius/** 95290000Sglebius * Backslashes are used for line continuations. We keep the newline 96290000Sglebius * characters, but trim out the backslash: 97181834Sroberto */ 98181834Srobertostatic void 99290000Sglebiusremove_continuation(char * src) 100181834Sroberto{ 101290000Sglebius char * pzD; 102181834Sroberto 103290000Sglebius do { 104290000Sglebius while (*src == NL) src++; 105290000Sglebius pzD = strchr(src, NL); 106290000Sglebius if (pzD == NULL) 107290000Sglebius return; 108181834Sroberto 109290000Sglebius /* 110290000Sglebius * pzD has skipped at least one non-newline character and now 111290000Sglebius * points to a newline character. It now becomes the source and 112290000Sglebius * pzD goes to the previous character. 113290000Sglebius */ 114290000Sglebius src = pzD--; 115290000Sglebius if (*pzD != '\\') 116290000Sglebius pzD++; 117290000Sglebius } while (pzD == src); 118290000Sglebius 119290000Sglebius /* 120290000Sglebius * Start shifting text. 121290000Sglebius */ 122181834Sroberto for (;;) { 123290000Sglebius char ch = ((*pzD++) = *(src++)); 124181834Sroberto switch (ch) { 125181834Sroberto case NUL: return; 126290000Sglebius case '\\': 127290000Sglebius if (*src == NL) 128290000Sglebius --pzD; /* rewrite on next iteration */ 129181834Sroberto } 130181834Sroberto } 131181834Sroberto} 132181834Sroberto 133290000Sglebius/** 134181834Sroberto * Find the end of a quoted string, skipping escaped quote characters. 135181834Sroberto */ 136290000Sglebiusstatic char const * 137290000Sglebiusscan_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 170290000Sglebius/** 171290000Sglebius * Associate a name with either a string or no value. 172181834Sroberto * 173290000Sglebius * @param[in,out] pp argument list to add to 174290000Sglebius * @param[in] name the name of the "suboption" 175290000Sglebius * @param[in] nm_len the length of the name 176290000Sglebius * @param[in] val the string value for the suboption 177290000Sglebius * @param[in] d_len the length of the value 178290000Sglebius * 179290000Sglebius * @returns the new value structure 180181834Sroberto */ 181290000Sglebiusstatic tOptionValue * 182290000Sglebiusadd_string(void ** pp, char const * name, size_t nm_len, 183290000Sglebius char const * val, size_t d_len) 184181834Sroberto{ 185290000Sglebius tOptionValue * pNV; 186290000Sglebius size_t sz = nm_len + d_len + sizeof(*pNV); 187181834Sroberto 188290000Sglebius pNV = AGALOC(sz, "option name/str value pair"); 189181834Sroberto 190290000Sglebius if (val == NULL) { 191181834Sroberto pNV->valType = OPARG_TYPE_NONE; 192181834Sroberto pNV->pzName = pNV->v.strVal; 193181834Sroberto 194181834Sroberto } else { 195181834Sroberto pNV->valType = OPARG_TYPE_STRING; 196290000Sglebius if (d_len > 0) { 197290000Sglebius char const * src = val; 198290000Sglebius char * pzDst = pNV->v.strVal; 199290000Sglebius int ct = (int)d_len; 200290000Sglebius do { 201290000Sglebius int ch = *(src++) & 0xFF; 202290000Sglebius if (ch == NUL) goto data_copy_done; 203290000Sglebius if (ch == '&') 204290000Sglebius ch = get_special_char(&src, &ct); 205290000Sglebius *(pzDst++) = (char)ch; 206290000Sglebius } while (--ct > 0); 207290000Sglebius data_copy_done: 208290000Sglebius *pzDst = NUL; 209290000Sglebius 210290000Sglebius } else { 211290000Sglebius pNV->v.strVal[0] = NUL; 212290000Sglebius } 213290000Sglebius 214290000Sglebius pNV->pzName = pNV->v.strVal + d_len + 1; 215181834Sroberto } 216181834Sroberto 217290000Sglebius memcpy(pNV->pzName, name, nm_len); 218290000Sglebius pNV->pzName[ nm_len ] = NUL; 219290000Sglebius addArgListEntry(pp, pNV); 220181834Sroberto return pNV; 221181834Sroberto} 222181834Sroberto 223290000Sglebius/** 224290000Sglebius * Associate a name with a boolean value 225181834Sroberto * 226290000Sglebius * @param[in,out] pp argument list to add to 227290000Sglebius * @param[in] name the name of the "suboption" 228290000Sglebius * @param[in] nm_len the length of the name 229290000Sglebius * @param[in] val the boolean value for the suboption 230290000Sglebius * @param[in] d_len the length of the value 231290000Sglebius * 232290000Sglebius * @returns the new value structure 233181834Sroberto */ 234290000Sglebiusstatic tOptionValue * 235290000Sglebiusadd_bool(void ** pp, char const * name, size_t nm_len, 236290000Sglebius char const * val, size_t d_len) 237181834Sroberto{ 238290000Sglebius size_t sz = nm_len + sizeof(tOptionValue) + 1; 239290000Sglebius tOptionValue * new_val = AGALOC(sz, "bool val"); 240181834Sroberto 241290000Sglebius /* 242290000Sglebius * Scan over whitespace is constrained by "d_len" 243290000Sglebius */ 244290000Sglebius while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 245290000Sglebius d_len--; val++; 246181834Sroberto } 247181834Sroberto 248290000Sglebius if (d_len == 0) 249290000Sglebius new_val->v.boolVal = 0; 250290000Sglebius 251290000Sglebius else if (IS_DEC_DIGIT_CHAR(*val)) 252290000Sglebius new_val->v.boolVal = (unsigned)atoi(val); 253290000Sglebius 254290000Sglebius else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val); 255290000Sglebius 256290000Sglebius new_val->valType = OPARG_TYPE_BOOLEAN; 257290000Sglebius new_val->pzName = (char *)(new_val + 1); 258290000Sglebius memcpy(new_val->pzName, name, nm_len); 259290000Sglebius new_val->pzName[ nm_len ] = NUL; 260290000Sglebius addArgListEntry(pp, new_val); 261290000Sglebius return new_val; 262181834Sroberto} 263181834Sroberto 264290000Sglebius/** 265290000Sglebius * Associate a name with strtol() value, defaulting to zero. 266181834Sroberto * 267290000Sglebius * @param[in,out] pp argument list to add to 268290000Sglebius * @param[in] name the name of the "suboption" 269290000Sglebius * @param[in] nm_len the length of the name 270290000Sglebius * @param[in] val the numeric value for the suboption 271290000Sglebius * @param[in] d_len the length of the value 272290000Sglebius * 273290000Sglebius * @returns the new value structure 274181834Sroberto */ 275290000Sglebiusstatic tOptionValue * 276290000Sglebiusadd_number(void ** pp, char const * name, size_t nm_len, 277290000Sglebius char const * val, size_t d_len) 278181834Sroberto{ 279290000Sglebius size_t sz = nm_len + sizeof(tOptionValue) + 1; 280290000Sglebius tOptionValue * new_val = AGALOC(sz, "int val"); 281181834Sroberto 282290000Sglebius /* 283290000Sglebius * Scan over whitespace is constrained by "d_len" 284290000Sglebius */ 285290000Sglebius while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 286290000Sglebius d_len--; val++; 287181834Sroberto } 288290000Sglebius if (d_len == 0) 289290000Sglebius new_val->v.longVal = 0; 290181834Sroberto else 291290000Sglebius new_val->v.longVal = strtol(val, 0, 0); 292181834Sroberto 293290000Sglebius new_val->valType = OPARG_TYPE_NUMERIC; 294290000Sglebius new_val->pzName = (char *)(new_val + 1); 295290000Sglebius memcpy(new_val->pzName, name, nm_len); 296290000Sglebius new_val->pzName[ nm_len ] = NUL; 297290000Sglebius addArgListEntry(pp, new_val); 298290000Sglebius return new_val; 299181834Sroberto} 300181834Sroberto 301290000Sglebius/** 302290000Sglebius * Associate a name with a nested/hierarchical value. 303181834Sroberto * 304290000Sglebius * @param[in,out] pp argument list to add to 305290000Sglebius * @param[in] name the name of the "suboption" 306290000Sglebius * @param[in] nm_len the length of the name 307290000Sglebius * @param[in] val the nested values for the suboption 308290000Sglebius * @param[in] d_len the length of the value 309290000Sglebius * 310290000Sglebius * @returns the new value structure 311181834Sroberto */ 312290000Sglebiusstatic tOptionValue * 313290000Sglebiusadd_nested(void ** pp, char const * name, size_t nm_len, 314290000Sglebius char * val, size_t d_len) 315181834Sroberto{ 316290000Sglebius tOptionValue * new_val; 317181834Sroberto 318290000Sglebius if (d_len == 0) { 319290000Sglebius size_t sz = nm_len + sizeof(*new_val) + 1; 320290000Sglebius new_val = AGALOC(sz, "empty nest"); 321290000Sglebius new_val->v.nestVal = NULL; 322290000Sglebius new_val->valType = OPARG_TYPE_HIERARCHY; 323290000Sglebius new_val->pzName = (char *)(new_val + 1); 324290000Sglebius memcpy(new_val->pzName, name, nm_len); 325290000Sglebius new_val->pzName[ nm_len ] = NUL; 326181834Sroberto 327181834Sroberto } else { 328290000Sglebius new_val = optionLoadNested(val, name, nm_len); 329181834Sroberto } 330181834Sroberto 331290000Sglebius if (new_val != NULL) 332290000Sglebius addArgListEntry(pp, new_val); 333181834Sroberto 334290000Sglebius return new_val; 335181834Sroberto} 336181834Sroberto 337290000Sglebius/** 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 */ 341290000Sglebiusstatic char const * 342290000Sglebiusscan_name(char const * name, tOptionValue * res) 343181834Sroberto{ 344290000Sglebius tOptionValue * new_val; 345290000Sglebius char const * pzScan = name+1; /* we know first char is a name char */ 346290000Sglebius char const * pzVal; 347290000Sglebius size_t nm_len = 1; 348290000Sglebius size_t d_len = 0; 349181834Sroberto 350290000Sglebius /* 351290000Sglebius * Scan over characters that name a value. These names may not end 352290000Sglebius * with a colon, but they may contain colons. 353290000Sglebius */ 354290000Sglebius pzScan = SPN_VALUE_NAME_CHARS(name + 1); 355290000Sglebius if (pzScan[-1] == ':') 356290000Sglebius pzScan--; 357290000Sglebius nm_len = (size_t)(pzScan - name); 358181834Sroberto 359290000Sglebius pzScan = SPN_HORIZ_WHITE_CHARS(pzScan); 360181834Sroberto 361290000Sglebius re_switch: 362290000Sglebius 363181834Sroberto switch (*pzScan) { 364181834Sroberto case '=': 365181834Sroberto case ':': 366290000Sglebius pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1); 367290000Sglebius if ((*pzScan == '=') || (*pzScan == ':')) 368290000Sglebius goto default_char; 369290000Sglebius goto re_switch; 370181834Sroberto 371290000Sglebius case NL: 372181834Sroberto case ',': 373181834Sroberto pzScan++; 374181834Sroberto /* FALLTHROUGH */ 375181834Sroberto 376181834Sroberto case NUL: 377290000Sglebius add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0); 378181834Sroberto break; 379181834Sroberto 380181834Sroberto case '"': 381181834Sroberto case '\'': 382181834Sroberto pzVal = pzScan; 383290000Sglebius pzScan = scan_q_str(pzScan); 384290000Sglebius d_len = (size_t)(pzScan - pzVal); 385290000Sglebius new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal, 386290000Sglebius d_len); 387290000Sglebius if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED)) 388290000Sglebius 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--; 403290000Sglebius d_len = (size_t)(pzScan - pzVal); 404181834Sroberto goto string_done; 405181834Sroberto /* FALLTHROUGH */ 406181834Sroberto 407290000Sglebius case NL: 408181834Sroberto if ( (pzScan > pzVal + 2) 409181834Sroberto && (pzScan[-2] == '\\') 410181834Sroberto && (pzScan[ 0] != NUL)) 411181834Sroberto continue; 412181834Sroberto /* FALLTHROUGH */ 413181834Sroberto 414181834Sroberto case ',': 415290000Sglebius d_len = (size_t)(pzScan - pzVal) - 1; 416181834Sroberto string_done: 417290000Sglebius new_val = add_string(&(res->v.nestVal), name, nm_len, 418290000Sglebius pzVal, d_len); 419290000Sglebius if (new_val != NULL) 420290000Sglebius 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 430290000Sglebius/** 431290000Sglebius * Some xml element that does not start with a name. 432290000Sglebius * The next character must be either '!' (introducing a comment), 433290000Sglebius * or '?' (introducing an XML meta-marker of some sort). 434290000Sglebius * We ignore these and indicate an error (NULL result) otherwise. 435181834Sroberto * 436290000Sglebius * @param[in] txt the text within an xml bracket 437290000Sglebius * @returns the address of the character after the closing marker, or NULL. 438181834Sroberto */ 439290000Sglebiusstatic char const * 440290000Sglebiusunnamed_xml(char const * txt) 441181834Sroberto{ 442290000Sglebius switch (*txt) { 443290000Sglebius default: 444290000Sglebius txt = NULL; 445290000Sglebius break; 446181834Sroberto 447290000Sglebius case '!': 448290000Sglebius txt = strstr(txt, "-->"); 449290000Sglebius if (txt != NULL) 450290000Sglebius txt += 3; 451290000Sglebius break; 452181834Sroberto 453290000Sglebius case '?': 454290000Sglebius txt = strchr(txt, '>'); 455290000Sglebius if (txt != NULL) 456290000Sglebius txt++; 457290000Sglebius break; 458181834Sroberto } 459290000Sglebius return txt; 460290000Sglebius} 461181834Sroberto 462290000Sglebius/** 463290000Sglebius * Scan off the xml element name, and the rest of the header, too. 464290000Sglebius * Set the value type to NONE if it ends with "/>". 465290000Sglebius * 466290000Sglebius * @param[in] name the first name character (alphabetic) 467290000Sglebius * @param[out] nm_len the length of the name 468290000Sglebius * @param[out] val set valType field to STRING or NONE. 469290000Sglebius * 470290000Sglebius * @returns the scan resumption point, or NULL on error 471290000Sglebius */ 472290000Sglebiusstatic char const * 473290000Sglebiusscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val) 474290000Sglebius{ 475290000Sglebius char const * scan = SPN_VALUE_NAME_CHARS(name + 1); 476290000Sglebius *nm_len = (size_t)(scan - name); 477290000Sglebius if (*nm_len > 64) 478181834Sroberto return NULL; 479290000Sglebius val->valType = OPARG_TYPE_STRING; 480181834Sroberto 481290000Sglebius if (IS_WHITESPACE_CHAR(*scan)) { 482290000Sglebius /* 483290000Sglebius * There are attributes following the name. Parse 'em. 484290000Sglebius */ 485290000Sglebius scan = SPN_WHITESPACE_CHARS(scan); 486290000Sglebius scan = parse_attrs(NULL, scan, &option_load_mode, val); 487290000Sglebius if (scan == NULL) 488290000Sglebius return NULL; /* oops */ 489290000Sglebius } 490181834Sroberto 491290000Sglebius if (! IS_END_XML_TOKEN_CHAR(*scan)) 492290000Sglebius return NULL; /* oops */ 493181834Sroberto 494290000Sglebius if (*scan == '/') { 495290000Sglebius /* 496290000Sglebius * Single element XML entries get inserted as an empty string. 497290000Sglebius */ 498290000Sglebius if (*++scan != '>') 499181834Sroberto return NULL; 500290000Sglebius val->valType = OPARG_TYPE_NONE; 501290000Sglebius } 502290000Sglebius return scan+1; 503290000Sglebius} 504181834Sroberto 505290000Sglebius/** 506290000Sglebius * We've found a closing '>' without a preceding '/', thus we must search 507290000Sglebius * the text for '<name/>' where "name" is the name of the XML element. 508290000Sglebius * 509290000Sglebius * @param[in] name the start of the name in the element header 510290000Sglebius * @param[in] nm_len the length of that name 511290000Sglebius * @param[out] len the length of the value (string between header and 512290000Sglebius * the trailer/tail. 513290000Sglebius * @returns the character after the trailer, or NULL if not found. 514290000Sglebius */ 515290000Sglebiusstatic char const * 516290000Sglebiusfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len) 517290000Sglebius{ 518290000Sglebius char z[72] = "</"; 519290000Sglebius char * dst = z + 2; 520181834Sroberto 521290000Sglebius do { 522290000Sglebius *(dst++) = *(src++); 523290000Sglebius } while (--nm_len > 0); /* nm_len is known to be 64 or less */ 524290000Sglebius *(dst++) = '>'; 525290000Sglebius *dst = NUL; 526290000Sglebius 527290000Sglebius { 528290000Sglebius char const * res = strstr(val, z); 529290000Sglebius 530290000Sglebius if (res != NULL) { 531290000Sglebius char const * end = (option_load_mode != OPTION_LOAD_KEEP) 532290000Sglebius ? SPN_WHITESPACE_BACK(val, res) 533290000Sglebius : res; 534290000Sglebius *len = (size_t)(end - val); /* includes trailing white space */ 535290000Sglebius res = SPN_WHITESPACE_CHARS(res + (dst - z)); 536290000Sglebius } 537290000Sglebius return res; 538181834Sroberto } 539290000Sglebius} 540181834Sroberto 541290000Sglebius/** 542290000Sglebius * We've found a '<' character. We ignore this if it is a comment or a 543290000Sglebius * directive. If it is something else, then whatever it is we are looking 544290000Sglebius * at is bogus. Returning NULL stops processing. 545290000Sglebius * 546290000Sglebius * @param[in] xml_name the name of an xml bracket (usually) 547290000Sglebius * @param[in,out] res_val the option data derived from the XML element 548290000Sglebius * 549290000Sglebius * @returns the place to resume scanning input 550290000Sglebius */ 551290000Sglebiusstatic char const * 552290000Sglebiusscan_xml(char const * xml_name, tOptionValue * res_val) 553290000Sglebius{ 554290000Sglebius size_t nm_len, v_len; 555290000Sglebius char const * scan; 556290000Sglebius char const * val_str; 557290000Sglebius tOptionValue valu; 558290000Sglebius tOptionLoadMode save_mode = option_load_mode; 559181834Sroberto 560290000Sglebius if (! IS_VAR_FIRST_CHAR(*++xml_name)) 561290000Sglebius return unnamed_xml(xml_name); 562181834Sroberto 563290000Sglebius /* 564290000Sglebius * "scan_xml_name()" may change "option_load_mode". 565290000Sglebius */ 566290000Sglebius val_str = scan_xml_name(xml_name, &nm_len, &valu); 567290000Sglebius if (val_str == NULL) 568290000Sglebius goto bail_scan_xml; 569181834Sroberto 570290000Sglebius if (valu.valType == OPARG_TYPE_NONE) 571290000Sglebius scan = val_str; 572290000Sglebius else { 573290000Sglebius if (option_load_mode != OPTION_LOAD_KEEP) 574290000Sglebius val_str = SPN_WHITESPACE_CHARS(val_str); 575290000Sglebius scan = find_end_xml(xml_name, nm_len, val_str, &v_len); 576290000Sglebius if (scan == NULL) 577290000Sglebius goto bail_scan_xml; 578181834Sroberto } 579181834Sroberto 580290000Sglebius /* 581290000Sglebius * "scan" now points to where the scan is to resume after returning. 582290000Sglebius * It either points after "/>" at the end of the XML element header, 583290000Sglebius * or it points after the "</name>" tail based on the name in the header. 584290000Sglebius */ 585290000Sglebius 586181834Sroberto switch (valu.valType) { 587181834Sroberto case OPARG_TYPE_NONE: 588290000Sglebius add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0); 589181834Sroberto break; 590181834Sroberto 591181834Sroberto case OPARG_TYPE_STRING: 592290000Sglebius { 593290000Sglebius tOptionValue * new_val = add_string( 594290000Sglebius &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 595181834Sroberto 596290000Sglebius if (option_load_mode != OPTION_LOAD_KEEP) 597290000Sglebius munge_str(new_val->v.strVal, option_load_mode); 598290000Sglebius 599181834Sroberto break; 600290000Sglebius } 601181834Sroberto 602181834Sroberto case OPARG_TYPE_BOOLEAN: 603290000Sglebius add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 604181834Sroberto break; 605181834Sroberto 606181834Sroberto case OPARG_TYPE_NUMERIC: 607290000Sglebius add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 608181834Sroberto break; 609181834Sroberto 610181834Sroberto case OPARG_TYPE_HIERARCHY: 611181834Sroberto { 612290000Sglebius char * pz = AGALOC(v_len+1, "h scan"); 613290000Sglebius memcpy(pz, val_str, v_len); 614290000Sglebius pz[v_len] = NUL; 615290000Sglebius 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; 627290000Sglebius return scan; 628290000Sglebius 629290000Sglebiusbail_scan_xml: 630290000Sglebius option_load_mode = save_mode; 631290000Sglebius return NULL; 632181834Sroberto} 633181834Sroberto 634181834Sroberto 635290000Sglebius/** 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 */ 641290000SglebiusLOCAL void 642290000Sglebiusunload_arg_list(tArgList * arg_list) 643181834Sroberto{ 644290000Sglebius int ct = arg_list->useCt; 645290000Sglebius char const ** pnew_val = arg_list->apzArgs; 646181834Sroberto 647181834Sroberto while (ct-- > 0) { 648290000Sglebius tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++)); 649290000Sglebius if (new_val->valType == OPARG_TYPE_HIERARCHY) 650290000Sglebius unload_arg_list(new_val->v.nestVal); 651290000Sglebius AGFREE(new_val); 652181834Sroberto } 653181834Sroberto 654290000Sglebius 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 668290000SglebiusoptionUnloadNested(tOptionValue const * opt_val) 669181834Sroberto{ 670290000Sglebius if (opt_val == NULL) return; 671290000Sglebius if (opt_val->valType != OPARG_TYPE_HIERARCHY) { 672181834Sroberto errno = EINVAL; 673181834Sroberto return; 674181834Sroberto } 675181834Sroberto 676290000Sglebius unload_arg_list(opt_val->v.nestVal); 677181834Sroberto 678290000Sglebius AGFREE(opt_val); 679181834Sroberto} 680181834Sroberto 681290000Sglebius/** 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 687290000Sglebiussort_list(tArgList * arg_list) 688181834Sroberto{ 689181834Sroberto int ix; 690290000Sglebius 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; 697290000Sglebius tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]); 698290000Sglebius 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 */ 705290000Sglebius while (strcmp(old_v->pzName, new_v->pzName) > 0) { 706290000Sglebius arg_list->apzArgs[iy+1] = VOIDP(old_v); 707290000Sglebius 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 */ 716290000Sglebius arg_list->apzArgs[iy+1] = VOIDP(new_v); 717181834Sroberto } 718181834Sroberto} 719181834Sroberto 720290000Sglebius/*= 721181834Sroberto * private: 722181834Sroberto * 723181834Sroberto * what: parse a hierarchical option argument 724290000Sglebius * arg: + char const * + pzTxt + the text to scan + 725290000Sglebius * arg: + char const * + pzName + the name for the text + 726290000Sglebius * arg: + size_t + nm_len + the length of "name" + 727181834Sroberto * 728290000Sglebius * 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. 735290000Sglebius * 736290000Sglebius * If NULL is returned, errno will be set: 737290000Sglebius * @itemize @bullet 738290000Sglebius * @item 739290000Sglebius * @code{EINVAL} the input text was NULL. 740290000Sglebius * @item 741290000Sglebius * @code{ENOMEM} the storage structures could not be allocated 742290000Sglebius * @item 743290000Sglebius * @code{ENOMSG} no configuration values were found 744290000Sglebius * @end itemize 745290000Sglebius=*/ 746290000SglebiusLOCAL tOptionValue * 747290000SglebiusoptionLoadNested(char const * text, char const * name, size_t nm_len) 748181834Sroberto{ 749290000Sglebius tOptionValue * res_val; 750181834Sroberto 751181834Sroberto /* 752181834Sroberto * Make sure we have some data and we have space to put what we find. 753181834Sroberto */ 754290000Sglebius if (text == NULL) { 755181834Sroberto errno = EINVAL; 756181834Sroberto return NULL; 757181834Sroberto } 758290000Sglebius text = SPN_WHITESPACE_CHARS(text); 759290000Sglebius if (*text == NUL) { 760290000Sglebius errno = ENOMSG; 761181834Sroberto return NULL; 762181834Sroberto } 763290000Sglebius res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args"); 764290000Sglebius res_val->valType = OPARG_TYPE_HIERARCHY; 765290000Sglebius res_val->pzName = (char *)(res_val + 1); 766290000Sglebius memcpy(res_val->pzName, name, nm_len); 767290000Sglebius res_val->pzName[nm_len] = NUL; 768181834Sroberto 769290000Sglebius { 770290000Sglebius tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l"); 771290000Sglebius 772290000Sglebius res_val->v.nestVal = arg_list; 773290000Sglebius arg_list->useCt = 0; 774290000Sglebius arg_list->allocCt = MIN_ARG_ALLOC_CT; 775181834Sroberto } 776181834Sroberto 777181834Sroberto /* 778181834Sroberto * Scan until we hit a NUL. 779181834Sroberto */ 780181834Sroberto do { 781290000Sglebius text = SPN_WHITESPACE_CHARS(text); 782290000Sglebius if (IS_VAR_FIRST_CHAR(*text)) 783290000Sglebius text = scan_name(text, res_val); 784290000Sglebius 785290000Sglebius else switch (*text) { 786181834Sroberto case NUL: goto scan_done; 787290000Sglebius case '<': text = scan_xml(text, res_val); 788290000Sglebius if (text == NULL) goto woops; 789290000Sglebius if (*text == ',') text++; break; 790290000Sglebius case '#': text = strchr(text, NL); break; 791181834Sroberto default: goto woops; 792181834Sroberto } 793290000Sglebius } while (text != NULL); scan_done:; 794181834Sroberto 795290000Sglebius { 796290000Sglebius tArgList * al = res_val->v.nestVal; 797290000Sglebius if (al->useCt == 0) { 798290000Sglebius errno = ENOMSG; 799290000Sglebius goto woops; 800290000Sglebius } 801290000Sglebius if (al->useCt > 1) 802290000Sglebius sort_list(al); 803181834Sroberto } 804181834Sroberto 805290000Sglebius return res_val; 806290000Sglebius 807181834Sroberto woops: 808290000Sglebius AGFREE(res_val->v.nestVal); 809290000Sglebius AGFREE(res_val); 810181834Sroberto return NULL; 811181834Sroberto} 812181834Sroberto 813181834Sroberto/*=export_func optionNestedVal 814181834Sroberto * private: 815181834Sroberto * 816181834Sroberto * what: parse a hierarchical option argument 817290000Sglebius * arg: + tOptions * + opts + program options descriptor + 818290000Sglebius * arg: + tOptDesc * + od + the descriptor for this arg + 819181834Sroberto * 820181834Sroberto * doc: 821181834Sroberto * Nested value was found on the command line 822181834Sroberto=*/ 823181834Srobertovoid 824290000SglebiusoptionNestedVal(tOptions * opts, tOptDesc * od) 825181834Sroberto{ 826290000Sglebius if (opts < OPTPROC_EMIT_LIMIT) 827290000Sglebius return; 828181834Sroberto 829290000Sglebius if (od->fOptState & OPTST_RESET) { 830290000Sglebius tArgList * arg_list = od->optCookie; 831290000Sglebius int ct; 832290000Sglebius char const ** av; 833290000Sglebius 834290000Sglebius if (arg_list == NULL) 835290000Sglebius return; 836290000Sglebius ct = arg_list->useCt; 837290000Sglebius av = arg_list->apzArgs; 838290000Sglebius 839290000Sglebius while (--ct >= 0) { 840290000Sglebius void * p = VOIDP(*(av++)); 841290000Sglebius optionUnloadNested((tOptionValue const *)p); 842290000Sglebius } 843290000Sglebius 844290000Sglebius AGFREE(od->optCookie); 845290000Sglebius 846290000Sglebius } else { 847290000Sglebius tOptionValue * opt_val = optionLoadNested( 848290000Sglebius od->optArg.argString, od->pz_Name, strlen(od->pz_Name)); 849290000Sglebius 850290000Sglebius if (opt_val != NULL) 851290000Sglebius addArgListEntry(&(od->optCookie), VOIDP(opt_val)); 852290000Sglebius } 853181834Sroberto} 854290000Sglebius 855290000Sglebius/** 856290000Sglebius * get_special_char 857290000Sglebius */ 858290000SglebiusLOCAL int 859290000Sglebiusget_special_char(char const ** ppz, int * ct) 860290000Sglebius{ 861290000Sglebius char const * pz = *ppz; 862294904Sdelphij char * rz; 863290000Sglebius 864290000Sglebius if (*ct < 3) 865290000Sglebius return '&'; 866290000Sglebius 867290000Sglebius if (*pz == '#') { 868290000Sglebius int base = 10; 869290000Sglebius int retch; 870290000Sglebius 871290000Sglebius pz++; 872290000Sglebius if (*pz == 'x') { 873290000Sglebius base = 16; 874290000Sglebius pz++; 875290000Sglebius } 876294904Sdelphij retch = (int)strtoul(pz, &rz, base); 877294904Sdelphij pz = rz; 878290000Sglebius if (*pz != ';') 879290000Sglebius return '&'; 880290000Sglebius base = (int)(++pz - *ppz); 881290000Sglebius if (base > *ct) 882290000Sglebius return '&'; 883290000Sglebius 884290000Sglebius *ct -= base; 885290000Sglebius *ppz = pz; 886290000Sglebius return retch; 887290000Sglebius } 888290000Sglebius 889290000Sglebius { 890290000Sglebius int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 891290000Sglebius xml_xlate_t const * xlatp = xml_xlate; 892290000Sglebius 893290000Sglebius for (;;) { 894290000Sglebius if ( (*ct >= xlatp->xml_len) 895290000Sglebius && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) { 896290000Sglebius *ppz += xlatp->xml_len; 897290000Sglebius *ct -= xlatp->xml_len; 898290000Sglebius return xlatp->xml_ch; 899290000Sglebius } 900290000Sglebius 901290000Sglebius if (--ctr <= 0) 902290000Sglebius break; 903290000Sglebius xlatp++; 904290000Sglebius } 905290000Sglebius } 906290000Sglebius return '&'; 907290000Sglebius} 908290000Sglebius 909290000Sglebius/** 910290000Sglebius * emit_special_char 911290000Sglebius */ 912290000SglebiusLOCAL void 913290000Sglebiusemit_special_char(FILE * fp, int ch) 914290000Sglebius{ 915290000Sglebius int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 916290000Sglebius xml_xlate_t const * xlatp = xml_xlate; 917290000Sglebius 918290000Sglebius putc('&', fp); 919290000Sglebius for (;;) { 920290000Sglebius if (ch == xlatp->xml_ch) { 921290000Sglebius fputs(xlatp->xml_txt, fp); 922290000Sglebius return; 923290000Sglebius } 924290000Sglebius if (--ctr <= 0) 925290000Sglebius break; 926290000Sglebius xlatp++; 927290000Sglebius } 928290000Sglebius fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF)); 929290000Sglebius} 930290000Sglebius 931290000Sglebius/** @} 932290000Sglebius * 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