LuaXML_lib.c 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332
  1. /*
  2. LuaXML License
  3. LuaXML is licensed under the terms of the MIT license reproduced below,
  4. the same as Lua itself. This means that LuaXML is free software and can be
  5. used for both academic and commercial purposes at absolutely no cost.
  6. Copyright (C) 2007-2013 Gerald Franz, eludi.net
  7. Permission is hereby granted, free of charge, to any person obtaining a copy
  8. of this software and associated documentation files (the "Software"), to deal
  9. in the Software without restriction, including without limitation the rights
  10. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. copies of the Software, and to permit persons to whom the Software is
  12. furnished to do so, subject to the following conditions:
  13. The above copyright notice and this permission notice shall be included in
  14. all copies or substantial portions of the Software.
  15. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. THE SOFTWARE.
  22. */
  23. /// @module LuaXML
  24. #include "LuaXML_lib.h"
  25. #include <ctype.h>
  26. #include <stdbool.h>
  27. #include <stdio.h>
  28. #include <stdlib.h>
  29. #include <string.h>
  30. /* compatibility with older Lua versions (<5.2) */
  31. #if LUA_VERSION_NUM < 502
  32. // Substitute lua_objlen() for lua_rawlen()
  33. #define lua_rawlen(L, index) lua_objlen(L, index)
  34. // Make use of luaL_register() to achieve same result as luaL_newlib()
  35. #define luaL_newlib(L, funcs) \
  36. do { \
  37. lua_newtable(L); \
  38. luaL_register(L, NULL, funcs); \
  39. } while (0)
  40. #endif
  41. /* API changes for 5.2+ */
  42. #if LUA_VERSION_NUM >= 502
  43. // lua_compare() has replaced lua_equal()
  44. #if !defined(lua_equal)
  45. #define lua_equal(L, index1, index2) lua_compare(L, index1, index2, LUA_OPEQ)
  46. #endif
  47. #endif
  48. /* API changes for 5.3+ */
  49. #if LUA_VERSION_NUM >= 503
  50. // luaL_optinteger() has replaced luaL_optint()
  51. #if !defined(luaL_optint)
  52. #define luaL_optint(L, arg, d) luaL_optinteger(L, arg, d)
  53. #endif
  54. #endif
  55. #define LUAXML_META "LuaXML" // name to be used for metatable
  56. //--- auxliary functions -------------------------------------------
  57. static size_t
  58. find(const char *s, const char *pattern, size_t start)
  59. {
  60. const char *found = strstr(s + start, pattern);
  61. return found ? (size_t)(found - s) : strlen(s);
  62. }
  63. // push (arbitrary Lua) value to be used as tag key, placing it on top of stack
  64. static inline void
  65. push_TAG_key(lua_State *L)
  66. {
  67. /* Note: Currently this is the number 0, which fits in nicely with using
  68. * string keys for attribute-value pairs and also 'stays clear' of the
  69. * array of sub-elements (starting at index 1).
  70. * Theoretically, this could be any kind of Lua value; but when using a
  71. * string key (e.g. "TAG"), extra care needs to be taken that it doesn't
  72. * get confused with an attribute - which means that the str() function
  73. * should be modified accordingly (to recognise and avoid the tag key).
  74. */
  75. lua_pushinteger(L, 0);
  76. }
  77. // convert Lua table at given index to an XML "object", by setting its metatable
  78. static void
  79. make_xml_object(lua_State *L, int index)
  80. {
  81. if (index < 0)
  82. index += lua_gettop(L) + 1; // relative to absolute index
  83. if (!lua_istable(L, index))
  84. luaL_error(L,
  85. "%s() error: invalid type at %d - expected table, got %s",
  86. __func__,
  87. index,
  88. luaL_typename(L, index));
  89. luaL_getmetatable(L, LUAXML_META);
  90. lua_setmetatable(L, index); // assign metatable
  91. }
  92. // push an indentation string for the given level to the Lua stack
  93. static void
  94. push_indentStr(lua_State *L, int level)
  95. {
  96. if (level <= 0) {
  97. lua_pushliteral(L, "");
  98. return;
  99. }
  100. luaL_Buffer b;
  101. luaL_buffinit(L, &b);
  102. // while (level-- > 0) luaL_addlstring(&b, " ", 2);
  103. while (level-- > 0)
  104. luaL_addchar(&b, '\t'); // one TAB char per level
  105. luaL_pushresult(&b);
  106. }
  107. // tests if a string consists entirely of whitespace
  108. static bool
  109. is_whitespace(const char *s)
  110. {
  111. if (!s)
  112. return false; // NULL pointer
  113. if (*s == 0)
  114. return false; // empty string
  115. while (*s)
  116. if (!isspace(*s++))
  117. return false;
  118. return true;
  119. }
  120. // We consider a token "lead in", if it 1) is all whitespace and 2) starts with
  121. // a newline. (This is typical for line breaks plus indentation on nested XML.)
  122. static bool
  123. is_lead_token(const char *s)
  124. {
  125. return is_whitespace(s) && (*s == '\n' || *s == '\r');
  126. }
  127. /*
  128. * For the string at given stack index, substitute any occurrence (exact string
  129. * match) of pattern "p" with the replacement string "r".
  130. * When done, this function will replace the original string with the result.
  131. */
  132. // TODO / Caveat:
  133. // We return the luaL_gsub() pointer, but it's unclear (and untested) if that
  134. // persists after the lua_replace(). Currently the result isn't used anywhere.
  135. static const char *
  136. do_gsub(lua_State *L, int index, const char *p, const char *r)
  137. {
  138. if (index < 0)
  139. index += lua_gettop(L) + 1; // relative to absolute index
  140. const char *result = luaL_gsub(L, lua_tostring(L, index), p, r);
  141. lua_replace(L, index);
  142. return result;
  143. }
  144. /*
  145. * Lua C function to replace a gsub() match with the corresponding character.
  146. * Xml_pushDecode() will use this as a replacement function argument to undo
  147. * the XML encodings, passing one match (sequence of digits) at a time.
  148. *
  149. * Due to the pattern used, the matched string may also be 'x' followed by
  150. * a sequence of hexadecimal characters ("xE4"), which is supported too.
  151. */
  152. static int
  153. XMLencoding_replacement(lua_State *L)
  154. {
  155. const char *matched = lua_tostring(L, 1);
  156. if (matched) {
  157. // support both decimal and hexadecimal conversion
  158. char c = *matched == 'x' ? strtol(++matched, NULL, 16) : atoi(matched);
  159. if (c) {
  160. lua_pushlstring(L, &c, 1); // return character as Lua string
  161. return 1;
  162. } // c == 0 probably indicates conversion failure, return `nil`
  163. }
  164. return 0;
  165. }
  166. /* Lua C callback function for a `find()` match. Sets the upvalue (that will
  167. * later be the result) and stops the iteration.
  168. *
  169. * A small problem here is that the callback handling by iterate() means this
  170. * function cannot simply return the result on the Lua stack. Instead we need
  171. * a "shared" upvalue that can be retrieved 'externally' later. Therefore a
  172. * simple, 'flat' Lua value won't do (it can't be shared); so we'll use a table
  173. * instead and assign the match to t[1].
  174. */
  175. static int
  176. find_on_match(lua_State *L)
  177. {
  178. // Upon entry the Lua stack will have `var` and `depth`
  179. lua_settop(L, 1); // discard depth, leaving var on the stack
  180. lua_rawseti(L, lua_upvalueindex(1), 1); // store to upvalue table
  181. lua_pushboolean(L, false); // return false to stop iteration
  182. return 1;
  183. }
  184. /// strip all leading / trailing whitespace
  185. // @field WS_TRIM
  186. /// remove "lead in" whitespace before tags
  187. // @field WS_NORMALIZE
  188. /// preserve all whitespace, even between tags
  189. // @field WS_PRESERVE
  190. enum whitespace_mode {
  191. WHITESPACE_TRIM,
  192. WHITESPACE_NORMALIZE,
  193. WHITESPACE_PRESERVE
  194. };
  195. // control chars used by the Tokenizer to denote special meanings
  196. #define ESC 27 /* end of scope, closing tag */
  197. #define OPN 28 /* "open", start of tag */
  198. #define CLS 29 /* closes opening tag, actual content follows */
  199. //--- internal tokenizer -------------------------------------------
  200. typedef struct Tokenizer_s {
  201. /// stores string to be tokenized
  202. const char *s;
  203. /// stores size of string to be tokenized
  204. size_t s_size;
  205. /// stores current read position
  206. size_t i;
  207. /// stores current read context
  208. int tagMode;
  209. /// stores flag for "raw" byte sequence, *DON'T* decode any further
  210. int cdata;
  211. /// stores next token, if already determined
  212. const char *m_next;
  213. /// size of next token
  214. size_t m_next_size;
  215. /// pointer to current token
  216. char *m_token;
  217. /// size of current token
  218. size_t m_token_size;
  219. /// capacity of current token
  220. size_t m_token_capacity;
  221. /// whitespace handling
  222. enum whitespace_mode mode;
  223. } Tokenizer;
  224. static Tokenizer *
  225. Tokenizer_new(const char *str, size_t str_size, enum whitespace_mode mode)
  226. {
  227. Tokenizer *tok = calloc(1, sizeof(Tokenizer));
  228. tok->s_size = str_size;
  229. tok->s = str;
  230. tok->mode = mode;
  231. return tok;
  232. }
  233. static void
  234. Tokenizer_delete(Tokenizer *tok)
  235. {
  236. free(tok->m_token);
  237. free(tok);
  238. }
  239. #if LUAXML_DEBUG
  240. static void
  241. Tokenizer_print(Tokenizer *tok)
  242. {
  243. printf(" @%u %s\n",
  244. tok->i,
  245. !tok->m_token ? "(null)"
  246. : (tok->m_token[0] == ESC)
  247. ? "(esc)"
  248. : (tok->m_token[0] == OPN)
  249. ? "(open)"
  250. : (tok->m_token[0] == CLS) ? "(close)"
  251. : tok->m_token);
  252. fflush(stdout);
  253. }
  254. #else
  255. #define Tokenizer_print(tok) /* ignore */
  256. #endif
  257. static const char *
  258. Tokenizer_set(Tokenizer *tok, const char *s, size_t size)
  259. {
  260. if (!size || !s)
  261. return NULL;
  262. free(tok->m_token);
  263. tok->m_token = malloc(size + 1);
  264. strncpy(tok->m_token, s, size);
  265. tok->m_token[size] = 0;
  266. tok->m_token_size = tok->m_token_capacity = size;
  267. Tokenizer_print(tok);
  268. return tok->m_token;
  269. }
  270. static void
  271. Tokenizer_append(Tokenizer *tok, char ch)
  272. {
  273. if (tok->m_token_size + 1 >= tok->m_token_capacity) {
  274. tok->m_token_capacity =
  275. tok->m_token_capacity ? tok->m_token_capacity * 2 : 16;
  276. tok->m_token = realloc(tok->m_token, tok->m_token_capacity);
  277. }
  278. tok->m_token[tok->m_token_size] = ch;
  279. tok->m_token[++tok->m_token_size] = 0;
  280. }
  281. static const char *
  282. Tokenizer_next(Tokenizer *tok)
  283. {
  284. // NUL-terminated strings for the special tokens
  285. static const char ESC_str[] = {ESC, 0};
  286. static const char OPEN_str[] = {OPN, 0};
  287. static const char CLOSE_str[] = {CLS, 0};
  288. if (tok->m_token) {
  289. free(tok->m_token);
  290. tok->m_token = NULL;
  291. tok->m_token_size = tok->m_token_capacity = 0;
  292. }
  293. char quotMode = 0;
  294. int tokenComplete = 0;
  295. while (tok->m_next_size || (tok->i < tok->s_size)) {
  296. tok->cdata = 0;
  297. if (tok->m_next_size) {
  298. Tokenizer_set(tok, tok->m_next, tok->m_next_size);
  299. tok->m_next = NULL;
  300. tok->m_next_size = 0;
  301. return tok->m_token;
  302. }
  303. switch (tok->s[tok->i]) {
  304. case '"':
  305. case '\'':
  306. if (tok->tagMode) {
  307. // toggle quotation mode
  308. if (!quotMode)
  309. quotMode = tok->s[tok->i];
  310. else if (quotMode == tok->s[tok->i])
  311. quotMode = 0;
  312. }
  313. Tokenizer_append(tok, tok->s[tok->i]);
  314. break;
  315. case '<':
  316. if (!quotMode && (tok->i + 4 < tok->s_size)
  317. && (strncmp(tok->s + tok->i, "<!--", 4) == 0))
  318. tok->i = find(tok->s, "-->", tok->i + 4) + 2; // strip comments
  319. else if (!quotMode && (tok->i + 9 < tok->s_size)
  320. && (strncmp(tok->s + tok->i, "<![CDATA[", 9) == 0)) {
  321. if (tok->m_token_size > 0)
  322. // finish current token first, after that reparse CDATA
  323. tokenComplete = 1;
  324. else {
  325. // interpret CDATA
  326. size_t b = tok->i + 9;
  327. tok->i = find(tok->s, "]]>", b) + 3;
  328. size_t cdata_len = tok->i - b - 3;
  329. if (cdata_len > 0) {
  330. tok->cdata = 1; // mark as "raw" byte sequence
  331. return Tokenizer_set(tok, tok->s + b, cdata_len);
  332. }
  333. }
  334. --tok->i;
  335. } else if (!quotMode && (tok->i + 1 < tok->s_size)
  336. && ((tok->s[tok->i + 1] == '?')
  337. || (tok->s[tok->i + 1] == '!')))
  338. tok->i =
  339. find(tok->s, ">", tok->i + 2); // strip meta information
  340. else if (!quotMode && !tok->tagMode) {
  341. if ((tok->i + 1 < tok->s_size) && (tok->s[tok->i + 1] == '/')) {
  342. // "</" sequence that starts a closing tag
  343. tok->m_next = ESC_str;
  344. tok->m_next_size = 1;
  345. tok->i = find(tok->s, ">", tok->i + 2);
  346. } else {
  347. // regular '<' opening a new tag
  348. tok->m_next = OPEN_str;
  349. tok->m_next_size = 1;
  350. tok->tagMode = 1;
  351. }
  352. tokenComplete = 1;
  353. } else
  354. Tokenizer_append(tok, tok->s[tok->i]);
  355. break;
  356. case '/':
  357. if (tok->tagMode && !quotMode) {
  358. tokenComplete = 1;
  359. if ((tok->i + 1 < tok->s_size) && (tok->s[tok->i + 1] == '>')) {
  360. // "/>" sequence = end of 'empty' tag
  361. tok->tagMode = 0;
  362. tok->m_next = ESC_str;
  363. tok->m_next_size = 1;
  364. ++tok->i;
  365. } else
  366. Tokenizer_append(tok, tok->s[tok->i]);
  367. } else
  368. Tokenizer_append(tok, tok->s[tok->i]);
  369. break;
  370. case '>':
  371. if (!quotMode && tok->tagMode) {
  372. // this '>' closes the current tag
  373. tok->tagMode = 0;
  374. tokenComplete = 1;
  375. tok->m_next = CLOSE_str;
  376. tok->m_next_size = 1;
  377. } else
  378. Tokenizer_append(tok, tok->s[tok->i]);
  379. break;
  380. case ' ':
  381. case '\r':
  382. case '\n':
  383. case '\t':
  384. if (tok->tagMode && !quotMode) {
  385. // within a tag, any unquoted whitespace ends the current token
  386. // (= attribute)
  387. if (tok->m_token_size)
  388. tokenComplete = 1;
  389. } else if (tok->m_token_size || tok->mode != WHITESPACE_TRIM)
  390. Tokenizer_append(tok, tok->s[tok->i]);
  391. break;
  392. default:
  393. Tokenizer_append(tok, tok->s[tok->i]);
  394. }
  395. ++tok->i;
  396. if (tok->i >= tok->s_size || (tokenComplete && tok->m_token_size)) {
  397. tokenComplete = 0;
  398. if (tok->mode == WHITESPACE_TRIM) // trim whitespace
  399. while (tok->m_token_size
  400. && isspace(tok->m_token[tok->m_token_size - 1]))
  401. tok->m_token[--tok->m_token_size] = 0;
  402. if (tok->m_token_size)
  403. break;
  404. }
  405. }
  406. Tokenizer_print(tok);
  407. return tok->m_token;
  408. }
  409. //--- local variables ----------------------------------------------
  410. // 'private' table mapping between special chars and their XML substitutions
  411. static int sv_code_ref; // (will receive a LUA reference)
  412. //--- public methods -----------------------------------------------
  413. /** sets or returns tag of a LuaXML object.
  414. This method is just "syntactic sugar" (using a typical Lua term) that allows
  415. the writing of clearer code. LuaXML stores the tag value of an XML statement
  416. at table index 0, hence it can be simply accessed or altered by `var[0]`.
  417. However, writing `var:tag()` for access or `var:tag("newTag")` for altering
  418. may be more self explanatory (and future-proof in case LuaXML's tag handling
  419. should ever change).
  420. @function tag
  421. @param var the variable whose tag should be accessed, a LuaXML object
  422. @tparam ?string tag the new tag to be set
  423. @return If you have passed a new tag, the function will return `var` (with
  424. its tag changed); otherwise the result will be the current tag of `var`
  425. (normally a string).
  426. */
  427. static int
  428. Xml_tag(lua_State *L)
  429. {
  430. // the function will only operate on tables
  431. if
  432. lua_istable(L, 1)
  433. {
  434. lua_settop(L, 2);
  435. push_TAG_key(L); // place tag key on top of stack (#3)
  436. if (lua_type(L, 2) == LUA_TSTRING) {
  437. lua_pushvalue(L, 2); // duplicate the value
  438. lua_rawset(L, 1);
  439. // we return the (modified) table
  440. lua_settop(L, 1);
  441. return 1;
  442. } else {
  443. // "tag" is empty or wrong type, retrieve the current tag
  444. lua_rawget(L, 1);
  445. return 1;
  446. }
  447. }
  448. return 0;
  449. }
  450. /** creates a LuaXML "object", and optionally sets its tag.
  451. The function either sets the metatable of an existing Lua table, or creates a
  452. new (empty) "object". If you pass an optional` tag` string, it will be assigned
  453. to the result.
  454. (It's also possible to call this as `new(tag)`, which creates a new XML object
  455. with the given tag and is equivalent to `new({}, tag)`.)
  456. Note that it's not mandatory to use this function in order to treat a Lua table
  457. as LuaXML object. Setting the metatable just allows the usage of a more
  458. object-oriented syntax (e.g. `xmlvar:str()` instead of `xml.str(xmlvar)`).
  459. XML objects created by `load` or `eval` automatically offer the
  460. object-oriented syntax.
  461. @function new
  462. @param arg (optional) _(1)_ a table to be converted to a LuaXML object,
  463. or _(2)_ the tag of the new LuaXML object
  464. @tparam ?string tag a tag value that will be assigned to the object
  465. @return LuaXML object, either newly created or the conversion of `arg`;
  466. optionally tagged as requested
  467. */
  468. static int
  469. Xml_new(lua_State *L)
  470. {
  471. if (!lua_istable(L, 1)) {
  472. // create a new table and move it to the bottom of the stack (#1),
  473. // possibly shifting other elements "one up"
  474. lua_newtable(L);
  475. lua_insert(L, 1);
  476. }
  477. // element at #1 now is a table, convert to "object"
  478. make_xml_object(L, 1);
  479. if (lua_type(L, 2) == LUA_TSTRING) {
  480. lua_pushcfunction(L, Xml_tag);
  481. lua_pushvalue(L, 1); // duplicate the object table
  482. lua_pushvalue(L, 2); // duplicate the tag (string)
  483. lua_call(L, 2, 0); // call the "tag" function, discarding any result
  484. }
  485. lua_settop(L, 1);
  486. return 1;
  487. }
  488. /** appends a new subordinate LuaXML object to an existing one.
  489. optionally sets tag
  490. @function append
  491. @param var the parent LuaXML object
  492. @tparam ?string tag the tag of the appended LuaXML object
  493. @return appended LuaXML object, or `nil` in case of errors
  494. */
  495. static int
  496. Xml_append(lua_State *L)
  497. {
  498. if (lua_type(L, 1) == LUA_TTABLE) {
  499. lua_settop(L, 2);
  500. lua_pushcfunction(L, Xml_new);
  501. lua_insert(L, 2);
  502. lua_call(L, 1, 1); // new(tag)
  503. lua_pushvalue(L, -1); // duplicate result
  504. lua_rawseti(L, 1, lua_rawlen(L, 1) + 1); // append to parent (elements)
  505. return 1;
  506. }
  507. return 0;
  508. }
  509. // Push XML-encoded string for the Lua value at given index.
  510. // Will automatically use a tostring() conversion first, if necessary.
  511. static void
  512. Xml_pushEncode(lua_State *L, int index)
  513. {
  514. if (index < 0)
  515. index += lua_gettop(L) + 1; // relative to absolute index
  516. if (lua_type(L, index) == LUA_TSTRING)
  517. lua_pushvalue(L, index); // already a string, just duplicate it
  518. else {
  519. lua_getglobal(L, "tostring");
  520. lua_pushvalue(L, index); // duplicate value
  521. lua_call(L, 1, 1); // tostring()
  522. }
  523. // always do "&amp;" first
  524. // (avoids later affecting other substitutions, which may contain '&')
  525. do_gsub(L, -1, "&", "&amp;");
  526. // encode other special entities
  527. lua_rawgeti(L, LUA_REGISTRYINDEX, sv_code_ref);
  528. lua_pushnil(L);
  529. while (lua_next(L, -2)) {
  530. // Lua stack has string to work on (-4), substitution table (-3),
  531. // table key (-2 = special char) and value (-1 = replacement)
  532. // (We want to replace the original char with the XML encoding.)
  533. do_gsub(L, -4, lua_tostring(L, -2), lua_tostring(L, -1));
  534. lua_pop(L, 1); // pop value, leaving key for the next iteration
  535. }
  536. lua_pop(L, 1); // pop substitution table to realign the stack
  537. // transfer string one character at a time, encoding any chars with MSB set
  538. char buf[8];
  539. const unsigned char *s = (unsigned char *)lua_tostring(L, -1);
  540. luaL_Buffer b;
  541. luaL_buffinit(L, &b);
  542. while (*s) {
  543. if (*s < 128)
  544. luaL_addchar(&b, *s); // copy character literally
  545. else {
  546. int len = snprintf(buf, sizeof(buf), "&#%d;", *s); // encode char
  547. luaL_addlstring(&b, buf, len);
  548. }
  549. s++;
  550. }
  551. luaL_pushresult(&b);
  552. lua_replace(L, -2); // (leaving the result on the stack)
  553. }
  554. /*
  555. // Push a string, then do XML conversion on it - result remains on top of stack.
  556. static void Xml_pushEncodeStr(lua_State *L, const char *s, int size) {
  557. if (size == 0) {
  558. lua_pushliteral(L, "");
  559. return;
  560. }
  561. if (size < 0) size = strlen(s);
  562. lua_pushlstring(L, s, size);
  563. Xml_pushEncode(L, -1);
  564. lua_replace(L, -2);
  565. }
  566. */
  567. // Push Lua representation of the given string, while decoding any special XML
  568. // encodings
  569. static void
  570. Xml_pushDecode(lua_State *L, const char *s, int size)
  571. {
  572. if (size == 0) {
  573. lua_pushliteral(L, "");
  574. return;
  575. }
  576. if (size < 0)
  577. size = strlen(s);
  578. // try a gsub() substition of decimal and hexadecimal character encodings
  579. lua_pushlstring(L, s, size); // initial string
  580. lua_pushliteral(L, "gsub");
  581. lua_gettable(L, -2); // using string as object, retrieve the "gsub" function
  582. lua_insert(L, -2); // swap with function, making string the arg #1
  583. lua_pushliteral(L, "&#(x?%x+);"); // pattern for XML encodings (arg #2)
  584. lua_pushcfunction(L, XMLencoding_replacement); // replacement func (arg #3)
  585. lua_call(L, 3, 1); // three parameters, one result (the substituted string)
  586. lua_rawgeti(L, LUA_REGISTRYINDEX, sv_code_ref);
  587. lua_pushnil(L);
  588. while (lua_next(L, -2)) {
  589. // Lua stack has string to work on (-4), substitution table (-3),
  590. // table key (-2 = special char) and value (-1 = replacement)
  591. // (We want to replace the XML encoding with the original char.)
  592. do_gsub(L, -4, lua_tostring(L, -1), lua_tostring(L, -2));
  593. lua_pop(L, 1); // pop value, leaving key for the next iteration
  594. }
  595. lua_pop(L, 1); // pop substitution table, leaving result string on stack
  596. do_gsub(L, -1, "&amp;", "&"); // this should always be done last
  597. }
  598. /** parses an XML string into a Lua table.
  599. The table will contain a representation of the XML tag, attributes (and their
  600. values), and element content / subelements (either as strings or nested LuaXML
  601. "objects").
  602. Note: Parsing "wide" strings or Unicode (UCS-2, UCS-4, UTF-16) currently is
  603. __not__ supported. If needed, convert such `xml` data to UTF-8 before passing it
  604. to `eval()`. UTF-8 should be safe to use, and this function will also recognize
  605. and ignore a UTF-8 BOM (byte order mark) at the start of `xml`.
  606. @function eval
  607. @tparam string|userdata xml
  608. the XML to be converted. When passing a userdata type `xml` value, it must
  609. point to a C-style (NUL-terminated) string.
  610. @tparam ?number mode
  611. whitespace handling mode, one of the `WS_*` constants - see [Fields](#Fields).
  612. defaults to `WS_TRIM` (compatible to previous LuaXML versions)
  613. @return a LuaXML object containing the XML data, or `nil` in case of errors
  614. */
  615. static int
  616. Xml_eval(lua_State *L)
  617. {
  618. enum whitespace_mode mode = luaL_optint(L, 2, WHITESPACE_TRIM);
  619. const char *str;
  620. size_t str_size;
  621. if (lua_isuserdata(L, 1)) {
  622. str = lua_touserdata(L, 1);
  623. str_size = strlen(str);
  624. } else
  625. str = luaL_checklstring(L, 1, &str_size);
  626. if (str_size >= 3 && strncmp(str, "\xEF\xBB\xBF", 3) == 0) {
  627. // ignore / skip over UTF-8 BOM (byte order mark)
  628. str += 3;
  629. str_size -= 3;
  630. }
  631. Tokenizer *tok = Tokenizer_new(str, str_size, mode);
  632. lua_settop(L, 1);
  633. const char *token;
  634. int firstStatement = 1;
  635. while ((token = Tokenizer_next(tok)))
  636. if (*token == OPN) { // new tag found
  637. if (lua_gettop(L) > 1) {
  638. lua_newtable(L);
  639. lua_pushvalue(L,
  640. -1); // duplicate table (keep one copy on stack)
  641. lua_rawseti(L,
  642. -3,
  643. lua_rawlen(L, -3) + 1); // set parent subelement
  644. } else {
  645. if (firstStatement) {
  646. lua_newtable(L);
  647. firstStatement = 0;
  648. } else
  649. return 0;
  650. }
  651. make_xml_object(L, -1); // assign metatable
  652. // parse tag and content:
  653. push_TAG_key(L); // place tag key on top of stack
  654. lua_pushstring(L, Tokenizer_next(tok));
  655. lua_rawset(L, -3);
  656. while ((token = Tokenizer_next(tok)) && (*token != CLS)
  657. && (*token != ESC)) {
  658. // parse tag header
  659. size_t sepPos = find(token, "=", 0);
  660. if (token[sepPos]) { // regular attribute (key="value")
  661. const char *aVal = token + sepPos + 2;
  662. lua_pushlstring(L, token, sepPos);
  663. Xml_pushDecode(L, aVal, strlen(aVal) - 1);
  664. lua_rawset(L, -3);
  665. }
  666. }
  667. if (!token || (*token == ESC)) {
  668. // this tag has no content, only attributes
  669. if (lua_gettop(L) > 2)
  670. lua_pop(L, 1);
  671. else
  672. break;
  673. }
  674. } else if (*token == ESC) { // previous tag is over
  675. if (lua_gettop(L) > 2)
  676. lua_pop(L, 1); // pop current table
  677. else
  678. break;
  679. } else { // read elements
  680. if (lua_gettop(L) > 1) {
  681. // when normalizing, we ignore tokens considered "lead-in" type
  682. if (mode != WHITESPACE_NORMALIZE || !is_lead_token(token)) {
  683. if (tok->cdata) // "raw" mode, don't change token string!
  684. lua_pushstring(L, token);
  685. else
  686. Xml_pushDecode(L, token, -1);
  687. lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
  688. }
  689. } else // element stack is empty, i.e. we encountered a token
  690. // *before* any tag
  691. if (!is_whitespace(token))
  692. luaL_error(L,
  693. "Malformed XML: non-empty string '%s' before any "
  694. "tag (parser pos %d)",
  695. token,
  696. (int)tok->i);
  697. }
  698. Tokenizer_delete(tok);
  699. return lua_gettop(L) - 1;
  700. }
  701. /** loads XML data from a file and returns it as table.
  702. Basically, this is just calling `eval` on the given file's content.
  703. @function load
  704. @tparam string filename the name and path of the file to be loaded
  705. @tparam ?number mode whitespace handling mode, defaults to `WS_TRIM`
  706. @return a Lua table representing the XML data, or `nil` in case of errors
  707. */
  708. static int
  709. Xml_load(lua_State *L)
  710. {
  711. const char *filename = luaL_checkstring(L, 1);
  712. FILE *file = fopen(filename, "r");
  713. if (!file)
  714. return luaL_error(L,
  715. "LuaXML ERROR: \"%s\" file error or file not found!",
  716. filename);
  717. fseek(file, 0, SEEK_END);
  718. size_t sz = ftell(file);
  719. rewind(file);
  720. char *buffer = malloc(sz + 1);
  721. sz = fread(buffer, 1, sz, file);
  722. fclose(file);
  723. buffer[sz] = 0;
  724. lua_pushlightuserdata(L, buffer);
  725. lua_replace(L, 1);
  726. int result = Xml_eval(L);
  727. free(buffer);
  728. return result;
  729. };
  730. /** registers a custom code for the conversion between non-standard characters
  731. and XML character entities.
  732. By default, only the most basic entities are known to LuaXML:
  733. " < > '
  734. On top (and independent) of that, the **ampersand** sign always gets encoded /
  735. decoded separately: `&amp;` &harr; `&amp;amp;`. Character codes above 127 are
  736. directly converted to an appropriate XML encoding, representing the character
  737. number (e.g. `&amp;#160;`). If other special encodings are needed, they can be
  738. registered using this function.
  739. Note: LuaXML now manages these encodings in a (private) standard Lua table.
  740. This allows you to replace entries by calling `registerCode()` again, using the
  741. same `decoded` and a different `encoded`. Encodings may even be removed later,
  742. by explictly registering a `nil` value: `registerCode(decoded, nil)`.
  743. @function registerCode
  744. @tparam string decoded the character (sequence) to be used within Lua
  745. @tparam string encoded the character entity to be used in XML
  746. @see encode, decode
  747. */
  748. static int
  749. Xml_registerCode(lua_State *L)
  750. {
  751. // We require the "decoded" string, but allow `nil` as argument #2.
  752. // That way, users may remove entries from the table again.
  753. luaL_checkstring(L, 1);
  754. if (!lua_isnoneornil(L, 2))
  755. luaL_checkstring(L, 2);
  756. lua_settop(L, 2);
  757. lua_rawgeti(L, LUA_REGISTRYINDEX, sv_code_ref); // get translation table
  758. lua_insert(L, 1);
  759. lua_rawset(L, 1); // assign key-value pair (k "decoded" -> v "encoded")
  760. return 0;
  761. }
  762. /** converts a string to XML encoding.
  763. This function transforms` str` by replacing any special characters with
  764. suitable XML encodings.
  765. @usage
  766. print(xml.encode("<->")) -- "&lt;-&gt;"
  767. @function encode
  768. @tparam string str string to be transformed
  769. @treturn string the XML-encoded string
  770. @see decode, registerCode
  771. */
  772. static int
  773. Xml_encode(lua_State *L)
  774. {
  775. luaL_checkstring(L, 1); // make sure arg #1 is a string
  776. Xml_pushEncode(L, 1); // and convert it
  777. return 1;
  778. }
  779. /** converts a string from XML encoding.
  780. This function transforms` str` by replacing any special XML encodings with
  781. their "plain text" counterparts.
  782. @usage
  783. print((xml.decode("&lt;-&gt;")) -- "<->"
  784. @function decode
  785. @tparam string str string to be transformed
  786. @treturn string the decoded string
  787. @see encode, registerCode
  788. */
  789. static int
  790. Xml_decode(lua_State *L)
  791. {
  792. size_t size;
  793. luaL_checklstring(L, 1, &size); // make sure arg #1 is a string
  794. Xml_pushDecode(L, lua_tostring(L, 1), size); // and convert it
  795. return 1;
  796. }
  797. /** converts any Lua value to an XML string.
  798. @function str
  799. @param value
  800. the value to be converted, normally a table (LuaXML object). However this
  801. function will 'encapsulate' other Lua values (of arbitrary type) in a way that
  802. should make them valid XML.
  803. <br>Note: Passing no `value` will cause the function to return `nil`.
  804. @tparam ?number indent
  805. indentation level for 'pretty' output. Mainly for internal use, defaults to 0.
  806. @tparam ?string tag
  807. the tag to be used in case `value` doesn't already have an 'implicit' tag.
  808. Mainly for internal use.
  809. @treturn string
  810. an XML string, or `nil` in case of errors.
  811. */
  812. static int
  813. Xml_str(lua_State *L)
  814. {
  815. // Note:
  816. // Be very careful about mixing Lua stack usage and buffer access here.
  817. // The stack *must* be (re)balanced before accessing "b", i.e. any output
  818. // should only occur at the same Lua stack level as the previous one!
  819. luaL_Buffer b;
  820. lua_settop(L, 3);
  821. int type = lua_type(L, 1); // type of "value"
  822. if (type == LUA_TNIL)
  823. return 0;
  824. if (type == LUA_TTABLE) {
  825. push_TAG_key(L);
  826. lua_rawget(L, 1); // retrieve tag entry from the table (may be `nil`)
  827. // order of precedence: value[0], explicit tag string, Lua type name
  828. const char *tag = lua_tostring(L, -1);
  829. if (!tag)
  830. tag = lua_tostring(L, 3);
  831. if (!tag)
  832. tag = lua_typename(L, type);
  833. // Four elements already on stack: value, indent, tag, value[0]
  834. // Use a string (#5) to manage (concatenate) simple attributes
  835. lua_pushliteral(L, "");
  836. // And a table (#6) to take care of (collect) 'extended' attributes
  837. lua_newtable(L);
  838. size_t table_attr = 0;
  839. luaL_buffinit(L, &b);
  840. push_indentStr(L, lua_tointeger(L, 2));
  841. luaL_addvalue(&b);
  842. luaL_addchar(&b, '<');
  843. luaL_addstring(&b, tag);
  844. // Iterate over string keys (= attributes)
  845. lua_pushnil(L);
  846. while (lua_next(L, 1)) {
  847. // (k, v) pair on the stack
  848. if (lua_type(L, -2) == LUA_TSTRING) {
  849. // (the "_M" test here is to avoid recursion on module tables)
  850. if (lua_istable(L, -1) && strcmp(lua_tostring(L, -2), "_M")) {
  851. lua_pushcfunction(L, Xml_str);
  852. lua_pushvalue(L, -2); // duplicate "v"
  853. lua_pushinteger(L, lua_tointeger(L, 2) + 1); // indent + 1
  854. lua_pushvalue(L, -4); // duplicate "k"
  855. lua_call(L, 3, 1); // xml.str(v, indent + 1, k)
  856. lua_rawseti(L, 6, ++table_attr); // append string to table
  857. } else {
  858. Xml_pushEncode(L, -1); // encode(tostring(v))
  859. lua_pushfstring(L,
  860. "%s %s=\"%s\"",
  861. lua_tostring(L, 5),
  862. lua_tostring(L, -3),
  863. lua_tostring(L, -1));
  864. lua_replace(L, 5); // new attribute string
  865. lua_pop(L, 1); // realign stack
  866. }
  867. }
  868. lua_pop(L, 1); // pop <v>alue, leaving <k>ey for next iteration
  869. }
  870. // append "simple" attribute string to the output
  871. if (lua_rawlen(L, 5) > 0)
  872. luaL_addstring(&b, lua_tostring(L, 5));
  873. size_t count = lua_rawlen(L, 1); // number of "array" (sub)elements
  874. if (count == 0 && table_attr == 0) {
  875. // no sub-elements and no extended attr -> close tag and we're done
  876. luaL_addlstring(&b, " />\n", 4);
  877. luaL_pushresult(&b);
  878. return 1;
  879. }
  880. luaL_addchar(&b, '>'); // close opening tag
  881. if (count == 1 && table_attr == 0) {
  882. // single subelement, no extended attributes
  883. lua_rawgeti(L, 1, 1); // value[1]
  884. if (!lua_istable(L, -1)) {
  885. // output as single string, then close tag
  886. Xml_pushEncode(L, -1); // encode(tostring(value[1]))
  887. lua_replace(L, -2);
  888. luaL_addvalue(&b); // add and pop
  889. luaL_addlstring(&b, "</", 2);
  890. luaL_addstring(&b, tag);
  891. luaL_addlstring(&b, ">\n", 2);
  892. luaL_pushresult(&b);
  893. return 1;
  894. }
  895. lua_pop(L, 1); // discard (table) value, to realign stack
  896. }
  897. luaL_addchar(&b, '\n');
  898. // Loop over all the sub-elements, placing each on a separate line
  899. size_t k;
  900. for (k = 1; k <= count; k++) {
  901. #if LUA_VERSION_NUM < 503
  902. lua_rawgeti(L, 1, k);
  903. type = lua_type(L, -1);
  904. #else
  905. type = lua_rawgeti(L, 1, k); // (Lua 5.3 returns type directly)
  906. #endif
  907. if (type == LUA_TSTRING) {
  908. push_indentStr(L, lua_tointeger(L, 2) + 1); // indentation
  909. Xml_pushEncode(L, -2);
  910. lua_remove(L, -3);
  911. lua_pushliteral(L, "\n");
  912. lua_concat(L, 3);
  913. } else {
  914. lua_pushcfunction(L, Xml_str);
  915. lua_insert(L, -2); // place function before value
  916. lua_pushinteger(L, lua_tointeger(L, 2) + 1); // indent + 1
  917. lua_call(L, 2, 1); // xml.str(v, indent + 1)
  918. }
  919. luaL_addvalue(&b); // add (string) to output, pop from stack
  920. }
  921. // Finally we'll take care of the "extended" (table-type) attributes.
  922. // The output is appended after the regular sub-elements, in order
  923. // not to affect their numbering.
  924. // Just process the corresponding table, concatenating all entries:
  925. for (k = 1; k <= table_attr; k++) {
  926. lua_rawgeti(L, 6, k);
  927. luaL_addvalue(&b);
  928. }
  929. // closing tag
  930. push_indentStr(L, lua_tointeger(L, 2));
  931. luaL_addvalue(&b);
  932. luaL_addlstring(&b, "</", 2);
  933. luaL_addstring(&b, tag);
  934. luaL_addlstring(&b, ">\n", 2);
  935. luaL_pushresult(&b);
  936. return 1;
  937. }
  938. // Getting here means a "flat" Lua value, format to XML as a single string
  939. const char *tag = lua_tostring(L, 3);
  940. if (!tag)
  941. tag = lua_typename(L, type); // use either tag or the type name
  942. luaL_buffinit(L, &b);
  943. push_indentStr(L, lua_tointeger(L, 2));
  944. luaL_addvalue(&b);
  945. luaL_addchar(&b, '<');
  946. luaL_addstring(&b, tag);
  947. luaL_addchar(&b, '>');
  948. Xml_pushEncode(L, 1); // encode(tostring(value))
  949. luaL_addvalue(&b);
  950. luaL_addlstring(&b, "</", 2);
  951. luaL_addstring(&b, tag);
  952. luaL_addlstring(&b, ">\n", 2);
  953. luaL_pushresult(&b);
  954. return 1;
  955. }
  956. /** match XML entity against given (optional) criteria.
  957. Passing `nil` for one of the` tag`, `key`, or `value` parameters means "don't
  958. care" (i.e. match anything for that particular aspect). So for example
  959. var:match(nil, "text", nil)
  960. -- or shorter, but identical: var:match(nil, "text")
  961. will look for an XML attribute (name) "text" to be present in `var`, but won't
  962. consider its value or the tag of `var`.
  963. Note: If you want to test for a specific attribute `value`, so also have to
  964. supply a `key` - otherwise `value` will be ignored.
  965. @usage
  966. -- each of these will either return `x`, or `nil` in case of no match
  967. x:match("foo") -- test for x:tag() == "foo"
  968. x:match(nil, "bar") -- test if x has a "bar" attribute
  969. x:match(nil, "foo", "bar") -- test if x has a "foo" attribute that equals "bar"
  970. x:match("foobar", "foo", "bar") -- test for "foobar" tag, and attr "foo" ==
  971. "bar"
  972. @function match
  973. @param var
  974. the variable to test, normally a Lua table or LuaXML object. (If `var` is not
  975. a table type, the test always fails.)
  976. @tparam ?string tag
  977. If set, has to match the XML `tag` (i.e. must be equal to the `tag(var, nil)`
  978. result)
  979. @tparam ?string key
  980. If set, a corresponding **attribute key** needs to be present (exact name
  981. match).
  982. @param value (optional)
  983. arbitrary Lua value. If set, the **attribute value** for `key` has to match it.
  984. @return
  985. either `nil` for no match; or the `var` argument properly converted to a
  986. LuaXML object, equivalent to `xml.new(var)`.
  987. This allows you to either make direct use of the matched LuaXML object, or to
  988. use the return value in a boolean test (`if xml.match(...)`), which is a common
  989. Lua idiom.
  990. */
  991. static int
  992. Xml_match(lua_State *L)
  993. {
  994. if (lua_type(L, 1) == LUA_TTABLE) {
  995. if (!lua_isnoneornil(L, 2)) {
  996. push_TAG_key(L);
  997. lua_rawget(L, 1); // get the tag value from var
  998. if (!lua_equal(L, -1, 2))
  999. return 0; // tag mismatch, return `nil`
  1000. lua_pop(L, 1); // realign stack
  1001. }
  1002. if (lua_type(L, 3) == LUA_TSTRING) {
  1003. lua_pushvalue(L, 3); // duplicate attribute key
  1004. lua_rawget(L, 1); // try to get value from var
  1005. if (lua_isnil(L, -1))
  1006. return 0; // no such attribute
  1007. if (!lua_isnoneornil(L, 4)) {
  1008. if (!lua_equal(L, -1, 4))
  1009. return 0; // attribute value mismatch
  1010. }
  1011. }
  1012. lua_settop(L, 1);
  1013. make_xml_object(L, 1);
  1014. return 1;
  1015. }
  1016. return 0;
  1017. }
  1018. /** iterates a LuaXML object,
  1019. invoking a callback function for all matching (sub)elements.
  1020. The iteration starts with the variable `var` itself (= default depth 0).
  1021. A callback function `cb` gets invoked for each `match`, depending on the
  1022. specified criteria. If the `r` flag is set, the process will
  1023. repeat **recursively** for the subelements of `var` (at depth + 1). You can
  1024. limit the scope by setting a maximum depth, or have the callback function
  1025. explicitly request to stop the iteration (by returning `false`).
  1026. @function iterate
  1027. @param var the table (LuaXML object) to iterate
  1028. @tparam function cb
  1029. callback function. `callback(var, depth)` will be called for each matching
  1030. element.<br>
  1031. The function may return `false` to request a stop; if its result is
  1032. any other value (including `nil`), the iteration will continue.
  1033. @tparam ?string tag XML tag to be matched
  1034. @tparam ?string key attribute key to be matched
  1035. @param value (optional) attribute value to be matched
  1036. @tparam ?boolean r
  1037. recursive operation. If `true`, also iterate over the subelements of `var`
  1038. @tparam ?number max maximum depth allowed
  1039. @tparam ?number d initial depth value, defaults to 0
  1040. @return
  1041. The function returns two values: a counter representing the number of elements
  1042. that were successfully matched (and processed), and a boolean completion flag.
  1043. The latter is `true` for an exhaustive iteration, and `false` if was stopped
  1044. from the callback.
  1045. @see match
  1046. */
  1047. static int
  1048. Xml_iterate(lua_State *L)
  1049. {
  1050. lua_settop(L, 8);
  1051. luaL_checktype(L, 2, LUA_TFUNCTION); // callback must be a function
  1052. int maxdepth = luaL_optint(L, 7, -1); // default (< 0) indicates "no limit"
  1053. int depth = lua_tointeger(L, 8);
  1054. int count = 0;
  1055. bool cont = true;
  1056. // examine "var" element first
  1057. lua_pushcfunction(L, Xml_match);
  1058. lua_pushvalue(L, 1); // var
  1059. lua_pushvalue(L, 3); // tag
  1060. lua_pushvalue(L, 4); // key
  1061. lua_pushvalue(L, 5); // value
  1062. lua_call(L, 4, 1);
  1063. if (!lua_isnil(L, -1)) { // "var" matches, invoke callback
  1064. count = 1;
  1065. lua_pushvalue(L, 2); // duplicate function
  1066. lua_insert(L, -2);
  1067. lua_pushinteger(L, depth);
  1068. lua_call(L, 2, 1);
  1069. lua_pushboolean(L, false);
  1070. cont = !lua_equal(L, -1, -2);
  1071. lua_pop(L, 2);
  1072. } else
  1073. lua_pop(L, 1);
  1074. if (cont && lua_toboolean(L, 6) && lua_type(L, 1) == LUA_TTABLE) {
  1075. // process "children" / sub-elements recursively
  1076. depth += 1;
  1077. if (maxdepth < 0 || depth <= maxdepth) {
  1078. int k = 0;
  1079. while (true) {
  1080. lua_pushcfunction(L, Xml_iterate);
  1081. lua_rawgeti(L, 1, ++k);
  1082. if (lua_isnil(L, -1))
  1083. break; // no element var[k], exit loop
  1084. lua_pushvalue(L, 2);
  1085. lua_pushvalue(L, 3);
  1086. lua_pushvalue(L, 4);
  1087. lua_pushvalue(L, 5);
  1088. lua_pushboolean(L, true);
  1089. lua_pushvalue(L, 7);
  1090. lua_pushinteger(L, depth);
  1091. lua_call(L, 8, 2); // done, continue = iterate(var[k], ...)
  1092. count += lua_tointeger(L, -2);
  1093. if (!lua_toboolean(L, -1)) {
  1094. lua_pushinteger(L, count);
  1095. lua_pushboolean(L, false);
  1096. return 2;
  1097. }
  1098. lua_pop(L, 2);
  1099. }
  1100. }
  1101. }
  1102. lua_pushinteger(L, count);
  1103. lua_pushboolean(L, true);
  1104. return 2;
  1105. }
  1106. /** recursively searches a Lua table for a subelement
  1107. matching the provided tag and attribute. See the description of `match` for
  1108. the logic involved with testing for` tag`, `key` and `value`.
  1109. @function find
  1110. @param var the table to be searched in
  1111. @tparam ?string tag the XML tag to be found
  1112. @tparam ?string key the attribute key (= exact name) to be found
  1113. @param value (optional) the attribute value to be found
  1114. @return the first (sub-)table that satisfies the search condition,
  1115. or `nil` for no match
  1116. */
  1117. static int
  1118. Xml_find(lua_State *L)
  1119. {
  1120. lua_settop(L, 4); // accept at most four parameters for this function
  1121. lua_newtable(L); // upon a match, this table will receive our result as t[1]
  1122. lua_insert(L, 1); // (move it before anything else)
  1123. lua_pushcfunction(L, Xml_iterate);
  1124. lua_insert(L, 2); // iterate is now stack arg #2, `var` at #3
  1125. lua_pushvalue(L, 1); // duplicate the table (for use as upvalue)
  1126. lua_pushcclosure(L, find_on_match, 1); // create a C closure
  1127. lua_insert(L, 4); // place the callback function (closure) at #4
  1128. // (`tag`, `key` and `value` have moved to #5, #6 and #7 respectively)
  1129. lua_pushboolean(L, true); // set "recursive" flag (#8)
  1130. // iterate(var, find_on_match, tag, key, value, true), discarding results
  1131. // (but if something matches, we expect that `find_on_match` sets t[1])
  1132. lua_call(L, 6, 0);
  1133. lua_rawgeti(L, 1, 1);
  1134. return 1; // returns result[1], which may be `nil` (if no match)
  1135. }
  1136. #ifdef __cplusplus
  1137. extern "C" {
  1138. #endif
  1139. int _EXPORT
  1140. luaopen_LuaXML_lib(lua_State *L)
  1141. {
  1142. static const struct luaL_Reg funcs[] = {{"append", Xml_append},
  1143. {"decode", Xml_decode},
  1144. {"encode", Xml_encode},
  1145. {"eval", Xml_eval},
  1146. {"find", Xml_find},
  1147. {"iterate", Xml_iterate},
  1148. {"load", Xml_load},
  1149. {"match", Xml_match},
  1150. {"new", Xml_new},
  1151. {"registerCode", Xml_registerCode},
  1152. {"str", Xml_str},
  1153. {"tag", Xml_tag},
  1154. {NULL, NULL}};
  1155. luaL_newlib(L, funcs);
  1156. // create a metatable for LuaXML "objects"
  1157. luaL_newmetatable(L, LUAXML_META);
  1158. lua_pushliteral(L, "__index");
  1159. lua_pushvalue(L, -3); // duplicate the module table
  1160. lua_rawset(L, -3); // and set it as metaindex
  1161. lua_pushliteral(L, "__tostring");
  1162. lua_pushcfunction(L, Xml_str);
  1163. lua_rawset(L, -3); // set metamethod
  1164. lua_pop(L, 1); // drop value (metatable)
  1165. // expose API constants (via the module table)
  1166. lua_pushinteger(L, WHITESPACE_TRIM);
  1167. lua_setfield(L, -2, "WS_TRIM");
  1168. lua_pushinteger(L, WHITESPACE_NORMALIZE);
  1169. lua_setfield(L, -2, "WS_NORMALIZE");
  1170. lua_pushinteger(L, WHITESPACE_PRESERVE);
  1171. lua_setfield(L, -2, "WS_PRESERVE");
  1172. // register default codes
  1173. // Note: We'll always handle "&amp;" separately!
  1174. lua_newtable(L);
  1175. lua_pushliteral(L, "&lt;");
  1176. lua_setfield(L, -2, "<");
  1177. lua_pushliteral(L, "&gt;");
  1178. lua_setfield(L, -2, ">");
  1179. lua_pushliteral(L, "&quot;");
  1180. lua_setfield(L, -2, "\"");
  1181. lua_pushliteral(L, "&apos;");
  1182. lua_setfield(L, -2, "'");
  1183. sv_code_ref = luaL_ref(L, LUA_REGISTRYINDEX); // reference (and pop table)
  1184. return 1; // return module (table)
  1185. }
  1186. #ifdef __cplusplus
  1187. } // extern "C"
  1188. #endif