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, "&"); break;
510 case '<': buf = str_append(buf, "<"); break;
511 case '>': buf = str_append(buf, ">"); break;
512 case '"': buf = str_append(buf, """); break;
513 case '\'': buf = str_append(buf, "'"); 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