tmpl_parser.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  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 <sys/queue.h>
  20. #include <err.h>
  21. #include <fcntl.h>
  22. #include <regex.h>
  23. #include <stdbool.h>
  24. #include <stdio.h>
  25. #include <stdlib.h>
  26. #include <string.h>
  27. #include "buffer.h"
  28. #include "template.h"
  29. typedef enum {
  30. ELSE = 0,
  31. IF, INCL, INCLUDE, LOOP, UNLESS, VAR,
  32. MAX__TAG
  33. } tag_type_t;
  34. struct tag_info {
  35. TAILQ_ENTRY(tag_info) entry;
  36. tag_type_t type;
  37. char *start; // Start of the tag
  38. char *end; // End of the tag
  39. struct tag_info *else_tag; // ELSE from an IF block
  40. struct tag_info *closing_tag; // Pointer to a closing tag
  41. bool close; // true if itself is close tag
  42. char *name; // The name attribute
  43. };
  44. struct parser_state {
  45. char *input;
  46. size_t size;
  47. struct buffer_list *output;
  48. struct tmpl_data *data;
  49. TAILQ_HEAD(tag_head, tag_info) tags;
  50. };
  51. struct tag_strings {
  52. tag_type_t type;
  53. const char *id;
  54. size_t len;
  55. char *(*handle_func)(struct parser_state *,
  56. struct tag_info *);
  57. };
  58. #define TMPL_RX_PATTERN "(<(TMPL_" \
  59. "(ELSE|" \
  60. "(IF|INCL|INCLUDE|LOOP|VAR|UNLESS)( +name=\"([^\"]+)\"))?)" \
  61. ">)|(</TMPL_(IF|INCL|LOOP|VAR|UNLESS *>))"
  62. #define TMPL_RX_MAX_GROUPS 10
  63. static regex_t _rx;
  64. static bool _rx_initialized = false;
  65. static const size_t _rx_max_groups = TMPL_RX_MAX_GROUPS;
  66. static regmatch_t _rx_group_array[TMPL_RX_MAX_GROUPS];
  67. static struct parser_state *parser_state_new(const char *, size_t,
  68. struct buffer_list *);
  69. static void parser_state_free(struct parser_state *);
  70. static regmatch_t *parser_find_tmpl_tag(char *);
  71. static void parser_init(void);
  72. static void parser_cleanup(void);
  73. static int parser_enumerate_tags(struct parser_state *);
  74. static struct tag_info *parser_find_close_tag(struct tag_info *);
  75. static struct tag_info *tag_info_new(char *, regmatch_t *);
  76. static void tag_info_free(struct tag_info *);
  77. static const char *_get_rxerrormsg(int);
  78. // Tag handler functions:
  79. static char *tmpl_handle_else(struct parser_state *, struct tag_info *);
  80. static char *tmpl_handle_if(struct parser_state *, struct tag_info *);
  81. static char *tmpl_handle_incl(struct parser_state *, struct tag_info *);
  82. static char *tmpl_handle_loop(struct parser_state *, struct tag_info *);
  83. static char *tmpl_handle_var(struct parser_state *, struct tag_info *);
  84. static struct tag_strings tags[MAX__TAG] = {
  85. { ELSE, "TMPL_ELSE", 9, tmpl_handle_else },
  86. { IF, "TMPL_IF", 7, tmpl_handle_if },
  87. { INCL, "TMPL_INCL", 9, tmpl_handle_incl },
  88. { INCLUDE, "TMPL_INCLUDE", 12, tmpl_handle_incl },
  89. { LOOP, "TMPL_LOOP", 9, tmpl_handle_loop },
  90. { UNLESS, "TMPL_UNLESS", 11, tmpl_handle_if },
  91. { VAR, "TMPL_VAR", 8, tmpl_handle_var },
  92. };
  93. const char *
  94. _get_rxerrormsg(int rc)
  95. {
  96. static char errmsg[512];
  97. regerror(rc, &_rx, errmsg, sizeof(errmsg));
  98. return errmsg;
  99. }
  100. void
  101. tag_info_free(struct tag_info *_info)
  102. {
  103. if (_info) {
  104. free(_info->name);
  105. free(_info);
  106. }
  107. }
  108. struct tag_info *
  109. tag_info_new(char *_s, regmatch_t *_tag)
  110. {
  111. regmatch_t *name_attr = &_tag[6]; // 6th group holds the attr value
  112. size_t name_attr_val_len = name_attr->rm_eo - name_attr->rm_so;
  113. char *name_attr_val = (name_attr_val_len > 0)
  114. ? _s + name_attr->rm_so
  115. : NULL;
  116. struct tag_info *info = NULL;
  117. char *s = _s + _tag->rm_so;
  118. if (*s++ != '<')
  119. return NULL;
  120. bool is_close = *s == '/';
  121. if (is_close)
  122. s++;
  123. for (int i = 0; i < MAX__TAG; ++i) {
  124. int r = strncmp(s, tags[i].id, tags[i].len);
  125. if (r < 0)
  126. break;
  127. else if (r == 0) {
  128. info = malloc(sizeof(struct tag_info));
  129. if (NULL == info)
  130. err(1, NULL);
  131. info->type = tags[i].type;
  132. info->start = _s + _tag->rm_so;
  133. info->end = _s + _tag->rm_eo;
  134. info->else_tag = NULL;
  135. info->closing_tag = NULL;
  136. info->close = is_close;
  137. if (name_attr_val)
  138. info->name = strndup(name_attr_val,
  139. name_attr_val_len);
  140. else
  141. info->name = NULL;
  142. break;
  143. }
  144. }
  145. #ifdef DEBUG
  146. if (info && info->name)
  147. printf("%s has name = %s\n", tags[info->type].id, info->name);
  148. #endif
  149. return info;
  150. }
  151. void
  152. parser_init(void)
  153. {
  154. if (_rx_initialized)
  155. return;
  156. int rc = regcomp(&_rx, TMPL_RX_PATTERN, REG_EXTENDED);
  157. if (rc != 0)
  158. errx(1, "regcomp: %s", _get_rxerrormsg(rc));
  159. _rx_initialized = true;
  160. }
  161. void
  162. parser_cleanup(void)
  163. {
  164. if (_rx_initialized) {
  165. regfree(&_rx);
  166. _rx_initialized = false;
  167. }
  168. }
  169. struct tag_info *
  170. parser_find_close_tag(struct tag_info *_open_tag)
  171. {
  172. int num_opened = 1;
  173. struct tag_info *next = _open_tag;
  174. while ((num_opened > 0) && (next = TAILQ_NEXT(next, entry))) {
  175. if (next->type == _open_tag->type) {
  176. if (next->close) {
  177. --num_opened;
  178. } else {
  179. num_opened++;
  180. }
  181. }
  182. if ((_open_tag->type == IF) && (num_opened == 1)
  183. && (next->type == ELSE)) {
  184. _open_tag->else_tag = next;
  185. }
  186. }
  187. if (next)
  188. _open_tag->closing_tag = next;
  189. return next;
  190. }
  191. struct parser_state *
  192. parser_state_new(const char *_input, size_t _size, struct buffer_list *_out)
  193. {
  194. struct parser_state *state = malloc(sizeof(struct parser_state));
  195. if (NULL == state)
  196. err(1, NULL);
  197. state->input = malloc(_size + 1);
  198. if (NULL == state->input)
  199. err(1, NULL);
  200. strlcpy(state->input, _input, _size);
  201. state->size = _size;
  202. state->output = _out;
  203. state->data = NULL;
  204. TAILQ_INIT(&state->tags);
  205. return state;
  206. }
  207. void
  208. parser_state_free(struct parser_state *_state)
  209. {
  210. if (_state) {
  211. struct tag_info *info;
  212. while ((info = TAILQ_FIRST(&_state->tags))) {
  213. TAILQ_REMOVE(&_state->tags, info, entry);
  214. tag_info_free(info);
  215. }
  216. free(_state->input);
  217. free(_state);
  218. }
  219. }
  220. /*
  221. * Searches for one of the <TMPL_xxxx> tags and returns the position of that
  222. * tag if found.
  223. * If no such tag is found NULL is returned.
  224. */
  225. regmatch_t *
  226. parser_find_tmpl_tag(char *_start)
  227. {
  228. if ('\0' != *_start) {
  229. int rc = regexec(&_rx, _start, _rx_max_groups,
  230. &_rx_group_array[0], 0);
  231. switch (rc) {
  232. case REG_NOMATCH:
  233. return NULL;
  234. case 0:
  235. return _rx_group_array;
  236. break;
  237. default:
  238. warnx("regexec: %s", _get_rxerrormsg(rc));
  239. break;
  240. }
  241. }
  242. return NULL;
  243. }
  244. int
  245. parser_enumerate_tags(struct parser_state *_state)
  246. {
  247. int n = 0;
  248. char *pos = _state->input;
  249. regmatch_t *loc;
  250. while ((loc = parser_find_tmpl_tag(pos) ) != NULL) {
  251. struct tag_info *info = tag_info_new(pos, loc);
  252. if (info) {
  253. TAILQ_INSERT_TAIL(&_state->tags, info, entry);
  254. n++;
  255. }
  256. pos += loc->rm_eo + 1;
  257. }
  258. return n;
  259. }
  260. char *
  261. tmpl_handle_else(struct parser_state *_parser, struct tag_info *_info)
  262. {
  263. // An ELSE tag should never be handled alone, so abort here
  264. errx(1, "Got ELSE tag without IF or UNLESS, aborting");
  265. }
  266. char *
  267. tmpl_handle_if(struct parser_state *_p, struct tag_info *_info)
  268. {
  269. bool cond = false;
  270. struct tmpl_var *var = tmpl_data_get_variable(_p->data, _info->name);
  271. if (var) {
  272. if ((var->value) && (strlen(var->value) > 0))
  273. cond = (strcmp(var->value, "0") != 0);
  274. } else {
  275. cond = !tmpl_loop_isempty(
  276. tmpl_data_get_loop(_p->data, _info->name)
  277. );
  278. }
  279. cond ^= (_info->type == UNLESS);
  280. char *block_start;
  281. char *block_end;
  282. struct tag_info *else_tag = _info->else_tag;
  283. if (cond) {
  284. block_start = _info->end + 1;
  285. if (else_tag)
  286. block_end = else_tag->start;
  287. else
  288. block_end = _info->closing_tag->start;
  289. } else {
  290. if (else_tag) {
  291. block_start = else_tag->end + 1;
  292. block_end = _info->closing_tag->start;
  293. } else
  294. return _info->closing_tag->end + 1;
  295. }
  296. struct buffer_list *block = tmpl_parse(block_start,
  297. block_end - block_start, _p->data);
  298. buffer_list_add_list(_p->output, block);
  299. buffer_list_free(block);
  300. return _info->closing_tag->end + 1;
  301. }
  302. char *
  303. tmpl_handle_incl(struct parser_state *_p, struct tag_info *_info)
  304. {
  305. struct buffer_list *block = tmpl_parse_file(_info->name, _p->data);
  306. buffer_list_add_list(_p->output, block);
  307. buffer_list_free(block);
  308. return _info->end;
  309. }
  310. char *
  311. tmpl_handle_loop(struct parser_state *_p, struct tag_info *_info)
  312. {
  313. struct tmpl_loop *loop = tmpl_data_get_loop(_p->data, _info->name);
  314. bool cond = !tmpl_loop_isempty(loop);
  315. #ifdef DEBUG
  316. printf("loop %s is %sempty\n", _info->name, cond ? "not " : "");
  317. #endif
  318. struct tag_info *closing_tag = _info->closing_tag;
  319. if (!cond)
  320. return closing_tag->end + 1;
  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 + 1,
  325. len, data);
  326. buffer_list_add_list(_p->output, block);
  327. buffer_list_free(block);
  328. }
  329. return closing_tag->end + 1;
  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. #ifdef DEBUG
  358. printf("%s%s tag at %td\n", (info->close) ? "/" : "",
  359. tags[info->type].id,
  360. (info->start - state->input));
  361. #endif
  362. if (info->close)
  363. continue;
  364. switch (info->type) {
  365. case ELSE:
  366. case VAR:
  367. case INCL:
  368. break;
  369. default:
  370. closer = parser_find_close_tag(info);
  371. if (NULL == closer) {
  372. errx(1, "template: %s for %s",
  373. "Unable to find closing tag",
  374. tags[info->type].id);
  375. }
  376. break;
  377. }
  378. }
  379. // Output each block with data and handle all the tags
  380. char *s = state->input;
  381. TAILQ_FOREACH(info, &state->tags, entry) {
  382. if (s > info->start)
  383. continue;
  384. if (s < info->start || info->close) {
  385. // Copy the data block from s to the tags start
  386. buffer_list_add_stringn(out, s, info->start - s + 1);
  387. s = info->end;
  388. }
  389. s = (*tags[info->type].handle_func)(state, info);
  390. }
  391. char *end = state->input + state->size;
  392. if (s < end)
  393. buffer_list_add_stringn(out, s, end - s);
  394. parser_state_free(state);
  395. parser_cleanup();
  396. return out;
  397. }
  398. struct buffer_list *
  399. tmpl_parse_file(const char *_filename, struct tmpl_data *_data)
  400. {
  401. struct stat sb;
  402. int fd = open(_filename, O_RDONLY);
  403. if (-1 == fd)
  404. err(1, NULL);
  405. if (-1 == fstat(fd, &sb))
  406. err(1, NULL);
  407. if (sb.st_size == 0)
  408. return NULL;
  409. void *tmpl = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
  410. if (NULL == tmpl)
  411. err(1, NULL);
  412. struct buffer_list *out = tmpl_parse((char *)tmpl, sb.st_size, _data);
  413. munmap(tmpl, sb.st_size);
  414. return out;
  415. }