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
[7][2] = {
24 { "write(\"", "\")" },
26 { "write(tostring(", " or \"\"))" },
27 { "include(\"", "\")" },
28 { "write(pcdata(translate(\"", "\")))" },
29 { "write(translate(\"", "\"))" },
33 /* Simple strstr() like function that takes len arguments for both haystack and needle. */
34 static char *strfind(char *haystack
, int hslen
, const char *needle
, int ndlen
)
39 for( i
= 0; i
< hslen
; i
++ )
41 if( haystack
[i
] == needle
[0] )
43 match
= ((ndlen
== 1) || ((i
+ ndlen
) <= hslen
));
45 for( j
= 1; (j
< ndlen
) && ((i
+ j
) < hslen
); j
++ )
47 if( haystack
[i
+j
] != needle
[j
] )
63 * Inspect current read buffer and find the number of "vague" characters at the end
64 * which could indicate an opening token. Returns the number of "vague" chars.
65 * The last continuous sequence of whitespace, optionally followed by a "<" is
66 * treated as "vague" because whitespace may be discarded if the upcoming opening
67 * token indicates pre-whitespace-removal ("<%-"). A single remaining "<" char
68 * can't be differentiated from an opening token ("<%"), so it's kept to be processed
71 static int stokscan(struct template_parser
*data
, int off
, int no_whitespace
)
75 int tokoff
= data
->bufsize
- 1;
77 for( i
= tokoff
; i
>= off
; i
-- )
79 if( data
->buf
[i
] == T_TOK_START
[0] )
81 skip
= tokoff
- i
+ 1;
89 for( i
= tokoff
; i
>= off
; i
-- )
91 if( isspace(data
->buf
[i
]) )
102 * Similar to stokscan() but looking for closing token indicators.
103 * Matches "-", optionally followed by a "%" char.
105 static int etokscan(struct template_parser
*data
)
109 if( (data
->bufsize
> 0) && (data
->buf
[data
->bufsize
-1] == T_TOK_END
[0]) )
112 if( (data
->bufsize
> skip
) && (data
->buf
[data
->bufsize
-skip
-1] == T_TOK_SKIPWS
[0]) )
119 * Generate Lua expressions from the given raw code, write it into the
120 * output buffer and set the lua_Reader specific size pointer.
121 * Takes parser-state, lua_Reader's size pointer and generator flags
122 * as parameter. The given flags indicate whether leading or trailing
123 * code should be added. Returns a pointer to the output buffer.
125 static const char * generate_expression(struct template_parser
*data
, size_t *sz
, int what
)
127 char tmp
[T_OUTBUFSZ
];
133 memset(tmp
, 0, T_OUTBUFSZ
);
135 /* Inject leading expression code (if any) */
136 if( (what
& T_GEN_START
) && (gen_code
[data
->type
][0] != NULL
) )
138 memcpy(tmp
, gen_code
[data
->type
][0], strlen(gen_code
[data
->type
][0]));
139 size
+= strlen(gen_code
[data
->type
][0]);
142 /* Parse source buffer */
143 for( i
= 0; i
< data
->outsize
; i
++ )
145 /* Skip leading whitespace for non-raw and non-expr chunks */
146 if( !start
&& isspace(data
->out
[i
]) && (data
->type
== T_TYPE_I18N
||
147 data
->type
== T_TYPE_I18N_RAW
|| data
->type
== T_TYPE_INCLUDE
) )
152 /* Found whitespace after i18n key */
153 if( data
->type
== T_TYPE_I18N
|| data
->type
== T_TYPE_I18N_RAW
)
155 /* Is initial whitespace, insert space */
156 if( !whitespace
&& isspace(data
->out
[i
]) )
162 /* Suppress subsequent whitespace, escape special chars */
163 else if( !isspace(data
->out
[i
]) )
165 if( data
->out
[i
] == '\\' || data
->out
[i
] == '"' )
168 tmp
[size
++] = data
->out
[i
];
173 /* Escape quotes, backslashes and newlines for plain and include expressions */
174 else if( (data
->type
== T_TYPE_TEXT
|| data
->type
== T_TYPE_INCLUDE
) &&
175 (data
->out
[i
] == '\\' || data
->out
[i
] == '"' || data
->out
[i
] == '\n' || data
->out
[i
] == '\t') )
190 tmp
[size
++] = data
->out
[i
];
197 tmp
[size
++] = data
->out
[i
];
201 /* Inject trailing expression code (if any) */
202 if( (what
& T_GEN_END
) && (gen_code
[data
->type
][1] != NULL
) )
204 /* Strip trailing space for i18n expressions */
205 if( data
->type
== T_TYPE_I18N
|| data
->type
== T_TYPE_I18N_RAW
)
206 if( (size
> 0) && (tmp
[size
-1] == ' ') )
209 memcpy(&tmp
[size
], gen_code
[data
->type
][1], strlen(gen_code
[data
->type
][1]));
210 size
+= strlen(gen_code
[data
->type
][1]);
213 *sz
= data
->outsize
= size
;
214 memset(data
->out
, 0, T_OUTBUFSZ
);
215 memcpy(data
->out
, tmp
, size
);
217 //printf("<<<%i|%i|%i|%s>>>\n", what, data->type, *sz, data->out);
223 * Move the number of bytes specified in data->bufsize from the
224 * given source pointer to the beginning of the read buffer.
226 static void bufmove(struct template_parser
*data
, const char *src
)
228 if( data
->bufsize
> 0 )
229 memmove(data
->buf
, src
, data
->bufsize
);
230 else if( data
->bufsize
< 0 )
233 data
->buf
[data
->bufsize
] = 0;
237 * Move the given amount of bytes from the given source pointer
238 * to the output buffer and set data->outputsize.
240 static void bufout(struct template_parser
*data
, const char *src
, int len
)
244 memset(data
->out
, 0, T_OUTBUFSZ
);
245 memcpy(data
->out
, src
, len
);
255 * lua_Reader compatible function that parses template code on demand from
256 * the given file handle.
258 const char *template_reader(lua_State
*L
, void *ud
, size_t *sz
)
260 struct template_parser
*data
= ud
;
268 while( !(data
->flags
& T_FLAG_EOF
) || (data
->bufsize
> 0) )
271 if( !(data
->flags
& T_FLAG_EOF
) && (data
->bufsize
< T_READBUFSZ
) )
273 if( (readlen
= read(data
->fd
, &data
->buf
[data
->bufsize
], T_READBUFSZ
- data
->bufsize
)) > 0 )
274 data
->bufsize
+= readlen
;
275 else if( readlen
== 0 )
276 data
->flags
|= T_FLAG_EOF
;
284 /* Plain text chunk (before "<%") */
285 case T_STATE_TEXT_INIT
:
286 case T_STATE_TEXT_NEXT
:
287 off
= 0; ignore
= 0; *sz
= 0;
288 data
->type
= T_TYPE_TEXT
;
290 /* Skip leading whitespace if requested */
291 if( data
->flags
& T_FLAG_SKIPWS
)
293 data
->flags
&= ~T_FLAG_SKIPWS
;
294 while( (off
< data
->bufsize
) && isspace(data
->buf
[off
]) )
299 if( (match
= strfind(&data
->buf
[off
], data
->bufsize
- off
- 1, T_TOK_START
, strlen(T_TOK_START
))) != NULL
)
301 readlen
= (int)(match
- &data
->buf
[off
]);
302 data
->bufsize
-= (readlen
+ strlen(T_TOK_START
) + off
);
303 match
+= strlen(T_TOK_START
);
305 /* Check for leading '-' */
306 if( match
[0] == T_TOK_SKIPWS
[0] )
311 while( (readlen
> 1) && isspace(data
->buf
[off
+readlen
-1]) )
317 bufout(data
, &data
->buf
[off
], readlen
);
318 bufmove(data
, match
);
319 data
->state
= T_STATE_CODE_INIT
;
322 /* Maybe plain chunk */
325 /* Preserve trailing "<" or white space, maybe a start token */
326 vague
= stokscan(data
, off
, 0);
328 /* We can process some bytes ... */
329 if( vague
< data
->bufsize
)
331 readlen
= data
->bufsize
- vague
- off
;
334 /* No bytes to process, so try to remove at least whitespace ... */
337 /* ... but try to preserve trailing "<" ... */
338 vague
= stokscan(data
, off
, 1);
340 if( vague
< data
->bufsize
)
342 readlen
= data
->bufsize
- vague
- off
;
345 /* ... no chance, push out buffer */
348 readlen
= vague
- off
;
353 bufout(data
, &data
->buf
[off
], readlen
);
355 data
->state
= T_STATE_TEXT_NEXT
;
356 data
->bufsize
= vague
;
357 bufmove(data
, &data
->buf
[off
+readlen
]);
360 if( ignore
|| data
->outsize
== 0 )
363 return generate_expression(data
, sz
, T_GEN_START
| T_GEN_END
);
367 /* Ignored chunk (inside "<%# ... %>") */
371 /* Initial code chunk ("<% ...") */
372 case T_STATE_CODE_INIT
:
375 /* Check for leading '-' */
376 if( data
->buf
[off
] == T_TOK_SKIPWS
[0] )
379 /* Determine code type */
380 switch(data
->buf
[off
])
385 data
->type
= T_TYPE_COMMENT
;
390 data
->type
= T_TYPE_EXPR
;
395 data
->type
= T_TYPE_INCLUDE
;
400 data
->type
= T_TYPE_I18N
;
405 data
->type
= T_TYPE_I18N_RAW
;
409 data
->type
= T_TYPE_CODE
;
413 /* Subsequent code chunk ("..." or "... %>") */
414 case T_STATE_CODE_NEXT
:
416 if( (match
= strfind(&data
->buf
[off
], data
->bufsize
- off
, T_TOK_END
, strlen(T_TOK_END
))) != NULL
)
418 genflags
= ( data
->state
== T_STATE_CODE_INIT
)
419 ? (T_GEN_START
| T_GEN_END
) : T_GEN_END
;
421 readlen
= (int)(match
- &data
->buf
[off
]);
423 /* Check for trailing '-' */
424 if( (match
> data
->buf
) && (*(match
-1) == T_TOK_SKIPWS
[0]) )
427 data
->flags
|= T_FLAG_SKIPWS
;
430 bufout(data
, &data
->buf
[off
], readlen
);
432 data
->state
= T_STATE_TEXT_INIT
;
433 data
->bufsize
-= ((int)(match
- &data
->buf
[off
]) + strlen(T_TOK_END
) + off
);
434 bufmove(data
, &match
[strlen(T_TOK_END
)]);
440 genflags
= ( data
->state
== T_STATE_CODE_INIT
) ? T_GEN_START
: 0;
442 /* Preserve trailing "%" and "-", maybe an end token */
443 vague
= etokscan(data
);
444 readlen
= data
->bufsize
- off
- vague
;
445 bufout(data
, &data
->buf
[off
], readlen
);
447 data
->state
= T_STATE_CODE_NEXT
;
448 data
->bufsize
= vague
;
449 bufmove(data
, &data
->buf
[readlen
+off
]);
452 if( ignore
|| (data
->outsize
== 0 && !genflags
) )
455 return generate_expression(data
, sz
, genflags
);