1 /* AirScan (a.k.a. eSCL) backend for SANE
2  *
3  * Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
4  * See LICENSE for license terms and conditions
5  *
6  * XML utilities
7  */
8 
9 #include "airscan.h"
10 
11 #include <fnmatch.h>
12 
13 #include <libxml/parser.h>
14 #include <libxml/tree.h>
15 
16 /******************** XML reader ********************/
17 /* XML reader
18  */
19 struct xml_rd {
20     xmlDoc        *doc;           /* XML document */
21     xmlNode       *node;          /* Current node */
22     xmlNode       *parent;        /* Parent node */
23     const char    *name;          /* Name of current node */
24     char          *path;          /* Path to current node, /-separated */
25     size_t        *pathlen;       /* Stack of path lengths */
26     const xmlChar *text;          /* Textual value of current node */
27     unsigned int  depth;          /* Depth of current node, 0 for root */
28     const xml_ns  *subst_rules;   /* Substitution rules */
29     xml_ns        *subst_cache;   /* In the cache, glob-style patterns are
30                                      replaced by exact-matching strings. */
31 };
32 
33 /* Forward declarations */
34 static const char*
35 xml_rd_ns_subst_lookup(xml_rd *xml, const char *prefix, const char *href);
36 
37 /* Skip dummy nodes. This is internal function, don't call directly
38  */
39 static void
xml_rd_skip_dummy(xml_rd * xml)40 xml_rd_skip_dummy (xml_rd *xml)
41 {
42     xmlNode *node = xml->node;
43 
44     while (node != NULL && node->type != XML_ELEMENT_NODE) {
45         node = node->next;
46     }
47 
48     xml->node = node;
49 }
50 
51 /* Invalidate cached value
52  */
53 static void
xml_rd_node_invalidate_value(xml_rd * xml)54 xml_rd_node_invalidate_value (xml_rd *xml)
55 {
56     xmlFree((xmlChar*) xml->text);
57     xml->text = NULL;
58 }
59 
60 /* xml_rd_node_switched called when current node is switched.
61  * It invalidates cached value and updates node name
62  */
63 static void
xml_rd_node_switched(xml_rd * xml)64 xml_rd_node_switched (xml_rd *xml)
65 {
66     size_t     pathlen;
67 
68     /* Invalidate cached value */
69     xml_rd_node_invalidate_value(xml);
70 
71     /* Update node name */
72     pathlen = xml->depth ? xml->pathlen[xml->depth - 1] : 0;
73     xml->path = str_resize(xml->path, pathlen);
74 
75     if (xml->node == NULL) {
76         xml->name = NULL;
77     } else {
78         const char *prefix = NULL;
79 
80         if (xml->node->ns != NULL && xml->node->ns->prefix != NULL) {
81             prefix = (const char*) xml->node->ns->prefix;
82             prefix = xml_rd_ns_subst_lookup(xml, prefix,
83                     (const char*) xml->node->ns->href);
84         }
85 
86         if (prefix != NULL) {
87             xml->path = str_append(xml->path, prefix);
88             xml->path = str_append_c(xml->path, ':');
89         }
90 
91         xml->path = str_append(xml->path, (const char*) xml->node->name);
92 
93         xml->name = xml->path + pathlen;
94     }
95 }
96 
97 /* XML parser error callback
98  *
99  * As XML parser leaves all error information in the xmlParserCtxt
100  * structure, this callback does nothing; it's purpose is to
101  * silence error message that libxml2 by default writes to
102  * the stderr
103  */
104 static void
xml_rd_error_callback(void * userdata,xmlErrorPtr error)105 xml_rd_error_callback (void *userdata, xmlErrorPtr error)
106 {
107     (void) userdata;
108     (void) error;
109 }
110 
111 /* Parse XML document
112  */
113 static error
xml_rd_parse(xmlDoc ** doc,const char * xml_text,size_t xml_len)114 xml_rd_parse (xmlDoc **doc, const char *xml_text, size_t xml_len)
115 {
116     xmlParserCtxtPtr ctxt;
117     error            err = NULL;
118 
119     /* Setup XML parser */
120     ctxt = xmlNewParserCtxt();
121     if (ctxt == NULL) {
122         err = ERROR("not enough memory");
123         goto DONE;
124     }
125 
126     ctxt->sax->serror = xml_rd_error_callback;
127 
128     /* Parse the document */
129     if (xmlCtxtResetPush(ctxt, xml_text, xml_len, NULL, NULL)) {
130         /* It's poorly documented, but xmlCtxtResetPush() fails
131          * only due to OOM.
132          */
133         err = ERROR("not enough memory");
134         goto DONE;
135     }
136 
137     xmlParseDocument(ctxt);
138 
139     if (ctxt->wellFormed) {
140         *doc = ctxt->myDoc;
141     } else {
142         if (ctxt->lastError.message != NULL) {
143             err = eloop_eprintf("XML: %s", ctxt->lastError.message);
144         } else {
145             err = ERROR("XML: parse error");
146         }
147 
148         *doc = NULL;
149     }
150 
151     /* Cleanup and exit */
152 DONE:
153     if (err != NULL && ctxt != NULL && ctxt->myDoc != NULL) {
154         xmlFreeDoc(ctxt->myDoc);
155     }
156 
157     if (ctxt != NULL) {
158         xmlFreeParserCtxt(ctxt);
159     }
160 
161     return err;
162 }
163 
164 /* Parse XML text and initialize reader to iterate
165  * starting from the root node
166  *
167  * The 'ns' argument, if not NULL, points to array of substitution
168  * rules. Last element must have NULL prefix and url
169  *
170  * Array of rules considered to be statically allocated
171  * (at least, it can remain valid during reader life time)
172  *
173  * On success, saves newly constructed reader into
174  * the xml parameter.
175  */
176 error
xml_rd_begin(xml_rd ** xml,const char * xml_text,size_t xml_len,const xml_ns * ns)177 xml_rd_begin (xml_rd **xml, const char *xml_text, size_t xml_len,
178         const xml_ns *ns)
179 {
180     xmlDoc *doc;
181     error  err = xml_rd_parse(&doc, xml_text, xml_len);
182 
183     *xml = NULL;
184     if (err != NULL) {
185         return err;
186     }
187 
188     *xml = mem_new(xml_rd, 1);
189     (*xml)->doc = doc;
190     (*xml)->node = xmlDocGetRootElement((*xml)->doc);
191     (*xml)->path = str_new();
192     (*xml)->pathlen = mem_new(size_t, 0);
193     (*xml)->subst_rules = ns;
194 
195     xml_rd_skip_dummy(*xml);
196     xml_rd_node_switched(*xml);
197 
198     return NULL;
199 }
200 
201 /* Finish reading, free allocated resources
202  */
203 void
xml_rd_finish(xml_rd ** xml)204 xml_rd_finish (xml_rd **xml)
205 {
206     if (*xml) {
207         if ((*xml)->doc) {
208             xmlFreeDoc((*xml)->doc);
209         }
210         xml_rd_node_invalidate_value(*xml);
211 
212         if ((*xml)->subst_cache != NULL) {
213             size_t i, len = mem_len((*xml)->subst_cache);
214             for (i = 0; i < len; i ++) {
215                 mem_free((char*) (*xml)->subst_cache[i].uri);
216             }
217             mem_free((*xml)->subst_cache);
218         }
219 
220         mem_free((*xml)->pathlen);
221         mem_free((*xml)->path);
222         mem_free(*xml);
223         *xml = NULL;
224     }
225 }
226 
227 /* Perform namespace prefix substitution. Is substitution
228  * is not setup or no match was found, the original prefix
229  * will be returned
230  */
231 static const char*
xml_rd_ns_subst_lookup(xml_rd * xml,const char * prefix,const char * href)232 xml_rd_ns_subst_lookup(xml_rd *xml, const char *prefix, const char *href)
233 {
234     size_t i, len = mem_len(xml->subst_cache);
235 
236     /* Substitution enabled? */
237     if (xml->subst_rules == NULL) {
238         return prefix;
239     }
240 
241     /* Lookup cache first */
242     for (i = 0; i < len; i ++) {
243         if (!strcmp(href, xml->subst_cache[i].uri)) {
244             return xml->subst_cache[i].prefix;
245         }
246     }
247 
248     /* Now try glob-style rules */
249     for (i = 0; xml->subst_rules[i].prefix != NULL; i ++) {
250         if (!fnmatch(xml->subst_rules[i].uri, href, 0)) {
251             prefix = xml->subst_rules[i].prefix;
252 
253             /* Update cache. Grow it if required */
254             xml->subst_cache = mem_resize(xml->subst_cache, len + 1, 0);
255             xml->subst_cache[len].prefix = prefix;
256             xml->subst_cache[len].uri = str_dup(href);
257 
258             /* Break out of loop */
259             break;
260         }
261     }
262 
263     return prefix;
264 }
265 
266 /* Get current node depth in the tree. Root depth is 0
267  */
268 unsigned int
xml_rd_depth(xml_rd * xml)269 xml_rd_depth (xml_rd *xml)
270 {
271     return xml->depth;
272 }
273 
274 /* Check for end-of-document condition
275  */
276 bool
xml_rd_end(xml_rd * xml)277 xml_rd_end (xml_rd *xml)
278 {
279     return xml->node == NULL;
280 }
281 
282 /* Shift to the next node
283  */
284 void
xml_rd_next(xml_rd * xml)285 xml_rd_next (xml_rd *xml)
286 {
287     if (xml->node) {
288         xml->node = xml->node->next;
289         xml_rd_skip_dummy(xml);
290         xml_rd_node_switched(xml);
291     }
292 }
293 
294 /* Shift to the next node, visiting the nested nodes on the way
295  *
296  * If depth > 0, it will not return from nested nodes
297  * upper the specified depth
298  */
299 void
xml_rd_deep_next(xml_rd * xml,unsigned int depth)300 xml_rd_deep_next (xml_rd *xml, unsigned int depth)
301 {
302     xml_rd_enter(xml);
303 
304     while (xml_rd_end(xml) && xml_rd_depth(xml) > depth + 1) {
305         xml_rd_leave(xml);
306         xml_rd_next(xml);
307     }
308 }
309 
310 /* Enter the current node - iterate its children
311  */
312 void
xml_rd_enter(xml_rd * xml)313 xml_rd_enter (xml_rd *xml)
314 {
315     if (xml->node) {
316         /* Save current path length into pathlen stack */
317         xml->path = str_append_c(xml->path, '/');
318 
319         xml->pathlen = mem_resize(xml->pathlen, xml->depth + 1, 0);
320         xml->pathlen[xml->depth] = mem_len(xml->path);
321 
322         /* Enter the node */
323         xml->parent = xml->node;
324         xml->node = xml->node->children;
325         xml_rd_skip_dummy(xml);
326 
327         /* Increment depth and recompute node name */
328         xml->depth ++;
329         xml_rd_skip_dummy(xml);
330         xml_rd_node_switched(xml);
331     }
332 }
333 
334 /* Leave the current node - return to its parent
335  */
336 void
xml_rd_leave(xml_rd * xml)337 xml_rd_leave (xml_rd *xml)
338 {
339     if (xml->depth > 0) {
340         xml->depth --;
341         xml->node = xml->parent;
342         if (xml->node) {
343             xml->parent = xml->node->parent;
344         }
345 
346         xml_rd_node_switched(xml);
347     }
348 }
349 
350 /* Get name of the current node.
351  *
352  * The returned string remains valid, until reader is cleaned up
353  * or current node is changed (by set/next/enter/leave operations).
354  * You don't need to free this string explicitly
355  */
356 const char*
xml_rd_node_name(xml_rd * xml)357 xml_rd_node_name (xml_rd *xml)
358 {
359     return xml->name;
360 }
361 
362 /* Get full path to the current node, '/'-separated
363  */
364 const char*
xml_rd_node_path(xml_rd * xml)365 xml_rd_node_path (xml_rd *xml)
366 {
367     return xml->node ? xml->path : NULL;
368 }
369 
370 /* Match name of the current node against the pattern
371  */
372 bool
xml_rd_node_name_match(xml_rd * xml,const char * pattern)373 xml_rd_node_name_match (xml_rd *xml, const char *pattern)
374 {
375     return xml->name != NULL && !strcmp(xml->name, pattern);
376 }
377 
378 /* Get value of the current node as text
379  *
380  * The returned string remains valid, until reader is cleaned up
381  * or current node is changed (by set/next/enter/leave operations).
382  * You don't need to free this string explicitly
383  */
384 const char*
xml_rd_node_value(xml_rd * xml)385 xml_rd_node_value (xml_rd *xml)
386 {
387     if (xml->text == NULL && xml->node != NULL) {
388         xml->text = xmlNodeGetContent(xml->node);
389         str_trim((char*) xml->text);
390     }
391 
392     return (const char*) xml->text;
393 }
394 
395 /* Get value of the current node as unsigned integer
396  */
397 error
xml_rd_node_value_uint(xml_rd * xml,SANE_Word * val)398 xml_rd_node_value_uint (xml_rd *xml, SANE_Word *val)
399 {
400     const char *s = xml_rd_node_value(xml);
401     char *end;
402     unsigned long v;
403 
404     log_assert(NULL, s != NULL);
405 
406     v = strtoul(s, &end, 10);
407     if (end == s || *end || v != (unsigned long) (SANE_Word) v) {
408         return eloop_eprintf("%s: invalid numerical value",
409                 xml_rd_node_name(xml));
410     }
411 
412     *val = (SANE_Word) v;
413     return NULL;
414 }
415 
416 /******************** XML writer ********************/
417 /* XML writer node
418  */
419 typedef struct xml_wr_node xml_wr_node;
420 struct xml_wr_node {
421     const char     *name;     /* Node name */
422     const char     *value;    /* Node value, if any */
423     const xml_attr *attrs;    /* Attributes, if any */
424     xml_wr_node    *children; /* Node children, if any */
425     xml_wr_node    *next;     /* Next sibling node, if any */
426     xml_wr_node    *parent;   /* Parent node, if any */
427 };
428 
429 /* XML writer
430  */
431 struct xml_wr {
432     xml_wr_node  *root;    /* Root node */
433     xml_wr_node  *current; /* Current node */
434     const xml_ns *ns;     /* Namespace */
435 };
436 
437 /* Create XML writer node
438  */
439 static xml_wr_node*
xml_wr_node_new(const char * name,const char * value,const xml_attr * attrs)440 xml_wr_node_new (const char *name, const char *value, const xml_attr *attrs)
441 {
442     xml_wr_node *node = mem_new(xml_wr_node, 1);
443     node->name = str_dup(name);
444     node->attrs = attrs;
445     if (value != NULL) {
446         node->value = str_dup(value);
447     }
448     return node;
449 }
450 
451 /* Free XML writer node
452  */
453 static void
xml_wr_node_free(xml_wr_node * node)454 xml_wr_node_free (xml_wr_node *node)
455 {
456     mem_free((char*) node->name);
457     mem_free((char*) node->value);
458     mem_free(node);
459 }
460 
461 /* Free XML writer node with its children
462  */
463 static void
xml_wr_node_free_recursive(xml_wr_node * node)464 xml_wr_node_free_recursive (xml_wr_node *node)
465 {
466     xml_wr_node *node2, *next;
467     for (node2 = node->children; node2 != NULL; node2 = next) {
468         next = node2->next;
469         xml_wr_node_free_recursive(node2);
470     }
471     xml_wr_node_free(node);
472 }
473 
474 /* Begin writing XML document. Root node will be created automatically
475  */
476 xml_wr*
xml_wr_begin(const char * root,const xml_ns * ns)477 xml_wr_begin (const char *root, const xml_ns *ns)
478 {
479     xml_wr *xml = mem_new(xml_wr, 1);
480     xml->root = xml_wr_node_new(root, NULL, NULL);
481     xml->current = xml->root;
482     xml->ns = ns;
483     return xml;
484 }
485 
486 /* Format indentation space
487  */
488 static char*
xml_wr_format_indent(char * buf,unsigned int level)489 xml_wr_format_indent (char *buf, unsigned int level)
490 {
491         unsigned int i;
492 
493         for (i = 0; i < level; i ++) {
494             buf = str_append_c(buf, ' ');
495             buf = str_append_c(buf, ' ');
496         }
497 
498         return buf;
499 }
500 
501 /* Format node's value
502  */
503 static char*
xml_wr_format_value(char * buf,const char * value)504 xml_wr_format_value (char *buf, const char *value)
505 {
506     for (;;) {
507         char c = *value ++;
508         switch (c) {
509         case '&':  buf = str_append(buf, "&amp;"); break;
510         case '<':  buf = str_append(buf, "&lt;"); break;
511         case '>':  buf = str_append(buf, "&gt;"); break;
512         case '"':  buf = str_append(buf, "&quot;"); break;
513         case '\'': buf = str_append(buf, "&apos;"); break;
514         case '\0': return buf;
515         default:   buf = str_append_c(buf, c);
516         }
517     }
518 
519     return buf;
520 }
521 
522 /* Format node with its children, recursively
523  */
524 static char*
xml_wr_format_node(xml_wr * xml,char * buf,xml_wr_node * node,unsigned int level,bool compact)525 xml_wr_format_node (xml_wr *xml, char *buf,
526         xml_wr_node *node, unsigned int level, bool compact)
527 {
528     if (!compact) {
529         buf = xml_wr_format_indent(buf, level);
530     }
531 
532     buf = str_append_printf(buf, "<%s", node->name);
533     if (level == 0) {
534         /* Root node defines namespaces */
535         int i;
536         for (i = 0; xml->ns[i].uri != NULL; i ++) {
537             buf = str_append_printf(buf, " xmlns:%s=\"%s\"",
538                 xml->ns[i].prefix, xml->ns[i].uri);
539         }
540     }
541     if (node->attrs != NULL) {
542         int i;
543         for (i = 0; node->attrs[i].name != NULL; i ++) {
544             buf = str_append_printf(buf, " %s=\"%s\"",
545                 node->attrs[i].name, node->attrs[i].value);
546         }
547     }
548     buf = str_append_c(buf, '>');
549 
550     if (node->children) {
551         xml_wr_node *node2;
552 
553         if (!compact) {
554             buf = str_append_c(buf, '\n');
555         }
556 
557         for (node2 = node->children; node2 != NULL; node2 = node2->next) {
558             buf = xml_wr_format_node(xml, buf, node2, level + 1, compact);
559         }
560 
561         if (!compact) {
562             buf = xml_wr_format_indent(buf, level);
563         }
564 
565         buf = str_append_printf(buf, "</%s>", node->name);
566         if (!compact && level != 0) {
567             buf = str_append_c(buf, '\n');
568         }
569     } else {
570         if (node->value != NULL) {
571             buf = xml_wr_format_value(buf, node->value);
572         }
573         buf = str_append_printf(buf,"</%s>", node->name);
574         if (!compact) {
575             buf = str_append_c(buf, '\n');
576         }
577     }
578 
579     return buf;
580 }
581 
582 /* Revert list of node's children, recursively
583  */
584 static void
xml_wr_revert_children(xml_wr_node * node)585 xml_wr_revert_children (xml_wr_node *node)
586 {
587     xml_wr_node *next, *prev = NULL, *node2;
588 
589     for (node2 = node->children; node2 != NULL; node2 = next) {
590         xml_wr_revert_children (node2);
591         next = node2->next;
592         node2->next = prev;
593         prev = node2;
594     }
595 
596     node->children = prev;
597 }
598 
599 /* xml_wr_finish(), internal version
600  */
601 static char*
xml_wr_finish_internal(xml_wr * xml,bool compact)602 xml_wr_finish_internal (xml_wr *xml, bool compact)
603 {
604     char *buf;
605 
606     buf = str_dup("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
607     if (!compact) {
608         buf = str_append_c(buf, '\n');
609     }
610 
611     xml_wr_revert_children(xml->root);
612     buf = xml_wr_format_node(xml, buf, xml->root, 0, compact);
613 
614     xml_wr_node_free_recursive(xml->root);
615     mem_free(xml);
616 
617     return buf;
618 }
619 
620 /* Finish writing, generate document string.
621  * Caller must mem_free() this string after use
622  */
623 char*
xml_wr_finish(xml_wr * xml)624 xml_wr_finish (xml_wr *xml)
625 {
626     return xml_wr_finish_internal(xml, false);
627 }
628 
629 /* Like xml_wr_finish, but returns compact representation
630  * of XML (without indentation and new lines)
631  */
632 char*
xml_wr_finish_compact(xml_wr * xml)633 xml_wr_finish_compact (xml_wr *xml)
634 {
635     return xml_wr_finish_internal(xml, true);
636 }
637 
638 /* Add XML writer node to the current node's children
639  */
640 static void
xml_wr_add_node(xml_wr * xml,xml_wr_node * node)641 xml_wr_add_node (xml_wr *xml, xml_wr_node *node)
642 {
643     node->parent = xml->current;
644     node->next = xml->current->children;
645     xml->current->children = node;
646 }
647 
648 /* Add node with textual value
649  */
650 void
xml_wr_add_text(xml_wr * xml,const char * name,const char * value)651 xml_wr_add_text (xml_wr *xml, const char *name, const char *value)
652 {
653     xml_wr_add_text_attr(xml, name, value, NULL);
654 }
655 
656 /* Add text node with attributes
657  */
658 void
xml_wr_add_text_attr(xml_wr * xml,const char * name,const char * value,const xml_attr * attrs)659 xml_wr_add_text_attr (xml_wr *xml, const char *name, const char *value,
660         const xml_attr *attrs)
661 {
662     xml_wr_add_node(xml, xml_wr_node_new(name, value, attrs));
663 }
664 
665 /* Add node with unsigned integer value
666  */
667 void
xml_wr_add_uint(xml_wr * xml,const char * name,unsigned int value)668 xml_wr_add_uint (xml_wr *xml, const char *name, unsigned int value)
669 {
670     xml_wr_add_uint_attr(xml, name, value, NULL);
671 }
672 
673 /* Add node with unsigned integer value and attributes
674  */
675 void
xml_wr_add_uint_attr(xml_wr * xml,const char * name,unsigned int value,const xml_attr * attrs)676 xml_wr_add_uint_attr (xml_wr *xml, const char *name, unsigned int value,
677         const xml_attr *attrs)
678 {
679     char buf[64];
680     sprintf(buf, "%u", value);
681     xml_wr_add_text_attr(xml, name, buf, attrs);
682 }
683 
684 /* Add node with boolean value
685  */
686 void
xml_wr_add_bool(xml_wr * xml,const char * name,bool value)687 xml_wr_add_bool (xml_wr *xml, const char *name, bool value)
688 {
689     xml_wr_add_bool_attr(xml, name, value, NULL);
690 }
691 
692 /* Add node with boolean value and attributes
693  */
694 void
xml_wr_add_bool_attr(xml_wr * xml,const char * name,bool value,const xml_attr * attrs)695 xml_wr_add_bool_attr (xml_wr *xml, const char *name, bool value,
696         const xml_attr *attrs)
697 {
698     xml_wr_add_text_attr(xml, name, value ? "true" : "false", attrs);
699 }
700 
701 /* Create node with children and enter newly added node
702  */
703 void
xml_wr_enter(xml_wr * xml,const char * name)704 xml_wr_enter (xml_wr *xml, const char *name)
705 {
706     xml_wr_enter_attr(xml, name, NULL);
707 }
708 
709 /* xml_wr_enter with attributes
710  */
711 void
xml_wr_enter_attr(xml_wr * xml,const char * name,const xml_attr * attrs)712 xml_wr_enter_attr (xml_wr *xml, const char *name, const xml_attr *attrs)
713 {
714     xml_wr_node *node = xml_wr_node_new(name, NULL, attrs);
715     xml_wr_add_node(xml, node);
716     xml->current = node;
717 }
718 
719 /* Leave the current node
720  */
721 void
xml_wr_leave(xml_wr * xml)722 xml_wr_leave (xml_wr *xml)
723 {
724     log_assert(NULL, xml->current->parent != NULL);
725     xml->current = xml->current->parent;
726 }
727 
728 /******************** XML formatter ********************/
729 /* Format node name with namespace prefix
730  */
731 static void
xml_format_node_name(FILE * fp,xmlNode * node)732 xml_format_node_name (FILE *fp, xmlNode *node)
733 {
734     if (node->ns != NULL && node->ns->prefix != NULL) {
735         fputs((char*) node->ns->prefix, fp);
736         putc(':', fp);
737     }
738     fputs((char*) node->name, fp);
739 }
740 
741 /* Format node attributes
742  */
743 static void
xml_format_node_attrs(FILE * fp,xmlNode * node)744 xml_format_node_attrs (FILE *fp, xmlNode *node)
745 {
746     xmlNs   *ns;
747     xmlAttr *attr;
748 
749     /* Format namespace attributes */
750     for (ns = node->nsDef; ns != NULL; ns = ns->next) {
751         if (ns->prefix == NULL) {
752             continue;
753         }
754 
755         /* Write namespace name */
756         putc(' ', fp);
757         fputs("xmlns:", fp);
758         fputs((char*) ns->prefix, fp);
759 
760         /* Write namespace value */
761         putc('=', fp);
762         putc('"', fp);
763         fputs((char*) ns->href, fp);
764         putc('"', fp);
765     }
766 
767     /* Format properties */
768     for (attr = node->properties; attr != NULL; attr = attr->next) {
769         xmlChar *val = xmlNodeListGetString(node->doc, attr->children, 1);
770 
771         /* Write attribute name with namespace prefix */
772         putc(' ', fp);
773         if (attr->ns != NULL && attr->ns->prefix != NULL) {
774             fputs((char*) attr->ns->prefix, fp);
775             putc(':', fp);
776         }
777         fputs((char*) attr->name, fp);
778 
779         /* Write attribute value */
780         putc('=', fp);
781         putc('"', fp);
782         fputs((char*) val, fp);
783         putc('"', fp);
784 
785         xmlFree(val);
786     }
787 }
788 
789 /* Format indent
790  */
791 static void
xml_format_indent(FILE * fp,int indent)792 xml_format_indent (FILE *fp, int indent)
793 {
794     int     i;
795 
796     for (i = 0; i < indent; i ++) {
797         putc(' ', fp);
798         putc(' ', fp);
799     }
800 }
801 
802 /* Format entire node
803  */
804 static void
xml_format_node(FILE * fp,xmlNode * node,int indent)805 xml_format_node (FILE *fp, xmlNode *node, int indent)
806 {
807     xmlNode *child;
808     bool    with_children = false;
809     bool    with_value = false;
810 
811     /* Format opening tag */
812     xml_format_indent(fp, indent);
813 
814     putc('<', fp);
815     xml_format_node_name(fp, node);
816     xml_format_node_attrs(fp, node);
817 
818     for (child = node->children; child != NULL; child = child->next) {
819         if (child->type == XML_ELEMENT_NODE) {
820             if (!with_children) {
821                 putc('>', fp);
822                 putc('\n', fp);
823                 with_children = true;
824             }
825             xml_format_node(fp, child, indent + 1);
826         }
827     }
828 
829     if (!with_children) {
830         xmlChar *val = xmlNodeGetContent(node);
831         str_trim((char*) val);
832 
833         if (*val != '\0') {
834             putc('>', fp);
835             fputs((char*) val, fp);
836             with_value = true;
837         }
838 
839         xmlFree(val);
840     }
841 
842     if (with_children) {
843         xml_format_indent(fp, indent);
844     }
845 
846     /* Format closing tag */
847     if (with_children || with_value) {
848         putc('<', fp);
849         putc('/', fp);
850         xml_format_node_name(fp, node);
851         putc('>', fp);
852     } else {
853         putc('/', fp);
854         putc('>', fp);
855     }
856 
857     putc('\n', fp);
858 }
859 
860 /* Format XML to file. It either succeeds, writes a formatted XML
861  * and returns true, or fails, writes nothing to file and returns false
862  */
863 bool
xml_format(FILE * fp,const char * xml_text,size_t xml_len)864 xml_format (FILE *fp, const char *xml_text, size_t xml_len)
865 {
866     xmlDoc  *doc;
867     error   err = xml_rd_parse(&doc, xml_text, xml_len);
868     xmlNode *node;
869 
870     if (err != NULL) {
871         return err;
872     }
873 
874     for (node = doc->children; node != NULL; node = node->next) {
875         xml_format_node(fp, node, 0);
876     }
877 
878     xmlFreeDoc(doc);
879 
880     return true;
881 }
882 
883 /* vim:ts=8:sw=4:et
884  */
885