Instrument Neutral Distributed Interface INDI  1.9.2
lilxml.c
Go to the documentation of this file.
1 #if 0
2 liblilxml
3 Copyright (C) 2003 Elwood C. Downey
4 
5 This library is free software;
6 you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation;
9 either
10 version 2.1 of the License, or (at your option) any later version.
11 
12 This library is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY;
14 without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17 
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library;
20 if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 
23 #endif
24 
25 /* little DOM-style XML parser.
26  * only handles elements, attributes and pcdata content.
27  * <! ... > and <? ... > are silently ignored.
28  * pcdata is collected into one string, sans leading whitespace first line.
29  *
30  * #define MAIN_TST to create standalone test program
31  */
32 
33 #include <ctype.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <assert.h>
38 
39 #if defined(_MSC_VER)
40 #define snprintf _snprintf
41 #pragma warning(push)
42 #pragma warning(disable : 4996)
44 #endif
45 
46 #include "lilxml.h"
47 
48 /* used to efficiently manage growing malloced string space */
49 typedef struct
50 {
51  char *s; /* malloced memory for string */
52  int sl; /* string length, sans trailing \0 */
53  int sm; /* total malloced bytes */
54 } String;
55 #define MINMEM 64 /* starting string length */
56 
57 static int oneXMLchar(LilXML *lp, int c, char ynot[]);
58 static void initParser(LilXML *lp);
59 static void pushXMLEle(LilXML *lp);
60 static void popXMLEle(LilXML *lp);
61 static void resetEndTag(LilXML *lp);
62 static XMLAtt *growAtt(XMLEle *e);
63 static XMLEle *growEle(XMLEle *pe);
64 static void freeAtt(XMLAtt *a);
65 static int isTokenChar(int start, int c);
66 static void growString(String *sp, int c);
67 static void appendString(String *sp, const char *str);
68 static void freeString(String *sp);
69 static void newString(String *sp);
70 static void *moremem(void *old, int n);
71 
72 typedef enum {
73  LOOK4START = 0, /* looking for first element start */
74  LOOK4TAG, /* looking for element tag */
75  INTAG, /* reading tag */
76  LOOK4ATTRN, /* looking for attr name, > or / */
77  INATTRN, /* reading attr name */
78  LOOK4ATTRV, /* looking for attr value */
79  SAWSLASH, /* saw / in element opening */
80  INATTRV, /* in attr value */
81  ENTINATTRV, /* in entity in attr value */
82  LOOK4CON, /* skipping leading content whitespc */
83  INCON, /* reading content */
84  ENTINCON, /* in entity in pcdata */
85  SAWLTINCON, /* saw < in content */
86  LOOK4CLOSETAG, /* looking for closing tag after < */
87  INCLOSETAG /* reading closing tag */
88 } State; /* parsing states */
89 
90 /* maintain state while parsing */
91 struct LilXML_
92 {
93  State cs; /* current state */
94  int ln; /* line number for diags */
95  XMLEle *ce; /* current element being built */
96  String endtag; /* to check for match with opening tag*/
97  String entity; /* collect entity seq */
98  int delim; /* attribute value delimiter */
99  int lastc; /* last char (just used wiht skipping)*/
100  int skipping; /* in comment or declaration */
101  int inblob; /* in oneBLOB element */
102 };
103 
104 /* internal representation of a (possibly nested) XML element */
105 struct xml_ele_
106 {
107  String tag; /* element tag */
108  XMLEle *pe; /* parent element, or NULL if root */
109  XMLAtt **at; /* list of attributes */
110  int nat; /* number of attributes */
111  int ait; /* used to iterate over at[] */
112  XMLEle **el; /* list of child elements */
113  int nel; /* number of child elements */
114  int eit; /* used to iterate over el[] */
115  String pcdata; /* character data in this element */
116  int pcdata_hasent; /* 1 if pcdata contains an entity char*/
117 };
118 
119 /* internal representation of an attribute */
120 struct xml_att_
121 {
122  String name; /* name */
123  String valu; /* value */
124  XMLEle *ce; /* containing element */
125 };
126 
127 /* characters that need escaping as "entities" in attr values and pcdata
128  */
129 static char entities[] = "&<>'\"";
130 
131 /* default memory managers, override with lilxmlMalloc() */
132 static void *(*mymalloc)(size_t size) = malloc;
133 static void *(*myrealloc)(void *ptr, size_t size) = realloc;
134 static void (*myfree)(void *ptr) = free;
135 
136 /* install new version of malloc/realloc/free.
137  * N.B. don't call after first use of any other lilxml function
138  */
139 void lilxmlMalloc(void *(*newmalloc)(size_t size), void *(*newrealloc)(void *ptr, size_t size),
140  void (*newfree)(void *ptr))
141 {
142  mymalloc = newmalloc;
143  myrealloc = newrealloc;
144  myfree = newfree;
145 }
146 
147 /* pass back a fresh handle for use with our other functions */
149 {
150  LilXML *lp = (LilXML *)moremem(NULL, sizeof *lp);
151  memset(lp, 0, sizeof *lp);
152  initParser(lp);
153  return (lp);
154 }
155 
156 /* discard */
157 void delLilXML(LilXML *lp)
158 {
159  delXMLEle(lp->ce);
160  freeString(&lp->endtag);
161  (*myfree)(lp);
162 }
163 
164 /* delete ep and all its children and remove from parent's list if known */
165 void delXMLEle(XMLEle *ep)
166 {
167  int i;
168 
169  /* benign if NULL */
170  if (!ep)
171  return;
172 
173  /* delete all parts of ep */
174  freeString(&ep->tag);
175  freeString(&ep->pcdata);
176  if (ep->at)
177  {
178  for (i = 0; i < ep->nat; i++)
179  freeAtt(ep->at[i]);
180  (*myfree)(ep->at);
181  }
182  if (ep->el)
183  {
184  for (i = 0; i < ep->nel; i++)
185  {
186  /* forget parent so deleting doesn't modify _this_ el[] */
187  ep->el[i]->pe = NULL;
188 
189  delXMLEle(ep->el[i]);
190  }
191  (*myfree)(ep->el);
192  }
193 
194  /* remove from parent's list if known */
195  if (ep->pe)
196  {
197  XMLEle *pe = ep->pe;
198  for (i = 0; i < pe->nel; i++)
199  {
200  if (pe->el[i] == ep)
201  {
202  memmove(&pe->el[i], &pe->el[i + 1], (--pe->nel - i) * sizeof(XMLEle *));
203  break;
204  }
205  }
206  }
207 
208  /* delete ep itself */
209  (*myfree)(ep);
210 }
211 
212 //#define WITH_MEMCHR
213 XMLEle **parseXMLChunk(LilXML *lp, char *buf, int size, char ynot[])
214 {
215  unsigned int nnodes = 1;
216  XMLEle **nodes = (XMLEle **)malloc(nnodes * sizeof *nodes);
217  *nodes = NULL;
218  char *curr = buf;
219  int s;
220  ynot[0] = '\0';
221 
222  if (lp->inblob)
223  {
224 #ifdef WITH_ENCLEN
225  if (size < lp->ce->pcdata.sm - lp->ce->pcdata.sl)
226  {
227  memcpy((void *)(lp->ce->pcdata.s + lp->ce->pcdata.sl), (const void *)buf, size);
228  lp->ce->pcdata.sl += size;
229  return nodes;
230  }
231  else
232  lp->inblob = 0;
233 #endif
234 #ifdef WITH_MEMCHR
235  char *ltpos = memchr(buf, '<', size);
236  if (!ltpos)
237  {
238  lp->ce->pcdata.s = (char *)moremem(lp->ce->pcdata.s, lp->ce->pcdata.sm + size);
239  lp->ce->pcdata.sm += size;
240  memcpy((void *)(lp->ce->pcdata.s + lp->ce->pcdata.sl), (const void *)buf, size);
241  lp->ce->pcdata.sl += size;
242  return nodes;
243  }
244  else
245  lp->inblob = 0;
246 #endif
247  }
248  else
249  {
250  if (lp->ce)
251  {
252  char *ctag = tagXMLEle(lp->ce);
253  if (ctag && !(strcmp(ctag, "oneBLOB")) && (lp->cs == INCON))
254  {
255 #ifdef WITH_ENCLEN
256  XMLAtt *blenatt = findXMLAtt(lp->ce, "enclen");
257  if (blenatt)
258  {
259  int blen;
260  sscanf(valuXMLAtt(blenatt), "%d", &blen);
261 
262  // Add room for those '\n' on every 72 character line + extra half-full line.
263  blen += (blen / 72) + 1;
264 
265  lp->ce->pcdata.s = (char *)moremem(lp->ce->pcdata.s, blen);
266  lp->ce->pcdata.sm = blen; // always set sm
267 
268  if (size <= blen - lp->ce->pcdata.sl)
269  {
270  memcpy((void *)(lp->ce->pcdata.s + lp->ce->pcdata.sl), (const void *)buf, size);
271  lp->ce->pcdata.sl += size;
272  lp->inblob = 1;
273  return nodes;
274  }
275  }
276 #endif
277 #ifdef WITH_MEMCHR
278  char *ltpos = memchr(buf, '<', size);
279  if (!ltpos)
280  {
281  lp->ce->pcdata.s = (char *)moremem(lp->ce->pcdata.s, lp->ce->pcdata.sm + size);
282  lp->ce->pcdata.sm += size;
283  memcpy((void *)(lp->ce->pcdata.s + lp->ce->pcdata.sl), (const void *)buf, size);
284  lp->ce->pcdata.sl += size;
285  lp->inblob = 1;
286  return nodes;
287  }
288  else
289  lp->inblob = 0;
290 #endif
291  }
292  }
293  }
294  while (curr - buf < size)
295  {
296  char newc = *curr;
297  /* EOF? */
298  if (newc == 0)
299  {
300  sprintf(ynot, "Line %d: early XML EOF", lp->ln);
301  initParser(lp);
302  curr++;
303  continue;
304  }
305 
306  /* new line? */
307  if (newc == '\n')
308  lp->ln++;
309 
310  /* skip comments and declarations. requires 1 char history */
311  if (!lp->skipping && lp->lastc == '<' && (newc == '?' || newc == '!'))
312  {
313  lp->skipping = 1;
314  lp->lastc = newc;
315  curr++;
316  continue;
317  }
318  if (lp->skipping)
319  {
320  if (newc == '>')
321  lp->skipping = 0;
322  lp->lastc = newc;
323  curr++;
324  continue;
325  }
326  if (newc == '<')
327  {
328  lp->lastc = '<';
329  curr++;
330  continue;
331  }
332 
333  /* do a pending '<' first then newc */
334  if (lp->lastc == '<')
335  {
336  if (oneXMLchar(lp, '<', ynot) < 0)
337  {
338  initParser(lp);
339  curr++;
340  continue;
341  }
342  /* N.B. we assume '<' will never result in closure */
343  }
344 
345  /* process newc (at last!) */
346  s = oneXMLchar(lp, newc, ynot);
347  if (s == 0)
348  {
349  lp->lastc = newc;
350  curr++;
351  continue;
352  }
353  if (s < 0)
354  {
355  initParser(lp);
356  curr++;
357  continue;
358  }
359 
360  /* Ok! store ce in nodes and we start over.
361  * N.B. up to caller to call delXMLEle with what we return.
362  */
363  nodes[nnodes - 1] = lp->ce;
364  nodes = (XMLEle **)realloc(nodes, (nnodes + 1) * sizeof *nodes);
365  nodes[nnodes] = NULL;
366  nnodes += 1;
367  lp->ce = NULL;
368  initParser(lp);
369  curr++;
370  }
371  /*
372  * N.B. up to caller to free nodes.
373  */
374  return nodes;
375 }
376 
377 /* process one more character of an XML file.
378  * when find closure with outter element return root of complete tree.
379  * when find error return NULL with reason in ynot[].
380  * when need more return NULL with ynot[0] = '\0'.
381  * N.B. it is up to the caller to delete any tree returned with delXMLEle().
382  */
383 XMLEle *readXMLEle(LilXML *lp, int newc, char ynot[])
384 {
385  XMLEle *root;
386  int s;
387 
388  /* start optimistic */
389  ynot[0] = '\0';
390 
391  /* EOF? */
392  if (newc == 0)
393  {
394  sprintf(ynot, "Line %d: early XML EOF", lp->ln);
395  initParser(lp);
396  return (NULL);
397  }
398 
399  /* new line? */
400  if (newc == '\n')
401  lp->ln++;
402 
403  /* skip comments and declarations. requires 1 char history */
404  if (!lp->skipping && lp->lastc == '<' && (newc == '?' || newc == '!'))
405  {
406  lp->skipping = 1;
407  lp->lastc = newc;
408  return (NULL);
409  }
410  if (lp->skipping)
411  {
412  if (newc == '>')
413  lp->skipping = 0;
414  lp->lastc = newc;
415  return (NULL);
416  }
417  if (newc == '<')
418  {
419  lp->lastc = '<';
420  return (NULL);
421  }
422 
423  /* do a pending '<' first then newc */
424  if (lp->lastc == '<')
425  {
426  if (oneXMLchar(lp, '<', ynot) < 0)
427  {
428  initParser(lp);
429  return (NULL);
430  }
431  /* N.B. we assume '<' will never result in closure */
432  }
433 
434  /* process newc (at last!) */
435  s = oneXMLchar(lp, newc, ynot);
436  if (s == 0)
437  {
438  lp->lastc = newc;
439  return (NULL);
440  }
441  if (s < 0)
442  {
443  initParser(lp);
444  return (NULL);
445  }
446 
447  /* Ok! return ce and we start over.
448  * N.B. up to caller to call delXMLEle with what we return.
449  */
450  root = lp->ce;
451  lp->ce = NULL;
452  initParser(lp);
453  return (root);
454 }
455 
456 /* parse the given XML string.
457  * return XMLEle* else NULL with reason why in ynot[]
458  */
459 XMLEle *parseXML(char buf[], char ynot[])
460 {
461  LilXML *lp = newLilXML();
462  XMLEle *root;
463 
464  do
465  {
466  root = readXMLEle(lp, *buf++, ynot);
467  } while (!root && !ynot[0]);
468 
469  delLilXML(lp);
470 
471  return (root);
472 }
473 
474 /* return a deep copy of the given XMLEle *
475  */
477 {
478  char *buf;
479  char ynot[1024];
480  XMLEle *newep;
481 
482  buf = (*mymalloc)(sprlXMLEle(ep, 0) + 1);
483  sprXMLEle(buf, ep, 0);
484  newep = parseXML(buf, ynot);
485  (*myfree)(buf);
486 
487  return (newep);
488 }
489 
490 /* search ep for an attribute with given name.
491  * return NULL if not found.
492  */
493 XMLAtt *findXMLAtt(XMLEle *ep, const char *name)
494 {
495  int i;
496 
497  for (i = 0; i < ep->nat; i++)
498  if (!strcmp(ep->at[i]->name.s, name))
499  return (ep->at[i]);
500  return (NULL);
501 }
502 
503 /* search ep for an element with given tag.
504  * return NULL if not found.
505  */
506 XMLEle *findXMLEle(XMLEle *ep, const char *tag)
507 {
508  int tl = strlen(tag);
509  int i;
510 
511  for (i = 0; i < ep->nel; i++)
512  {
513  String *sp = &ep->el[i]->tag;
514  if (sp->sl == tl && !strcmp(sp->s, tag))
515  return (ep->el[i]);
516  }
517  return (NULL);
518 }
519 
520 /* iterate over each child element of ep.
521  * call first time with first set to 1, then 0 from then on.
522  * returns NULL when no more or err
523  */
524 XMLEle *nextXMLEle(XMLEle *ep, int init)
525 {
526  int eit;
527 
528  if (init)
529  ep->eit = 0;
530 
531  eit = ep->eit++;
532  if (eit < 0 || eit >= ep->nel)
533  return (NULL);
534  return (ep->el[eit]);
535 }
536 
537 /* iterate over each attribute of ep.
538  * call first time with first set to 1, then 0 from then on.
539  * returns NULL when no more or err
540  */
541 XMLAtt *nextXMLAtt(XMLEle *ep, int init)
542 {
543  int ait;
544 
545  if (init)
546  ep->ait = 0;
547 
548  ait = ep->ait++;
549  if (ait < 0 || ait >= ep->nat)
550  return (NULL);
551  return (ep->at[ait]);
552 }
553 
554 /* return parent of given XMLEle */
556 {
557  return (ep->pe);
558 }
559 
560 /* return parent element of given XMLAtt */
562 {
563  return (ap->ce);
564 }
565 
566 /* access functions */
567 
568 /* return the tag name of the given element */
569 char *tagXMLEle(XMLEle *ep)
570 {
571  return (ep->tag.s);
572 }
573 
574 /* return the pcdata portion of the given element */
576 {
577  return (ep->pcdata.s);
578 }
579 
580 /* return the number of characters in the pcdata portion of the given element */
582 {
583  return (ep->pcdata.sl);
584 }
585 
586 /* return the name of the given attribute */
587 char *nameXMLAtt(XMLAtt *ap)
588 {
589  return (ap->name.s);
590 }
591 
592 /* return the value of the given attribute */
593 char *valuXMLAtt(XMLAtt *ap)
594 {
595  return (ap->valu.s);
596 }
597 
598 /* return the number of child elements of the given element */
599 int nXMLEle(XMLEle *ep)
600 {
601  return (ep->nel);
602 }
603 
604 /* return the number of attributes in the given element */
605 int nXMLAtt(XMLEle *ep)
606 {
607  return (ep->nat);
608 }
609 
610 /* search ep for an attribute with the given name and return its value.
611  * return "" if not found.
612  */
613 const char *findXMLAttValu(XMLEle *ep, const char *name)
614 {
615  XMLAtt *a = findXMLAtt(ep, name);
616  return (a ? a->valu.s : "");
617 }
618 
619 /* handy wrapper to read one xml file.
620  * return root element else NULL with report in ynot[]
621  */
622 XMLEle *readXMLFile(FILE *fp, LilXML *lp, char ynot[])
623 {
624  int c;
625 
626  while ((c = fgetc(fp)) != EOF)
627  {
628  XMLEle *root = readXMLEle(lp, c, ynot);
629  if (root || ynot[0])
630  return (root);
631  }
632 
633  return (NULL);
634 }
635 
636 /* add an element with the given tag to the given element.
637  * parent can be NULL to make a new root.
638  */
639 XMLEle *addXMLEle(XMLEle *parent, const char *tag)
640 {
641  XMLEle *ep = growEle(parent);
642  appendString(&ep->tag, tag);
643  return (ep);
644 }
645 
646 /* append an existing element to the given element.
647  * N.B. be mindful of when these are deleted, this is not a deep copy.
648  */
649 void appXMLEle(XMLEle *ep, XMLEle *newep)
650 {
651  ep->el = (XMLEle **)moremem(ep->el, (ep->nel + 1) * sizeof(XMLEle *));
652  ep->el[ep->nel++] = newep;
653 }
654 
655 /* set the pcdata of the given element */
656 void editXMLEle(XMLEle *ep, const char *pcdata)
657 {
658  freeString(&ep->pcdata);
659  appendString(&ep->pcdata, pcdata);
660  ep->pcdata_hasent = (strpbrk(pcdata, entities) != NULL);
661 }
662 
663 /* add an attribute to the given XML element */
664 XMLAtt *addXMLAtt(XMLEle *ep, const char *name, const char *valu)
665 {
666  XMLAtt *ap = growAtt(ep);
667  appendString(&ap->name, name);
668  appendString(&ap->valu, valu);
669  return (ap);
670 }
671 
672 /* remove the named attribute from ep, if any */
673 void rmXMLAtt(XMLEle *ep, const char *name)
674 {
675  int i;
676 
677  for (i = 0; i < ep->nat; i++)
678  {
679  if (strcmp(ep->at[i]->name.s, name) == 0)
680  {
681  freeAtt(ep->at[i]);
682  memmove(&ep->at[i], &ep->at[i + 1], (--ep->nat - i) * sizeof(XMLAtt *));
683  return;
684  }
685  }
686 }
687 
688 /* change the value of an attribute to str */
689 void editXMLAtt(XMLAtt *ap, const char *str)
690 {
691  freeString(&ap->valu);
692  appendString(&ap->valu, str);
693 }
694 
695 /* sample print ep to fp
696  * N.B. set level = 0 on first call
697  */
698 #define PRINDENT 4 /* sample print indent each level */
699 void prXMLEle(FILE *fp, XMLEle *ep, int level)
700 {
701  int indent = level * PRINDENT;
702  int i;
703 
704  fprintf(fp, "%*s<%s", indent, "", ep->tag.s);
705  for (i = 0; i < ep->nat; i++)
706  fprintf(fp, " %s=\"%s\"", ep->at[i]->name.s, entityXML(ep->at[i]->valu.s));
707  if (ep->nel > 0)
708  {
709  fprintf(fp, ">\n");
710  for (i = 0; i < ep->nel; i++)
711  prXMLEle(fp, ep->el[i], level + 1);
712  }
713  if (ep->pcdata.sl > 0)
714  {
715  if (ep->nel == 0)
716  fprintf(fp, ">\n");
717  if (ep->pcdata_hasent)
718  fprintf(fp, "%s", entityXML(ep->pcdata.s));
719  else
720  fprintf(fp, "%s", ep->pcdata.s);
721  if (ep->pcdata.s[ep->pcdata.sl - 1] != '\n')
722  fprintf(fp, "\n");
723  }
724  if (ep->nel > 0 || ep->pcdata.sl > 0)
725  fprintf(fp, "%*s</%s>\n", indent, "", ep->tag.s);
726  else
727  fprintf(fp, "/>\n");
728 }
729 
730 /* sample print ep to string s.
731  * N.B. s must be at least as large as that reported by sprlXMLEle()+1.
732  * N.B. set level = 0 on first call
733  * return length of resulting string (sans trailing \0)
734  */
735 int sprXMLEle(char *s, XMLEle *ep, int level)
736 {
737  int indent = level * PRINDENT;
738  int sl = 0;
739  int i;
740 
741  sl += sprintf(s + sl, "%*s<%s", indent, "", ep->tag.s);
742  for (i = 0; i < ep->nat; i++)
743  sl += sprintf(s + sl, " %s=\"%s\"", ep->at[i]->name.s, entityXML(ep->at[i]->valu.s));
744  if (ep->nel > 0)
745  {
746  sl += sprintf(s + sl, ">\n");
747  for (i = 0; i < ep->nel; i++)
748  sl += sprXMLEle(s + sl, ep->el[i], level + 1);
749  }
750  if (ep->pcdata.sl > 0)
751  {
752  if (ep->nel == 0)
753  sl += sprintf(s + sl, ">\n");
754  if (ep->pcdata_hasent)
755  sl += sprintf(s + sl, "%s", entityXML(ep->pcdata.s));
756  else
757  {
758  strcpy(s + sl, ep->pcdata.s);
759  sl += ep->pcdata.sl;
760  }
761  if (ep->pcdata.s[ep->pcdata.sl - 1] != '\n')
762  sl += sprintf(s + sl, "\n");
763  }
764  if (ep->nel > 0 || ep->pcdata.sl > 0)
765  sl += sprintf(s + sl, "%*s</%s>\n", indent, "", ep->tag.s);
766  else
767  sl += sprintf(s + sl, "/>\n");
768 
769  return (sl);
770 }
771 
772 /* return number of bytes in a string guaranteed able to hold result of
773  * sprXLMEle(ep) (sans trailing \0).
774  * N.B. set level = 0 on first call
775  */
776 int sprlXMLEle(XMLEle *ep, int level)
777 {
778  int indent = level * PRINDENT;
779  int l = 0;
780  int i;
781 
782  l += indent + 1 + ep->tag.sl;
783  for (i = 0; i < ep->nat; i++)
784  l += ep->at[i]->name.sl + 4 + strlen(entityXML(ep->at[i]->valu.s));
785 
786  if (ep->nel > 0)
787  {
788  l += 2;
789  for (i = 0; i < ep->nel; i++)
790  l += sprlXMLEle(ep->el[i], level + 1);
791  }
792  if (ep->pcdata.sl > 0)
793  {
794  if (ep->nel == 0)
795  l += 2;
796  if (ep->pcdata_hasent)
797  l += strlen(entityXML(ep->pcdata.s));
798  else
799  l += ep->pcdata.sl;
800  if (ep->pcdata.s[ep->pcdata.sl - 1] != '\n')
801  l += 1;
802  }
803  if (ep->nel > 0 || ep->pcdata.sl > 0)
804  l += indent + 4 + ep->tag.sl;
805  else
806  l += 3;
807 
808  return (l);
809 }
810 
811 /* return a string with all xml-sensitive characters within the passed string s
812  * replaced with their entity sequence equivalents.
813  * N.B. caller must use the returned string before calling us again.
814  */
815 char *entityXML(char *s)
816 {
817  static char *malbuf;
818  int nmalbuf = 0;
819  char *sret = NULL;
820  char *ep = NULL;
821 
822  /* scan for each entity, if any */
823  for (sret = s; (ep = strpbrk(s, entities)) != NULL; s = ep + 1)
824  {
825  /* found another entity, copy preceding to malloced buffer */
826  int nnew = ep - s; /* all but entity itself */
827  sret = malbuf = moremem(malbuf, nmalbuf + nnew + 10);
828  memcpy(malbuf + nmalbuf, s, nnew);
829  nmalbuf += nnew;
830 
831  /* replace with entity encoding */
832  switch (*ep)
833  {
834  case '&':
835  nmalbuf += sprintf(malbuf + nmalbuf, "&amp;");
836  break;
837  case '<':
838  nmalbuf += sprintf(malbuf + nmalbuf, "&lt;");
839  break;
840  case '>':
841  nmalbuf += sprintf(malbuf + nmalbuf, "&gt;");
842  break;
843  case '\'':
844  nmalbuf += sprintf(malbuf + nmalbuf, "&apos;");
845  break;
846  case '"':
847  nmalbuf += sprintf(malbuf + nmalbuf, "&quot;");
848  break;
849  }
850  }
851 
852  /* return s if no entities, else malloc cleaned-up copy */
853  if (sret == s)
854  {
855  /* using s, so free any alloced memory from last time */
856  if (malbuf)
857  {
858  free(malbuf);
859  malbuf = NULL;
860  }
861  return s;
862  }
863  else
864  {
865  /* put remaining part of s into malbuf */
866  int nleft = strlen(s) + 1; /* include \0 */
867  sret = malbuf = moremem(malbuf, nmalbuf + nleft);
868  memcpy(malbuf + nmalbuf, s, nleft);
869  }
870 
871  return (sret);
872 }
873 
874 /* if ent is a recognized xml entity sequence, set *cp to char and return 1
875  * else return 0
876  */
877 static int decodeEntity(char *ent, int *cp)
878 {
879  static struct
880  {
881  char *ent;
882  char c;
883  } enttable[] = {
884  { "&amp;", '&' }, { "&apos;", '\'' }, { "&lt;", '<' }, { "&gt;", '>' }, { "&quot;", '"' },
885  };
886  for (int i = 0; i < (int)(sizeof(enttable) / sizeof(enttable[0])); i++)
887  {
888  if (strcmp(ent, enttable[i].ent) == 0)
889  {
890  *cp = enttable[i].c;
891  return (1);
892  }
893  }
894 
895  return (0);
896 }
897 
898 /* process one more char in XML file.
899  * if find final closure, return 1 and tree is in ce.
900  * if need more, return 0.
901  * if real trouble, return -1 and put reason in ynot.
902  */
903 static int oneXMLchar(LilXML *lp, int c, char ynot[])
904 {
905  switch (lp->cs)
906  {
907  case LOOK4START: /* looking for first element start */
908  if (c == '<')
909  {
910  pushXMLEle(lp);
911  lp->cs = LOOK4TAG;
912  }
913  /* silently ignore until resync */
914  break;
915 
916  case LOOK4TAG: /* looking for element tag */
917  if (isTokenChar(1, c))
918  {
919  growString(&lp->ce->tag, c);
920  lp->cs = INTAG;
921  }
922  else if (!isspace(c))
923  {
924  sprintf(ynot, "Line %d: Bogus tag char %c", lp->ln, c);
925  return (-1);
926  }
927  break;
928 
929  case INTAG: /* reading tag */
930  if (isTokenChar(0, c))
931  growString(&lp->ce->tag, c);
932  else if (c == '>')
933  lp->cs = LOOK4CON;
934  else if (c == '/')
935  lp->cs = SAWSLASH;
936  else
937  lp->cs = LOOK4ATTRN;
938  break;
939 
940  case LOOK4ATTRN: /* looking for attr name, > or / */
941  if (c == '>')
942  lp->cs = LOOK4CON;
943  else if (c == '/')
944  lp->cs = SAWSLASH;
945  else if (isTokenChar(1, c))
946  {
947  XMLAtt *ap = growAtt(lp->ce);
948  growString(&ap->name, c);
949  lp->cs = INATTRN;
950  }
951  else if (!isspace(c))
952  {
953  sprintf(ynot, "Line %d: Bogus leading attr name char: %c", lp->ln, c);
954  return (-1);
955  }
956  break;
957 
958  case SAWSLASH: /* saw / in element opening */
959  if (c == '>')
960  {
961  if (!lp->ce->pe)
962  return (1); /* root has no content */
963  popXMLEle(lp);
964  lp->cs = LOOK4CON;
965  }
966  else
967  {
968  sprintf(ynot, "Line %d: Bogus char %c before >", lp->ln, c);
969  return (-1);
970  }
971  break;
972 
973  case INATTRN: /* reading attr name */
974  if (isTokenChar(0, c))
975  growString(&lp->ce->at[lp->ce->nat - 1]->name, c);
976  else if (isspace(c) || c == '=')
977  lp->cs = LOOK4ATTRV;
978  else
979  {
980  sprintf(ynot, "Line %d: Bogus attr name char: %c", lp->ln, c);
981  return (-1);
982  }
983  break;
984 
985  case LOOK4ATTRV: /* looking for attr value */
986  if (c == '\'' || c == '"')
987  {
988  lp->delim = c;
989  lp->cs = INATTRV;
990  }
991  else if (!(isspace(c) || c == '='))
992  {
993  sprintf(ynot, "Line %d: No value for attribute %s", lp->ln, lp->ce->at[lp->ce->nat - 1]->name.s);
994  return (-1);
995  }
996  break;
997 
998  case INATTRV: /* in attr value */
999  if (c == '&')
1000  {
1001  newString(&lp->entity);
1002  growString(&lp->entity, c);
1003  lp->cs = ENTINATTRV;
1004  }
1005  else if (c == lp->delim)
1006  lp->cs = LOOK4ATTRN;
1007  else if (!iscntrl(c))
1008  growString(&lp->ce->at[lp->ce->nat - 1]->valu, c);
1009  break;
1010 
1011  case ENTINATTRV: /* working on entity in attr valu */
1012  if (c == ';')
1013  {
1014  /* if find a recongized esp seq, add equiv char else raw seq */
1015  growString(&lp->entity, c);
1016  if (decodeEntity(lp->entity.s, &c))
1017  growString(&lp->ce->at[lp->ce->nat - 1]->valu, c);
1018  else
1019  appendString(&lp->ce->at[lp->ce->nat - 1]->valu, lp->entity.s);
1020  freeString(&lp->entity);
1021  lp->cs = INATTRV;
1022  }
1023  else
1024  growString(&lp->entity, c);
1025  break;
1026 
1027  case LOOK4CON: /* skipping leading content whitespace*/
1028  if (c == '<')
1029  lp->cs = SAWLTINCON;
1030  else if (!isspace(c))
1031  {
1032  growString(&lp->ce->pcdata, c);
1033  lp->cs = INCON;
1034  }
1035  break;
1036 
1037  case INCON: /* reading content */
1038  if (c == '&')
1039  {
1040  newString(&lp->entity);
1041  growString(&lp->entity, c);
1042  lp->cs = ENTINCON;
1043  }
1044  else if (c == '<')
1045  {
1046  /* chomp trailing whitespace */
1047  while (lp->ce->pcdata.sl > 0 && isspace(lp->ce->pcdata.s[lp->ce->pcdata.sl - 1]))
1048  lp->ce->pcdata.s[--(lp->ce->pcdata.sl)] = '\0';
1049  lp->cs = SAWLTINCON;
1050  }
1051  else
1052  {
1053  growString(&lp->ce->pcdata, c);
1054  }
1055  break;
1056 
1057  case ENTINCON: /* working on entity in content */
1058  if (c == ';')
1059  {
1060  /* if find a recognized esc seq, add equiv char else raw seq */
1061  growString(&lp->entity, c);
1062  if (decodeEntity(lp->entity.s, &c))
1063  growString(&lp->ce->pcdata, c);
1064  else
1065  {
1066  appendString(&lp->ce->pcdata, lp->entity.s);
1067  //lp->ce->pcdata_hasent = 1;
1068  }
1069  // JM 2018-09-26: Even if decoded, we always set
1070  // pcdata_hasent to 1 since we need to encode it again
1071  // before sending it over to clients and drivers.
1072  lp->ce->pcdata_hasent = 1;
1073  freeString(&lp->entity);
1074  lp->cs = INCON;
1075  }
1076  else
1077  growString(&lp->entity, c);
1078  break;
1079 
1080  case SAWLTINCON: /* saw < in content */
1081  if (c == '/')
1082  {
1083  resetEndTag(lp);
1084  lp->cs = LOOK4CLOSETAG;
1085  }
1086  else
1087  {
1088  pushXMLEle(lp);
1089  if (isTokenChar(1, c))
1090  {
1091  growString(&lp->ce->tag, c);
1092  lp->cs = INTAG;
1093  }
1094  else
1095  lp->cs = LOOK4TAG;
1096  }
1097  break;
1098 
1099  case LOOK4CLOSETAG: /* looking for closing tag after < */
1100  if (isTokenChar(1, c))
1101  {
1102  growString(&lp->endtag, c);
1103  lp->cs = INCLOSETAG;
1104  }
1105  else if (!isspace(c))
1106  {
1107  sprintf(ynot, "Line %d: Bogus preend tag char %c", lp->ln, c);
1108  return (-1);
1109  }
1110  break;
1111 
1112  case INCLOSETAG: /* reading closing tag */
1113  if (isTokenChar(0, c))
1114  growString(&lp->endtag, c);
1115  else if (c == '>')
1116  {
1117  if (strcmp(lp->ce->tag.s, lp->endtag.s))
1118  {
1119  sprintf(ynot, "Line %d: closing tag %s does not match %s", lp->ln, lp->endtag.s, lp->ce->tag.s);
1120  return (-1);
1121  }
1122  else if (lp->ce->pe)
1123  {
1124  popXMLEle(lp);
1125  lp->cs = LOOK4CON; /* back to content after nested elem */
1126  }
1127  else
1128  return (1); /* yes! */
1129  }
1130  else if (!isspace(c))
1131  {
1132  sprintf(ynot, "Line %d: Bogus end tag char %c", lp->ln, c);
1133  return (-1);
1134  }
1135  break;
1136  }
1137 
1138  return (0);
1139 }
1140 
1141 /* set up for a fresh start again */
1142 static void initParser(LilXML *lp)
1143 {
1144  delXMLEle(lp->ce);
1145  freeString(&lp->endtag);
1146  memset(lp, 0, sizeof(*lp));
1147  newString(&lp->endtag);
1148  lp->cs = LOOK4START;
1149  lp->ln = 1;
1150 }
1151 
1152 /* start a new XMLEle.
1153  * point ce to a new XMLEle.
1154  * if ce already set up, add to its list of child elements too.
1155  * endtag no longer valid.
1156  */
1157 static void pushXMLEle(LilXML *lp)
1158 {
1159  lp->ce = growEle(lp->ce);
1160  resetEndTag(lp);
1161 }
1162 
1163 /* point ce to parent of current ce.
1164  * endtag no longer valid.
1165  */
1166 static void popXMLEle(LilXML *lp)
1167 {
1168  lp->ce = lp->ce->pe;
1169  resetEndTag(lp);
1170 }
1171 
1172 /* return one new XMLEle, added to the given element if given */
1173 static XMLEle *growEle(XMLEle *pe)
1174 {
1175  XMLEle *newe = (XMLEle *)moremem(NULL, sizeof(XMLEle));
1176 
1177  memset(newe, 0, sizeof(XMLEle));
1178  newString(&newe->tag);
1179  newString(&newe->pcdata);
1180  newe->pe = pe;
1181 
1182  if (pe)
1183  {
1184  pe->el = (XMLEle **)moremem(pe->el, (pe->nel + 1) * sizeof(XMLEle *));
1185  pe->el[pe->nel++] = newe;
1186  }
1187 
1188  return (newe);
1189 }
1190 
1191 /* add room for and return one new XMLAtt to the given element */
1192 static XMLAtt *growAtt(XMLEle *ep)
1193 {
1194  XMLAtt *newa = (XMLAtt *)moremem(NULL, sizeof *newa);
1195 
1196  memset(newa, 0, sizeof(*newa));
1197  newString(&newa->name);
1198  newString(&newa->valu);
1199  newa->ce = ep;
1200 
1201  ep->at = (XMLAtt **)moremem(ep->at, (ep->nat + 1) * sizeof(XMLAtt *));
1202  ep->at[ep->nat++] = newa;
1203 
1204  return (newa);
1205 }
1206 
1207 /* free a and all it holds */
1208 static void freeAtt(XMLAtt *a)
1209 {
1210  if (!a)
1211  return;
1212  freeString(&a->name);
1213  freeString(&a->valu);
1214  (*myfree)(a);
1215 }
1216 
1217 /* reset endtag */
1218 static void resetEndTag(LilXML *lp)
1219 {
1220  freeString(&lp->endtag);
1221  newString(&lp->endtag);
1222 }
1223 
1224 /* 1 if c is a valid token character, else 0.
1225  * it can be alpha or '_' or numeric unless start.
1226  */
1227 static int isTokenChar(int start, int c)
1228 {
1229  return (isalpha(c) || c == '_' || (!start && isdigit(c)));
1230 }
1231 
1232 /* grow the String storage at *sp to append c */
1233 static void growString(String *sp, int c)
1234 {
1235  int l = sp->sl + 2; /* need room for '\0' plus c */
1236 
1237  if (l > sp->sm)
1238  {
1239  if (!sp->s)
1240  newString(sp);
1241  else {
1242  sp->s = (char *)moremem(sp->s, sp->sm *= 2);
1243  }
1244  }
1245  sp->s[--l] = '\0';
1246  sp->s[--l] = (char)c;
1247  sp->sl++;
1248 }
1249 
1250 /* append str to the String storage at *sp */
1251 static void appendString(String *sp, const char *str)
1252 {
1253  if (!sp || !str)
1254  return;
1255 
1256  int strl = strlen(str);
1257  int l = sp->sl + strl + 1; /* need room for '\0' */
1258 
1259  if (l > sp->sm)
1260  {
1261  if (!sp->s)
1262  newString(sp);
1263  if (l > sp->sm) {
1264  sp->s = (char *)moremem(sp->s, (sp->sm = l));
1265  }
1266  }
1267  if (sp->s)
1268  {
1269  strcpy(&sp->s[sp->sl], str);
1270  sp->sl += strl;
1271  }
1272 }
1273 
1274 /* init a String with a malloced string containing just \0 */
1275 static void newString(String *sp)
1276 {
1277  if (!sp)
1278  return;
1279 
1280  sp->s = (char *)moremem(NULL, MINMEM);
1281  sp->sm = MINMEM;
1282  *sp->s = '\0';
1283  sp->sl = 0;
1284 }
1285 
1286 /* free memory used by the given String */
1287 static void freeString(String *sp)
1288 {
1289  if (sp->s)
1290  (*myfree)(sp->s);
1291  sp->s = NULL;
1292  sp->sl = 0;
1293  sp->sm = 0;
1294 }
1295 
1296 /* like malloc but knows to use realloc if already started */
1297 static void *moremem(void *old, int n)
1298 {
1299  void *p = (old ? (*myrealloc)(old, n) : (*mymalloc)(n));
1300  if (p == 0) {
1301  fprintf(stderr, "%s(%s): Failed to allocate memory.\n", __FILE__, __func__);
1302  exit(1);
1303  }
1304  return p;
1305 }
1306 
1307 #if defined(MAIN_TST)
1308 int main(int ac, char *av[])
1309 {
1310  LilXML *lp = newLilXML();
1311  char ynot[1024];
1312  XMLEle *root;
1313 
1314  root = readXMLFile(stdin, lp, ynot);
1315  if (root)
1316  {
1317  char *str;
1318  int l;
1319 
1320  if (ac > 1)
1321  {
1322  XMLEle *theend = addXMLEle(root, "theend");
1323  editXMLEle(theend, "Added to test editing");
1324  addXMLAtt(theend, "hello", "world");
1325  }
1326 
1327  fprintf(stderr, "::::::::::::: %s\n", tagXMLEle(root));
1328  prXMLEle(stdout, root, 0);
1329 
1330  l = sprlXMLEle(root, 0);
1331  str = malloc(l + 1);
1332  fprintf(stderr, "::::::::::::: %s : %d : %d", tagXMLEle(root), l, sprXMLEle(str, root, 0));
1333  fprintf(stderr, ": %d\n", printf("%s", str));
1334 
1335  delXMLEle(root);
1336  }
1337  else if (ynot[0])
1338  {
1339  fprintf(stderr, "Error: %s\n", ynot);
1340  }
1341 
1342  delLilXML(lp);
1343 
1344  return (0);
1345 }
1346 #endif
1347 
1348 #if defined(_MSC_VER)
1349 #undef snprintf
1350 #pragma warning(pop)
1351 #endif
LilXML_::inblob
int inblob
Definition: lilxml.c:101
parentXMLEle
XMLEle * parentXMLEle(XMLEle *ep)
Return the parent of an XML element.
Definition: lilxml.c:555
INATTRV
@ INATTRV
Definition: lilxml.c:80
newLilXML
LilXML * newLilXML()
Create a new lilxml parser.
Definition: lilxml.c:148
String::sm
int sm
Definition: lilxml.c:53
xml_att_::name
String name
Definition: lilxml.c:122
xml_ele_::ait
int ait
Definition: lilxml.c:111
Aux::ANY
@ ANY
Definition: celestronauxpacket.h:86
sprlXMLEle
int sprlXMLEle(XMLEle *ep, int level)
return number of bytes in a string guaranteed able to hold result of sprXLMEle(ep) (sans trailing \0@...
Definition: lilxml.c:776
entityXML
char * entityXML(char *s)
return a string with all xml-sensitive characters within the passed string replaced with their entity...
Definition: lilxml.c:815
LOOK4CLOSETAG
@ LOOK4CLOSETAG
Definition: lilxml.c:86
pcdataXMLEle
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.c:575
nextXMLEle
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition: lilxml.c:524
xml_ele_::pcdata
String pcdata
Definition: lilxml.c:115
LOOK4TAG
@ LOOK4TAG
Definition: lilxml.c:74
xml_ele_::pe
XMLEle * pe
Definition: lilxml.c:108
PRINDENT
#define PRINDENT
Definition: lilxml.c:698
xml_ele_::el
XMLEle ** el
Definition: lilxml.c:112
String::sl
int sl
Definition: lilxml.c:52
LilXML_::ln
int ln
Definition: lilxml.c:94
nXMLEle
int nXMLEle(XMLEle *ep)
Return the number of nested XML elements in a parent XML element.
Definition: lilxml.c:599
LilXML_::cs
State cs
Definition: lilxml.c:93
xml_ele_::pcdata_hasent
int pcdata_hasent
Definition: lilxml.c:116
lilxmlMalloc
void lilxmlMalloc(void *(*newmalloc)(size_t size), void *(*newrealloc)(void *ptr, size_t size), void(*newfree)(void *ptr))
Definition: lilxml.c:139
addXMLAtt
XMLAtt * addXMLAtt(XMLEle *ep, const char *name, const char *valu)
Add an XML attribute to an existing XML element.
Definition: lilxml.c:664
LOOK4ATTRV
@ LOOK4ATTRV
Definition: lilxml.c:78
ENTINATTRV
@ ENTINATTRV
Definition: lilxml.c:81
readXMLEle
XMLEle * readXMLEle(LilXML *lp, int newc, char ynot[])
Process an XML one char at a time.
Definition: lilxml.c:383
rmXMLAtt
void rmXMLAtt(XMLEle *ep, const char *name)
Remove an XML attribute from an XML element.
Definition: lilxml.c:673
INCLOSETAG
@ INCLOSETAG
Definition: lilxml.c:87
pcdatalenXMLEle
int pcdatalenXMLEle(XMLEle *ep)
Return the number of characters in pcdata in an XML element.
Definition: lilxml.c:581
ENTINCON
@ ENTINCON
Definition: lilxml.c:84
delLilXML
void delLilXML(LilXML *lp)
Delete a lilxml parser.
Definition: lilxml.c:157
LOOK4CON
@ LOOK4CON
Definition: lilxml.c:82
xml_att_
Definition: lilxml.c:120
xml_ele_::tag
String tag
Definition: lilxml.c:107
editXMLEle
void editXMLEle(XMLEle *ep, const char *pcdata)
set the pcdata of the given element
Definition: lilxml.c:656
readXMLFile
XMLEle * readXMLFile(FILE *fp, LilXML *lp, char ynot[])
Handy wrapper to read one xml file.
Definition: lilxml.c:622
LilXML_::entity
String entity
Definition: lilxml.c:97
tagXMLEle
char * tagXMLEle(XMLEle *ep)
Return the tag of an XML element.
Definition: lilxml.c:569
xml_ele_
Definition: lilxml.c:105
appXMLEle
void appXMLEle(XMLEle *ep, XMLEle *newep)
Definition: lilxml.c:649
String
Definition: lilxml.c:49
LilXML_::lastc
int lastc
Definition: lilxml.c:99
INCON
@ INCON
Definition: lilxml.c:83
nXMLAtt
int nXMLAtt(XMLEle *ep)
Return the number of XML attributes in a parent XML element.
Definition: lilxml.c:605
lilxml.h
A little DOM-style library to handle parsing and processing an XML file.
LilXML_::ce
XMLEle * ce
Definition: lilxml.c:95
SAWSLASH
@ SAWSLASH
Definition: lilxml.c:79
String::s
char * s
Definition: lilxml.c:51
parseXML
XMLEle * parseXML(char buf[], char ynot[])
Definition: lilxml.c:459
parseXMLChunk
XMLEle ** parseXMLChunk(LilXML *lp, char *buf, int size, char ynot[])
Process an XML chunk.
Definition: lilxml.c:213
valuXMLAtt
char * valuXMLAtt(XMLAtt *ap)
Return the value of an XML attribute.
Definition: lilxml.c:593
findXMLEle
XMLEle * findXMLEle(XMLEle *ep, const char *tag)
Find an XML element within an XML element.
Definition: lilxml.c:506
xml_ele_::nat
int nat
Definition: lilxml.c:110
SAWLTINCON
@ SAWLTINCON
Definition: lilxml.c:85
main
int main(int, char **)
Definition: tutorial_client.cpp:53
xml_ele_::nel
int nel
Definition: lilxml.c:113
prXMLEle
void prXMLEle(FILE *fp, XMLEle *ep, int level)
Print an XML element.
Definition: lilxml.c:699
xml_att_::ce
XMLEle * ce
Definition: lilxml.c:124
LilXML_::skipping
int skipping
Definition: lilxml.c:100
LOOK4START
@ LOOK4START
Definition: lilxml.c:73
findXMLAttValu
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition: lilxml.c:613
nextXMLAtt
XMLAtt * nextXMLAtt(XMLEle *ep, int init)
Iterate an XML element for a list of XML attributes.
Definition: lilxml.c:541
LilXML_
Definition: lilxml.c:91
INTAG
@ INTAG
Definition: lilxml.c:75
nameXMLAtt
char * nameXMLAtt(XMLAtt *ap)
Return the name of an XML attribute.
Definition: lilxml.c:587
sprXMLEle
int sprXMLEle(char *s, XMLEle *ep, int level)
sample print ep to string s. N.B. s must be at least as large as that reported by sprlXMLEle()+1....
Definition: lilxml.c:735
xml_ele_::at
XMLAtt ** at
Definition: lilxml.c:109
State
State
Definition: lilxml.c:72
INATTRN
@ INATTRN
Definition: lilxml.c:77
LilXML_::delim
int delim
Definition: lilxml.c:98
editXMLAtt
void editXMLAtt(XMLAtt *ap, const char *str)
change the value of an attribute to str.
Definition: lilxml.c:689
xml_att_::valu
String valu
Definition: lilxml.c:123
LOOK4ATTRN
@ LOOK4ATTRN
Definition: lilxml.c:76
addXMLEle
XMLEle * addXMLEle(XMLEle *parent, const char *tag)
add an element with the given tag to the given element. parent can be NULL to make a new root.
Definition: lilxml.c:639
delXMLEle
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
Definition: lilxml.c:165
LilXML_::endtag
String endtag
Definition: lilxml.c:96
parentXMLAtt
XMLEle * parentXMLAtt(XMLAtt *ap)
Return the parent of an XML attribute.
Definition: lilxml.c:561
cloneXMLEle
XMLEle * cloneXMLEle(XMLEle *ep)
Definition: lilxml.c:476
xml_ele_::eit
int eit
Definition: lilxml.c:114
findXMLAtt
XMLAtt * findXMLAtt(XMLEle *ep, const char *name)
Find an XML attribute within an XML element.
Definition: lilxml.c:493
MINMEM
#define MINMEM
Definition: lilxml.c:55