tmpl_parser.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. /*
  2. * Copyright (c) 2018 Markus Hennecke <markus-hennecke@markus-hennecke.de>
  3. *
  4. * Permission to use, copy, modify, and distribute this software for any
  5. * purpose with or without fee is hereby granted, provided that the above
  6. * copyright notice and this permission notice appear in all copies.
  7. *
  8. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #include <sys/mman.h>
  17. #include <sys/stat.h>
  18. #include <sys/types.h>
  19. #include <err.h>
  20. #include <fcntl.h>
  21. #include <regex.h>
  22. #include <stdbool.h>
  23. #if defined(DEBUG)
  24. #include <stdio.h>
  25. #endif
  26. #include <stdlib.h>
  27. #include <string.h>
  28. #include <unistd.h>
  29. #include "buffer.h"
  30. #include "helper.h"
  31. #include "template.h"
  32. typedef enum {
  33. ELSE = 0,
  34. IF, INCL, INCLUDE, LOOP, UNLESS, VAR,
  35. MAX__TAG
  36. } tag_type_t;
  37. struct tag_info {
  38. TAILQ_ENTRY(tag_info) entry;
  39. tag_type_t type;
  40. char *start; // Start of the tag
  41. char *end; // End of the tag
  42. struct tag_info *else_tag; // ELSE from an IF block
  43. struct tag_info *closing_tag; // Pointer to a closing tag
  44. bool close; // true if itself is close tag
  45. char *name; // The name attribute
  46. };
  47. struct parser_state {
  48. char *input;
  49. size_t size;
  50. struct buffer_list *output;
  51. struct tmpl_data *data;
  52. TAILQ_HEAD(tag_head, tag_info) tags;
  53. };
  54. struct tag_strings {
  55. tag_type_t type;
  56. const char *id;
  57. size_t len;
  58. char *(*handle_func)(struct parser_state *,
  59. struct tag_info *);
  60. };
  61. #define TMPL_RX_PATTERN "(<(TMPL_" \
  62. "(ELSE|" \
  63. "(IF|INCL|INCLUDE|LOOP|VAR|UNLESS)( +name=\"([^\"]+)\"))?)" \
  64. " *>)|(</TMPL_(IF|LOOP|VAR|UNLESS) *>)"
  65. #define TMPL_RX_MAX_GROUPS 10
  66. static regex_t _rx;
  67. static bool _rx_initialized = false;
  68. static const size_t _rx_max_groups = TMPL_RX_MAX_GROUPS;
  69. static regmatch_t _rx_group_array[TMPL_RX_MAX_GROUPS];
  70. static struct parser_state *parser_state_new(const char *, size_t,
  71. struct buffer_list *);
  72. static void parser_state_free(struct parser_state *);
  73. static regmatch_t *parser_find_tmpl_tag(char *);
  74. static void parser_init(void);
  75. static void parser_cleanup(void);
  76. static int parser_enumerate_tags(struct parser_state *);
  77. static struct tag_info *parser_find_close_tag(struct tag_info *);
  78. static struct tag_info *tag_info_new(char *, regmatch_t *);
  79. static void tag_info_free(struct tag_info *);
  80. // Tag handler functions:
  81. static char *tmpl_handle_else(struct parser_state *, struct tag_info *);
  82. static char *tmpl_handle_if(struct parser_state *, struct tag_info *);
  83. static char *tmpl_handle_incl(struct parser_state *, struct tag_info *);
  84. static char *tmpl_handle_loop(struct parser_state *, struct tag_info *);
  85. static char *tmpl_handle_var(struct parser_state *, struct tag_info *);
  86. static struct tag_strings tags[MAX__TAG] = {
  87. { ELSE, "TMPL_ELSE", 9, tmpl_handle_else },
  88. { IF, "TMPL_IF", 7, tmpl_handle_if },
  89. { INCL, "TMPL_INCL", 9, tmpl_handle_incl },
  90. { INCLUDE, "TMPL_INCLUDE", 12, tmpl_handle_incl },
  91. { LOOP, "TMPL_LOOP", 9, tmpl_handle_loop },
  92. { UNLESS, "TMPL_UNLESS", 11, tmpl_handle_if },
  93. { VAR, "TMPL_VAR", 8, tmpl_handle_var },
  94. };
  95. void
  96. tag_info_free(struct tag_info *_info)
  97. {
  98. if (_info) {
  99. free(_info->name);
  100. free(_info);
  101. }
  102. }
  103. struct tag_info *
  104. tag_info_new(char *_s, regmatch_t *_tag)
  105. {
  106. regmatch_t *name_attr = &_tag[6]; // 6th group holds the attr value
  107. size_t name_attr_val_len = name_attr->rm_eo - name_attr->rm_so;
  108. char *name_attr_val = (name_attr_val_len > 0)
  109. ? _s + name_attr->rm_so
  110. : NULL;
  111. struct tag_info *info = NULL;
  112. char *s = _s + _tag->rm_so;
  113. if (*s++ != '<')
  114. return NULL;
  115. bool is_close = *s == '/';
  116. if (is_close)
  117. s++;
  118. for (int i = 0; i < MAX__TAG; ++i) {
  119. int r = strncmp(s, tags[i].id, tags[i].len);
  120. if (r < 0) {
  121. break;
  122. } else if (r == 0) {
  123. info = malloc(sizeof(struct tag_info));
  124. if (NULL == info)
  125. err(1, NULL);
  126. info->type = tags[i].type;
  127. info->start = _s + _tag->rm_so;
  128. info->end = _s + _tag->rm_eo;
  129. info->else_tag = NULL;
  130. info->closing_tag = NULL;
  131. info->close = is_close;
  132. if (name_attr_val)
  133. info->name = strndup(name_attr_val,
  134. name_attr_val_len);
  135. else
  136. info->name = NULL;
  137. break;
  138. }
  139. }
  140. #if defined(DEBUG)
  141. if (info && info->name)
  142. dprintf(STDERR_FILENO, "%s has name = %s\n",
  143. tags[info->type].id, info->name);
  144. #endif
  145. return info;
  146. }
  147. void
  148. parser_init(void)
  149. {
  150. if (_rx_initialized)
  151. return;
  152. int rc = regcomp(&_rx, TMPL_RX_PATTERN, REG_EXTENDED | REG_ICASE);
  153. if (rc != 0)
  154. errx(1, "regcomp: %s", rx_get_errormsg(rc, &_rx));
  155. _rx_initialized = true;
  156. }
  157. void
  158. parser_cleanup(void)
  159. {
  160. if (_rx_initialized) {
  161. regfree(&_rx);
  162. _rx_initialized = false;
  163. }
  164. }
  165. struct tag_info *
  166. parser_find_close_tag(struct tag_info *_open_tag)
  167. {
  168. int num_opened = 1;
  169. struct tag_info *next = _open_tag;
  170. while ((num_opened > 0) && (next = TAILQ_NEXT(next, entry))) {
  171. if (next->type == _open_tag->type) {
  172. if (next->close) {
  173. --num_opened;
  174. } else {
  175. num_opened++;
  176. }
  177. }
  178. if ((_open_tag->type == IF) && (num_opened == 1)
  179. && (next->type == ELSE)) {
  180. _open_tag->else_tag = next;
  181. }
  182. }
  183. if (next)
  184. _open_tag->closing_tag = next;
  185. return next;
  186. }
  187. struct parser_state *
  188. parser_state_new(const char *_input, size_t _size, struct buffer_list *_out)
  189. {
  190. struct parser_state *state = malloc(sizeof(struct parser_state));
  191. if (NULL == state)
  192. err(1, NULL);
  193. state->input = malloc(_size + 1);
  194. if (NULL == state->input)
  195. err(1, NULL);
  196. memcpy(state->input, _input, _size);
  197. state->input[_size] = '\0';
  198. state->size = _size;
  199. state->output = _out;
  200. state->data = NULL;
  201. TAILQ_INIT(&state->tags);
  202. return state;
  203. }
  204. void
  205. parser_state_free(struct parser_state *_state)
  206. {
  207. if (_state) {
  208. struct tag_info *info;
  209. while ((info = TAILQ_FIRST(&_state->tags))) {
  210. TAILQ_REMOVE(&_state->tags, info, entry);
  211. tag_info_free(info);
  212. }
  213. free(_state->input);
  214. free(_state);
  215. }
  216. }
  217. /*
  218. * Searches for one of the <TMPL_xxxx> tags and returns the position of that
  219. * tag if found.
  220. * If no such tag is found NULL is returned.
  221. */
  222. regmatch_t *
  223. parser_find_tmpl_tag(char *_start)
  224. {
  225. if ('\0' != *_start) {
  226. int rc = regexec(&_rx, _start, _rx_max_groups,
  227. &_rx_group_array[0], 0);
  228. switch (rc) {
  229. case REG_NOMATCH:
  230. return NULL;
  231. case 0:
  232. return _rx_group_array;
  233. break;
  234. default:
  235. warnx("regexec: %s", rx_get_errormsg(rc, &_rx));
  236. break;
  237. }
  238. }
  239. return NULL;
  240. }
  241. int
  242. parser_enumerate_tags(struct parser_state *_state)
  243. {
  244. int n = 0;
  245. char *pos = _state->input;
  246. regmatch_t *loc;
  247. while ((loc = parser_find_tmpl_tag(pos) ) != NULL) {
  248. struct tag_info *info = tag_info_new(pos, loc);
  249. if (info) {
  250. TAILQ_INSERT_TAIL(&_state->tags, info, entry);
  251. n++;
  252. }
  253. pos += loc->rm_eo;
  254. }
  255. return n;
  256. }
  257. char *
  258. tmpl_handle_else(struct parser_state *_parser, struct tag_info *_info)
  259. {
  260. // An ELSE tag should never be handled alone, so abort here
  261. errx(1, "Got ELSE tag without IF or UNLESS, aborting");
  262. }
  263. char *
  264. tmpl_handle_if(struct parser_state *_p, struct tag_info *_info)
  265. {
  266. bool cond = false;
  267. struct tmpl_var *var = tmpl_data_get_variable(_p->data, _info->name);
  268. if (var) {
  269. if ((var->value) && (strlen(var->value) > 0))
  270. cond = (strcmp(var->value, "0") != 0);
  271. } else {
  272. cond = !tmpl_loop_isempty(
  273. tmpl_data_get_loop(_p->data, _info->name)
  274. );
  275. }
  276. cond ^= (_info->type == UNLESS);
  277. char *block_start;
  278. char *block_end;
  279. struct tag_info *else_tag = _info->else_tag;
  280. if (cond) {
  281. block_start = _info->end;
  282. if (else_tag)
  283. block_end = else_tag->start;
  284. else
  285. block_end = _info->closing_tag->start;
  286. } else {
  287. if (else_tag) {
  288. block_start = else_tag->end + 1;
  289. block_end = _info->closing_tag->start;
  290. } else
  291. return _info->closing_tag->end;
  292. }
  293. struct buffer_list *block = tmpl_parse(block_start,
  294. block_end - block_start, _p->data);
  295. buffer_list_add_list(_p->output, block);
  296. buffer_list_free(block);
  297. return _info->closing_tag->end;
  298. }
  299. char *
  300. tmpl_handle_incl(struct parser_state *_p, struct tag_info *_info)
  301. {
  302. struct buffer_list *block = tmpl_parse_file(_info->name, _p->data);
  303. if (block) {
  304. buffer_list_add_list(_p->output, block);
  305. buffer_list_free(block);
  306. }
  307. return _info->end;
  308. }
  309. char *
  310. tmpl_handle_loop(struct parser_state *_p, struct tag_info *_info)
  311. {
  312. struct tmpl_loop *loop = tmpl_data_get_loop(_p->data, _info->name);
  313. bool cond = !tmpl_loop_isempty(loop);
  314. #if defined(DEBUG)
  315. dprintf(STDERR_FILENO, "loop %s is %sempty\n", _info->name,
  316. cond ? "not " : "");
  317. #endif
  318. struct tag_info *closing_tag = _info->closing_tag;
  319. if (!cond)
  320. return closing_tag->end;
  321. const size_t len = closing_tag->start - _info->end;
  322. struct tmpl_data *data;
  323. TAILQ_FOREACH(data, &loop->data, entry) {
  324. struct buffer_list *block = tmpl_parse(_info->end,
  325. len, data);
  326. buffer_list_add_list(_p->output, block);
  327. buffer_list_free(block);
  328. }
  329. return closing_tag->end;
  330. }
  331. char *
  332. tmpl_handle_var(struct parser_state *_parser, struct tag_info *_info)
  333. {
  334. const struct tmpl_var *var = tmpl_data_get_variable(
  335. _parser->data, _info->name
  336. );
  337. if (var) {
  338. buffer_list_add_string(_parser->output, var->value);
  339. }
  340. return _info->end;
  341. }
  342. struct buffer_list *
  343. tmpl_parse(const char *_tmpl, size_t _len, struct tmpl_data *_data)
  344. {
  345. struct tag_info *info;
  346. parser_init();
  347. struct buffer_list *out = buffer_list_new();
  348. struct parser_state *state = parser_state_new(_tmpl, _len, out);
  349. state->data = _data;
  350. // Get all tags from the current template
  351. parser_enumerate_tags(state);
  352. // Try to find all closing tags for tags defining a block and
  353. // put the information about the closing tag into the tag_info
  354. // for the opening tag
  355. TAILQ_FOREACH(info, &state->tags, entry) {
  356. struct tag_info *closer;
  357. #if defined(DEBUG)
  358. dprintf(STDERR_FILENO, "%s%s tag at %td\n",
  359. (info->close) ? "/" : "",
  360. tags[info->type].id,
  361. (info->start - state->input));
  362. #endif
  363. if (info->close)
  364. continue;
  365. switch (info->type) {
  366. case ELSE:
  367. case VAR:
  368. case INCL:
  369. case INCLUDE:
  370. break;
  371. default:
  372. closer = parser_find_close_tag(info);
  373. if (NULL == closer) {
  374. errx(1, "template: %s for %s",
  375. "Unable to find closing tag",
  376. tags[info->type].id);
  377. }
  378. break;
  379. }
  380. }
  381. // Output each block with data and handle all the tags
  382. char *s = state->input;
  383. TAILQ_FOREACH(info, &state->tags, entry) {
  384. if (s > info->start)
  385. continue;
  386. if (s < info->start || info->close) {
  387. // Copy the data block from s to the tags start
  388. buffer_list_add_stringn(out, s, info->start - s);
  389. s = info->end;
  390. }
  391. s = (*tags[info->type].handle_func)(state, info);
  392. }
  393. char *end = state->input + state->size;
  394. if (s < end)
  395. buffer_list_add_stringn(out, s, end - s);
  396. parser_state_free(state);
  397. parser_cleanup();
  398. return out;
  399. }
  400. struct buffer_list *
  401. tmpl_parse_file(const char *_filename, struct tmpl_data *_data)
  402. {
  403. struct stat sb;
  404. int fd = open(_filename, O_RDONLY);
  405. if (-1 == fd) {
  406. warn("%s", _filename);
  407. return NULL;
  408. }
  409. if (-1 == fstat(fd, &sb)) {
  410. warn("%s", _filename);
  411. return NULL;
  412. }
  413. if (sb.st_size == 0)
  414. return NULL;
  415. void *tmpl = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
  416. close(fd);
  417. if (NULL == tmpl)
  418. err(1, NULL);
  419. struct buffer_list *out = tmpl_parse((char *)tmpl, sb.st_size, _data);
  420. munmap(tmpl, sb.st_size);
  421. return out;
  422. }