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