Add LaughSize, a type that handles absolute and relative values
[laugh.git] / src / laugh-dom.c
blob1c8157beffcbeaffeb6983a60ae91b29094bb6c0
1 /*
2 * Laugh.
4 * An glib SMIL library.
6 * Authored By Koos Vriezen <koos.vriezen@gmail.com>
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; either
11 * version 2 of the License, or (at your option) any later version.
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.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
24 /**
25 * SECTION:laugh-io
26 * @short_description: DOM bases classes.
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
33 #include "laugh-dom.h"
34 #include "laugh-io.h"
35 #include "laugh-layout.h"
37 #include <string.h>
38 #include <expat.h>
39 #include <glib/gprintf.h>
41 #define LAUGH_NODE_GET_PRIVATE(obj) \
42 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), LAUGH_TYPE_NODE, LaughNodePrivate))
43 #define LAUGH_DOCUMENT_GET_PRIVATE(obj) \
44 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), LAUGH_TYPE_DOCUMENT, LaughDocumentPrivate))
46 enum
48 INITIALIZED,
49 STARTED,
50 STOPPED,
52 LAST_SIGNAL
55 typedef struct _DocumentBuilder
57 LaughNode *top_node;
58 LaughNode *current;
59 } DocumentBuilder;
61 static guint laugh_node_signals[LAST_SIGNAL] = { 0 };
63 static GHashTable *_laugh_tag_id;
64 static GHashTable *_laugh_id_tag;
65 static long _laugh_tag_unknown = (long) LaughTagIdLast;
67 struct _LaughNodePrivate
69 GHashTable *attributes;
72 static gpointer laugh_node_parent_class = ((void *)0);
74 static void laugh_node_finalize (GObject *object)
76 G_OBJECT_CLASS (laugh_node_parent_class)->finalize (object);
79 static void laugh_node_dispose (GObject *object)
81 LaughNode *node = LAUGH_NODE(object);
82 LaughNode *child;
84 if (node->priv->attributes)
85 g_hash_table_destroy (node->priv->attributes);
87 for (child = node->first_child; child; ) {
88 LaughNode *tmp = child->next_sibling;
89 g_object_unref (child);
90 child = tmp;
93 G_OBJECT_CLASS (laugh_node_parent_class)->dispose (object);
96 static
97 void _laugh_node_set_attribute (LaughNode *node, GQuark att, const gchar *val)
99 LaughNodePrivate *priv = node->priv;
101 /* TODO: create a dynamic attribute list */
102 if (priv->attributes)
103 g_hash_table_insert (priv->attributes,
104 (gpointer) (long) att, (gpointer) val);
107 static const gchar *_laugh_node_get_attribute (LaughNode *node, GQuark att)
109 LaughNodePrivate *priv = node->priv;
111 /* TODO: create a dynamic attribute list */
112 if (priv->attributes)
113 return (gchar *) g_hash_table_lookup (
114 priv->attributes, (gpointer) (long) att);
115 return NULL;
118 static void _laugh_node_init (LaughNode *node, LaughInitializer *initializer)
120 LaughNode *child;
122 for (child = node->first_child; child; child = child->next_sibling)
123 laugh_node_init (child, initializer);
126 static void _laugh_register_tag (LaughNodeTagId id, const gchar *tag)
128 g_hash_table_insert (_laugh_tag_id, (gpointer) tag, (gpointer) id);
129 g_hash_table_insert (_laugh_id_tag, (gpointer) id, (gpointer) tag);
132 static gboolean _laugh_cmp_string (gconstpointer a, gconstpointer b)
134 return !strcmp ((const gchar *) a, (const gchar *) b);
137 static void laugh_node_class_init (LaughNodeClass *klass)
139 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
141 laugh_node_parent_class = g_type_class_peek_parent (klass);
143 gobject_class->finalize = laugh_node_finalize;
144 gobject_class->dispose = laugh_node_dispose;
145 klass->set_attribute = _laugh_node_set_attribute;
146 klass->get_attribute = _laugh_node_get_attribute;
147 klass->init = _laugh_node_init;
149 g_type_class_add_private (gobject_class, sizeof (LaughNodePrivate));
151 laugh_node_signals[INITIALIZED] =
152 g_signal_new ("initialized",
153 G_TYPE_FROM_CLASS (gobject_class),
154 G_SIGNAL_RUN_LAST,
155 G_STRUCT_OFFSET (LaughNodeClass, initialized),
156 NULL, NULL,
157 g_cclosure_marshal_VOID__VOID,
158 G_TYPE_NONE, 0);
160 laugh_node_signals[STARTED] =
161 g_signal_new ("started",
162 G_TYPE_FROM_CLASS (gobject_class),
163 G_SIGNAL_RUN_LAST,
164 G_STRUCT_OFFSET (LaughNodeClass, started),
165 NULL, NULL,
166 g_cclosure_marshal_VOID__VOID,
167 G_TYPE_NONE, 0);
169 laugh_node_signals[STOPPED] =
170 g_signal_new ("stopped",
171 G_TYPE_FROM_CLASS (gobject_class),
172 G_SIGNAL_RUN_LAST,
173 G_STRUCT_OFFSET (LaughNodeClass, stopped),
174 NULL, NULL,
175 g_cclosure_marshal_VOID__VOID,
176 G_TYPE_NONE, 0);
177 _laugh_tag_id = g_hash_table_new (g_str_hash, _laugh_cmp_string);
178 _laugh_id_tag = g_hash_table_new (NULL, NULL);
179 _laugh_register_tag (LaughTagIdSmil, "smil");
180 _laugh_register_tag (LaughTagIdHead, "head");
181 _laugh_register_tag (LaughTagIdLayout, "layout");
182 _laugh_register_tag (LaughTagIdRootLayout, "root-layout");
183 _laugh_register_tag (LaughTagIdRegion, "region");
184 _laugh_register_tag (LaughTagIdBody, "body");
185 _laugh_register_tag (LaughTagIdSeq, "seq");
186 _laugh_register_tag (LaughTagIdPar, "par");
187 _laugh_register_tag (LaughTagIdExcl, "excl");
188 _laugh_register_tag (LaughTagIdSwitch, "switch");
189 _laugh_register_tag (LaughTagIdAudio, "audio");
190 _laugh_register_tag (LaughTagIdImg, "img");
191 _laugh_register_tag (LaughTagIdRef, "ref");
192 _laugh_register_tag (LaughTagIdText, "text");
193 _laugh_register_tag (LaughTagIdVideo, "video");
196 static void laugh_node_instance_init (GTypeInstance *instance, gpointer g_class)
198 LaughNode *self = (LaughNode *)instance;
200 self->priv = LAUGH_NODE_GET_PRIVATE (self);
202 self->parent_node = NULL;
203 self->first_child = NULL;
204 self->previous_sibling = NULL;
205 self->next_sibling = NULL;
206 self->priv->attributes = NULL;
209 GType laugh_node_get_type (void)
211 static GType type = 0;
212 if (type == 0) {
213 static const GTypeInfo info = {
214 sizeof (LaughNodeClass),
215 NULL, /* base_init */
216 NULL, /* base_finalize */
217 (GClassInitFunc) laugh_node_class_init, /* class_init */
218 NULL, /* class_finalize */
219 NULL, /* class_data */
220 sizeof (LaughNode),
221 0, /* n_preallocs */
222 laugh_node_instance_init /* instance_init */
224 type = g_type_register_static (G_TYPE_OBJECT,
225 "LaughNodeType",
226 &info, 0);
228 return type;
231 struct _LaughDocumentPrivate
233 gchar *uri;
234 LaughIO *io;
237 static gpointer laugh_document_parent_class = ((void *)0);
239 static void laugh_document_finalize (GObject *object)
241 G_OBJECT_CLASS (laugh_document_parent_class)->finalize (object);
244 static void laugh_document_dispose (GObject *object)
246 LaughDocument *document = LAUGH_DOCUMENT(object);
248 g_free (document->priv->uri);
250 G_OBJECT_CLASS (laugh_document_parent_class)->dispose (object);
253 static
254 void _lauch_document_mime_type (LaughIO *io, const gchar *mime, gpointer d)
256 g_printf ("lauch_document_mime_type: %s\n", mime);
257 if (strcmp (mime, "application/smil")) {
258 LaughNode *node = LAUGH_NODE (d);
260 g_printerr ("not a smil document\n");
261 laugh_io_cancel (io);
263 g_object_unref (G_OBJECT (io));
265 laugh_node_base_emit_initialized (node);
269 static void
270 _lauch_document_completed (LaughIO *io, gsize sz, gpointer data, gpointer d)
272 LaughNode *node = LAUGH_NODE (d);
274 g_printf ("_lauch_document_completed: %u\n", sz);
275 if (data) {
276 laugh_node_set_inner_xml (node, (const gchar *) data);
278 g_free (data);
280 g_object_unref (G_OBJECT (io));
282 /*TODO init children and wait for initialized signals*/
283 laugh_node_base_emit_initialized (node);
286 static void _laugh_document_init (LaughNode *doc, LaughInitializer *initializer)
288 LaughDocumentPrivate *priv = LAUGH_DOCUMENT (doc)->priv;
290 priv->io = laugh_io_new (priv->uri);
292 g_signal_connect (G_OBJECT (priv->io), "mime-type",
293 (GCallback) _lauch_document_mime_type, (gpointer) doc);
294 g_signal_connect (G_OBJECT (priv->io), "completed",
295 (GCallback) _lauch_document_completed, (gpointer) doc);
297 laugh_io_open (priv->io);
300 static void laugh_document_class_init (LaughDocumentClass *klass)
302 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
303 LaughNodeClass *node_class = (LaughNodeClass *) klass;
305 laugh_document_parent_class = g_type_class_peek_parent (klass);
307 gobject_class->finalize = laugh_document_finalize;
308 gobject_class->dispose = laugh_document_dispose;
309 node_class->init = _laugh_document_init;
311 g_type_class_add_private (gobject_class, sizeof (LaughDocumentPrivate));
314 static
315 void laugh_document_instance_init (GTypeInstance *instance, gpointer g_class)
317 LaughDocument *self = (LaughDocument *)instance;
319 self->priv = LAUGH_DOCUMENT_GET_PRIVATE (self);
322 GType laugh_document_get_type (void)
324 static GType type = 0;
325 if (type == 0) {
326 static const GTypeInfo info = {
327 sizeof (LaughDocumentClass),
328 NULL, /* base_init */
329 NULL, /* base_finalize */
330 (GClassInitFunc) laugh_document_class_init, /* class_init */
331 NULL, /* class_finalize */
332 NULL, /* class_data */
333 sizeof (LaughDocument),
334 0, /* n_preallocs */
335 laugh_document_instance_init /* instance_init */
337 type = g_type_register_static (LAUGH_TYPE_NODE,
338 "LaughDocumentType",
339 &info, 0);
341 return type;
344 LaughNodeTagId laugh_tag_from_string (const gchar *tag)
346 gpointer id = g_hash_table_lookup (_laugh_tag_id, (gpointer) tag);
347 if (!id) {
348 id = (gpointer) _laugh_tag_unknown++;
349 _laugh_register_tag ((LaughNodeTagId) id, g_strdup (tag));
351 return (LaughNodeTagId) (long) id;
354 const gchar *laugh_tag_from_id (LaughNodeTagId id)
356 return (const gchar *) g_hash_table_lookup (_laugh_id_tag, (gpointer) id);
360 LaughNode *laugh_node_new (LaughDocument *document, LaughNodeTagId id,
361 GHashTable *attrs)
363 LaughNode *node = LAUGH_NODE (g_object_new (LAUGH_TYPE_NODE, NULL));
365 laugh_node_base_init (document, node, id, attrs);
367 return node;
370 LaughDocument *laugh_document_new (const gchar *uri)
372 LaughDocument *d = LAUGH_DOCUMENT(g_object_new (LAUGH_TYPE_DOCUMENT, NULL));
373 LaughNode *node = (LaughNode *)d;
375 laugh_node_base_init (d, node, LaughTagIdDocument, NULL);
376 d->priv->uri = g_strdup (uri);
378 return d;
381 void laugh_node_base_init (LaughDocument *document, LaughNode *node,
382 LaughNodeTagId id, GHashTable *attributes)
384 node->id = id;
385 node->document = document;
386 node->priv->attributes = attributes;
389 void laugh_node_base_emit_initialized (LaughNode *node)
391 g_signal_emit (node, laugh_node_signals[INITIALIZED], 0);
394 const gchar *laugh_node_get_attribute (LaughNode *node, GQuark attribute)
396 LaughNodeClass *klass = LAUGH_NODE_GET_CLASS(node);
398 return klass->get_attribute (node, attribute);
401 void laugh_node_set_attribute (LaughNode *node, GQuark attr, const gchar *value)
403 LaughNodeClass *klass = LAUGH_NODE_GET_CLASS(node);
405 klass->set_attribute (node, attr, value);
408 static
409 void _laugh_xml_start_tag (void *data, const char *tag, const char **attr)
411 DocumentBuilder *builder = (DocumentBuilder *) data;
412 LaughNode *node;
413 GHashTable *attrs = NULL;
414 LaughNodeTagId id = laugh_tag_from_string (tag);
416 if (attr && attr[0]) {
417 int i;
418 attrs = g_hash_table_new_full (
419 NULL, NULL, NULL, (GDestroyNotify) g_free);
420 for (i = 0; attr[i]; i += 2)
421 g_hash_table_insert (attrs,
422 (gpointer) (long) g_quark_from_string (attr[i]),
423 g_strdup (attr[i+1]));
425 switch (id) {
426 case LaughTagIdLayout:
427 case LaughTagIdRootLayout:
428 case LaughTagIdRegion:
429 node = laugh_layout_new (builder->top_node->document, id, attrs);
430 break;
431 default:
432 node = laugh_node_new (builder->top_node->document, id, attrs);
434 laugh_node_append_child (builder->current, node);
436 builder->current = node;
439 static void _laugh_xml_end_tag (void *data, const char *tag)
441 DocumentBuilder *builder = (DocumentBuilder *) data;
443 if (laugh_tag_from_string (tag) != builder->current->id)
444 g_printerr ("unmatched tag %s, expects %s\n",
445 tag, laugh_tag_from_id (builder->current->id));
446 else if (builder->current != builder->top_node)
447 builder->current = builder->current->parent_node;
448 else
449 g_printerr ("spurious end tag %s", tag);
452 void laugh_node_set_inner_xml (LaughNode *node, const gchar *xml)
454 DocumentBuilder builder;
455 XML_Parser parser = XML_ParserCreate (0L);
457 /*TODO: clear all node's child nodes*/
458 builder.top_node = node;
459 builder.current = node;
461 XML_SetUserData (parser, &builder);
462 XML_SetElementHandler (parser, _laugh_xml_start_tag, _laugh_xml_end_tag);
464 if (XML_Parse (parser, xml, strlen (xml), TRUE) == XML_STATUS_ERROR) {
465 /*TODO: clear all*/
468 XML_ParserFree(parser);
471 static void _laugh_node_get_outer_xml (LaughNode *node, GString *buffer, int sp)
473 LaughNodePrivate *priv = node->priv;
474 gchar *indent = "";
476 if (sp > 0) {
477 indent = (gchar *) g_malloc (sp + 1);
478 memset (indent, ' ', sp);
479 indent[sp] = 0;
480 g_string_append (buffer, indent);
483 g_string_append_c (buffer, '<');
484 g_string_append (buffer, laugh_tag_from_id (node->id));
485 if (priv->attributes) {
486 GHashTableIter iter;
487 gpointer key, value;
488 g_hash_table_iter_init (&iter, priv->attributes);
489 while (g_hash_table_iter_next (&iter, &key, &value)) {
490 g_string_append_c (buffer, ' ');
491 g_string_append (buffer, g_quark_to_string ((GQuark) (long) key));
492 g_string_append (buffer, "=\"");
493 g_string_append (buffer, (const gchar *)value);/*TODO escape*/
494 g_string_append_c (buffer, '"');
497 if (node->first_child) {
498 LaughNode *child;
499 g_string_append (buffer, ">\n");
500 for (child = node->first_child; child; child = child->next_sibling)
501 _laugh_node_get_outer_xml (child, buffer, sp+2);
502 g_string_append (buffer, indent);
503 g_string_append (buffer, "</");
504 g_string_append (buffer, laugh_tag_from_id (node->id));
505 g_string_append (buffer, ">\n");
506 } else {
507 g_string_append (buffer, "/>\n");
510 if (*indent)
511 g_free (indent);
514 gchar *laugh_node_get_inner_xml (LaughNode *node)
516 GString *buffer = g_string_new ("");
517 LaughNode *child;
518 gchar *xml;
520 for (child = node->first_child; child; child = child->next_sibling)
521 _laugh_node_get_outer_xml (child, buffer, 0);
523 xml = g_string_free (buffer, FALSE);
525 return xml;
528 void laugh_node_append_child (LaughNode *node, LaughNode *child)
530 LaughNode *n = node->last_child;
532 if (n) {
533 n->next_sibling = child;
534 child->previous_sibling = n;
535 child->next_sibling = NULL;
536 } else {
537 node->first_child = child;
538 child->previous_sibling = NULL;
539 child->next_sibling = NULL;
541 node->last_child = child;
542 child->parent_node = node;
545 void laugh_node_init (LaughNode *node, LaughInitializer *initializer)
547 LaughNodeClass *klass = LAUGH_NODE_GET_CLASS(node);
549 klass->init (node, initializer);
552 #ifdef LAUGH_DOM_TEST
553 /* gcc laugh-dom.c laugh-io.c -o domtest `pkg-config --libs --cflags gio-2.0 glib-2.0 gthread-2.0` -lexpat */
554 #include <unistd.h>
555 #include <sys/types.h>
556 #include <sys/stat.h>
557 #include <fcntl.h>
559 static GMainLoop *mainloop;
561 void initialized_callback (LaughNode *node, gpointer d)
563 gchar *xml = laugh_node_get_inner_xml (node);
564 g_printf ("cb initialized\n%s\n", xml);
566 g_free (xml);
568 g_main_loop_quit (mainloop);
571 int main (int argc, char **argv)
573 LaughDocument *doc;
574 LaughInitializer initializer;
576 if (argc < 2) {
577 g_printerr ("usage %s uri\n", argv[0]);
578 return 1;
581 g_thread_init (NULL);
582 g_type_init ();
584 doc = laugh_document_new (argv[1]);
586 g_signal_connect (G_OBJECT (doc), "initialized",
587 (GCallback) initialized_callback, (gpointer) 0);
589 laugh_node_init (LAUGH_NODE (doc), &initializer);
591 mainloop = g_main_loop_new (NULL, TRUE);
592 g_main_loop_run (mainloop);
594 g_object_unref (G_OBJECT (doc));
596 return 0;
599 #endif