source: branches/blt4/lang/tcl/src/RpSlice.c @ 1897

Last change on this file since 1897 was 1578, checked in by mmc, 15 years ago

Changed the new Rappture::split command to Rappture::slice. George found
problems in other Rappture:: routines that were accidentally using the
Rappture version of split, which does a slightly different thing.

File size: 8.2 KB
Line 
1/*
2 * ----------------------------------------------------------------------
3 *  Rappture::slice
4 *
5 *  This is similar to the usual Tcl "split" command, in that it
6 *  splits a string into a series of words.  The difference is that
7 *  this command understands quoting characters and will ignore the
8 *  split inside quotes, and it will also treat a bunch of split
9 *  characters as synonymous and group them together, treating any
10 *  combination of them together as a single split.  (Tcl gives you
11 *  a bunch of {} empty strings between such characters.)
12 *
13 *  EXAMPLES:
14 *    Rappture::slice -open { -close } -separators ", \t\n" $string
15 *    Rappture::slice -open \" -close \" -separators ", " $string
16 *
17 * ======================================================================
18 *  AUTHOR:  Michael McLennan, Purdue University
19 *  Copyright (c) 2004-2009  Purdue Research Foundation
20 *
21 *  See the file "license.terms" for information on usage and
22 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
23 * ======================================================================
24 */
25#include <tcl.h>
26#include <string.h>
27
28static Tcl_ObjCmdProc RpSliceCmd;
29
30/*
31 * ------------------------------------------------------------------------
32 *  RpSlice_Init()
33 *
34 *  Called in Rappture_Init() to initialize the commands defined
35 *  in this file.
36 * ------------------------------------------------------------------------
37 */
38int
39RpSlice_Init(interp)
40    Tcl_Interp *interp;  /* interpreter being initialized */
41{
42    Tcl_CreateObjCommand(interp, "::Rappture::slice", RpSliceCmd, NULL, NULL);
43    return TCL_OK;
44}
45
46/*
47 * ------------------------------------------------------------------------
48 *  RpSliceCmd()
49 *
50 *  Invoked whenever someone uses the "slice" command to slice a string
51 *  into multiple components.  Handles the following syntax:
52 *
53 *      slice ?-open <char>? ?-close <char>? ?-separators <abc>? <string>
54 *
55 *  Returns TCL_OK on success, and TCL_ERROR (along with an error
56 *  message in the interpreter) if anything goes wrong.
57 * ------------------------------------------------------------------------
58 */
59static int
60RpSliceCmd(cdata, interp, objc, objv)
61    ClientData cdata;         /* not used */
62    Tcl_Interp *interp;       /* interpreter handling this request */
63    int objc;                 /* number of command line args */
64    Tcl_Obj *const *objv;     /* strings for command line args */
65{
66    char *openq = ""; int openqLen = 0;
67    char *closeq = ""; int closeqLen = 0;
68    char *sep = " \t\n"; int sepLen = 3;
69
70    Tcl_Obj *resultPtr, *objPtr;
71    char *arg, *str, *token;
72    int len, tlen, pos, quotec;
73
74    /*
75     * Handle any flags on the command line.
76     */
77    pos = 1;
78    while (pos < objc-1) {
79        arg = Tcl_GetStringFromObj(objv[pos], &len);
80        if (*arg == '-') {
81            if (strcmp(arg,"-open") == 0) {
82                if (pos+1 < objc) {
83                    openq = Tcl_GetStringFromObj(objv[pos+1], &openqLen);
84                    pos += 2;
85                } else {
86                    Tcl_AppendResult(interp, "missing value for -open",
87                        (char*)NULL);
88                    return TCL_ERROR;
89                }
90            }
91            else if (strcmp(arg,"-close") == 0) {
92                if (pos+1 < objc) {
93                    closeq = Tcl_GetStringFromObj(objv[pos+1], &closeqLen);
94                    pos += 2;
95                } else {
96                    Tcl_AppendResult(interp, "missing value for -close",
97                        (char*)NULL);
98                    return TCL_ERROR;
99                }
100            }
101            else if (strcmp(arg,"-separators") == 0) {
102                if (pos+1 < objc) {
103                    sep = Tcl_GetStringFromObj(objv[pos+1], &sepLen);
104                    pos += 2;
105                } else {
106                    Tcl_AppendResult(interp, "missing value for -separators",
107                        (char*)NULL);
108                    return TCL_ERROR;
109                }
110            }
111            else if (strcmp(arg,"--") == 0) {
112                pos++;
113                break;
114            }
115            else {
116                Tcl_AppendResult(interp, "bad option \"", arg, "\":",
117                    " should be -open, -close, -separators, --", (char*)NULL);
118                return TCL_ERROR;
119            }
120        } else {
121            break;
122        }
123    }
124
125    /*
126     * Open/close quote strings must match in length.  Each char in openq
127     * corresponds to a close quote in closeq.
128     */
129    if (openqLen != closeqLen) {
130        Tcl_AppendResult(interp, "must have same number of quote characters"
131            " for -open and -close", (char*)NULL);
132        return TCL_ERROR;
133    }
134
135    if (pos != objc-1) {
136        Tcl_AppendResult(interp, "wrong # args: should be \"",
137            Tcl_GetString(objv[0]), " ?-open chars? ?-close chars?",
138            " ?-separators chars? ?--? string", (char*)NULL);
139        return TCL_ERROR;
140    }
141    str = Tcl_GetStringFromObj(objv[pos], &len);
142
143    /*
144     * Scan through all characters.  If we find a match with an open quote,
145     * then go into "quotes" mode; keep scanning until we find a matching
146     * close quote.  If we find the start of a token, then mark it and keep
147     * searching.  If we find a separator, then add the token to the list
148     * and skip over remaining separators.
149     */
150    resultPtr = Tcl_NewListObj(0, NULL);
151    token = NULL;
152    quotec = -1;
153
154    while (len > 0) {
155        /*
156         * If we're in a quoted part, then look for a closing quote.
157         */
158        if (quotec >= 0) {
159            if (*str == '\\') {
160                /* ignore the next character no matter what it is */
161                str++; len--;
162                if (len > 0) { str++; len--; }
163                continue;
164            }
165
166            if (*str == closeq[quotec]) {
167                /* found a close quote -- out of quote mode and move on */
168                tlen = str-token;
169                objPtr = Tcl_NewStringObj(token,tlen);
170                Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
171                token = NULL;
172                quotec = -1;
173            }
174        }
175
176        /*
177         * If we're in the middle of a token, then look for the next
178         * separator.  When we find it, add the token to the result
179         * list.
180         */
181        else if (token) {
182            if (*str == '\\') {
183                /* ignore the next character no matter what it is */
184                str++; len--;
185                if (len > 0) { str++; len--; }
186                continue;
187            }
188            for (pos=0; pos < sepLen; pos++) {
189                if (*str == sep[pos]) {
190                    break;
191                }
192            }
193            if (pos < sepLen) {
194                /* found a separator -- add the token */
195                tlen = str-token;
196                objPtr = Tcl_NewStringObj(token,tlen);
197                Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
198                token = NULL;
199                continue;
200            }
201        }
202
203        /*
204         * If we're between tokens, then look for either a separator
205         * or an open quote.  If we don't find either, then we've found
206         * the start of a token.
207         */
208        else {
209            for (pos=0; pos < sepLen; pos++) {
210                if (*str == sep[pos]) {
211                    break;
212                }
213            }
214            if (pos >= sepLen) {
215                /* no separator -- look for an open quote */
216                for (pos=0; pos < openqLen; pos++) {
217                    if (*str == openq[pos]) {
218                        break;
219                    }
220                }
221                if (pos < openqLen) {
222                    /* found an open quote -- start quote mode */
223                    quotec = pos;
224                    token = str+1;
225                } else {
226                    /* not a sep or a quote -- start a token */
227                    token = str;
228                }
229            }
230        }
231
232        /* next character */
233        str++; len--;
234    }
235
236    /*
237     * At the end of the string.  Have a token in progress?  Then add
238     * it onto the result.
239     */
240    if (token) {
241        tlen = str-token;
242        objPtr = Tcl_NewStringObj(token, tlen);
243        Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
244    }
245
246    Tcl_SetObjResult(interp, resultPtr);
247    return TCL_OK;
248}
Note: See TracBrowser for help on using the repository browser.