1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2008 Free Software Foundation, Inc.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include "dom.h"
26 
27 #define XML_DOCUMENT_TAG_NAME ("#document")
28 #define XML_TEXT_NODE_TAG_NAME ("#text")
29 #define XML_TAB_WIDTH (2)
30 #define XML_RC ("\n")
31 #define XML_SORT_ATTRIBUTES
32 
33 
34 struct _DomDocumentPrivate {
35 	GQueue *open_nodes;
36 };
37 
38 
39 GQuark
dom_error_quark(void)40 dom_error_quark (void)
41 {
42 	return g_quark_from_static_string ("dom-error-quark");
43 }
44 
45 
46 static void
_g_list_free_g_object_unref(GList * self)47 _g_list_free_g_object_unref (GList *self)
48 {
49 	g_list_foreach (self, ((GFunc) (g_object_unref)), NULL);
50 	g_list_free (self);
51 }
52 
53 
54 static void
_g_string_append_c_n_times(GString * string,char c,int times)55 _g_string_append_c_n_times (GString *string,
56 			    char     c,
57 		            int      times)
58 {
59 	int i;
60 
61 	for (i = 1; i <= times; i++)
62 		g_string_append_c (string, c);
63 }
64 
65 
66 static char *
_g_xml_attribute_quote(const char * value)67 _g_xml_attribute_quote (const char *value)
68 {
69 	char       *escaped;
70 	GString    *dest;
71 	const char *p;
72 
73 	g_return_val_if_fail (value != NULL, NULL);
74 
75 	if (! g_utf8_validate (value, -1, NULL)) {
76 		char *temp;
77 
78 		temp = g_locale_to_utf8 (value, -1, NULL, NULL, NULL);
79 		if (temp == NULL)
80 			temp = g_utf16_to_utf8 ((gunichar2 *) value, -1, NULL, NULL, NULL);
81 
82 		if (temp != NULL) {
83 			escaped = g_markup_escape_text (temp, -1);
84 			g_free (temp);
85 		}
86 		else
87 			escaped = g_strdup (value);
88 	}
89 	else
90 		escaped = g_markup_escape_text (value, -1);
91 
92   	dest = g_string_new ("\"");
93 	for (p = escaped; (*p); p++) {
94 		if (*p == '"')
95 			g_string_append (dest, "\\\"");
96 		else
97 			g_string_append_c (dest, *p);
98 	}
99 	g_string_append_c (dest, '"');
100 
101   	g_free (escaped);
102 
103 	return g_string_free (dest, FALSE);
104 }
105 
106 
107 static gboolean
_g_xml_tag_is_special(const char * tag_name)108 _g_xml_tag_is_special (const char *tag_name)
109 {
110 	return (*tag_name == '#');
111 }
112 
113 
114 /* -- DomElement -- */
115 
116 
G_DEFINE_TYPE(DomElement,dom_element,G_TYPE_INITIALLY_UNOWNED)117 G_DEFINE_TYPE (DomElement, dom_element, G_TYPE_INITIALLY_UNOWNED)
118 
119 
120 static void
121 dom_attribute_dump (gconstpointer key,
122 		    gconstpointer value,
123 		    gpointer user_data)
124 {
125 	GString *xml = user_data;
126 	char    *quoted_value;
127 
128 	g_string_append_c (xml, ' ');
129 	g_string_append (xml, key);
130 	g_string_append_c (xml, '=');
131 	quoted_value = _g_xml_attribute_quote (value);
132 	g_string_append (xml, quoted_value);
133 	g_free (quoted_value);
134 }
135 
136 
137 static char *
dom_element_dump(DomElement * self,int level)138 dom_element_dump (DomElement *self,
139 		  int         level)
140 {
141 	return DOM_ELEMENT_GET_CLASS (self)->dump (self, level);
142 }
143 
144 
145 static gboolean
dom_element_equal(DomElement * self,DomElement * other)146 dom_element_equal (DomElement *self,
147 		   DomElement *other)
148 {
149 	return DOM_ELEMENT_GET_CLASS (self)->equal (self, other);
150 }
151 
152 
153 static char *
dom_element_real_dump(DomElement * self,int level)154 dom_element_real_dump (DomElement *self,
155 		       int         level)
156 {
157 	GString *xml;
158 	GList   *scan;
159 
160 	xml = g_string_new ("");
161 
162 	if ((self->parent_node != NULL) && (self != self->parent_node->first_child))
163 		 g_string_append (xml, XML_RC);
164 
165 	/* start tag */
166 
167 	if (! _g_xml_tag_is_special (self->tag_name)) {
168 		_g_string_append_c_n_times (xml, ' ', level * XML_TAB_WIDTH);
169 		g_string_append_c (xml, '<');
170 		g_string_append (xml, self->tag_name);
171 
172 #ifdef XML_SORT_ATTRIBUTES
173 		/* attributes, sorted by name to make the xml testable */
174 
175 		{
176 			GList *attrs = g_hash_table_get_keys (self->attributes);
177 			GList *sorted_attrs = g_list_sort (attrs, (GCompareFunc) g_utf8_collate);
178 			GList *scan;
179 
180 			for (scan = sorted_attrs; scan; scan = scan->next) {
181 				const char *key = scan->data;
182 				const char *value = g_hash_table_lookup (self->attributes, key);
183 				dom_attribute_dump (key, value, xml);
184 			}
185 		}
186 #else
187 		g_hash_table_foreach (self->attributes, (GHFunc) dom_attribute_dump, xml);
188 #endif
189 		if (self->child_nodes != NULL) {
190 			g_string_append_c (xml, '>');
191 			if (! DOM_IS_TEXT_NODE (self->first_child))
192 				g_string_append (xml, XML_RC);
193 		}
194 		else {
195 			g_string_append (xml, "/>");
196 			return g_string_free (xml, FALSE);
197 		}
198 	}
199 
200 	/* tag children */
201 
202 	for (scan = self->child_nodes; scan != NULL; scan = scan->next) {
203 		DomElement *child = scan->data;
204 		char       *child_dump;
205 
206 		child_dump = dom_element_dump (child, level + 1);
207 		g_string_append (xml, child_dump);
208 		g_free (child_dump);
209         }
210 
211 	/* end tag */
212 
213 	if (! _g_xml_tag_is_special (self->tag_name)) {
214 		if ((self->child_nodes != NULL) && ! DOM_IS_TEXT_NODE (self->first_child)) {
215 			g_string_append (xml, XML_RC);
216 			_g_string_append_c_n_times (xml, ' ', level * XML_TAB_WIDTH);
217 		}
218 		g_string_append (xml, "</");
219 		g_string_append (xml, self->tag_name);
220 		g_string_append_c (xml, '>');
221 	}
222 
223 	return g_string_free (xml, FALSE);
224 }
225 
226 
227 static gboolean
_empty_text_node(DomElement * node)228 _empty_text_node (DomElement *node)
229 {
230 	GRegex   *regex;
231 	char     *data;
232 	gboolean  empty;
233 
234 	if (! DOM_IS_TEXT_NODE (node))
235 		return FALSE;
236 
237 	regex = g_regex_new ("[ \t\r\n]+", G_REGEX_MULTILINE, G_REGEX_MATCH_NEWLINE_ANY, NULL);
238 	data = g_regex_replace_literal (regex,
239 					DOM_TEXT_NODE (node)->data,
240 					-1,
241 					0,
242 					"",
243 					G_REGEX_MATCH_NEWLINE_ANY,
244 					NULL);
245 	empty = g_utf8_collate (data, "") == 0;
246 	/*g_print ("data: >>%s<<\n", data);*/
247 
248 	g_free (data);
249 	g_regex_unref (regex);
250 
251 	return empty;
252 }
253 
254 
255 static gboolean
dom_element_real_equal(DomElement * a,DomElement * b)256 dom_element_real_equal (DomElement *a,
257 		        DomElement *b)
258 {
259 	gboolean  equal;
260 	GList    *scan_a, *scan_b;
261 
262 	if (a == b)
263 		return TRUE;
264 
265 	if ((a == NULL) || (b == NULL))
266 		return FALSE;
267 
268 	equal = TRUE;
269 
270 	/* tag */
271 
272 	if (g_utf8_collate (a->tag_name, b->tag_name) != 0)
273 		equal = FALSE;
274 
275 	/* attributes */
276 
277 	if (equal) {
278 		GList *a_attributes, *b_attributes;
279 
280 		a_attributes = g_hash_table_get_keys (a->attributes);
281 		b_attributes = g_hash_table_get_keys (b->attributes);
282 
283 		if (g_list_length (a_attributes) != g_list_length (b_attributes))
284 			equal = FALSE;
285 
286 		for (scan_a = a_attributes; equal && scan_a; scan_a = scan_a->next) {
287 			const char *key_a = scan_a->data;
288 			gboolean    key_found = FALSE;
289 
290 			for (scan_b = b_attributes; equal && ! key_found && scan_b; scan_b = scan_b->next) {
291 				const char *key_b = scan_b->data;
292 
293 				if (strcmp (key_a, key_b) == 0) {
294 					key_found = TRUE;
295 					equal = g_utf8_collate (dom_element_get_attribute (a, key_a), dom_element_get_attribute (b, key_b)) == 0;
296 				}
297 			}
298 
299 			if (! key_found)
300 				equal = FALSE;
301 		}
302 
303 		g_list_free (a_attributes);
304 		g_list_free (b_attributes);
305 	}
306 
307 	/* children */
308 
309 	if (equal) {
310 		scan_a = a->child_nodes;
311 		scan_b = b->child_nodes;
312 		while (equal && scan_a && scan_b) {
313 			while (scan_a && _empty_text_node (scan_a->data))
314 				scan_a = scan_a->next;
315 
316 			while (scan_b && _empty_text_node (scan_b->data))
317 				scan_b = scan_b->next;
318 
319 			if ((scan_a == NULL) && (scan_b == NULL))
320 				break;
321 
322 			if ((scan_a == NULL) || (scan_b == NULL))
323 				equal = FALSE;
324 
325 			if (equal && ! dom_element_equal (scan_a->data, scan_b->data))
326 				equal = FALSE;
327 
328 			scan_a = scan_a->next;
329 			scan_b = scan_b->next;
330 		}
331 	}
332 
333 	return equal;
334 }
335 
336 
337 static void
dom_element_init(DomElement * self)338 dom_element_init (DomElement *self)
339 {
340 	self->tag_name = NULL;
341 	self->prefix = NULL;
342 	self->attributes = g_hash_table_new_full (g_str_hash,
343 						  g_str_equal,
344 						  (GDestroyNotify) g_free,
345 						  (GDestroyNotify )g_free);
346 	self->next_sibling = NULL;
347 	self->previous_sibling = NULL;
348 	self->parent_node = NULL;
349 	self->child_nodes = NULL;
350 	self->first_child = NULL;
351 	self->last_child = NULL;
352 }
353 
354 
355 static void
dom_element_finalize(GObject * obj)356 dom_element_finalize (GObject *obj)
357 {
358 	DomElement *self;
359 
360 	self = DOM_ELEMENT (obj);
361 	g_free (self->tag_name);
362 	g_free (self->prefix);
363 	g_hash_table_unref (self->attributes);
364 	_g_list_free_g_object_unref (self->child_nodes);
365 
366 	G_OBJECT_CLASS (dom_element_parent_class)->finalize (obj);
367 }
368 
369 
370 static void
dom_element_class_init(DomElementClass * klass)371 dom_element_class_init (DomElementClass *klass)
372 {
373 	G_OBJECT_CLASS (klass)->finalize = dom_element_finalize;
374 	DOM_ELEMENT_CLASS (klass)->dump = dom_element_real_dump;
375 	DOM_ELEMENT_CLASS (klass)->equal = dom_element_real_equal;
376 }
377 
378 
379 static DomElement *
dom_element_new(const char * a_tag_name)380 dom_element_new (const char *a_tag_name)
381 {
382 	DomElement *self;
383 
384 	g_return_val_if_fail (a_tag_name != NULL, NULL);
385 
386 	self = g_object_new (DOM_TYPE_ELEMENT, NULL);
387 	self->tag_name = (a_tag_name == NULL) ? NULL : g_strdup (a_tag_name);
388 
389 	return self;
390 }
391 
392 
393 void
dom_element_append_child(DomElement * self,DomElement * child)394 dom_element_append_child (DomElement *self,
395 			  DomElement *child)
396 {
397 	GList *child_link;
398 
399 	g_return_if_fail (DOM_IS_ELEMENT (self));
400 	g_return_if_fail (DOM_IS_ELEMENT (child));
401 
402 	self->child_nodes = g_list_append (self->child_nodes, g_object_ref_sink (child));
403 
404 	/* update child attributes */
405 
406 	child_link = g_list_find (self->child_nodes, child);
407 	child->parent_node = self;
408 	if (child_link->prev != NULL) {
409 		DomElement *prev_sibling;
410 
411 		prev_sibling = child_link->prev->data;
412 		child->previous_sibling = prev_sibling;
413 		prev_sibling->next_sibling = child;
414 	}
415 	else
416 		child->previous_sibling = NULL;
417 	child->next_sibling = NULL;
418 
419 	/* update self attributes */
420 
421 	self->first_child = (self->first_child == NULL) ? child_link->data : self->first_child;
422 	self->last_child = child;
423 }
424 
425 
426 const char *
dom_element_get_attribute(DomElement * self,const char * name)427 dom_element_get_attribute (DomElement *self,
428 			   const char *name)
429 {
430 	g_return_val_if_fail (DOM_IS_ELEMENT (self), NULL);
431 	g_return_val_if_fail (name != NULL, NULL);
432 
433 	return (const char*) g_hash_table_lookup (self->attributes, name);
434 }
435 
436 
437 int
dom_element_get_attribute_as_int(DomElement * self,const char * name)438 dom_element_get_attribute_as_int (DomElement *self,
439 				  const char *name)
440 {
441 	const char *value;
442 	int         i;
443 
444 	i = 0;
445 	value = dom_element_get_attribute (self, name);
446 	if (value != NULL)
447 		i = atoi (value);
448 
449 	return i;
450 }
451 
452 
453 gboolean
dom_element_has_attribute(DomElement * self,const char * name)454 dom_element_has_attribute (DomElement *self,
455 			   const char *name)
456 {
457 	g_return_val_if_fail (DOM_IS_ELEMENT (self), FALSE);
458 	g_return_val_if_fail (name != NULL, FALSE);
459 
460 	return ((const char*) (g_hash_table_lookup (self->attributes, name))) != NULL;
461 }
462 
463 
464 gboolean
dom_element_has_child_nodes(DomElement * self)465 dom_element_has_child_nodes (DomElement *self)
466 {
467 	g_return_val_if_fail (DOM_IS_ELEMENT (self), FALSE);
468 
469 	return self->child_nodes != NULL;
470 }
471 
472 
473 void
dom_element_remove_attribute(DomElement * self,const char * name)474 dom_element_remove_attribute (DomElement *self,
475 			      const char *name)
476 {
477 	g_return_if_fail (DOM_IS_ELEMENT (self));
478 	g_return_if_fail (name != NULL);
479 
480 	g_hash_table_remove (self->attributes, name);
481 }
482 
483 
484 DomElement *
dom_element_remove_child(DomElement * self,DomElement * node)485 dom_element_remove_child (DomElement *self,
486 			  DomElement *node)
487 {
488 	g_return_val_if_fail (DOM_IS_ELEMENT (self), NULL);
489 	g_return_val_if_fail (DOM_IS_ELEMENT (node), NULL);
490 
491 	self->child_nodes = g_list_remove (self->child_nodes, node);
492 	return node;
493 }
494 
495 
496 void
dom_element_replace_child(DomElement * self,DomElement * new_child,DomElement * old_child)497 dom_element_replace_child (DomElement *self,
498 			   DomElement *new_child,
499 			   DomElement *old_child)
500 {
501 	GList *link;
502 
503 	g_return_if_fail (DOM_IS_ELEMENT (self));
504 	g_return_if_fail (DOM_IS_ELEMENT (new_child));
505 	g_return_if_fail (DOM_IS_ELEMENT (old_child));
506 
507 	link = g_list_find (self->child_nodes, old_child);
508 	if (link == NULL)
509 		return;
510 	g_object_unref (link->data);
511 	link->data = g_object_ref (new_child);
512 }
513 
514 
515 void
dom_element_set_attribute(DomElement * self,const char * name,const char * value)516 dom_element_set_attribute (DomElement *self,
517 			   const char *name,
518 			   const char *value)
519 {
520 	g_return_if_fail (DOM_IS_ELEMENT (self));
521 	g_return_if_fail (name != NULL);
522 
523 	if (value == NULL)
524 		g_hash_table_remove (self->attributes, name);
525 	else
526 		g_hash_table_insert (self->attributes, g_strdup (name), g_strdup (value));
527 }
528 
529 
530 const char *
dom_element_get_inner_text(DomElement * self)531 dom_element_get_inner_text (DomElement *self)
532 {
533 	if (! DOM_IS_TEXT_NODE (self->first_child))
534 		return NULL;
535 	else
536 		return DOM_TEXT_NODE (self->first_child)->data;
537 }
538 
539 
540 /* -- DomTextNode -- */
541 
542 
G_DEFINE_TYPE(DomTextNode,dom_text_node,DOM_TYPE_ELEMENT)543 G_DEFINE_TYPE (DomTextNode, dom_text_node, DOM_TYPE_ELEMENT)
544 
545 
546 static char *
547 dom_text_node_real_dump (DomElement *base,
548 			 int         level)
549 {
550 	DomTextNode *self;
551 
552 	self = DOM_TEXT_NODE (base);
553 	return (self->data == NULL ? g_strdup ("") : g_markup_escape_text (self->data, -1));
554 }
555 
556 
557 static gboolean
dom_text_node_real_equal(DomElement * a,DomElement * b)558 dom_text_node_real_equal (DomElement *a,
559 		          DomElement *b)
560 {
561 	if (a == b)
562 		return TRUE;
563 
564 	if ((a == NULL) || (b == NULL))
565 		return FALSE;
566 
567 	if (! DOM_IS_TEXT_NODE (b))
568 		return FALSE;
569 
570 	return dom_str_equal (DOM_TEXT_NODE (a)->data, DOM_TEXT_NODE (b)->data);
571 }
572 
573 
574 static void
dom_text_node_finalize(GObject * obj)575 dom_text_node_finalize (GObject *obj)
576 {
577 	DomTextNode *self;
578 
579 	self = DOM_TEXT_NODE (obj);
580 	g_free (self->data);
581 
582 	G_OBJECT_CLASS (dom_text_node_parent_class)->finalize (obj);
583 }
584 
585 
586 static void
dom_text_node_class_init(DomTextNodeClass * klass)587 dom_text_node_class_init (DomTextNodeClass *klass)
588 {
589 	G_OBJECT_CLASS (klass)->finalize = dom_text_node_finalize;
590 	DOM_ELEMENT_CLASS (klass)->dump = dom_text_node_real_dump;
591 	DOM_ELEMENT_CLASS (klass)->equal = dom_text_node_real_equal;
592 }
593 
594 
595 static void
dom_text_node_init(DomTextNode * self)596 dom_text_node_init (DomTextNode *self)
597 {
598 	DOM_ELEMENT (self)->tag_name = g_strdup (XML_TEXT_NODE_TAG_NAME);
599 	self->data = NULL;
600 }
601 
602 
603 static DomTextNode *
dom_text_node_new(const char * a_data)604 dom_text_node_new (const char *a_data)
605 {
606 	DomTextNode *self;
607 
608 	self = g_object_new (DOM_TYPE_TEXT_NODE, NULL);
609 	self->data = (a_data == NULL ? g_strdup ("") : g_strdup (a_data));
610 
611 	return self;
612 }
613 
614 
615 /* -- DomDocument -- */
616 
617 
G_DEFINE_TYPE_WITH_CODE(DomDocument,dom_document,DOM_TYPE_ELEMENT,G_ADD_PRIVATE (DomDocument))618 G_DEFINE_TYPE_WITH_CODE (DomDocument,
619 			 dom_document,
620 			 DOM_TYPE_ELEMENT,
621 			 G_ADD_PRIVATE (DomDocument))
622 
623 
624 static void
625 dom_document_finalize (GObject *obj)
626 {
627 	DomDocument *self;
628 
629 	self = DOM_DOCUMENT (obj);
630 
631 	g_queue_free (self->priv->open_nodes);
632 
633 	G_OBJECT_CLASS (dom_document_parent_class)->finalize (obj);
634 }
635 
636 
637 static void
dom_document_class_init(DomDocumentClass * klass)638 dom_document_class_init (DomDocumentClass *klass)
639 {
640 	G_OBJECT_CLASS (klass)->finalize = dom_document_finalize;
641 }
642 
643 
644 static void
dom_document_init(DomDocument * self)645 dom_document_init (DomDocument *self)
646 {
647 	DOM_ELEMENT (self)->tag_name = g_strdup (XML_DOCUMENT_TAG_NAME);
648 
649 	self->priv = dom_document_get_instance_private (self);
650 	self->priv->open_nodes = g_queue_new ();
651 }
652 
653 
654 DomDocument *
dom_document_new(void)655 dom_document_new (void)
656 {
657 	DomDocument *self;
658 
659 	self = g_object_new (DOM_TYPE_DOCUMENT, NULL);
660 	return g_object_ref_sink (self);
661 }
662 
663 
664 DomElement *
dom_document_create_element(DomDocument * self,const char * tag_name,...)665 dom_document_create_element (DomDocument *self,
666 			     const char  *tag_name,
667 			     ...)
668 {
669 	DomElement *element;
670 	va_list     var_args;
671 	const char  *attr;
672 	const char *value;
673 
674 	g_return_val_if_fail (DOM_IS_DOCUMENT (self), NULL);
675 	g_return_val_if_fail (tag_name != NULL, NULL);
676 
677 	element = dom_element_new (tag_name);
678 	va_start (var_args, tag_name);
679         while ((attr = va_arg (var_args, const char *)) != NULL) {
680         	value = va_arg (var_args, const char *);
681 		dom_element_set_attribute (element, attr, value);
682         }
683 	va_end (var_args);
684 
685 	return element;
686 }
687 
688 
689 DomElement *
dom_document_create_text_node(DomDocument * self,const char * data)690 dom_document_create_text_node (DomDocument *self,
691 			       const char  *data)
692 {
693 	g_return_val_if_fail (DOM_IS_DOCUMENT (self), NULL);
694 	g_return_val_if_fail (data != NULL, NULL);
695 
696 	return (DomElement *) dom_text_node_new (data);
697 }
698 
699 
700 DomElement *
dom_document_create_element_with_text(DomDocument * self,const char * text_data,const char * tag_name,...)701 dom_document_create_element_with_text (DomDocument  *self,
702 				       const char   *text_data,
703 				       const char   *tag_name,
704 				       ...)
705 {
706 	DomElement *element;
707 	va_list     var_args;
708 	const char  *attr;
709 	const char *value;
710 
711 	g_return_val_if_fail (DOM_IS_DOCUMENT (self), NULL);
712 	g_return_val_if_fail (tag_name != NULL, NULL);
713 
714 	element = dom_element_new (tag_name);
715 	va_start (var_args, tag_name);
716         while ((attr = va_arg (var_args, const char *)) != NULL) {
717         	value = va_arg (var_args, const char *);
718 		dom_element_set_attribute (element, attr, value);
719         }
720 	va_end (var_args);
721 
722 	if (text_data != NULL)
723 		dom_element_append_child (element, dom_document_create_text_node (self, text_data));
724 
725 	return element;
726 }
727 
728 
729 char *
dom_document_dump(DomDocument * self,gsize * len)730 dom_document_dump (DomDocument *self,
731 		   gsize       *len)
732 {
733 	GString *xml;
734 	char    *body;
735 
736 	xml = g_string_sized_new (4096);
737 	g_string_append (xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
738 	g_string_append (xml, XML_RC);
739 
740 	body = dom_element_dump (DOM_ELEMENT (self), -1);
741 	g_string_append (xml, body);
742 	g_free (body);
743 
744 	if (dom_element_has_child_nodes (DOM_ELEMENT (self)))
745 		g_string_append (xml, XML_RC);
746 
747 	if (len != NULL)
748 		*len = xml->len;
749 
750 	return g_string_free (xml, FALSE);
751 }
752 
753 
754 static void
start_element_cb(GMarkupParseContext * context,const char * element_name,const char ** attribute_names,const char ** attribute_values,gpointer user_data,GError ** error)755 start_element_cb (GMarkupParseContext  *context,
756                   const char           *element_name,
757                   const char          **attribute_names,
758                   const char          **attribute_values,
759                   gpointer              user_data,
760                   GError              **error)
761 {
762 	DomDocument *doc = user_data;
763 	DomElement  *node;
764 	int          i;
765 
766 	node = dom_document_create_element (doc, element_name, NULL);
767 	for (i = 0; attribute_names[i]; i++)
768 		dom_element_set_attribute (node, attribute_names[i], attribute_values[i]);
769 	dom_element_append_child (DOM_ELEMENT (g_queue_peek_head (doc->priv->open_nodes)), node);
770 
771 	g_queue_push_head (doc->priv->open_nodes, node);
772 }
773 
774 
775 static void
end_element_cb(GMarkupParseContext * context,const char * element_name,gpointer user_data,GError ** error)776 end_element_cb (GMarkupParseContext  *context,
777 		const char           *element_name,
778 		gpointer              user_data,
779 		GError              **error)
780 {
781 	DomDocument *doc = user_data;
782 
783 	g_queue_pop_head (doc->priv->open_nodes);
784 }
785 
786 
787 static void
text_cb(GMarkupParseContext * context,const char * text,gsize text_len,gpointer user_data,GError ** error)788 text_cb (GMarkupParseContext  *context,
789 	 const char           *text,
790 	 gsize                 text_len,
791 	 gpointer              user_data,
792 	 GError              **error)
793 {
794 	DomDocument *doc = user_data;
795 	char        *data;
796 
797 	data = g_strndup (text, text_len);
798 	dom_element_append_child (DOM_ELEMENT (g_queue_peek_head (doc->priv->open_nodes)),
799 				  dom_document_create_text_node (doc, data));
800 	g_free (data);
801 }
802 
803 
804 static const GMarkupParser markup_parser = {
805 	start_element_cb,     /* start_element */
806 	end_element_cb,       /* end_element */
807 	text_cb,              /* text */
808 	NULL,                 /* passthrough */
809 	NULL                  /* error */
810 };
811 
812 
813 gboolean
dom_document_load(DomDocument * self,const char * xml,gssize len,GError ** error)814 dom_document_load (DomDocument  *self,
815 		   const char   *xml,
816 		   gssize        len,
817                    GError      **error)
818 {
819 	GMarkupParseContext *context;
820 
821 	g_return_val_if_fail (DOM_IS_DOCUMENT (self), FALSE);
822 
823 	if (xml == NULL)
824 		return FALSE;
825 
826 	g_queue_clear (self->priv->open_nodes);
827 	g_queue_push_head (self->priv->open_nodes, self);
828 
829 	context = g_markup_parse_context_new (&markup_parser, 0, self, NULL);
830 	if (! g_markup_parse_context_parse (context, xml, (len < 0) ? strlen (xml) : len, error))
831 		return FALSE;
832 	if (! g_markup_parse_context_end_parse (context, error))
833 		return FALSE;
834 	g_markup_parse_context_free (context);
835 
836 	return TRUE;
837 }
838 
839 
840 gboolean
dom_document_equal(DomDocument * a,DomDocument * b)841 dom_document_equal (DomDocument *a,
842 		    DomDocument *b)
843 {
844 	return dom_element_equal (DOM_ELEMENT (a), DOM_ELEMENT (b));
845 }
846 
847 
848 /* -- DomDomizable -- */
849 
850 
851 G_DEFINE_INTERFACE (DomDomizable, dom_domizable, 0)
852 
853 
854 static void
dom_domizable_default_init(DomDomizableInterface * iface)855 dom_domizable_default_init (DomDomizableInterface *iface)
856 {
857 	/* void */
858 }
859 
860 
861 DomElement *
dom_domizable_create_element(DomDomizable * self,DomDocument * doc)862 dom_domizable_create_element (DomDomizable *self,
863 			      DomDocument  *doc)
864 {
865 	return DOM_DOMIZABLE_GET_INTERFACE (self)->create_element (self, doc);
866 }
867 
868 
869 void
dom_domizable_load_from_element(DomDomizable * self,DomElement * e)870 dom_domizable_load_from_element (DomDomizable *self,
871 				 DomElement   *e)
872 {
873 	DOM_DOMIZABLE_GET_INTERFACE (self)->load_from_element (self, e);
874 }
875 
876 
877 /* -- Utilities -- */
878 
879 
880 /* GMarkupParser converts \r into \n, this function compares two strings
881  * treating \r characters as they were equal to \n.  Furthermore treats invalid
882  * utf8 strings as null values. */
883 gboolean
dom_str_equal(const char * a,const char * b)884 dom_str_equal (const char *a,
885 	       const char *b)
886 {
887 	const char *ai, *bi;
888 
889 	if ((a != NULL) && ! g_utf8_validate (a, -1, NULL))
890 		a = NULL;
891 
892 	if ((b != NULL) && ! g_utf8_validate (b, -1, NULL))
893 		b = NULL;
894 
895 	if ((a == NULL) && (b == NULL))
896 		return TRUE;
897 
898 	if ((a == NULL) || (b == NULL))
899 		return FALSE;
900 
901 	if (g_utf8_collate (a, b) == 0)
902 		return TRUE;
903 
904 	ai = a;
905 	bi = b;
906 	while ((*ai != '\0') && (*bi != '\0')) {
907 		if (*ai != *bi) {
908 			/* \r equal to \n */
909 
910 			if (! (((*ai == '\r') && (*bi == '\n'))
911 			       || ((*ai == '\n') && (*bi == '\r'))))
912 			{
913 				return FALSE;
914 			}
915 
916 			/* \r\n equal to \n */
917 
918 			if ((*ai == '\r') && (*(ai + 1) == '\n') && (*bi == '\n') && (*(bi + 1) != '\n'))
919 				ai = g_utf8_next_char (ai);
920 			if ((*bi == '\r') && (*(bi + 1) == '\n') && (*ai == '\n') && (*(ai + 1) != '\n'))
921 				ai = g_utf8_next_char (ai);
922 		}
923 		ai = g_utf8_next_char (ai);
924 		bi = g_utf8_next_char (bi);
925 	}
926 
927 	/* 'end of string' equal to \n and to \r\n */
928 
929 	return (((*ai == '\0') && (*bi == '\0'))
930 		|| ((*ai == '\0') && (*bi == '\n') && (*(bi + 1) == '\0'))
931 		|| ((*bi == '\0') && (*ai == '\n') && (*(ai + 1) == '\0'))
932 		|| ((*ai == '\0') && (*bi == '\r') && (*(bi + 1) == '\n') && (*(bi + 2) == '\0'))
933 		|| ((*bi == '\0') && (*ai == '\r') && (*(ai + 1) == '\n') && (*(ai + 2) == '\0')));
934 }
935 
936 
937 int
dom_str_find(const char * a,const char * b)938 dom_str_find (const char *a,
939               const char *b)
940 {
941 	return dom_str_equal (a, b) ? 0 : -1;
942 }
943