gio/ docs/reference/gio Merged gio-standalone into glib.
[platform/upstream/glib.git] / gio / xdgmime / xdgmimeglob.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* xdgmimeglob.c: Private file.  Datastructure for storing the globs.
3  *
4  * More info can be found at http://www.freedesktop.org/standards/
5  *
6  * Copyright (C) 2003  Red Hat, Inc.
7  * Copyright (C) 2003  Jonathan Blandford <jrb@alum.mit.edu>
8  *
9  * Licensed under the Academic Free License version 2.0
10  * Or under the following terms:
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25  * Boston, MA 02111-1307, USA.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31
32 #include "xdgmimeglob.h"
33 #include "xdgmimeint.h"
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <assert.h>
37 #include <string.h>
38 #include <fnmatch.h>
39
40 #ifndef FALSE
41 #define FALSE   (0)
42 #endif
43
44 #ifndef TRUE
45 #define TRUE    (!FALSE)
46 #endif
47
48 typedef struct XdgGlobHashNode XdgGlobHashNode;
49 typedef struct XdgGlobList XdgGlobList;
50
51 struct XdgGlobHashNode
52 {
53   xdg_unichar_t character;
54   const char *mime_type;
55   XdgGlobHashNode *next;
56   XdgGlobHashNode *child;
57 };
58 struct XdgGlobList
59 {
60   const char *data;
61   const char *mime_type;
62   XdgGlobList *next;
63 };
64
65 struct XdgGlobHash
66 {
67   XdgGlobList *literal_list;
68   XdgGlobHashNode *simple_node;
69   XdgGlobList *full_list;
70 };
71
72
73 /* XdgGlobList
74  */
75 static XdgGlobList *
76 _xdg_glob_list_new (void)
77 {
78   XdgGlobList *new_element;
79
80   new_element = calloc (1, sizeof (XdgGlobList));
81
82   return new_element;
83 }
84
85 /* Frees glob_list and all of it's children */
86 static void
87 _xdg_glob_list_free (XdgGlobList *glob_list)
88 {
89   XdgGlobList *ptr, *next;
90
91   ptr = glob_list;
92
93   while (ptr != NULL)
94     {
95       next = ptr->next;
96
97       if (ptr->data)
98         free ((void *) ptr->data);
99       if (ptr->mime_type)
100         free ((void *) ptr->mime_type);
101       free (ptr);
102
103       ptr = next;
104     }
105 }
106
107 static XdgGlobList *
108 _xdg_glob_list_append (XdgGlobList *glob_list,
109                        void        *data,
110                        const char  *mime_type)
111 {
112   XdgGlobList *new_element;
113   XdgGlobList *tmp_element;
114
115   new_element = _xdg_glob_list_new ();
116   new_element->data = data;
117   new_element->mime_type = mime_type;
118   if (glob_list == NULL)
119     return new_element;
120
121   tmp_element = glob_list;
122   while (tmp_element->next != NULL)
123     tmp_element = tmp_element->next;
124
125   tmp_element->next = new_element;
126
127   return glob_list;
128 }
129
130 #if 0
131 static XdgGlobList *
132 _xdg_glob_list_prepend (XdgGlobList *glob_list,
133                         void        *data,
134                         const char  *mime_type)
135 {
136   XdgGlobList *new_element;
137
138   new_element = _xdg_glob_list_new ();
139   new_element->data = data;
140   new_element->next = glob_list;
141   new_element->mime_type = mime_type;
142
143   return new_element;
144 }
145 #endif
146
147 /* XdgGlobHashNode
148  */
149
150 static XdgGlobHashNode *
151 _xdg_glob_hash_node_new (void)
152 {
153   XdgGlobHashNode *glob_hash_node;
154
155   glob_hash_node = calloc (1, sizeof (XdgGlobHashNode));
156
157   return glob_hash_node;
158 }
159
160 static void
161 _xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node,
162                           int depth)
163 {
164   int i;
165   for (i = 0; i < depth; i++)
166     printf (" ");
167
168   printf ("%c", (char)glob_hash_node->character);
169   if (glob_hash_node->mime_type)
170     printf (" - %s\n", glob_hash_node->mime_type);
171   else
172     printf ("\n");
173   if (glob_hash_node->child)
174     _xdg_glob_hash_node_dump (glob_hash_node->child, depth + 1);
175   if (glob_hash_node->next)
176     _xdg_glob_hash_node_dump (glob_hash_node->next, depth);
177 }
178
179 static XdgGlobHashNode *
180 _xdg_glob_hash_insert_text (XdgGlobHashNode *glob_hash_node,
181                             const char      *text,
182                             const char      *mime_type)
183 {
184   XdgGlobHashNode *node;
185   xdg_unichar_t character;
186
187   character = _xdg_utf8_to_ucs4 (text);
188
189   if ((glob_hash_node == NULL) ||
190       (character < glob_hash_node->character))
191     {
192       node = _xdg_glob_hash_node_new ();
193       node->character = character;
194       node->next = glob_hash_node;
195       glob_hash_node = node;
196     }
197   else if (character == glob_hash_node->character)
198     {
199       node = glob_hash_node;
200     }
201   else
202     {
203       XdgGlobHashNode *prev_node;
204       int found_node = FALSE;
205
206       /* Look for the first character of text in glob_hash_node, and insert it if we
207        * have to.*/
208       prev_node = glob_hash_node;
209       node = prev_node->next;
210
211       while (node != NULL)
212         {
213           if (character < node->character)
214             {
215               node = _xdg_glob_hash_node_new ();
216               node->character = character;
217               node->next = prev_node->next;
218               prev_node->next = node;
219
220               found_node = TRUE;
221               break;
222             }
223           else if (character == node->character)
224             {
225               found_node = TRUE;
226               break;
227             }
228           prev_node = node;
229           node = node->next;
230         }
231
232       if (! found_node)
233         {
234           node = _xdg_glob_hash_node_new ();
235           node->character = character;
236           node->next = prev_node->next;
237           prev_node->next = node;
238         }
239     }
240
241   text = _xdg_utf8_next_char (text);
242   if (*text == '\000')
243     {
244       if (node->mime_type)
245         {
246           if (strcmp (node->mime_type, mime_type))
247             {
248               XdgGlobHashNode *child;
249               int found_node = FALSE;
250               
251               child = node->child;
252               while (child && child->character == '\0')
253                 {
254                   if (strcmp (child->mime_type, mime_type) == 0)
255                     {
256                       found_node = TRUE;
257                       break;
258                     }
259                   child = child->next;
260                 }
261
262               if (!found_node)
263                 {
264                   child = _xdg_glob_hash_node_new ();
265                   child->character = '\000';
266                   child->mime_type = strdup (mime_type);
267                   child->child = NULL;
268                   child->next = node->child;
269                   node->child = child;
270                 }
271             }
272         }
273       else
274         {
275           node->mime_type = strdup (mime_type);
276         }
277     }
278   else
279     {
280       node->child = _xdg_glob_hash_insert_text (node->child, text, mime_type);
281     }
282   return glob_hash_node;
283 }
284
285 static int
286 _xdg_glob_hash_node_lookup_file_name (XdgGlobHashNode *glob_hash_node,
287                                       const char      *file_name,
288                                       int              ignore_case,
289                                       const char      *mime_types[],
290                                       int              n_mime_types)
291 {
292   int n;
293   XdgGlobHashNode *node;
294   xdg_unichar_t character;
295
296   if (glob_hash_node == NULL)
297     return 0;
298
299   character = _xdg_utf8_to_ucs4 (file_name);
300   if (ignore_case)
301     character = _xdg_ucs4_to_lower(character);
302
303   for (node = glob_hash_node; node && character >= node->character; node = node->next)
304     {
305       if (character == node->character)
306         {
307           file_name = _xdg_utf8_next_char (file_name);
308           if (*file_name == '\000')
309             {
310               n = 0;
311               if (node->mime_type)
312                 mime_types[n++] = node->mime_type;
313               node = node->child;
314               while (n < n_mime_types && node && node->character == 0)
315                 {
316                   if (node->mime_type)
317                     mime_types[n++] = node->mime_type;
318                   node = node->next;
319                 }
320             }
321           else
322             {
323               n = _xdg_glob_hash_node_lookup_file_name (node->child,
324                                                         file_name,
325                                                         ignore_case,
326                                                         mime_types,
327                                                         n_mime_types);
328             }
329           return n;
330         }
331     }
332
333   return 0;
334 }
335
336 int
337 _xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash,
338                                  const char  *file_name,
339                                  const char  *mime_types[],
340                                  int          n_mime_types)
341 {
342   XdgGlobList *list;
343   const char *ptr;
344   char stopchars[128];
345   int i, n;
346   XdgGlobHashNode *node;
347
348   /* First, check the literals */
349
350   assert (file_name != NULL && n_mime_types > 0);
351
352   for (list = glob_hash->literal_list; list; list = list->next)
353     {
354       if (strcmp ((const char *)list->data, file_name) == 0)
355         {
356           mime_types[0] = list->mime_type;
357           return 1;
358         }
359     }
360
361   i = 0;
362   for (node = glob_hash->simple_node; node; node = node->next)
363     {
364       if (node->character < 128)
365         stopchars[i++] = (char)node->character;
366     }
367   stopchars[i] = '\0';
368  
369   ptr = strpbrk (file_name, stopchars);
370   while (ptr)
371     {
372       n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ptr, FALSE,
373                                                 mime_types, n_mime_types);
374       if (n > 0)
375         return n;
376       
377       n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ptr, TRUE,
378                                                 mime_types, n_mime_types);
379       if (n > 0)
380         return n;
381       
382       ptr = strpbrk (ptr + 1, stopchars);
383     }
384
385   /* FIXME: Not UTF-8 safe */
386   n = 0;
387   for (list = glob_hash->full_list; list && n < n_mime_types; list = list->next)
388     {
389       if (fnmatch ((const char *)list->data, file_name, 0) == 0)
390         mime_types[n++] = list->mime_type;
391     }
392
393   return n;
394 }
395
396
397
398 /* XdgGlobHash
399  */
400
401 XdgGlobHash *
402 _xdg_glob_hash_new (void)
403 {
404   XdgGlobHash *glob_hash;
405
406   glob_hash = calloc (1, sizeof (XdgGlobHash));
407
408   return glob_hash;
409 }
410
411
412 static void
413 _xdg_glob_hash_free_nodes (XdgGlobHashNode *node)
414 {
415   if (node)
416     {
417       if (node->child)
418        _xdg_glob_hash_free_nodes (node->child);
419       if (node->next)
420        _xdg_glob_hash_free_nodes (node->next);
421       if (node->mime_type)
422         free ((void *) node->mime_type);
423       free (node);
424     }
425 }
426
427 void
428 _xdg_glob_hash_free (XdgGlobHash *glob_hash)
429 {
430   _xdg_glob_list_free (glob_hash->literal_list);
431   _xdg_glob_list_free (glob_hash->full_list);
432   _xdg_glob_hash_free_nodes (glob_hash->simple_node);
433   free (glob_hash);
434 }
435
436 XdgGlobType
437 _xdg_glob_determine_type (const char *glob)
438 {
439   const char *ptr;
440   int maybe_in_simple_glob = FALSE;
441   int first_char = TRUE;
442
443   ptr = glob;
444
445   while (*ptr != '\000')
446     {
447       if (*ptr == '*' && first_char)
448         maybe_in_simple_glob = TRUE;
449       else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*')
450           return XDG_GLOB_FULL;
451
452       first_char = FALSE;
453       ptr = _xdg_utf8_next_char (ptr);
454     }
455   if (maybe_in_simple_glob)
456     return XDG_GLOB_SIMPLE;
457   else
458     return XDG_GLOB_LITERAL;
459 }
460
461 /* glob must be valid UTF-8 */
462 void
463 _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash,
464                             const char  *glob,
465                             const char  *mime_type)
466 {
467   XdgGlobType type;
468
469   assert (glob_hash != NULL);
470   assert (glob != NULL);
471
472   type = _xdg_glob_determine_type (glob);
473
474   switch (type)
475     {
476     case XDG_GLOB_LITERAL:
477       glob_hash->literal_list = _xdg_glob_list_append (glob_hash->literal_list, strdup (glob), strdup (mime_type));
478       break;
479     case XDG_GLOB_SIMPLE:
480       glob_hash->simple_node = _xdg_glob_hash_insert_text (glob_hash->simple_node, glob + 1, mime_type);
481       break;
482     case XDG_GLOB_FULL:
483       glob_hash->full_list = _xdg_glob_list_append (glob_hash->full_list, strdup (glob), strdup (mime_type));
484       break;
485     }
486 }
487
488 void
489 _xdg_glob_hash_dump (XdgGlobHash *glob_hash)
490 {
491   XdgGlobList *list;
492   printf ("LITERAL STRINGS\n");
493   if (glob_hash->literal_list == NULL)
494     {
495       printf ("    None\n");
496     }
497   else
498     {
499       for (list = glob_hash->literal_list; list; list = list->next)
500         printf ("    %s - %s\n", (char *)list->data, list->mime_type);
501     }
502   printf ("\nSIMPLE GLOBS\n");
503   _xdg_glob_hash_node_dump (glob_hash->simple_node, 4);
504
505   printf ("\nFULL GLOBS\n");
506   if (glob_hash->full_list == NULL)
507     {
508       printf ("    None\n");
509     }
510   else
511     {
512       for (list = glob_hash->full_list; list; list = list->next)
513         printf ("    %s - %s\n", (char *)list->data, list->mime_type);
514     }
515 }
516
517
518 void
519 _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash,
520                                const char  *file_name)
521 {
522   FILE *glob_file;
523   char line[255];
524
525   glob_file = fopen (file_name, "r");
526
527   if (glob_file == NULL)
528     return;
529
530   /* FIXME: Not UTF-8 safe.  Doesn't work if lines are greater than 255 chars.
531    * Blah */
532   while (fgets (line, 255, glob_file) != NULL)
533     {
534       char *colon;
535       if (line[0] == '#')
536         continue;
537
538       colon = strchr (line, ':');
539       if (colon == NULL)
540         continue;
541       *(colon++) = '\000';
542       colon[strlen (colon) -1] = '\000';
543       _xdg_glob_hash_append_glob (glob_hash, colon, line);
544     }
545
546   fclose (glob_file);
547 }