expat_api.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /*
  2. * ------------------------------------------------------
  3. * shell_expat.c
  4. *
  5. * This file implements the means to read an XML encoded file
  6. * and populate the CLI tree based on the contents. It implements
  7. * the kxml_ API using the expat XML parser
  8. *
  9. * expat is not your typicall XML parser. It does not work
  10. * by creating a full in-memory XML tree, but by calling specific
  11. * callbacks (element handlers) regularly while parsing. It's up
  12. * to the user to create the corresponding XML tree if needed
  13. * (obviously, this is what we're doing, as we really need the XML
  14. * tree in klish).
  15. *
  16. * The code below do that. It transforms the output of expat
  17. * to a DOM representation of the underlying XML file. This is
  18. * a bit overkill, and maybe a later implementation will help to
  19. * cut the work to something simpler, but the current klish
  20. * implementation requires this.
  21. * ------------------------------------------------------
  22. */
  23. #ifdef HAVE_CONFIG_H
  24. #include "config.h"
  25. #endif
  26. #include <string.h>
  27. #include <stdlib.h>
  28. #include <fcntl.h>
  29. #include <sys/stat.h>
  30. #include <unistd.h>
  31. #include <errno.h>
  32. /* FreeBSD have verbatim version of expat named bsdxml */
  33. #ifdef HAVE_LIB_BSDXML
  34. #include <bsdxml.h>
  35. #else
  36. #include <expat.h>
  37. #endif
  38. #include <faux/faux.h>
  39. #include <faux/str.h>
  40. #include <klish/kxml.h>
  41. /** DOM_like XML node
  42. *
  43. * @struct kxml_node_s
  44. */
  45. struct kxml_node_s {
  46. char *name;
  47. kxml_node_t *parent; /**< parent node */
  48. kxml_node_t *children; /**< list of children */
  49. kxml_node_t *next; /**< next sibling */
  50. kxml_node_t *attributes; /**< attributes are nodes too */
  51. char *content; /**< !NULL for text and attributes nodes */
  52. kxml_nodetype_e type; /**< node type */
  53. int depth; /**< node depth */
  54. kxml_doc_t *doc;
  55. };
  56. /** DOM-like XML document
  57. *
  58. * @struct kxml_doc_s
  59. */
  60. struct kxml_doc_s {
  61. kxml_node_t *root; /**< list of root elements */
  62. kxml_node_t *current; /**< current element */
  63. char *filename; /**< current filename */
  64. };
  65. /*
  66. * Expat need these functions to be able to build a DOM-like tree that
  67. * will be usable by klish.
  68. */
  69. /** Put a element at the and of an element list
  70. *
  71. * @param first first element of the list
  72. * @param node element to add
  73. * @return new first element of the list
  74. */
  75. static kxml_node_t *kexpat_list_push_back(kxml_node_t *first, kxml_node_t *node)
  76. {
  77. kxml_node_t *cur = first;
  78. kxml_node_t *prev = NULL;
  79. while (cur) {
  80. prev = cur;
  81. cur = cur->next;
  82. }
  83. if (prev) {
  84. prev->next = node;
  85. return first;
  86. }
  87. return node;
  88. }
  89. /** Generic add_attr() function
  90. *
  91. * @param first first attribute in the attribute list
  92. * @param n attribute name
  93. * @param v attribute value
  94. * @return the new first attribute in the attribute list
  95. */
  96. static kxml_node_t *kexpat_add_attr(kxml_node_t *first, const char *n, const char *v)
  97. {
  98. kxml_node_t *node = NULL;
  99. node = malloc(sizeof(kxml_node_t));
  100. if (!node)
  101. return first;
  102. node->name = strdup(n);
  103. node->content = strdup(v);
  104. node->children = NULL;
  105. node->attributes = NULL;
  106. node->next = NULL;
  107. node->type = KXML_NODE_ATTR;
  108. node->depth = 0;
  109. return kexpat_list_push_back(first, node);
  110. }
  111. /** Run through an expat attribute list, and create a DOM-like attribute list
  112. *
  113. * @param node parent node
  114. * @param attr NULL-terminated attribute liste
  115. *
  116. * Each attribute uses two slots in the expat attribute list. The first one is
  117. * used to store the name, the second one is used to store the value.
  118. */
  119. static void kexpat_add_attrlist(kxml_node_t *node, const char **attr)
  120. {
  121. int i = 0;
  122. for (i = 0; attr[i]; i += 2) {
  123. node->attributes = kexpat_add_attr(node->attributes,
  124. attr[i], attr[i+1]);
  125. }
  126. }
  127. /** Generic make_node() function
  128. *
  129. * @param parent XML parent node
  130. * @param type XML node type
  131. * @param n node name (can be NULL, strdup'ed)
  132. * @param v node content (can be NULL, strdup'ed)
  133. * @param attr attribute list
  134. * @return a new node or NULL on error
  135. */
  136. static kxml_node_t *kexpat_make_node(kxml_node_t *parent,
  137. kxml_nodetype_e type,
  138. const char *n,
  139. const char *v,
  140. const char **attr)
  141. {
  142. kxml_node_t *node = NULL;
  143. node = malloc(sizeof(kxml_node_t));
  144. if (!node)
  145. return NULL;
  146. node->name = n ? strdup(n) : NULL;
  147. node->content = v ? strdup(v) : NULL;
  148. node->children = NULL;
  149. node->attributes = NULL;
  150. node->next = NULL;
  151. node->parent = parent;
  152. node->doc = parent ? parent->doc : NULL;
  153. node->depth = parent ? parent->depth + 1 : 0;
  154. node->type = type;
  155. if (attr)
  156. kexpat_add_attrlist(node, attr);
  157. if (parent)
  158. parent->children = kexpat_list_push_back(parent->children, node);
  159. return node;
  160. }
  161. /** Add a new XML root
  162. *
  163. * @param doc XML document
  164. * @param el root node name
  165. * @param attr expat attribute list
  166. * @return a new root element
  167. */
  168. static kxml_node_t *kexpat_add_root(kxml_doc_t *doc, const char *el, const char **attr)
  169. {
  170. kxml_node_t *node = NULL;
  171. node = kexpat_make_node(NULL, KXML_NODE_ELM, el, NULL, attr);
  172. if (!node)
  173. return doc->root;
  174. doc->root = kexpat_list_push_back(doc->root, node);
  175. return node;
  176. }
  177. /** Add a new XML element as a child
  178. *
  179. * @param cur parent XML element
  180. * @param el element name
  181. * @param attr expat attribute list
  182. * @return a new XMl element
  183. */
  184. static kxml_node_t *kexpat_add_child(kxml_node_t *cur, const char *el, const char **attr)
  185. {
  186. kxml_node_t *node;
  187. node = kexpat_make_node(cur, KXML_NODE_ELM, el, NULL, attr);
  188. if (!node)
  189. return cur;
  190. return node;
  191. }
  192. /** Expat handler: element content
  193. *
  194. * @param data user data
  195. * @param s content (not nul-termainated)
  196. * @param len content length
  197. */
  198. static void kexpat_chardata_handler(void *data, const char *s, int len)
  199. {
  200. kxml_doc_t *doc = data;
  201. if (doc->current) {
  202. char *content = malloc(len + 1);
  203. strncpy(content, s, len);
  204. content[len] = '\0';
  205. kexpat_make_node(doc->current, KXML_NODE_TEXT, NULL, content, NULL);
  206. /*
  207. * the previous call is a bit too generic, and strdup() content
  208. * so we need to free out own version of content.
  209. */
  210. free(content);
  211. }
  212. }
  213. /** Expat handler: start XML element
  214. *
  215. * @param data user data
  216. * @param el element name (nul-terminated)
  217. * @param attr expat attribute list
  218. */
  219. static void kexpat_element_start(void *data, const char *el, const char **attr)
  220. {
  221. kxml_doc_t *doc = data;
  222. if (!doc->current) {
  223. doc->current = kexpat_add_root(doc, el, attr);
  224. } else {
  225. doc->current = kexpat_add_child(doc->current, el, attr);
  226. }
  227. }
  228. /** Expat handler: end XML element
  229. *
  230. * @param data user data
  231. * @param el element name
  232. */
  233. static void kexpat_element_end(void *data, const char *el)
  234. {
  235. kxml_doc_t *doc = data;
  236. if (doc->current) {
  237. doc->current = doc->current->parent;
  238. }
  239. el = el; /* Happy compiler */
  240. }
  241. /** Free a node, its children and its attributes
  242. *
  243. * @param node node to free
  244. */
  245. static void kexpat_free_node(kxml_node_t *cur)
  246. {
  247. kxml_node_t *node;
  248. kxml_node_t *first;
  249. if (cur->attributes) {
  250. first = cur->attributes;
  251. while (first) {
  252. node = first;
  253. first = first->next;
  254. kexpat_free_node(node);
  255. }
  256. }
  257. if (cur->children) {
  258. first = cur->children;
  259. while (first) {
  260. node = first;
  261. first = first->next;
  262. kexpat_free_node(node);
  263. }
  264. }
  265. if (cur->name)
  266. free(cur->name);
  267. if (cur->content)
  268. free(cur->content);
  269. free(cur);
  270. }
  271. /*
  272. * Public interface
  273. */
  274. bool_t kxml_doc_start(void)
  275. {
  276. return BOOL_TRUE;
  277. }
  278. bool_t kxml_doc_stop(void)
  279. {
  280. return BOOL_TRUE;
  281. }
  282. kxml_doc_t *kxml_doc_read(const char *filename)
  283. {
  284. kxml_doc_t *doc = NULL;
  285. struct stat sb = {};
  286. int fd = -1;
  287. char *buffer = NULL;
  288. XML_Parser parser;
  289. int rb = 0;
  290. doc = malloc(sizeof(kxml_doc_t));
  291. if (!doc)
  292. return NULL;
  293. memset(doc, 0, sizeof(kxml_doc_t));
  294. doc->filename = strdup(filename);
  295. parser = XML_ParserCreate(NULL);
  296. if (!parser)
  297. goto error_parser_create;
  298. XML_SetUserData(parser, doc);
  299. XML_SetCharacterDataHandler(parser, kexpat_chardata_handler);
  300. XML_SetElementHandler(parser,
  301. kexpat_element_start,
  302. kexpat_element_end);
  303. fd = open(filename, O_RDONLY);
  304. if (fd < 0)
  305. goto error_open;
  306. fstat(fd, &sb);
  307. buffer = malloc(sb.st_size+1);
  308. rb = read(fd, buffer, sb.st_size);
  309. if (rb < 0) {
  310. close(fd);
  311. goto error_parse;
  312. }
  313. buffer[sb.st_size] = 0;
  314. close(fd);
  315. if (!XML_Parse(parser, buffer, sb.st_size, 1))
  316. goto error_parse;
  317. XML_ParserFree(parser);
  318. free(buffer);
  319. return doc;
  320. error_parse:
  321. free(buffer);
  322. error_open:
  323. XML_ParserFree(parser);
  324. error_parser_create:
  325. kxml_doc_release(doc);
  326. return NULL;
  327. }
  328. void kxml_doc_release(kxml_doc_t *doc)
  329. {
  330. if (doc) {
  331. kxml_node_t *node;
  332. while (doc->root) {
  333. node = doc->root;
  334. doc->root = node->next;
  335. kexpat_free_node(node);
  336. }
  337. if (doc->filename)
  338. free(doc->filename);
  339. free(doc);
  340. }
  341. }
  342. bool_t kxml_doc_is_valid(const kxml_doc_t *doc)
  343. {
  344. return (bool_t)(doc && doc->root);
  345. }
  346. /*
  347. int kxml_doc_error_caps(kxml_doc_t *doc)
  348. {
  349. doc = doc; // Happy compiler
  350. return kxml_ERR_NOCAPS;
  351. }
  352. int kxml_doc_get_err_line(kxml_doc_t *doc)
  353. {
  354. doc = doc; // Happy compiler
  355. return -1;
  356. }
  357. int kxml_doc_get_err_col(kxml_doc_t *doc)
  358. {
  359. doc = doc; // Happy compiler
  360. return -1;
  361. }
  362. const char *kxml_doc_get_err_msg(kxml_doc_t *doc)
  363. {
  364. doc = doc; // Happy compiler
  365. return "";
  366. }
  367. */
  368. kxml_nodetype_e kxml_node_type(const kxml_node_t *node)
  369. {
  370. if (node)
  371. return node->type;
  372. return KXML_NODE_UNKNOWN;
  373. }
  374. kxml_node_t *kxml_doc_root(const kxml_doc_t *doc)
  375. {
  376. if (doc)
  377. return doc->root;
  378. return NULL;
  379. }
  380. kxml_node_t *kxml_node_parent(const kxml_node_t *node)
  381. {
  382. if (node)
  383. return node->parent;
  384. return NULL;
  385. }
  386. const kxml_node_t *kxml_node_next_child(const kxml_node_t *parent,
  387. const kxml_node_t *curchild)
  388. {
  389. if (curchild)
  390. return curchild->next;
  391. if (parent)
  392. return parent->children;
  393. return NULL;
  394. }
  395. char *kxml_node_attr(const kxml_node_t *node,
  396. const char *attrname)
  397. {
  398. kxml_node_t *n = NULL;
  399. if (!node)
  400. return NULL;
  401. n = node->attributes;
  402. while (n) {
  403. if (strcmp(n->name, attrname) == 0)
  404. return n->content;
  405. n = n->next;
  406. }
  407. return NULL;
  408. }
  409. void kxml_node_attr_free(char *str)
  410. {
  411. str = str; // Happy compiler
  412. // kxml_node_attr() doesn't allocate any memory
  413. // so we don't need to free()
  414. }
  415. char *kxml_node_content(const kxml_node_t *node)
  416. {
  417. char *content = NULL;
  418. kxml_node_t *children = NULL;
  419. if (!node)
  420. return NULL;
  421. children = node->children;
  422. while (children) {
  423. if (children->type == KXML_NODE_TEXT && children->content)
  424. faux_str_cat(&content, children->content);
  425. children = children->next;
  426. }
  427. return content;
  428. }
  429. void kxml_node_content_free(char *str)
  430. {
  431. if (!str)
  432. return;
  433. faux_str_free(str);
  434. }
  435. char *kxml_node_name(const kxml_node_t *node)
  436. {
  437. if (!node)
  438. return NULL;
  439. return node->name;
  440. }
  441. void kxml_node_name_free(char *str)
  442. {
  443. str = str; // Happy compiler
  444. // kxml_node_attr() doesn't allocate any memory
  445. // so we don't need to free()
  446. }