1181834Sroberto
2285612Sdelphij/**
3285612Sdelphij * \file nested.c
4181834Sroberto *
5285612Sdelphij *  Handle options with arguments that contain nested values.
6285612Sdelphij *
7285612Sdelphij * @addtogroup autoopts
8285612Sdelphij * @{
9181834Sroberto */
10181834Sroberto/*
11285612Sdelphij *   Automated Options Nested Values module.
12181834Sroberto *
13285612Sdelphij *  This file is part of AutoOpts, a companion to AutoGen.
14285612Sdelphij *  AutoOpts is free software.
15285612Sdelphij *  AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
16181834Sroberto *
17285612Sdelphij *  AutoOpts is available under any one of two licenses.  The license
18285612Sdelphij *  in use must be one of these two and the choice is under the control
19285612Sdelphij *  of the user of the license.
20181834Sroberto *
21285612Sdelphij *   The GNU Lesser General Public License, version 3 or later
22285612Sdelphij *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
23181834Sroberto *
24285612Sdelphij *   The Modified Berkeley Software Distribution License
25285612Sdelphij *      See the file "COPYING.mbsd"
26181834Sroberto *
27285612Sdelphij *  These files have the following sha256 sums:
28181834Sroberto *
29285612Sdelphij *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
30285612Sdelphij *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
31285612Sdelphij *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
32181834Sroberto */
33285612Sdelphij
34285612Sdelphijtypedef struct {
35285612Sdelphij    int     xml_ch;
36285612Sdelphij    int     xml_len;
37285612Sdelphij    char    xml_txt[8];
38285612Sdelphij} xml_xlate_t;
39285612Sdelphij
40285612Sdelphijstatic xml_xlate_t const xml_xlate[] = {
41285612Sdelphij    { '&', 4, "amp;"  },
42285612Sdelphij    { '<', 3, "lt;"   },
43285612Sdelphij    { '>', 3, "gt;"   },
44285612Sdelphij    { '"', 5, "quot;" },
45285612Sdelphij    { '\'',5, "apos;" }
46285612Sdelphij};
47285612Sdelphij
48285612Sdelphij#ifndef ENOMSG
49285612Sdelphij#define ENOMSG ENOENT
50285612Sdelphij#endif
51285612Sdelphij
52181834Sroberto/* = = = START-STATIC-FORWARD = = = */
53181834Srobertostatic void
54285612Sdelphijremove_continuation(char * src);
55181834Sroberto
56285612Sdelphijstatic char const *
57285612Sdelphijscan_q_str(char const * pzTxt);
58181834Sroberto
59285612Sdelphijstatic tOptionValue *
60285612Sdelphijadd_string(void ** pp, char const * name, size_t nm_len,
61285612Sdelphij           char const * val, size_t d_len);
62181834Sroberto
63285612Sdelphijstatic tOptionValue *
64285612Sdelphijadd_bool(void ** pp, char const * name, size_t nm_len,
65285612Sdelphij         char const * val, size_t d_len);
66181834Sroberto
67285612Sdelphijstatic tOptionValue *
68285612Sdelphijadd_number(void ** pp, char const * name, size_t nm_len,
69285612Sdelphij           char const * val, size_t d_len);
70181834Sroberto
71285612Sdelphijstatic tOptionValue *
72285612Sdelphijadd_nested(void ** pp, char const * name, size_t nm_len,
73285612Sdelphij           char * val, size_t d_len);
74181834Sroberto
75285612Sdelphijstatic char const *
76285612Sdelphijscan_name(char const * name, tOptionValue * res);
77181834Sroberto
78285612Sdelphijstatic char const *
79285612Sdelphijunnamed_xml(char const * txt);
80181834Sroberto
81285612Sdelphijstatic char const *
82285612Sdelphijscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val);
83181834Sroberto
84285612Sdelphijstatic char const *
85285612Sdelphijfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len);
86285612Sdelphij
87285612Sdelphijstatic char const *
88285612Sdelphijscan_xml(char const * xml_name, tOptionValue * res_val);
89285612Sdelphij
90181834Srobertostatic void
91285612Sdelphijsort_list(tArgList * arg_list);
92181834Sroberto/* = = = END-STATIC-FORWARD = = = */
93181834Sroberto
94285612Sdelphij/**
95285612Sdelphij *  Backslashes are used for line continuations.  We keep the newline
96285612Sdelphij *  characters, but trim out the backslash:
97181834Sroberto */
98181834Srobertostatic void
99285612Sdelphijremove_continuation(char * src)
100181834Sroberto{
101285612Sdelphij    char * pzD;
102181834Sroberto
103285612Sdelphij    do  {
104285612Sdelphij        while (*src == NL)  src++;
105285612Sdelphij        pzD = strchr(src, NL);
106285612Sdelphij        if (pzD == NULL)
107285612Sdelphij            return;
108181834Sroberto
109285612Sdelphij        /*
110285612Sdelphij         *  pzD has skipped at least one non-newline character and now
111285612Sdelphij         *  points to a newline character.  It now becomes the source and
112285612Sdelphij         *  pzD goes to the previous character.
113285612Sdelphij         */
114285612Sdelphij        src = pzD--;
115285612Sdelphij        if (*pzD != '\\')
116285612Sdelphij            pzD++;
117285612Sdelphij    } while (pzD == src);
118285612Sdelphij
119285612Sdelphij    /*
120285612Sdelphij     *  Start shifting text.
121285612Sdelphij     */
122181834Sroberto    for (;;) {
123285612Sdelphij        char ch = ((*pzD++) = *(src++));
124181834Sroberto        switch (ch) {
125181834Sroberto        case NUL:  return;
126285612Sdelphij        case '\\':
127285612Sdelphij            if (*src == NL)
128285612Sdelphij                --pzD; /* rewrite on next iteration */
129181834Sroberto        }
130181834Sroberto    }
131181834Sroberto}
132181834Sroberto
133285612Sdelphij/**
134181834Sroberto *  Find the end of a quoted string, skipping escaped quote characters.
135181834Sroberto */
136285612Sdelphijstatic char const *
137285612Sdelphijscan_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
170285612Sdelphij/**
171285612Sdelphij *  Associate a name with either a string or no value.
172181834Sroberto *
173285612Sdelphij * @param[in,out] pp        argument list to add to
174285612Sdelphij * @param[in]     name      the name of the "suboption"
175285612Sdelphij * @param[in]     nm_len    the length of the name
176285612Sdelphij * @param[in]     val       the string value for the suboption
177285612Sdelphij * @param[in]     d_len     the length of the value
178285612Sdelphij *
179285612Sdelphij * @returns the new value structure
180181834Sroberto */
181285612Sdelphijstatic tOptionValue *
182285612Sdelphijadd_string(void ** pp, char const * name, size_t nm_len,
183285612Sdelphij           char const * val, size_t d_len)
184181834Sroberto{
185285612Sdelphij    tOptionValue * pNV;
186285612Sdelphij    size_t sz = nm_len + d_len + sizeof(*pNV);
187181834Sroberto
188285612Sdelphij    pNV = AGALOC(sz, "option name/str value pair");
189181834Sroberto
190285612Sdelphij    if (val == NULL) {
191181834Sroberto        pNV->valType = OPARG_TYPE_NONE;
192181834Sroberto        pNV->pzName = pNV->v.strVal;
193181834Sroberto
194181834Sroberto    } else {
195181834Sroberto        pNV->valType = OPARG_TYPE_STRING;
196285612Sdelphij        if (d_len > 0) {
197285612Sdelphij            char const * src = val;
198285612Sdelphij            char * pzDst = pNV->v.strVal;
199285612Sdelphij            int    ct    = (int)d_len;
200285612Sdelphij            do  {
201285612Sdelphij                int ch = *(src++) & 0xFF;
202285612Sdelphij                if (ch == NUL) goto data_copy_done;
203285612Sdelphij                if (ch == '&')
204285612Sdelphij                    ch = get_special_char(&src, &ct);
205285612Sdelphij                *(pzDst++) = (char)ch;
206285612Sdelphij            } while (--ct > 0);
207285612Sdelphij        data_copy_done:
208285612Sdelphij            *pzDst = NUL;
209285612Sdelphij
210285612Sdelphij        } else {
211285612Sdelphij            pNV->v.strVal[0] = NUL;
212285612Sdelphij        }
213285612Sdelphij
214285612Sdelphij        pNV->pzName = pNV->v.strVal + d_len + 1;
215181834Sroberto    }
216181834Sroberto
217285612Sdelphij    memcpy(pNV->pzName, name, nm_len);
218285612Sdelphij    pNV->pzName[ nm_len ] = NUL;
219285612Sdelphij    addArgListEntry(pp, pNV);
220181834Sroberto    return pNV;
221181834Sroberto}
222181834Sroberto
223285612Sdelphij/**
224285612Sdelphij *  Associate a name with a boolean value
225181834Sroberto *
226285612Sdelphij * @param[in,out] pp        argument list to add to
227285612Sdelphij * @param[in]     name      the name of the "suboption"
228285612Sdelphij * @param[in]     nm_len    the length of the name
229285612Sdelphij * @param[in]     val       the boolean value for the suboption
230285612Sdelphij * @param[in]     d_len     the length of the value
231285612Sdelphij *
232285612Sdelphij * @returns the new value structure
233181834Sroberto */
234285612Sdelphijstatic tOptionValue *
235285612Sdelphijadd_bool(void ** pp, char const * name, size_t nm_len,
236285612Sdelphij         char const * val, size_t d_len)
237181834Sroberto{
238285612Sdelphij    size_t sz = nm_len + sizeof(tOptionValue) + 1;
239285612Sdelphij    tOptionValue * new_val = AGALOC(sz, "bool val");
240181834Sroberto
241285612Sdelphij    /*
242285612Sdelphij     * Scan over whitespace is constrained by "d_len"
243285612Sdelphij     */
244285612Sdelphij    while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
245285612Sdelphij        d_len--; val++;
246181834Sroberto    }
247181834Sroberto
248285612Sdelphij    if (d_len == 0)
249285612Sdelphij        new_val->v.boolVal = 0;
250285612Sdelphij
251285612Sdelphij    else if (IS_DEC_DIGIT_CHAR(*val))
252285612Sdelphij        new_val->v.boolVal = (unsigned)atoi(val);
253285612Sdelphij
254285612Sdelphij    else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val);
255285612Sdelphij
256285612Sdelphij    new_val->valType = OPARG_TYPE_BOOLEAN;
257285612Sdelphij    new_val->pzName = (char *)(new_val + 1);
258285612Sdelphij    memcpy(new_val->pzName, name, nm_len);
259285612Sdelphij    new_val->pzName[ nm_len ] = NUL;
260285612Sdelphij    addArgListEntry(pp, new_val);
261285612Sdelphij    return new_val;
262181834Sroberto}
263181834Sroberto
264285612Sdelphij/**
265285612Sdelphij *  Associate a name with strtol() value, defaulting to zero.
266181834Sroberto *
267285612Sdelphij * @param[in,out] pp        argument list to add to
268285612Sdelphij * @param[in]     name      the name of the "suboption"
269285612Sdelphij * @param[in]     nm_len    the length of the name
270285612Sdelphij * @param[in]     val       the numeric value for the suboption
271285612Sdelphij * @param[in]     d_len     the length of the value
272285612Sdelphij *
273285612Sdelphij * @returns the new value structure
274181834Sroberto */
275285612Sdelphijstatic tOptionValue *
276285612Sdelphijadd_number(void ** pp, char const * name, size_t nm_len,
277285612Sdelphij           char const * val, size_t d_len)
278181834Sroberto{
279285612Sdelphij    size_t sz = nm_len + sizeof(tOptionValue) + 1;
280285612Sdelphij    tOptionValue * new_val = AGALOC(sz, "int val");
281181834Sroberto
282285612Sdelphij    /*
283285612Sdelphij     * Scan over whitespace is constrained by "d_len"
284285612Sdelphij     */
285285612Sdelphij    while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
286285612Sdelphij        d_len--; val++;
287181834Sroberto    }
288285612Sdelphij    if (d_len == 0)
289285612Sdelphij        new_val->v.longVal = 0;
290181834Sroberto    else
291285612Sdelphij        new_val->v.longVal = strtol(val, 0, 0);
292181834Sroberto
293285612Sdelphij    new_val->valType = OPARG_TYPE_NUMERIC;
294285612Sdelphij    new_val->pzName  = (char *)(new_val + 1);
295285612Sdelphij    memcpy(new_val->pzName, name, nm_len);
296285612Sdelphij    new_val->pzName[ nm_len ] = NUL;
297285612Sdelphij    addArgListEntry(pp, new_val);
298285612Sdelphij    return new_val;
299181834Sroberto}
300181834Sroberto
301285612Sdelphij/**
302285612Sdelphij *  Associate a name with a nested/hierarchical value.
303181834Sroberto *
304285612Sdelphij * @param[in,out] pp        argument list to add to
305285612Sdelphij * @param[in]     name      the name of the "suboption"
306285612Sdelphij * @param[in]     nm_len    the length of the name
307285612Sdelphij * @param[in]     val       the nested values for the suboption
308285612Sdelphij * @param[in]     d_len     the length of the value
309285612Sdelphij *
310285612Sdelphij * @returns the new value structure
311181834Sroberto */
312285612Sdelphijstatic tOptionValue *
313285612Sdelphijadd_nested(void ** pp, char const * name, size_t nm_len,
314285612Sdelphij           char * val, size_t d_len)
315181834Sroberto{
316285612Sdelphij    tOptionValue * new_val;
317181834Sroberto
318285612Sdelphij    if (d_len == 0) {
319285612Sdelphij        size_t sz = nm_len + sizeof(*new_val) + 1;
320285612Sdelphij        new_val = AGALOC(sz, "empty nest");
321285612Sdelphij        new_val->v.nestVal = NULL;
322285612Sdelphij        new_val->valType = OPARG_TYPE_HIERARCHY;
323285612Sdelphij        new_val->pzName = (char *)(new_val + 1);
324285612Sdelphij        memcpy(new_val->pzName, name, nm_len);
325285612Sdelphij        new_val->pzName[ nm_len ] = NUL;
326181834Sroberto
327181834Sroberto    } else {
328285612Sdelphij        new_val = optionLoadNested(val, name, nm_len);
329181834Sroberto    }
330181834Sroberto
331285612Sdelphij    if (new_val != NULL)
332285612Sdelphij        addArgListEntry(pp, new_val);
333181834Sroberto
334285612Sdelphij    return new_val;
335181834Sroberto}
336181834Sroberto
337285612Sdelphij/**
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 */
341285612Sdelphijstatic char const *
342285612Sdelphijscan_name(char const * name, tOptionValue * res)
343181834Sroberto{
344285612Sdelphij    tOptionValue * new_val;
345285612Sdelphij    char const *   pzScan = name+1; /* we know first char is a name char */
346285612Sdelphij    char const *   pzVal;
347285612Sdelphij    size_t         nm_len = 1;
348285612Sdelphij    size_t         d_len = 0;
349181834Sroberto
350285612Sdelphij    /*
351285612Sdelphij     *  Scan over characters that name a value.  These names may not end
352285612Sdelphij     *  with a colon, but they may contain colons.
353285612Sdelphij     */
354285612Sdelphij    pzScan = SPN_VALUE_NAME_CHARS(name + 1);
355285612Sdelphij    if (pzScan[-1] == ':')
356285612Sdelphij        pzScan--;
357285612Sdelphij    nm_len = (size_t)(pzScan - name);
358181834Sroberto
359285612Sdelphij    pzScan = SPN_HORIZ_WHITE_CHARS(pzScan);
360181834Sroberto
361285612Sdelphij re_switch:
362285612Sdelphij
363181834Sroberto    switch (*pzScan) {
364181834Sroberto    case '=':
365181834Sroberto    case ':':
366285612Sdelphij        pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1);
367285612Sdelphij        if ((*pzScan == '=') || (*pzScan == ':'))
368285612Sdelphij            goto default_char;
369285612Sdelphij        goto re_switch;
370181834Sroberto
371285612Sdelphij    case NL:
372181834Sroberto    case ',':
373181834Sroberto        pzScan++;
374181834Sroberto        /* FALLTHROUGH */
375181834Sroberto
376181834Sroberto    case NUL:
377285612Sdelphij        add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0);
378181834Sroberto        break;
379181834Sroberto
380181834Sroberto    case '"':
381181834Sroberto    case '\'':
382181834Sroberto        pzVal = pzScan;
383285612Sdelphij        pzScan = scan_q_str(pzScan);
384285612Sdelphij        d_len = (size_t)(pzScan - pzVal);
385285612Sdelphij        new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal,
386285612Sdelphij                         d_len);
387285612Sdelphij        if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
388285612Sdelphij            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--;
403285612Sdelphij                d_len = (size_t)(pzScan - pzVal);
404181834Sroberto                goto string_done;
405181834Sroberto                /* FALLTHROUGH */
406181834Sroberto
407285612Sdelphij            case NL:
408181834Sroberto                if (   (pzScan > pzVal + 2)
409181834Sroberto                    && (pzScan[-2] == '\\')
410181834Sroberto                    && (pzScan[ 0] != NUL))
411181834Sroberto                    continue;
412181834Sroberto                /* FALLTHROUGH */
413181834Sroberto
414181834Sroberto            case ',':
415285612Sdelphij                d_len = (size_t)(pzScan - pzVal) - 1;
416181834Sroberto            string_done:
417285612Sdelphij                new_val = add_string(&(res->v.nestVal), name, nm_len,
418285612Sdelphij                                     pzVal, d_len);
419285612Sdelphij                if (new_val != NULL)
420285612Sdelphij                    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
430285612Sdelphij/**
431285612Sdelphij * Some xml element that does not start with a name.
432285612Sdelphij * The next character must be either '!' (introducing a comment),
433285612Sdelphij * or '?' (introducing an XML meta-marker of some sort).
434285612Sdelphij * We ignore these and indicate an error (NULL result) otherwise.
435181834Sroberto *
436285612Sdelphij * @param[in] txt  the text within an xml bracket
437285612Sdelphij * @returns the address of the character after the closing marker, or NULL.
438181834Sroberto */
439285612Sdelphijstatic char const *
440285612Sdelphijunnamed_xml(char const * txt)
441181834Sroberto{
442285612Sdelphij    switch (*txt) {
443285612Sdelphij    default:
444285612Sdelphij        txt = NULL;
445285612Sdelphij        break;
446181834Sroberto
447285612Sdelphij    case '!':
448285612Sdelphij        txt = strstr(txt, "-->");
449285612Sdelphij        if (txt != NULL)
450285612Sdelphij            txt += 3;
451285612Sdelphij        break;
452181834Sroberto
453285612Sdelphij    case '?':
454285612Sdelphij        txt = strchr(txt, '>');
455285612Sdelphij        if (txt != NULL)
456285612Sdelphij            txt++;
457285612Sdelphij        break;
458181834Sroberto    }
459285612Sdelphij    return txt;
460285612Sdelphij}
461181834Sroberto
462285612Sdelphij/**
463285612Sdelphij *  Scan off the xml element name, and the rest of the header, too.
464285612Sdelphij *  Set the value type to NONE if it ends with "/>".
465285612Sdelphij *
466285612Sdelphij * @param[in]  name    the first name character (alphabetic)
467285612Sdelphij * @param[out] nm_len  the length of the name
468285612Sdelphij * @param[out] val     set valType field to STRING or NONE.
469285612Sdelphij *
470285612Sdelphij * @returns the scan resumption point, or NULL on error
471285612Sdelphij */
472285612Sdelphijstatic char const *
473285612Sdelphijscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val)
474285612Sdelphij{
475285612Sdelphij    char const * scan = SPN_VALUE_NAME_CHARS(name + 1);
476285612Sdelphij    *nm_len = (size_t)(scan - name);
477285612Sdelphij    if (*nm_len > 64)
478181834Sroberto        return NULL;
479285612Sdelphij    val->valType = OPARG_TYPE_STRING;
480181834Sroberto
481285612Sdelphij    if (IS_WHITESPACE_CHAR(*scan)) {
482285612Sdelphij        /*
483285612Sdelphij         * There are attributes following the name.  Parse 'em.
484285612Sdelphij         */
485285612Sdelphij        scan = SPN_WHITESPACE_CHARS(scan);
486285612Sdelphij        scan = parse_attrs(NULL, scan, &option_load_mode, val);
487285612Sdelphij        if (scan == NULL)
488285612Sdelphij            return NULL; /* oops */
489285612Sdelphij    }
490181834Sroberto
491285612Sdelphij    if (! IS_END_XML_TOKEN_CHAR(*scan))
492285612Sdelphij        return NULL; /* oops */
493181834Sroberto
494285612Sdelphij    if (*scan == '/') {
495285612Sdelphij        /*
496285612Sdelphij         * Single element XML entries get inserted as an empty string.
497285612Sdelphij         */
498285612Sdelphij        if (*++scan != '>')
499181834Sroberto            return NULL;
500285612Sdelphij        val->valType = OPARG_TYPE_NONE;
501285612Sdelphij    }
502285612Sdelphij    return scan+1;
503285612Sdelphij}
504181834Sroberto
505285612Sdelphij/**
506285612Sdelphij * We've found a closing '>' without a preceding '/', thus we must search
507285612Sdelphij * the text for '<name/>' where "name" is the name of the XML element.
508285612Sdelphij *
509285612Sdelphij * @param[in]  name     the start of the name in the element header
510285612Sdelphij * @param[in]  nm_len   the length of that name
511285612Sdelphij * @param[out] len      the length of the value (string between header and
512285612Sdelphij *                      the trailer/tail.
513285612Sdelphij * @returns the character after the trailer, or NULL if not found.
514285612Sdelphij */
515285612Sdelphijstatic char const *
516285612Sdelphijfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len)
517285612Sdelphij{
518285612Sdelphij    char z[72] = "</";
519285612Sdelphij    char * dst = z + 2;
520181834Sroberto
521285612Sdelphij    do  {
522285612Sdelphij        *(dst++) = *(src++);
523285612Sdelphij    } while (--nm_len > 0); /* nm_len is known to be 64 or less */
524285612Sdelphij    *(dst++) = '>';
525285612Sdelphij    *dst = NUL;
526285612Sdelphij
527285612Sdelphij    {
528285612Sdelphij        char const * res = strstr(val, z);
529285612Sdelphij
530285612Sdelphij        if (res != NULL) {
531285612Sdelphij            char const * end = (option_load_mode != OPTION_LOAD_KEEP)
532285612Sdelphij                ? SPN_WHITESPACE_BACK(val, res)
533285612Sdelphij                : res;
534285612Sdelphij            *len = (size_t)(end - val); /* includes trailing white space */
535285612Sdelphij            res =  SPN_WHITESPACE_CHARS(res + (dst - z));
536285612Sdelphij        }
537285612Sdelphij        return res;
538181834Sroberto    }
539285612Sdelphij}
540181834Sroberto
541285612Sdelphij/**
542285612Sdelphij *  We've found a '<' character.  We ignore this if it is a comment or a
543285612Sdelphij *  directive.  If it is something else, then whatever it is we are looking
544285612Sdelphij *  at is bogus.  Returning NULL stops processing.
545285612Sdelphij *
546285612Sdelphij * @param[in]     xml_name  the name of an xml bracket (usually)
547285612Sdelphij * @param[in,out] res_val   the option data derived from the XML element
548285612Sdelphij *
549285612Sdelphij * @returns the place to resume scanning input
550285612Sdelphij */
551285612Sdelphijstatic char const *
552285612Sdelphijscan_xml(char const * xml_name, tOptionValue * res_val)
553285612Sdelphij{
554285612Sdelphij    size_t          nm_len, v_len;
555285612Sdelphij    char const *    scan;
556285612Sdelphij    char const *    val_str;
557285612Sdelphij    tOptionValue    valu;
558285612Sdelphij    tOptionLoadMode save_mode = option_load_mode;
559181834Sroberto
560285612Sdelphij    if (! IS_VAR_FIRST_CHAR(*++xml_name))
561285612Sdelphij        return unnamed_xml(xml_name);
562181834Sroberto
563285612Sdelphij    /*
564285612Sdelphij     * "scan_xml_name()" may change "option_load_mode".
565285612Sdelphij     */
566285612Sdelphij    val_str = scan_xml_name(xml_name, &nm_len, &valu);
567285612Sdelphij    if (val_str == NULL)
568285612Sdelphij        goto bail_scan_xml;
569181834Sroberto
570285612Sdelphij    if (valu.valType == OPARG_TYPE_NONE)
571285612Sdelphij        scan = val_str;
572285612Sdelphij    else {
573285612Sdelphij        if (option_load_mode != OPTION_LOAD_KEEP)
574285612Sdelphij            val_str = SPN_WHITESPACE_CHARS(val_str);
575285612Sdelphij        scan = find_end_xml(xml_name, nm_len, val_str, &v_len);
576285612Sdelphij        if (scan == NULL)
577285612Sdelphij            goto bail_scan_xml;
578181834Sroberto    }
579181834Sroberto
580285612Sdelphij    /*
581285612Sdelphij     * "scan" now points to where the scan is to resume after returning.
582285612Sdelphij     * It either points after "/>" at the end of the XML element header,
583285612Sdelphij     * or it points after the "</name>" tail based on the name in the header.
584285612Sdelphij     */
585285612Sdelphij
586181834Sroberto    switch (valu.valType) {
587181834Sroberto    case OPARG_TYPE_NONE:
588285612Sdelphij        add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0);
589181834Sroberto        break;
590181834Sroberto
591181834Sroberto    case OPARG_TYPE_STRING:
592285612Sdelphij    {
593285612Sdelphij        tOptionValue * new_val = add_string(
594285612Sdelphij            &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
595181834Sroberto
596285612Sdelphij        if (option_load_mode != OPTION_LOAD_KEEP)
597285612Sdelphij            munge_str(new_val->v.strVal, option_load_mode);
598285612Sdelphij
599181834Sroberto        break;
600285612Sdelphij    }
601181834Sroberto
602181834Sroberto    case OPARG_TYPE_BOOLEAN:
603285612Sdelphij        add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
604181834Sroberto        break;
605181834Sroberto
606181834Sroberto    case OPARG_TYPE_NUMERIC:
607285612Sdelphij        add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
608181834Sroberto        break;
609181834Sroberto
610181834Sroberto    case OPARG_TYPE_HIERARCHY:
611181834Sroberto    {
612285612Sdelphij        char * pz = AGALOC(v_len+1, "h scan");
613285612Sdelphij        memcpy(pz, val_str, v_len);
614285612Sdelphij        pz[v_len] = NUL;
615285612Sdelphij        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;
627285612Sdelphij    return scan;
628285612Sdelphij
629285612Sdelphijbail_scan_xml:
630285612Sdelphij    option_load_mode = save_mode;
631285612Sdelphij    return NULL;
632181834Sroberto}
633181834Sroberto
634181834Sroberto
635285612Sdelphij/**
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 */
641285612SdelphijLOCAL void
642285612Sdelphijunload_arg_list(tArgList * arg_list)
643181834Sroberto{
644285612Sdelphij    int ct = arg_list->useCt;
645285612Sdelphij    char const ** pnew_val = arg_list->apzArgs;
646181834Sroberto
647181834Sroberto    while (ct-- > 0) {
648285612Sdelphij        tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++));
649285612Sdelphij        if (new_val->valType == OPARG_TYPE_HIERARCHY)
650285612Sdelphij            unload_arg_list(new_val->v.nestVal);
651285612Sdelphij        AGFREE(new_val);
652181834Sroberto    }
653181834Sroberto
654285612Sdelphij    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
668285612SdelphijoptionUnloadNested(tOptionValue const * opt_val)
669181834Sroberto{
670285612Sdelphij    if (opt_val == NULL) return;
671285612Sdelphij    if (opt_val->valType != OPARG_TYPE_HIERARCHY) {
672181834Sroberto        errno = EINVAL;
673181834Sroberto        return;
674181834Sroberto    }
675181834Sroberto
676285612Sdelphij    unload_arg_list(opt_val->v.nestVal);
677181834Sroberto
678285612Sdelphij    AGFREE(opt_val);
679181834Sroberto}
680181834Sroberto
681285612Sdelphij/**
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
687285612Sdelphijsort_list(tArgList * arg_list)
688181834Sroberto{
689181834Sroberto    int ix;
690285612Sdelphij    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;
697285612Sdelphij        tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]);
698285612Sdelphij        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         */
705285612Sdelphij        while (strcmp(old_v->pzName, new_v->pzName) > 0) {
706285612Sdelphij            arg_list->apzArgs[iy+1] = VOIDP(old_v);
707285612Sdelphij            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         */
716285612Sdelphij        arg_list->apzArgs[iy+1] = VOIDP(new_v);
717181834Sroberto    }
718181834Sroberto}
719181834Sroberto
720285612Sdelphij/*=
721181834Sroberto * private:
722181834Sroberto *
723181834Sroberto * what:  parse a hierarchical option argument
724285612Sdelphij * arg:   + char const * + pzTxt  + the text to scan      +
725285612Sdelphij * arg:   + char const * + pzName + the name for the text +
726285612Sdelphij * arg:   + size_t       + nm_len + the length of "name"  +
727181834Sroberto *
728285612Sdelphij * 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.
735285612Sdelphij *
736285612Sdelphij *  If NULL is returned, errno will be set:
737285612Sdelphij *  @itemize @bullet
738285612Sdelphij *  @item
739285612Sdelphij *  @code{EINVAL} the input text was NULL.
740285612Sdelphij *  @item
741285612Sdelphij *  @code{ENOMEM} the storage structures could not be allocated
742285612Sdelphij *  @item
743285612Sdelphij *  @code{ENOMSG} no configuration values were found
744285612Sdelphij *  @end itemize
745285612Sdelphij=*/
746285612SdelphijLOCAL tOptionValue *
747285612SdelphijoptionLoadNested(char const * text, char const * name, size_t nm_len)
748181834Sroberto{
749285612Sdelphij    tOptionValue * res_val;
750181834Sroberto
751181834Sroberto    /*
752181834Sroberto     *  Make sure we have some data and we have space to put what we find.
753181834Sroberto     */
754285612Sdelphij    if (text == NULL) {
755181834Sroberto        errno = EINVAL;
756181834Sroberto        return NULL;
757181834Sroberto    }
758285612Sdelphij    text = SPN_WHITESPACE_CHARS(text);
759285612Sdelphij    if (*text == NUL) {
760285612Sdelphij        errno = ENOMSG;
761181834Sroberto        return NULL;
762181834Sroberto    }
763285612Sdelphij    res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args");
764285612Sdelphij    res_val->valType = OPARG_TYPE_HIERARCHY;
765285612Sdelphij    res_val->pzName  = (char *)(res_val + 1);
766285612Sdelphij    memcpy(res_val->pzName, name, nm_len);
767285612Sdelphij    res_val->pzName[nm_len] = NUL;
768181834Sroberto
769285612Sdelphij    {
770285612Sdelphij        tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l");
771285612Sdelphij
772285612Sdelphij        res_val->v.nestVal = arg_list;
773285612Sdelphij        arg_list->useCt   = 0;
774285612Sdelphij        arg_list->allocCt = MIN_ARG_ALLOC_CT;
775181834Sroberto    }
776181834Sroberto
777181834Sroberto    /*
778181834Sroberto     *  Scan until we hit a NUL.
779181834Sroberto     */
780181834Sroberto    do  {
781285612Sdelphij        text = SPN_WHITESPACE_CHARS(text);
782285612Sdelphij        if (IS_VAR_FIRST_CHAR(*text))
783285612Sdelphij            text = scan_name(text, res_val);
784285612Sdelphij
785285612Sdelphij        else switch (*text) {
786181834Sroberto        case NUL: goto scan_done;
787285612Sdelphij        case '<': text = scan_xml(text, res_val);
788285612Sdelphij                  if (text == NULL) goto woops;
789285612Sdelphij                  if (*text == ',') text++; break;
790285612Sdelphij        case '#': text = strchr(text, NL);  break;
791181834Sroberto        default:  goto woops;
792181834Sroberto        }
793285612Sdelphij    } while (text != NULL); scan_done:;
794181834Sroberto
795285612Sdelphij    {
796285612Sdelphij        tArgList * al = res_val->v.nestVal;
797285612Sdelphij        if (al->useCt == 0) {
798285612Sdelphij            errno = ENOMSG;
799285612Sdelphij            goto woops;
800285612Sdelphij        }
801285612Sdelphij        if (al->useCt > 1)
802285612Sdelphij            sort_list(al);
803181834Sroberto    }
804181834Sroberto
805285612Sdelphij    return res_val;
806285612Sdelphij
807181834Sroberto woops:
808285612Sdelphij    AGFREE(res_val->v.nestVal);
809285612Sdelphij    AGFREE(res_val);
810181834Sroberto    return NULL;
811181834Sroberto}
812181834Sroberto
813181834Sroberto/*=export_func  optionNestedVal
814181834Sroberto * private:
815181834Sroberto *
816181834Sroberto * what:  parse a hierarchical option argument
817285612Sdelphij * arg:   + tOptions * + opts + program options descriptor +
818285612Sdelphij * arg:   + tOptDesc * + od   + the descriptor for this arg +
819181834Sroberto *
820181834Sroberto * doc:
821181834Sroberto *  Nested value was found on the command line
822181834Sroberto=*/
823181834Srobertovoid
824285612SdelphijoptionNestedVal(tOptions * opts, tOptDesc * od)
825181834Sroberto{
826285612Sdelphij    if (opts < OPTPROC_EMIT_LIMIT)
827285612Sdelphij        return;
828181834Sroberto
829285612Sdelphij    if (od->fOptState & OPTST_RESET) {
830285612Sdelphij        tArgList *    arg_list = od->optCookie;
831285612Sdelphij        int           ct;
832285612Sdelphij        char const ** av;
833285612Sdelphij
834285612Sdelphij        if (arg_list == NULL)
835285612Sdelphij            return;
836285612Sdelphij        ct = arg_list->useCt;
837285612Sdelphij        av = arg_list->apzArgs;
838285612Sdelphij
839285612Sdelphij        while (--ct >= 0) {
840285612Sdelphij            void * p = VOIDP(*(av++));
841285612Sdelphij            optionUnloadNested((tOptionValue const *)p);
842285612Sdelphij        }
843285612Sdelphij
844285612Sdelphij        AGFREE(od->optCookie);
845285612Sdelphij
846285612Sdelphij    } else {
847285612Sdelphij        tOptionValue * opt_val = optionLoadNested(
848285612Sdelphij            od->optArg.argString, od->pz_Name, strlen(od->pz_Name));
849285612Sdelphij
850285612Sdelphij        if (opt_val != NULL)
851285612Sdelphij            addArgListEntry(&(od->optCookie), VOIDP(opt_val));
852285612Sdelphij    }
853181834Sroberto}
854285612Sdelphij
855285612Sdelphij/**
856285612Sdelphij * get_special_char
857285612Sdelphij */
858285612SdelphijLOCAL int
859285612Sdelphijget_special_char(char const ** ppz, int * ct)
860285612Sdelphij{
861285612Sdelphij    char const * pz = *ppz;
862294569Sdelphij    char *       rz;
863285612Sdelphij
864285612Sdelphij    if (*ct < 3)
865285612Sdelphij        return '&';
866285612Sdelphij
867285612Sdelphij    if (*pz == '#') {
868285612Sdelphij        int base = 10;
869285612Sdelphij        int retch;
870285612Sdelphij
871285612Sdelphij        pz++;
872285612Sdelphij        if (*pz == 'x') {
873285612Sdelphij            base = 16;
874285612Sdelphij            pz++;
875285612Sdelphij        }
876294569Sdelphij        retch = (int)strtoul(pz, &rz, base);
877294569Sdelphij        pz = rz;
878285612Sdelphij        if (*pz != ';')
879285612Sdelphij            return '&';
880285612Sdelphij        base = (int)(++pz - *ppz);
881285612Sdelphij        if (base > *ct)
882285612Sdelphij            return '&';
883285612Sdelphij
884285612Sdelphij        *ct -= base;
885285612Sdelphij        *ppz = pz;
886285612Sdelphij        return retch;
887285612Sdelphij    }
888285612Sdelphij
889285612Sdelphij    {
890285612Sdelphij        int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
891285612Sdelphij        xml_xlate_t const * xlatp = xml_xlate;
892285612Sdelphij
893285612Sdelphij        for (;;) {
894285612Sdelphij            if (  (*ct >= xlatp->xml_len)
895285612Sdelphij               && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) {
896285612Sdelphij                *ppz += xlatp->xml_len;
897285612Sdelphij                *ct  -= xlatp->xml_len;
898285612Sdelphij                return xlatp->xml_ch;
899285612Sdelphij            }
900285612Sdelphij
901285612Sdelphij            if (--ctr <= 0)
902285612Sdelphij                break;
903285612Sdelphij            xlatp++;
904285612Sdelphij        }
905285612Sdelphij    }
906285612Sdelphij    return '&';
907285612Sdelphij}
908285612Sdelphij
909285612Sdelphij/**
910285612Sdelphij * emit_special_char
911285612Sdelphij */
912285612SdelphijLOCAL void
913285612Sdelphijemit_special_char(FILE * fp, int ch)
914285612Sdelphij{
915285612Sdelphij    int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
916285612Sdelphij    xml_xlate_t const * xlatp = xml_xlate;
917285612Sdelphij
918285612Sdelphij    putc('&', fp);
919285612Sdelphij    for (;;) {
920285612Sdelphij        if (ch == xlatp->xml_ch) {
921285612Sdelphij            fputs(xlatp->xml_txt, fp);
922285612Sdelphij            return;
923285612Sdelphij        }
924285612Sdelphij        if (--ctr <= 0)
925285612Sdelphij            break;
926285612Sdelphij        xlatp++;
927285612Sdelphij    }
928285612Sdelphij    fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF));
929285612Sdelphij}
930285612Sdelphij
931285612Sdelphij/** @}
932285612Sdelphij *
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