2 * LuCI Template - Parser implementation
4 * Copyright (C) 2009 Jo-Philipp Wich <xm@subsignal.org>
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 #include "template_parser.h"
22 /* leading and trailing code for different types */
23 const char * gen_code[6][2] = {
24 { "write(\"", "\")" },
26 { "write(tostring(", " or \"\"))" },
27 { "include(\"", "\")" },
28 { "write(translate(\"", "\"))" },
32 /* Simple strstr() like function that takes len arguments for both haystack and needle. */
33 static char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
38 for( i = 0; i < hslen; i++ )
40 if( haystack[i] == needle[0] )
42 match = ((ndlen == 1) || ((i + ndlen) <= hslen));
44 for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ )
46 if( haystack[i+j] != needle[j] )
62 * Inspect current read buffer and find the number of "vague" characters at the end
63 * which could indicate an opening token. Returns the number of "vague" chars.
64 * The last continuous sequence of whitespace, optionally followed by a "<" is
65 * treated as "vague" because whitespace may be discarded if the upcoming opening
66 * token indicates pre-whitespace-removal ("<%-"). A single remaining "<" char
67 * can't be differentiated from an opening token ("<%"), so it's kept to be processed
70 static int stokscan(struct template_parser *data, int off, int no_whitespace)
74 int tokoff = data->bufsize - 1;
76 for( i = tokoff; i >= off; i-- )
78 if( data->buf[i] == T_TOK_START[0] )
80 skip = tokoff - i + 1;
88 for( i = tokoff; i >= off; i-- )
90 if( isspace(data->buf[i]) )
101 * Similar to stokscan() but looking for closing token indicators.
102 * Matches "-", optionally followed by a "%" char.
104 static int etokscan(struct template_parser *data)
108 if( (data->bufsize > 0) && (data->buf[data->bufsize-1] == T_TOK_END[0]) )
111 if( (data->bufsize > skip) && (data->buf[data->bufsize-skip-1] == T_TOK_SKIPWS[0]) )
118 * Generate Lua expressions from the given raw code, write it into the
119 * output buffer and set the lua_Reader specific size pointer.
120 * Takes parser-state, lua_Reader's size pointer and generator flags
121 * as parameter. The given flags indicate whether leading or trailing
122 * code should be added. Returns a pointer to the output buffer.
124 static const char * generate_expression(struct template_parser *data, size_t *sz, int what)
126 char tmp[T_OUTBUFSZ];
132 memset(tmp, 0, T_OUTBUFSZ);
134 /* Inject leading expression code (if any) */
135 if( (what & T_GEN_START) && (gen_code[data->type][0] != NULL) )
137 memcpy(tmp, gen_code[data->type][0], strlen(gen_code[data->type][0]));
138 size += strlen(gen_code[data->type][0]);
141 /* Parse source buffer */
142 for( i = 0; i < data->outsize; i++ )
144 /* Skip leading whitespace for non-raw and non-expr chunks */
145 if( !start && isspace(data->out[i]) && (data->type == T_TYPE_I18N || data->type == T_TYPE_INCLUDE) )
150 /* Found whitespace after i18n key */
151 if( (data->type == T_TYPE_I18N) && (i18n_hasdef == 1) )
153 /* At non-whitespace char, inject seperator token */
154 if( !isspace(data->out[i]) )
156 memcpy(&tmp[size], T_TOK_I18NSEP, strlen(T_TOK_I18NSEP));
157 size += strlen(T_TOK_I18NSEP);
161 /* At further whitespace, skip */
168 /* Escape quotes, backslashes and newlines for plain, i18n and include expressions */
169 if( (data->type == T_TYPE_TEXT || data->type == T_TYPE_I18N || data->type == T_TYPE_INCLUDE) &&
170 (data->out[i] == '\\' || data->out[i] == '"' || data->out[i] == '\n' || data->out[i] == '\t') )
185 tmp[size++] = data->out[i];
189 /* Found first whitespace in i18n expression, raise flag */
190 else if( isspace(data->out[i]) && (data->type == T_TYPE_I18N) && (i18n_hasdef == 0) )
198 tmp[size++] = data->out[i];
202 /* Processed i18n expression without default text, inject separator */
203 if( (data->type == T_TYPE_I18N) && (i18n_hasdef < 2) )
205 memcpy(&tmp[size], T_TOK_I18NSEP, strlen(T_TOK_I18NSEP));
206 size += strlen(T_TOK_I18NSEP);
209 /* Inject trailing expression code (if any) */
210 if( (what & T_GEN_END) && (gen_code[data->type][1] != NULL) )
212 memcpy(&tmp[size], gen_code[data->type][1], strlen(gen_code[data->type][1]));
213 size += strlen(gen_code[data->type][1]);
216 *sz = data->outsize = size;
217 memset(data->out, 0, T_OUTBUFSZ);
218 memcpy(data->out, tmp, size);
220 //printf("<<<%i|%i|%i|%s>>>\n", what, data->type, *sz, data->out);
226 * Move the number of bytes specified in data->bufsize from the
227 * given source pointer to the beginning of the read buffer.
229 static void bufmove(struct template_parser *data, const char *src)
231 if( data->bufsize > 0 )
232 memmove(data->buf, src, data->bufsize);
233 else if( data->bufsize < 0 )
236 data->buf[data->bufsize] = 0;
240 * Move the given amount of bytes from the given source pointer
241 * to the output buffer and set data->outputsize.
243 static void bufout(struct template_parser *data, const char *src, int len)
247 memset(data->out, 0, T_OUTBUFSZ);
248 memcpy(data->out, src, len);
258 * lua_Reader compatible function that parses template code on demand from
259 * the given file handle.
261 const char *template_reader(lua_State *L, void *ud, size_t *sz)
263 struct template_parser *data = ud;
271 while( !(data->flags & T_FLAG_EOF) || (data->bufsize > 0) )
274 if( !(data->flags & T_FLAG_EOF) && (data->bufsize < T_READBUFSZ) )
276 if( (readlen = read(data->fd, &data->buf[data->bufsize], T_READBUFSZ - data->bufsize)) > 0 )
277 data->bufsize += readlen;
278 else if( readlen == 0 )
279 data->flags |= T_FLAG_EOF;
287 /* Plain text chunk (before "<%") */
288 case T_STATE_TEXT_INIT:
289 case T_STATE_TEXT_NEXT:
290 off = 0; ignore = 0; *sz = 0;
291 data->type = T_TYPE_TEXT;
293 /* Skip leading whitespace if requested */
294 if( data->flags & T_FLAG_SKIPWS )
296 data->flags &= ~T_FLAG_SKIPWS;
297 while( (off < data->bufsize) && isspace(data->buf[off]) )
302 if( (match = strfind(&data->buf[off], data->bufsize - off - 1, T_TOK_START, strlen(T_TOK_START))) != NULL )
304 readlen = (int)(match - &data->buf[off]);
305 data->bufsize -= (readlen + strlen(T_TOK_START) + off);
306 match += strlen(T_TOK_START);
308 /* Check for leading '-' */
309 if( match[0] == T_TOK_SKIPWS[0] )
314 while( (readlen > 1) && isspace(data->buf[off+readlen-1]) )
320 bufout(data, &data->buf[off], readlen);
321 bufmove(data, match);
322 data->state = T_STATE_CODE_INIT;
325 /* Maybe plain chunk */
328 /* Preserve trailing "<" or white space, maybe a start token */
329 vague = stokscan(data, off, 0);
331 /* We can process some bytes ... */
332 if( vague < data->bufsize )
334 readlen = data->bufsize - vague - off;
337 /* No bytes to process, so try to remove at least whitespace ... */
340 /* ... but try to preserve trailing "<" ... */
341 vague = stokscan(data, off, 1);
343 if( vague < data->bufsize )
345 readlen = data->bufsize - vague - off;
348 /* ... no chance, push out buffer */
351 readlen = vague - off;
356 bufout(data, &data->buf[off], readlen);
358 data->state = T_STATE_TEXT_NEXT;
359 data->bufsize = vague;
360 bufmove(data, &data->buf[off+readlen]);
363 if( ignore || data->outsize == 0 )
366 return generate_expression(data, sz, T_GEN_START | T_GEN_END);
370 /* Ignored chunk (inside "<%# ... %>") */
374 /* Initial code chunk ("<% ...") */
375 case T_STATE_CODE_INIT:
378 /* Check for leading '-' */
379 if( data->buf[off] == T_TOK_SKIPWS[0] )
382 /* Determine code type */
383 switch(data->buf[off])
388 data->type = T_TYPE_COMMENT;
393 data->type = T_TYPE_EXPR;
398 data->type = T_TYPE_INCLUDE;
403 data->type = T_TYPE_I18N;
407 data->type = T_TYPE_CODE;
411 /* Subsequent code chunk ("..." or "... %>") */
412 case T_STATE_CODE_NEXT:
414 if( (match = strfind(&data->buf[off], data->bufsize - off, T_TOK_END, strlen(T_TOK_END))) != NULL )
416 genflags = ( data->state == T_STATE_CODE_INIT )
417 ? (T_GEN_START | T_GEN_END) : T_GEN_END;
419 readlen = (int)(match - &data->buf[off]);
421 /* Check for trailing '-' */
422 if( (match > data->buf) && (*(match-1) == T_TOK_SKIPWS[0]) )
425 data->flags |= T_FLAG_SKIPWS;
428 bufout(data, &data->buf[off], readlen);
430 data->state = T_STATE_TEXT_INIT;
431 data->bufsize -= ((int)(match - &data->buf[off]) + strlen(T_TOK_END) + off);
432 bufmove(data, &match[strlen(T_TOK_END)]);
438 genflags = ( data->state == T_STATE_CODE_INIT ) ? T_GEN_START : 0;
440 /* Preserve trailing "%" and "-", maybe an end token */
441 vague = etokscan(data);
442 readlen = data->bufsize - off - vague;
443 bufout(data, &data->buf[off], readlen);
445 data->state = T_STATE_CODE_NEXT;
446 data->bufsize = vague;
447 bufmove(data, &data->buf[readlen+off]);
450 if( ignore || (data->outsize == 0 && !genflags) )
453 return generate_expression(data, sz, genflags);