source: lib/xmltree.c @ 660cb00

Last change on this file since 660cb00 was d912fe4, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-08-14T23:00:53Z

Add xt_find_path() to simplify digging through multi-level XML trees.

  • Property mode set to 100644
File size: 16.8 KB
RevLine 
[8f243ad]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly)       *
5*                                                                           *
6*  Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net>                   *
7*                                                                           *
8*  This library is free software; you can redistribute it and/or            *
9*  modify it under the terms of the GNU Lesser General Public               *
10*  License as published by the Free Software Foundation, version            *
11*  2.1.                                                                     *
12*                                                                           *
13*  This library 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 GNU        *
16*  Lesser General Public License for more details.                          *
17*                                                                           *
18*  You should have received a copy of the GNU Lesser General Public License *
19*  along with this library; if not, write to the Free Software Foundation,  *
20*  Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA           *
21*                                                                           *
22****************************************************************************/
23
24#include <glib.h>
25#include <string.h>
26#include <unistd.h>
27#include <ctype.h>
28#include <stdio.h>
29
30#include "xmltree.h"
31
32static void xt_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error )
33{
34        struct xt_parser *xt = data;
35        struct xt_node *node = g_new0( struct xt_node, 1 ), *nt;
36        int i;
37       
38        node->parent = xt->cur;
39        node->name = g_strdup( element_name );
40       
41        /* First count the number of attributes */
42        for( i = 0; attr_names[i]; i ++ );
43       
44        /* Then allocate a NULL-terminated array. */
45        node->attr = g_new0( struct xt_attr, i + 1 );
46       
47        /* And fill it, saving one variable by starting at the end. */
48        for( i --; i >= 0; i -- )
49        {
50                node->attr[i].key = g_strdup( attr_names[i] );
51                node->attr[i].value = g_strdup( attr_values[i] );
52        }
53       
54        /* Add it to the linked list of children nodes, if we have a current
55           node yet. */
56        if( xt->cur )
57        {
58                if( xt->cur->children )
59                {
60                        for( nt = xt->cur->children; nt->next; nt = nt->next );
61                        nt->next = node;
62                }
63                else
64                {
65                        xt->cur->children = node;
66                }
67        }
68        else if( xt->root )
69        {
70                /* ERROR situation: A second root-element??? */
71        }
72       
73        /* Now this node will be the new current node. */
74        xt->cur = node;
75        /* And maybe this is the root? */
76        if( xt->root == NULL )
77                xt->root = node;
78}
79
80static void xt_text( GMarkupParseContext *ctx, const gchar *text, gsize text_len, gpointer data, GError **error )
81{
82        struct xt_parser *xt = data;
83        struct xt_node *node = xt->cur;
84       
85        if( node == NULL )
86                return;
87       
88        /* FIXME: Does g_renew also OFFICIALLY accept NULL arguments? */
89        node->text = g_renew( char, node->text, node->text_len + text_len + 1 );
90        memcpy( node->text + node->text_len, text, text_len );
91        node->text_len += text_len;
92        /* Zero termination is always nice to have. */
93        node->text[node->text_len] = 0;
94}
95
96static void xt_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error )
97{
98        struct xt_parser *xt = data;
99       
100        xt->cur->flags |= XT_COMPLETE;
101        xt->cur = xt->cur->parent;
102}
103
104GMarkupParser xt_parser_funcs =
105{
106        xt_start_element,
107        xt_end_element,
108        xt_text,
109        NULL,
110        NULL
111};
112
[4bbcba3]113struct xt_parser *xt_new( const struct xt_handler_entry *handlers, gpointer data )
[8f243ad]114{
115        struct xt_parser *xt = g_new0( struct xt_parser, 1 );
116       
117        xt->data = data;
[4bbcba3]118        xt->handlers = handlers;
[8f243ad]119        xt_reset( xt );
120       
121        return xt;
122}
123
124/* Reset the parser, flush everything we have so far. For example, we need
125   this for XMPP when doing TLS/SASL to restart the stream. */
126void xt_reset( struct xt_parser *xt )
127{
128        if( xt->parser )
129                g_markup_parse_context_free( xt->parser );
130       
131        xt->parser = g_markup_parse_context_new( &xt_parser_funcs, 0, xt, NULL );
132       
133        if( xt->root )
134        {
135                xt_free_node( xt->root );
136                xt->root = NULL;
137                xt->cur = NULL;
138        }
139}
140
141/* Feed the parser, don't execute any handler. Returns -1 on errors, 0 on
142   end-of-stream and 1 otherwise. */
[d93c0eb9]143int xt_feed( struct xt_parser *xt, const char *text, int text_len )
[8f243ad]144{
145        if( !g_markup_parse_context_parse( xt->parser, text, text_len, &xt->gerr ) )
146        {
147                return -1;
148        }
149       
150        return !( xt->root && xt->root->flags & XT_COMPLETE );
151}
152
153/* Find completed nodes and see if a handler has to be called. Passing
154   a node isn't necessary if you want to start at the root, just pass
[101d84f]155   NULL. This second argument is needed for recursive calls. */
156int xt_handle( struct xt_parser *xt, struct xt_node *node, int depth )
[8f243ad]157{
158        struct xt_node *c;
159        xt_status st;
160        int i;
161       
[101d84f]162        /* Just in case someone likes infinite loops... */
163        if( xt->root == NULL )
164                return 0;
165       
[8f243ad]166        if( node == NULL )
[101d84f]167                return xt_handle( xt, xt->root, depth );
[8f243ad]168       
[101d84f]169        if( depth != 0 )
170                for( c = node->children; c; c = c->next )
171                        if( !xt_handle( xt, c, depth > 0 ? depth - 1 : depth ) )
172                                return 0;
[8f243ad]173       
174        if( node->flags & XT_COMPLETE && !( node->flags & XT_SEEN ) )
175        {
[4452e69]176                if( xt->handlers ) for( i = 0; xt->handlers[i].func; i ++ )
[8f243ad]177                {
178                        /* This one is fun! \o/ */
179                       
[4452e69]180                            /* If handler.name == NULL it means it should always match. */
[8f243ad]181                        if( ( xt->handlers[i].name == NULL || 
[4452e69]182                              /* If it's not, compare. There should always be a name. */
[8f243ad]183                              g_strcasecmp( xt->handlers[i].name, node->name ) == 0 ) &&
[4452e69]184                            /* If handler.parent == NULL, it's a match. */
[8f243ad]185                            ( xt->handlers[i].parent == NULL ||
[4452e69]186                              /* If there's a parent node, see if the name matches. */
[8f243ad]187                              ( node->parent ? g_strcasecmp( xt->handlers[i].parent, node->parent->name ) == 0 : 
[4452e69]188                              /* If there's no parent, the handler should mention <root> as a parent. */
189                                               strcmp( xt->handlers[i].parent, "<root>" ) == 0 ) ) )
[8f243ad]190                        {
191                                st = xt->handlers[i].func( node, xt->data );
192                               
193                                if( st == XT_ABORT )
194                                        return 0;
195                                else if( st != XT_NEXT )
196                                        break;
197                        }
198                }
199               
200                node->flags |= XT_SEEN;
201        }
202       
203        return 1;
204}
205
206/* Garbage collection: Cleans up all nodes that are handled. Useful for
207   streams because there's no reason to keep a complete packet history
208   in memory. */
[101d84f]209void xt_cleanup( struct xt_parser *xt, struct xt_node *node, int depth )
[8f243ad]210{
211        struct xt_node *c, *prev;
212       
[21167d2]213        if( !xt || !xt->root )
214                return;
215       
[8f243ad]216        if( node == NULL )
[daae10f]217        {
218                xt_cleanup( xt, xt->root, depth );
219                return;
220        }
[8f243ad]221       
222        if( node->flags & XT_SEEN && node == xt->root )
223        {
224                xt_free_node( xt->root );
225                xt->root = xt->cur = NULL;
226                /* xt->cur should be NULL already, BTW... */
227               
228                return;
229        }
230       
231        /* c contains the current node, prev the previous node (or NULL).
232           I admit, this one's pretty horrible. */
233        for( c = node->children, prev = NULL; c; prev = c, c = c ? c->next : node->children )
234        {
235                if( c->flags & XT_SEEN )
236                {
237                        /* Remove the node from the linked list. */
238                        if( prev )
239                                prev->next = c->next;
240                        else
241                                node->children = c->next;
242                       
243                        xt_free_node( c );
244                       
245                        /* Since the for loop wants to get c->next, make sure
246                           c points at something that exists (and that c->next
247                           will actually be the next item we should check). c
248                           can be NULL now, if we just removed the first item.
249                           That explains the ? thing in for(). */
250                        c = prev;
251                }
252                else
253                {
254                        /* This node can't be cleaned up yet, but maybe a
255                           subnode can. */
[101d84f]256                        if( depth != 0 )
257                                xt_cleanup( xt, c, depth > 0 ? depth - 1 : depth );
[8f243ad]258                }
259        }
260}
261
[d93c0eb9]262struct xt_node *xt_from_string( const char *in )
263{
264        struct xt_parser *parser;
265        struct xt_node *ret;
266       
267        parser = xt_new( NULL, NULL );
268        xt_feed( parser, in, strlen( in ) );
269        ret = parser->root;
270        parser->root = NULL;
271        xt_free( parser );
272       
273        return ret;
274}
275
[8f243ad]276static void xt_to_string_real( struct xt_node *node, GString *str )
277{
278        char *buf;
279        struct xt_node *c;
280        int i;
281       
282        g_string_append_printf( str, "<%s", node->name );
283       
284        for( i = 0; node->attr[i].key; i ++ )
285        {
286                buf = g_markup_printf_escaped( " %s=\"%s\"", node->attr[i].key, node->attr[i].value );
287                g_string_append( str, buf );
288                g_free( buf );
289        }
290       
291        if( node->text == NULL && node->children == NULL )
292        {
293                g_string_append( str, "/>" );
294                return;
295        }
296       
297        g_string_append( str, ">" );
298        if( node->text_len > 0 )
299        {
300                buf = g_markup_escape_text( node->text, node->text_len );
301                g_string_append( str, buf );
302                g_free( buf );
303        }
304       
305        for( c = node->children; c; c = c->next )
306                xt_to_string_real( c, str );
307       
308        g_string_append_printf( str, "</%s>", node->name );
309}
310
311char *xt_to_string( struct xt_node *node )
312{
313        GString *ret;
314        char *real;
315       
316        ret = g_string_new( "" );
317        xt_to_string_real( node, ret );
318       
319        real = ret->str;
320        g_string_free( ret, FALSE );
321       
322        return real;
323}
324
[1bf1ae6]325#ifdef DEBUG
[8f243ad]326void xt_print( struct xt_node *node )
327{
328        int i;
329        struct xt_node *c;
330       
331        /* Indentation */
332        for( c = node; c->parent; c = c->parent )
333                printf( "\t" );
334       
335        /* Start the tag */
336        printf( "<%s", node->name );
337       
338        /* Print the attributes */
339        for( i = 0; node->attr[i].key; i ++ )
340                printf( " %s=\"%s\"", node->attr[i].key, g_markup_escape_text( node->attr[i].value, -1 ) );
341       
342        /* /> in case there's really *nothing* inside this tag, otherwise
343           just >. */
344        /* If this tag doesn't have any content at all... */
345        if( node->text == NULL && node->children == NULL )
346        {
347                printf( "/>\n" );
348                return;
349                /* Then we're finished! */
350        }
351       
352        /* Otherwise... */
353        printf( ">" );
354       
355        /* Only print the text if it contains more than whitespace (TEST). */
356        if( node->text_len > 0 )
357        {
358                for( i = 0; node->text[i] && isspace( node->text[i] ); i ++ );
359                if( node->text[i] )
360                        printf( "%s", g_markup_escape_text( node->text, -1 ) );
361        }
362       
363        if( node->children )
364                printf( "\n" );
365       
366        for( c = node->children; c; c = c->next )
367                xt_print( c );
368       
369        if( node->children )
370                for( c = node; c->parent; c = c->parent )
371                        printf( "\t" );
372       
373        /* Non-empty tag is now finished. */
374        printf( "</%s>\n", node->name );
375}
[1bf1ae6]376#endif
[8f243ad]377
[022df46]378struct xt_node *xt_dup( struct xt_node *node )
379{
380        struct xt_node *dup = g_new0( struct xt_node, 1 );
381        struct xt_node *c, *dc = NULL;
382        int i;
383       
384        /* Let's NOT copy the parent element here BTW! Only do it for children. */
385       
386        dup->name = g_strdup( node->name );
387        dup->flags = node->flags;
388        if( node->text )
389        {
390                dup->text = g_memdup( node->text, node->text_len + 1 );
391                dup->text_len = node->text_len;
392        }
393       
394        /* Count the number of attributes and allocate the new array. */
395        for( i = 0; node->attr[i].key; i ++ );
396        dup->attr = g_new0( struct xt_attr, i + 1 );
397       
398        /* Copy them all! */
399        for( i --; i >= 0; i -- )
400        {
401                dup->attr[i].key = g_strdup( node->attr[i].key );
402                dup->attr[i].value = g_strdup( node->attr[i].value );
403        }
404       
405        /* This nice mysterious loop takes care of the children. */
406        for( c = node->children; c; c = c->next )
407        {
408                if( dc == NULL )
409                        dc = dup->children = xt_dup( c );
410                else
411                        dc = ( dc->next = xt_dup( c ) );
412               
413                dc->parent = dup;
414        }
415       
416        return dup;
417}
418
[8f243ad]419/* Frees a node. This doesn't clean up references to itself from parents! */
420void xt_free_node( struct xt_node *node )
421{
422        int i;
423       
[21167d2]424        if( !node )
425                return;
426       
[8f243ad]427        g_free( node->name );
428        g_free( node->text );
429       
430        for( i = 0; node->attr[i].key; i ++ )
431        {
432                g_free( node->attr[i].key );
433                g_free( node->attr[i].value );
434        }
435        g_free( node->attr );
436       
437        while( node->children )
438        {
439                struct xt_node *next = node->children->next;
440               
441                xt_free_node( node->children );
442                node->children = next;
443        }
444       
445        g_free( node );
446}
447
448void xt_free( struct xt_parser *xt )
449{
[21167d2]450        if( !xt )
451                return;
452       
[8f243ad]453        if( xt->root )
454                xt_free_node( xt->root );
455       
456        g_markup_parse_context_free( xt->parser );
457       
458        g_free( xt );
459}
460
461/* To find a node's child with a specific name, pass the node's children
462   list, not the node itself! The reason you have to do this by hand: So
463   that you can also use this function as a find-next. */
[40ef702]464struct xt_node *xt_find_node( struct xt_node *node, const char *name )
[8f243ad]465{
466        while( node )
467        {
[3742fb6]468                char *colon;
469               
470                if( g_strcasecmp( node->name, name ) == 0 ||
471                    ( ( colon = strchr( node->name, ':' ) ) &&
472                      g_strcasecmp( colon + 1, name ) == 0 ) )
[8f243ad]473                        break;
474               
475                node = node->next;
476        }
477       
478        return node;
479}
480
[d912fe4]481/* More advanced than the one above, understands something like
482   ../foo/bar to find a subnode bar of a node foo which is a child
483   of node's parent. Pass the node directly, not its list of children. */
484struct xt_node *xt_find_path( struct xt_node *node, const char *name )
485{
486        while( name && *name && node )
487        {
488                char *colon, *slash;
489                int n;
490               
491                if( ( slash = strchr( name, '/' ) ) )
492                        n = slash - name;
493                else
494                        n = strlen( name );
495               
496                if( strncmp( name, "..", n ) == 0 )
497                {
498                        node = node->parent;
499                }
500                else
501                {
502                        node = node->children;
503                       
504                        while( node )
505                        {
506                                if( g_strncasecmp( node->name, name, n ) == 0 ||
507                                    ( ( colon = strchr( node->name, ':' ) ) &&
508                                      g_strncasecmp( colon + 1, name, n ) == 0 ) )
509                                        break;
510                               
511                                node = node->next;
512                        }
513                }
514               
515                name = slash ? slash + 1 : NULL;
516        }
517       
518        return node;
519}
520
[40ef702]521char *xt_find_attr( struct xt_node *node, const char *key )
[8f243ad]522{
523        int i;
[3742fb6]524        char *colon;
[8f243ad]525       
[21167d2]526        if( !node )
527                return NULL;
528       
[8f243ad]529        for( i = 0; node->attr[i].key; i ++ )
530                if( g_strcasecmp( node->attr[i].key, key ) == 0 )
531                        break;
532       
[3742fb6]533        /* This is an awful hack that only takes care of namespace prefixes
534           inside a tag. Since IMHO excessive namespace usage in XMPP is
535           massive overkill anyway (this code exists for almost four years
536           now and never really missed it): Meh. */
537        if( !node->attr[i].key && strcmp( key, "xmlns" ) == 0 &&
538            ( colon = strchr( node->name, ':' ) ) )
539        {
540                *colon = '\0';
541                for( i = 0; node->attr[i].key; i ++ )
542                        if( strncmp( node->attr[i].key, "xmlns:", 6 ) == 0 &&
543                            strcmp( node->attr[i].key + 6, node->name ) == 0 )
544                                break;
545                *colon = ':';
546        }
547       
[8f243ad]548        return node->attr[i].value;
549}
550
[94acdd0]551struct xt_node *xt_new_node( char *name, const char *text, struct xt_node *children )
[8f243ad]552{
553        struct xt_node *node, *c;
554       
555        node = g_new0( struct xt_node, 1 );
556        node->name = g_strdup( name );
557        node->children = children;
558        node->attr = g_new0( struct xt_attr, 1 );
559       
560        if( text )
561        {
562                node->text_len = strlen( text );
[4ecdc69]563                node->text = g_memdup( text, node->text_len + 1 );
[8f243ad]564        }
565       
566        for( c = children; c; c = c->next )
567        {
568                if( c->parent != NULL )
569                {
570                        /* ERROR CONDITION: They seem to have a parent already??? */
571                }
572               
573                c->parent = node;
574        }
575       
576        return node;
577}
578
579void xt_add_child( struct xt_node *parent, struct xt_node *child )
580{
581        struct xt_node *node;
582       
583        /* This function can actually be used to add more than one child, so
584           do handle this properly. */
585        for( node = child; node; node = node->next )
586        {
587                if( node->parent != NULL )
588                {
589                        /* ERROR CONDITION: They seem to have a parent already??? */
590                }
591               
592                node->parent = parent;
593        }
594       
595        if( parent->children == NULL )
596        {
597                parent->children = child;
598        }
599        else
600        {
601                for( node = parent->children; node->next; node = node->next );
602                node->next = child;
603        }
604}
605
[b46769d]606/* Same, but at the beginning. */
607void xt_insert_child( struct xt_node *parent, struct xt_node *child )
608{
609        struct xt_node *node, *last;
610       
611        for( node = child; node; node = node->next )
612        {
613                if( node->parent != NULL )
614                {
615                        /* ERROR CONDITION: They seem to have a parent already??? */
616                }
617               
618                node->parent = parent;
619                last = node;
620        }
621       
622        last->next = parent->children;
623        parent->children = child;
624}
625
[40ef702]626void xt_add_attr( struct xt_node *node, const char *key, const char *value )
[8f243ad]627{
628        int i;
629       
[090f1cb]630        /* Now actually it'd be nice if we can also change existing attributes
631           (which actually means this function doesn't have the right name).
632           So let's find out if we have this attribute already... */
633        for( i = 0; node->attr[i].key; i ++ )
634                if( strcmp( node->attr[i].key, key ) == 0 )
635                        break;
636       
637        if( node->attr[i].key == NULL )
638        {
639                /* If not, allocate space for a new attribute. */
640                node->attr = g_renew( struct xt_attr, node->attr, i + 2 );
641                node->attr[i].key = g_strdup( key );
642                node->attr[i+1].key = NULL;
643        }
644        else
645        {
646                /* Otherwise, free the old value before setting the new one. */
647                g_free( node->attr[i].value );
648        }
649       
[8f243ad]650        node->attr[i].value = g_strdup( value );
651}
[259edd4]652
[40ef702]653int xt_remove_attr( struct xt_node *node, const char *key )
[259edd4]654{
655        int i, last;
656       
657        for( i = 0; node->attr[i].key; i ++ )
658                if( strcmp( node->attr[i].key, key ) == 0 )
659                        break;
660       
661        /* If we didn't find the attribute... */
662        if( node->attr[i].key == NULL )
663                return 0;
664       
665        g_free( node->attr[i].key );
666        g_free( node->attr[i].value );
667       
668        /* If it's the last, this is easy: */
669        if( node->attr[i+1].key == NULL )
670        {
671                node->attr[i].key = node->attr[i].value = NULL;
672        }
673        else /* It's also pretty easy, actually. */
674        {
675                /* Find the last item. */
676                for( last = i + 1; node->attr[last+1].key; last ++ );
677               
678                node->attr[i] = node->attr[last];
679                node->attr[last].key = NULL;
680                node->attr[last].value = NULL;
681        }
682       
683        /* Let's not bother with reallocating memory here. It takes time and
684           most packets don't stay in memory for long anyway. */
685       
686        return 1;
687}
Note: See TracBrowser for help on using the repository browser.