No issues found
1 /*
2 * Copyright (C) 2009, Nokia <ivan.frade@nokia.com>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 */
19
20 #include <glib-object.h>
21 #include <glib/gprintf.h>
22 #include <gio/gio.h>
23
24 #include <libtracker-data/tracker-data.h>
25
26 #define RDF_PREFIX TRACKER_RDF_PREFIX
27 #define RDF_PROPERTY RDF_PREFIX "Property"
28 #define RDF_TYPE RDF_PREFIX "type"
29
30 #define RDFS_PREFIX TRACKER_RDFS_PREFIX
31 #define RDFS_CLASS RDFS_PREFIX "Class"
32 #define RDFS_DOMAIN RDFS_PREFIX "domain"
33 #define RDFS_RANGE RDFS_PREFIX "range"
34 #define RDFS_SUB_CLASS_OF RDFS_PREFIX "subClassOf"
35 #define RDFS_SUB_PROPERTY_OF RDFS_PREFIX "subPropertyOf"
36
37 #define NRL_PREFIX TRACKER_NRL_PREFIX
38 #define NRL_INVERSE_FUNCTIONAL_PROPERTY TRACKER_NRL_PREFIX "InverseFunctionalProperty"
39 #define NRL_MAX_CARDINALITY NRL_PREFIX "maxCardinality"
40
41 #define TRACKER_PREFIX TRACKER_TRACKER_PREFIX
42
43 static gchar *ontology_dir = NULL;
44 static gchar *output_file = NULL;
45
46 static gint indenting = 0;
47 static GList *context = NULL;
48
49 enum {
50 CONTEXT_GRAPH,
51 CONTEXT_SUBGRAPH,
52 CONTEXT_PROPERTY
53 };
54
55 static const gchar *colors[] = {
56 "#dd0000",
57 "#00dd00",
58 "#0000dd",
59 "#dd00dd",
60 "#dddd00",
61 "#00dddd",
62 "#dddddd"
63 "#bb0000",
64 "#00bb00",
65 "#0000bb",
66 "#bb00bb",
67 "#bbbb00",
68 "#00bbbb",
69 "#bbbbbb"
70 };
71
72 static GOptionEntry entries[] = {
73 { "ontology-dir", 'd', 0, G_OPTION_ARG_FILENAME, &ontology_dir,
74 "Ontology directory",
75 NULL
76 },
77 { "output", 'o', 0, G_OPTION_ARG_FILENAME, &output_file,
78 "File to write the output (default stdout)",
79 NULL
80 },
81 { NULL }
82 };
83
84 /* Stripped from tracker-data-manager.h */
85 static void
86 load_ontology_file_from_path (const gchar *ontology_file)
87 {
88 TrackerTurtleReader *reader;
89 GError *error = NULL;
90
91 reader = tracker_turtle_reader_new (ontology_file, &error);
92 if (error) {
93 g_critical ("Turtle parse error: %s", error->message);
94 g_error_free (error);
95 return;
96 }
97
98 while (error == NULL && tracker_turtle_reader_next (reader, &error)) {
99 const gchar *subject, *predicate, *object;
100
101 subject = tracker_turtle_reader_get_subject (reader);
102 predicate = tracker_turtle_reader_get_predicate (reader);
103 object = tracker_turtle_reader_get_object (reader);
104
105 if (g_strcmp0 (predicate, RDF_TYPE) == 0) {
106 if (g_strcmp0 (object, RDFS_CLASS) == 0) {
107 TrackerClass *class;
108
109 if (tracker_ontologies_get_class_by_uri (subject) != NULL) {
110 g_critical ("%s: Duplicate definition of class %s", ontology_file, subject);
111 continue;
112 }
113
114 class = tracker_class_new (FALSE);
115 tracker_class_set_uri (class, subject);
116 tracker_ontologies_add_class (class);
117 g_object_unref (class);
118 } else if (g_strcmp0 (object, RDF_PROPERTY) == 0) {
119 TrackerProperty *property;
120
121 if (tracker_ontologies_get_property_by_uri (subject) != NULL) {
122 g_critical ("%s: Duplicate definition of property %s", ontology_file, subject);
123 continue;
124 }
125
126 property = tracker_property_new (FALSE);
127 tracker_property_set_uri (property, subject);
128 tracker_ontologies_add_property (property);
129 g_object_unref (property);
130 } else if (g_strcmp0 (object, NRL_INVERSE_FUNCTIONAL_PROPERTY) == 0) {
131 TrackerProperty *property;
132
133 property = tracker_ontologies_get_property_by_uri (subject);
134 if (property == NULL) {
135 g_critical ("%s: Unknown property %s", ontology_file, subject);
136 continue;
137 }
138
139 tracker_property_set_is_inverse_functional_property (property, TRUE);
140 } else if (g_strcmp0 (object, TRACKER_PREFIX "Namespace") == 0) {
141 TrackerNamespace *namespace;
142
143 if (tracker_ontologies_get_namespace_by_uri (subject) != NULL) {
144 g_critical ("%s: Duplicate definition of namespace %s", ontology_file, subject);
145 continue;
146 }
147
148 namespace = tracker_namespace_new (FALSE);
149 tracker_namespace_set_uri (namespace, subject);
150 tracker_ontologies_add_namespace (namespace);
151 g_object_unref (namespace);
152 }
153 } else if (g_strcmp0 (predicate, RDFS_SUB_CLASS_OF) == 0) {
154 TrackerClass *class, *super_class;
155
156 class = tracker_ontologies_get_class_by_uri (subject);
157 if (class == NULL) {
158 g_critical ("%s: Unknown class %s", ontology_file, subject);
159 continue;
160 }
161
162 super_class = tracker_ontologies_get_class_by_uri (object);
163 if (super_class == NULL) {
164 g_critical ("%s: Unknown class %s", ontology_file, object);
165 continue;
166 }
167
168 tracker_class_add_super_class (class, super_class);
169 } else if (g_strcmp0 (predicate, RDFS_SUB_PROPERTY_OF) == 0) {
170 TrackerProperty *property, *super_property;
171
172 property = tracker_ontologies_get_property_by_uri (subject);
173 if (property == NULL) {
174 g_critical ("%s: Unknown property %s", ontology_file, subject);
175 continue;
176 }
177
178 super_property = tracker_ontologies_get_property_by_uri (object);
179 if (super_property == NULL) {
180 g_critical ("%s: Unknown property %s", ontology_file, object);
181 continue;
182 }
183
184 tracker_property_add_super_property (property, super_property);
185 } else if (g_strcmp0 (predicate, RDFS_DOMAIN) == 0) {
186 TrackerProperty *property;
187 TrackerClass *domain;
188
189 property = tracker_ontologies_get_property_by_uri (subject);
190 if (property == NULL) {
191 g_critical ("%s: Unknown property %s", ontology_file, subject);
192 continue;
193 }
194
195 domain = tracker_ontologies_get_class_by_uri (object);
196 if (domain == NULL) {
197 g_critical ("%s: Unknown class %s", ontology_file, object);
198 continue;
199 }
200
201 tracker_property_set_domain (property, domain);
202 } else if (g_strcmp0 (predicate, RDFS_RANGE) == 0) {
203 TrackerProperty *property;
204 TrackerClass *range;
205
206 property = tracker_ontologies_get_property_by_uri (subject);
207 if (property == NULL) {
208 g_critical ("%s: Unknown property %s", ontology_file, subject);
209 continue;
210 }
211
212 range = tracker_ontologies_get_class_by_uri (object);
213 if (range == NULL) {
214 g_critical ("%s: Unknown class %s", ontology_file, object);
215 continue;
216 }
217
218 tracker_property_set_range (property, range);
219 } else if (g_strcmp0 (predicate, NRL_MAX_CARDINALITY) == 0) {
220 TrackerProperty *property;
221
222 property = tracker_ontologies_get_property_by_uri (subject);
223 if (property == NULL) {
224 g_critical ("%s: Unknown property %s", ontology_file, subject);
225 continue;
226 }
227
228 if (atoi (object) == 1) {
229 tracker_property_set_multiple_values (property, FALSE);
230 }
231 } else if (g_strcmp0 (predicate, TRACKER_PREFIX "indexed") == 0) {
232 TrackerProperty *property;
233
234 property = tracker_ontologies_get_property_by_uri (subject);
235 if (property == NULL) {
236 g_critical ("%s: Unknown property %s", ontology_file, subject);
237 continue;
238 }
239
240 if (strcmp (object, "true") == 0) {
241 tracker_property_set_indexed (property, TRUE);
242 }
243 } else if (g_strcmp0 (predicate, TRACKER_PREFIX "transient") == 0) {
244 TrackerProperty *property;
245
246 property = tracker_ontologies_get_property_by_uri (subject);
247 if (property == NULL) {
248 g_critical ("%s: Unknown property %s", ontology_file, subject);
249 continue;
250 }
251
252 if (g_strcmp0 (object, "true") == 0) {
253 tracker_property_set_transient (property, TRUE);
254 }
255 } else if (g_strcmp0 (predicate, TRACKER_PREFIX "fulltextIndexed") == 0) {
256 TrackerProperty *property;
257
258 property = tracker_ontologies_get_property_by_uri (subject);
259 if (property == NULL) {
260 g_critical ("%s: Unknown property %s", ontology_file, subject);
261 continue;
262 }
263
264 if (strcmp (object, "true") == 0) {
265 tracker_property_set_fulltext_indexed (property, TRUE);
266 }
267 } else if (g_strcmp0 (predicate, TRACKER_PREFIX "prefix") == 0) {
268 TrackerNamespace *namespace;
269
270 namespace = tracker_ontologies_get_namespace_by_uri (subject);
271 if (namespace == NULL) {
272 g_critical ("%s: Unknown namespace %s", ontology_file, subject);
273 continue;
274 }
275
276 tracker_namespace_set_prefix (namespace, object);
277 }
278 }
279
280 g_object_unref (reader);
281
282 if (error) {
283 g_critical ("Turtle parse error: %s", error->message);
284 g_error_free (error);
285 }
286 }
287
288 static void
289 load_ontology_file (GFile *file)
290 {
291 gchar *path;
292
293 path = g_file_get_path (file);
294 load_ontology_file_from_path (path);
295 g_free (path);
296 }
297
298 static gboolean
299 load_ontology_dir (GFile *dir)
300 {
301 GFileEnumerator *enumerator;
302 GFileInfo *info;
303 GList *files, *f;
304 const gchar *name;
305
306 enumerator = g_file_enumerate_children (dir,
307 G_FILE_ATTRIBUTE_STANDARD_NAME,
308 G_FILE_QUERY_INFO_NONE,
309 NULL, NULL);
310
311 if (!enumerator) {
312 return FALSE;
313 }
314
315 files = NULL;
316
317 while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
318 name = g_file_info_get_name (info);
319
320 if (g_str_has_suffix (name, ".ontology")) {
321 files = g_list_insert_sorted (files, g_strdup (name),
322 (GCompareFunc) g_strcmp0);
323 }
324
325 g_object_unref (info);
326 }
327
328 g_object_unref (enumerator);
329
330 for (f = files; f; f = f->next) {
331 GFile *child;
332
333 child = g_file_get_child (dir, f->data);
334 load_ontology_file (child);
335 g_object_unref (child);
336 }
337
338 g_list_foreach (files, (GFunc) g_free, NULL);
339 g_list_free (files);
340
341 return TRUE;
342 }
343
344 static const gchar *
345 snip_name (const gchar *name)
346 {
347 const gchar *subname;
348
349 subname = strchr (name, ':');
350 subname++;
351
352 return subname;
353 }
354
355 static gchar *
356 get_prefix (const gchar *name)
357 {
358 const gchar *str;
359 gchar *prefix;
360
361 str = strchr (name, ':');
362 prefix = g_strndup (name, str - name);
363
364 return prefix;
365 }
366
367 static void
368 print_string (FILE *f,
369 const gchar *format,
370 ...)
371 {
372 va_list args;
373
374 va_start (args, format);
375
376 if (indenting > 0) {
377 g_fprintf (f, "%*s", indenting, " ");
378 }
379 g_vfprintf (f, format, args);
380 g_fprintf (f, "\n");
381
382 va_end (args);
383 }
384
385 static void
386 push_context_graph (FILE *f,
387 const gchar *name,
388 gboolean directed)
389 {
390 if (directed) {
391 print_string (f, "digraph %s {", name);
392 } else {
393 print_string (f, "graph %s {", name);
394 }
395
396 indenting += 2;
397 context = g_list_prepend (context, GINT_TO_POINTER (CONTEXT_GRAPH));
398 }
399
400 static void
401 push_context_subgraph (FILE *f,
402 const gchar *name)
403 {
404 print_string (f, "subgraph \"%s\" {", name);
405 indenting += 2;
406 context = g_list_prepend (context, GINT_TO_POINTER (CONTEXT_SUBGRAPH));
407 }
408
409 static void
410 pop_context (FILE *f)
411 {
412 guint c;
413
414 g_assert (context != NULL);
415
416 c = GPOINTER_TO_INT (context->data);
417 context = g_list_remove (context, context);
418
419 indenting -= 2;
420 g_assert (indenting >= 0);
421
422 switch (c) {
423 case CONTEXT_GRAPH:
424 case CONTEXT_SUBGRAPH:
425 print_string (f, "}\n");
426 break;
427 case CONTEXT_PROPERTY:
428 print_string (f, "];\n");
429 break;
430 default:
431 g_assert_not_reached ();
432 }
433 }
434
435 static void
436 print_properties (FILE *f,
437 const gchar *separator,
438 ...)
439 {
440 va_list args;
441 const gchar *prop, *value;
442
443 va_start (args, separator);
444
445 prop = va_arg (args, gchar *);
446
447 while (prop) {
448 value = va_arg (args, gchar *);
449 print_string (f, "%s = \"%s\"%s", prop, value, separator);
450 prop = va_arg (args, gchar *);
451 }
452
453 va_end (args);
454 }
455
456 static void
457 print_element (FILE *f,
458 const gchar *name,
459 gboolean push_property)
460 {
461 if (push_property) {
462 print_string (f, "%s [", name);
463 context = g_list_prepend (context, GINT_TO_POINTER (CONTEXT_PROPERTY));
464 indenting += 2;
465 } else {
466 print_string (f, "%s;", name);
467 }
468 }
469
470 static void
471 print_relation (FILE *f,
472 const gchar *from,
473 const gchar *to,
474 gboolean directed,
475 gboolean push_property)
476 {
477 if (directed) {
478 if (push_property) {
479 print_string (f, "%s -> %s [", from, to);
480 } else {
481 print_string (f, "%s -> %s;", from, to);
482 }
483 } else {
484 if (push_property) {
485 print_string (f, "%s -- %s [", from, to);
486 } else {
487 print_string (f, "%s -- %s;", from, to);
488 }
489 }
490
491 if (push_property) {
492 context = g_list_prepend (context, GINT_TO_POINTER (CONTEXT_PROPERTY));
493 indenting += 2;
494 }
495 }
496
497 static void
498 generate_class_info (FILE *f)
499 {
500 TrackerClass **classes;
501 GHashTable *info;
502 guint length, i, j;
503 GHashTableIter iter;
504 gpointer key, value;
505 gint cur_color = 0;
506
507 info = g_hash_table_new_full (g_str_hash,
508 g_str_equal,
509 NULL, NULL);
510
511 classes = tracker_ontologies_get_classes (&length);
512
513 push_context_graph (f, "G", FALSE);
514
515 print_properties (f, ";",
516 "size", "22,22",
517 "shape", "record",
518 "ratio", "1.0",
519 "concentrate", "true",
520 "compound", "true",
521 "dim", "10",
522 "rankdir", "LR",
523 NULL);
524
525 for (i = 0; i < length; i++) {
526 const gchar *name;
527 TrackerClass **superclasses;
528 gchar *prefix;
529 GList *subgraph_elements;
530 gchar *elem_name, *elem_label;
531
532 name = snip_name (tracker_class_get_name (classes[i]));
533 prefix = get_prefix (tracker_class_get_name (classes[i]));
534 superclasses = tracker_class_get_super_classes (classes[i]);
535
536 subgraph_elements = g_hash_table_lookup (info, prefix);
537 subgraph_elements = g_list_prepend (subgraph_elements, (gpointer) name);
538 g_hash_table_replace (info, prefix, subgraph_elements);
539
540 elem_name = g_strdup_printf ("%s_%s", prefix, name);
541 elem_label = g_strdup_printf ("%s:%s", prefix, name);
542
543 print_element (f, elem_name, TRUE);
544
545 print_properties (f, ",",
546 "label", elem_label,
547 "style", "filled",
548 NULL);
549
550 pop_context (f);
551
552 g_free (elem_name);
553 g_free (elem_label);
554
555 for (j = 0; superclasses[j]; j++) {
556 const gchar *super_name;
557 gchar *super_prefix;
558 gchar *from_name, *to_name;
559
560 super_name = snip_name (tracker_class_get_name (superclasses[j]));
561 super_prefix = get_prefix (tracker_class_get_name (superclasses[j]));
562
563 from_name = g_strdup_printf ("%s_%s", prefix, name);
564 to_name = g_strdup_printf ("%s_%s", super_prefix, super_name);
565
566 print_relation (f, from_name, to_name, FALSE, TRUE);
567 print_properties (f, ",", "dir", "forward", NULL);
568
569 if (g_strcmp0 (prefix, super_prefix) != 0) {
570 gchar *cluster_from, *cluster_to;
571
572 cluster_from = g_strdup_printf ("cluster_%s", prefix);
573 cluster_to = g_strdup_printf ("cluster_%s", super_prefix);
574
575 print_properties (f, ",",
576 "ltail", cluster_from,
577 "lhead", cluster_to,
578 NULL);
579
580 g_free (cluster_from);
581 g_free (cluster_to);
582 }
583
584 pop_context (f);
585 g_free (super_prefix);
586 }
587 }
588
589 g_hash_table_iter_init (&iter, info);
590
591 while (g_hash_table_iter_next (&iter, &key, &value)) {
592 gchar *prefix = key;
593 gchar *subgraph_name, *subgraph_label;
594 GList *subgraph_elements = value;
595
596 subgraph_name = g_strdup_printf ("cluster_%s", prefix);
597 subgraph_label = g_strdup_printf ("%s ontology", prefix);
598
599 push_context_subgraph (f, subgraph_name);
600
601 print_properties (f, ";",
602 "label", subgraph_label,
603 "fontsize", "30",
604 "bgcolor", colors[cur_color],
605 NULL);
606
607 while (subgraph_elements) {
608 gchar *subelement_name;
609
610 subelement_name = g_strdup_printf ("%s_%s", prefix, (gchar *) subgraph_elements->data);
611 print_element (f, subelement_name, FALSE);
612 g_free (subelement_name);
613
614 subgraph_elements = subgraph_elements->next;
615 }
616
617 pop_context (f);
618
619 cur_color++;
620
621 if (cur_color >= G_N_ELEMENTS (colors)) {
622 cur_color = 0;
623 }
624 }
625
626 g_hash_table_destroy (info);
627
628 pop_context (f);
629 }
630
631 int
632 main (int argc, char *argv[])
633 {
634 GOptionContext *context;
635 FILE *f = NULL;
636 GFile *dir;
637
638 /* Translators: this messagge will apper immediately after the */
639 /* usage string - Usage: COMMAND [OPTION]... <THIS_MESSAGE> */
640 context = g_option_context_new ("- Generates graphviz for a TTL file");
641
642 /* Translators: this message will appear after the usage string */
643 /* and before the list of options. */
644 g_option_context_add_main_entries (context, entries, NULL);
645 g_option_context_parse (context, &argc, &argv, NULL);
646
647 if (!ontology_dir) {
648 gchar *help;
649
650 g_printerr ("%s\n\n",
651 "Ontology dir is mandatory");
652
653 help = g_option_context_get_help (context, TRUE, NULL);
654 g_option_context_free (context);
655 g_printerr ("%s", help);
656 g_free (help);
657
658 return -1;
659 }
660
661 if (output_file) {
662 f = fopen (output_file, "w");
663 } else {
664 f = stdout;
665 }
666 g_assert (f != NULL);
667
668 tracker_ontologies_init ();
669
670 dir = g_file_new_for_commandline_arg (ontology_dir);
671 load_ontology_dir (dir);
672 g_object_unref (dir);
673
674 generate_class_info (f);
675
676 tracker_ontologies_shutdown ();
677
678 fclose (f);
679
680 return 0;
681 }