tracker-0.16.2/docs/tools/ontology-graph.c

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 }