2 * gstcmmlparser.c - GStreamer CMML document parser
3 * Copyright (C) 2005 Alessandro Decina
6 * Alessandro Decina <alessandro@nnva.org>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library 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 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library 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.
28 #include "gstcmmlparser.h"
29 #include "gstannodex.h"
30 #include "gstcmmlutils.h"
32 GST_DEBUG_CATEGORY_STATIC (cmmlparser);
33 #define GST_CAT_DEFAULT cmmlparser
35 static void gst_cmml_parser_generic_error (void *ctx, const char *msg, ...);
36 static xmlNodePtr gst_cmml_parser_new_node (GstCmmlParser * parser,
37 const gchar * name, ...);
39 gst_cmml_parser_parse_start_element_ns (xmlParserCtxt * ctxt,
40 const xmlChar * name, const xmlChar * prefix, const xmlChar * URI,
41 int nb_preferences, const xmlChar ** namespaces,
42 int nb_attributes, int nb_defaulted, const xmlChar ** attributes);
43 static void gst_cmml_parser_parse_end_element_ns (xmlParserCtxt * ctxt,
44 const xmlChar * name, const xmlChar * prefix, const xmlChar * URI);
45 static void gst_cmml_parser_parse_processing_instruction (xmlParserCtxtPtr ctxt,
46 const xmlChar * target, const xmlChar * data);
47 static void gst_cmml_parser_meta_to_string (GstCmmlParser * parser,
48 xmlNodePtr parent, GValueArray * meta);
50 /* initialize the parser */
52 gst_cmml_parser_init (void)
54 GST_DEBUG_CATEGORY_INIT (cmmlparser, "cmmlparser", 0, "annodex CMML parser");
56 xmlGenericError = gst_cmml_parser_generic_error;
59 /* create a new CMML parser
62 gst_cmml_parser_new (GstCmmlParserMode mode)
64 GstCmmlParser *parser = g_malloc (sizeof (GstCmmlParser));
67 parser->context = xmlCreatePushParserCtxt (NULL, NULL,
68 NULL, 0, "cmml-bitstream");
69 xmlCtxtUseOptions (parser->context, XML_PARSE_NONET | XML_PARSE_NOERROR);
70 parser->context->_private = parser;
71 parser->context->sax->startElementNs =
72 (startElementNsSAX2Func) gst_cmml_parser_parse_start_element_ns;
73 parser->context->sax->endElementNs =
74 (endElementNsSAX2Func) gst_cmml_parser_parse_end_element_ns;
75 parser->context->sax->processingInstruction = (processingInstructionSAXFunc)
76 gst_cmml_parser_parse_processing_instruction;
77 parser->preamble_callback = NULL;
78 parser->cmml_end_callback = NULL;
79 parser->stream_callback = NULL;
80 parser->head_callback = NULL;
81 parser->clip_callback = NULL;
82 parser->user_data = NULL;
87 /* free a CMML parser instance
90 gst_cmml_parser_free (GstCmmlParser * parser)
93 xmlFreeDoc (parser->context->myDoc);
94 xmlFreeParserCtxt (parser->context);
101 * returns false if the xml is invalid
104 gst_cmml_parser_parse_chunk (GstCmmlParser * parser,
105 const gchar * data, guint size, GError ** err)
109 xmlres = xmlParseChunk (parser->context, data, size, 0);
110 if (xmlres != XML_ERR_OK) {
111 xmlErrorPtr xml_error = xmlCtxtGetLastError (parser->context);
113 GST_DEBUG ("Error occurred decoding chunk %s", data);
115 GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED, "%s", xml_error->message);
122 /* convert an xmlNodePtr to a string
125 gst_cmml_parser_node_to_string (GstCmmlParser * parser, xmlNodePtr node)
127 xmlBufferPtr xml_buffer;
132 doc = parser->context->myDoc;
136 xml_buffer = xmlBufferCreate ();
137 xmlNodeDump (xml_buffer, doc, node, 0, 0);
138 str = xmlStrndup (xml_buffer->content, xml_buffer->use);
139 xmlBufferFree (xml_buffer);
145 gst_cmml_parser_tag_stream_to_string (GstCmmlParser * parser,
146 GstCmmlTagStream * stream)
152 node = gst_cmml_parser_new_node (parser, "stream", NULL);
153 if (stream->timebase)
154 xmlSetProp (node, (xmlChar *) "timebase", stream->timebase);
157 xmlSetProp (node, (xmlChar *) "utc", stream->utc);
159 if (stream->imports) {
163 for (i = 0; i < stream->imports->n_values; ++i) {
164 val = g_value_array_get_nth (stream->imports, i);
165 import = gst_cmml_parser_new_node (parser, "import",
166 "src", g_value_get_string (val), NULL);
167 xmlAddChild (node, import);
171 ret = gst_cmml_parser_node_to_string (parser, node);
173 xmlUnlinkNode (node);
179 /* convert a GstCmmlTagHead to its string representation
182 gst_cmml_parser_tag_head_to_string (GstCmmlParser * parser,
183 GstCmmlTagHead * head)
189 node = gst_cmml_parser_new_node (parser, "head", NULL);
191 tmp = gst_cmml_parser_new_node (parser, "title", NULL);
192 xmlNodeSetContent (tmp, head->title);
193 xmlAddChild (node, tmp);
197 tmp = gst_cmml_parser_new_node (parser, "base", "uri", head->base, NULL);
198 xmlAddChild (node, tmp);
202 gst_cmml_parser_meta_to_string (parser, node, head->meta);
204 ret = gst_cmml_parser_node_to_string (parser, node);
206 xmlUnlinkNode (node);
212 /* convert a GstCmmlTagClip to its string representation
215 gst_cmml_parser_tag_clip_to_string (GstCmmlParser * parser,
216 GstCmmlTagClip * clip)
222 node = gst_cmml_parser_new_node (parser, "clip",
223 "id", clip->id, "track", clip->track, NULL);
224 /* add the anchor element */
225 if (clip->anchor_href) {
226 tmp = gst_cmml_parser_new_node (parser, "a",
227 "href", clip->anchor_href, NULL);
228 if (clip->anchor_text)
229 xmlNodeSetContent (tmp, clip->anchor_text);
231 xmlAddChild (node, tmp);
233 /* add the img element */
235 tmp = gst_cmml_parser_new_node (parser, "img",
236 "src", clip->img_src, "alt", clip->img_alt, NULL);
238 xmlAddChild (node, tmp);
240 /* add the desc element */
241 if (clip->desc_text) {
242 tmp = gst_cmml_parser_new_node (parser, "desc", NULL);
243 xmlNodeSetContent (tmp, clip->desc_text);
245 xmlAddChild (node, tmp);
247 /* add the meta elements */
249 gst_cmml_parser_meta_to_string (parser, node, clip->meta);
251 if (parser->mode == GST_CMML_PARSER_DECODE) {
254 time_str = gst_cmml_clock_time_to_npt (clip->start_time);
255 if (time_str == NULL)
258 xmlSetProp (node, (xmlChar *) "start", (xmlChar *) time_str);
261 if (clip->end_time != GST_CLOCK_TIME_NONE) {
262 time_str = gst_cmml_clock_time_to_npt (clip->end_time);
263 if (time_str == NULL)
266 xmlSetProp (node, (xmlChar *) "end", (xmlChar *) time_str);
271 ret = gst_cmml_parser_node_to_string (parser, node);
273 xmlUnlinkNode (node);
278 xmlUnlinkNode (node);
284 gst_cmml_parser_tag_object_to_string (GstCmmlParser * parser, GObject * tag)
286 guchar *tag_string = NULL;
287 GType tag_type = G_OBJECT_TYPE (tag);
289 if (tag_type == GST_TYPE_CMML_TAG_STREAM)
290 tag_string = gst_cmml_parser_tag_stream_to_string (parser,
291 GST_CMML_TAG_STREAM (tag));
292 else if (tag_type == GST_TYPE_CMML_TAG_HEAD)
293 tag_string = gst_cmml_parser_tag_head_to_string (parser,
294 GST_CMML_TAG_HEAD (tag));
295 else if (tag_type == GST_TYPE_CMML_TAG_CLIP)
296 tag_string = gst_cmml_parser_tag_clip_to_string (parser,
297 GST_CMML_TAG_CLIP (tag));
299 g_warning ("could not convert object to cmml");
304 /*** private section ***/
308 * helper to create a node and set its attributes
311 gst_cmml_parser_new_node (GstCmmlParser * parser, const gchar * name, ...)
315 xmlChar *prop_name, *prop_value;
317 node = xmlNewNode (NULL, (xmlChar *) name);
319 va_start (args, name);
321 prop_name = va_arg (args, xmlChar *);
322 while (prop_name != NULL) {
323 prop_value = va_arg (args, xmlChar *);
324 if (prop_value != NULL)
325 xmlSetProp (node, prop_name, prop_value);
327 prop_name = va_arg (args, xmlChar *);
334 /* get the last node of the stream
336 * returns the last node at depth 1 (if any) or the root node
339 gst_cmml_parser_get_last_element (GstCmmlParser * parser)
343 node = xmlDocGetRootElement (parser->context->myDoc);
345 g_warning ("no last cmml element");
350 node = xmlGetLastChild (node);
356 gst_cmml_parser_parse_preamble (GstCmmlParser * parser,
357 const guchar * attributes)
361 const gchar *version;
362 const gchar *encoding;
363 const gchar *standalone;
366 doc = parser->context->myDoc;
368 version = doc->version ? (gchar *) doc->version : "1.0";
369 encoding = doc->encoding ? (gchar *) doc->encoding : "UTF-8";
370 standalone = doc->standalone ? "yes" : "no";
372 preamble = g_strdup_printf ("<?xml version=\"%s\""
373 " encoding=\"%s\" standalone=\"%s\"?>\n"
374 "<!DOCTYPE cmml SYSTEM \"cmml.dtd\">\n", version, encoding, standalone);
376 if (attributes == NULL)
377 attributes = (guchar *) "";
379 if (parser->mode == GST_CMML_PARSER_ENCODE)
380 element = g_strdup_printf ("<?cmml %s?>", attributes);
382 element = g_strdup_printf ("<cmml %s>", attributes);
384 parser->preamble_callback (parser->user_data,
385 (guchar *) preamble, (guchar *) element);
391 /* parse the cmml stream tag */
393 gst_cmml_parser_parse_stream (GstCmmlParser * parser, xmlNodePtr stream)
395 GstCmmlTagStream *stream_tag;
396 GValue str_val = { 0 };
400 g_value_init (&str_val, G_TYPE_STRING);
402 /* read the timebase and utc attributes */
403 timebase = xmlGetProp (stream, (xmlChar *) "timebase");
404 if (timebase == NULL)
405 timebase = (guchar *) g_strdup ("0");
407 stream_tag = g_object_new (GST_TYPE_CMML_TAG_STREAM,
408 "timebase", timebase, NULL);
411 stream_tag->utc = xmlGetProp (stream, (xmlChar *) "utc");
413 /* walk the children nodes */
414 for (walk = stream->children; walk; walk = walk->next) {
415 /* for every import tag add its src attribute to stream_tag->imports */
416 if (!xmlStrcmp (walk->name, (xmlChar *) "import")) {
417 g_value_take_string (&str_val,
418 (gchar *) xmlGetProp (walk, (xmlChar *) "src"));
420 if (stream_tag->imports == NULL)
421 stream_tag->imports = g_value_array_new (0);
423 g_value_array_append (stream_tag->imports, &str_val);
426 g_value_unset (&str_val);
428 parser->stream_callback (parser->user_data, stream_tag);
429 g_object_unref (stream_tag);
432 /* parse the cmml head tag */
434 gst_cmml_parser_parse_head (GstCmmlParser * parser, xmlNodePtr head)
436 GstCmmlTagHead *head_tag;
438 GValue str_val = { 0 };
440 head_tag = g_object_new (GST_TYPE_CMML_TAG_HEAD, NULL);
442 g_value_init (&str_val, G_TYPE_STRING);
444 /* Parse the content of the node and setup the GST_TAG_CMML_HEAD tag.
445 * Create a GST_TAG_TITLE when we find the title element.
447 for (walk = head->children; walk; walk = walk->next) {
448 if (!xmlStrcmp (walk->name, (xmlChar *) "title")) {
449 head_tag->title = xmlNodeGetContent (walk);
450 } else if (!xmlStrcmp (walk->name, (xmlChar *) "base")) {
451 head_tag->base = xmlGetProp (walk, (xmlChar *) "uri");
452 } else if (!xmlStrcmp (walk->name, (xmlChar *) "meta")) {
453 if (head_tag->meta == NULL)
454 head_tag->meta = g_value_array_new (0);
455 /* add a pair name, content to the meta value array */
456 g_value_take_string (&str_val,
457 (gchar *) xmlGetProp (walk, (xmlChar *) "name"));
458 g_value_array_append (head_tag->meta, &str_val);
459 g_value_take_string (&str_val,
460 (gchar *) xmlGetProp (walk, (xmlChar *) "content"));
461 g_value_array_append (head_tag->meta, &str_val);
464 g_value_unset (&str_val);
466 parser->head_callback (parser->user_data, head_tag);
467 g_object_unref (head_tag);
470 /* parse a cmml clip tag */
472 gst_cmml_parser_parse_clip (GstCmmlParser * parser, xmlNodePtr clip)
474 GstCmmlTagClip *clip_tag;
475 GValue str_val = { 0 };
476 guchar *id, *track, *start, *end;
478 GstClockTime start_time = GST_CLOCK_TIME_NONE;
479 GstClockTime end_time = GST_CLOCK_TIME_NONE;
481 start = xmlGetProp (clip, (xmlChar *) "start");
482 if (parser->mode == GST_CMML_PARSER_ENCODE && start == NULL)
483 /* XXX: validate the document */
486 id = xmlGetProp (clip, (xmlChar *) "id");
487 track = xmlGetProp (clip, (xmlChar *) "track");
488 end = xmlGetProp (clip, (xmlChar *) "end");
491 track = (guchar *) g_strdup ("default");
494 if (!strncmp ((gchar *) start, "smpte", 5))
495 start_time = gst_cmml_clock_time_from_smpte ((gchar *) start);
497 start_time = gst_cmml_clock_time_from_npt ((gchar *) start);
501 if (!strncmp ((gchar *) end, "smpte", 5))
502 start_time = gst_cmml_clock_time_from_smpte ((gchar *) end);
504 end_time = gst_cmml_clock_time_from_npt ((gchar *) end);
507 clip_tag = g_object_new (GST_TYPE_CMML_TAG_CLIP, "id", id,
508 "track", track, "start-time", start_time, "end-time", end_time, NULL);
515 g_value_init (&str_val, G_TYPE_STRING);
517 /* parse the children */
518 for (walk = clip->children; walk; walk = walk->next) {
519 /* the clip is not empty */
520 clip_tag->empty = FALSE;
522 if (!xmlStrcmp (walk->name, (xmlChar *) "a")) {
523 clip_tag->anchor_href = xmlGetProp (walk, (xmlChar *) "href");
524 clip_tag->anchor_text = xmlNodeGetContent (walk);
525 } else if (!xmlStrcmp (walk->name, (xmlChar *) "img")) {
526 clip_tag->img_src = xmlGetProp (walk, (xmlChar *) "src");
527 clip_tag->img_alt = xmlGetProp (walk, (xmlChar *) "alt");
528 } else if (!xmlStrcmp (walk->name, (xmlChar *) "desc")) {
529 clip_tag->desc_text = xmlNodeGetContent (walk);
530 } else if (!xmlStrcmp (walk->name, (xmlChar *) "meta")) {
531 if (clip_tag->meta == NULL)
532 clip_tag->meta = g_value_array_new (0);
533 /* add a pair name, content to the meta value array */
534 g_value_take_string (&str_val,
535 (char *) xmlGetProp (walk, (xmlChar *) "name"));
536 g_value_array_append (clip_tag->meta, &str_val);
537 g_value_take_string (&str_val,
538 (char *) xmlGetProp (walk, (xmlChar *) "content"));
539 g_value_array_append (clip_tag->meta, &str_val);
542 g_value_unset (&str_val);
544 parser->clip_callback (parser->user_data, clip_tag);
545 g_object_unref (clip_tag);
549 gst_cmml_parser_meta_to_string (GstCmmlParser * parser,
550 xmlNodePtr parent, GValueArray * array)
554 GValue *name, *content;
556 for (i = 0; i < array->n_values - 1; i += 2) {
557 name = g_value_array_get_nth (array, i);
558 content = g_value_array_get_nth (array, i + 1);
559 node = gst_cmml_parser_new_node (parser, "meta",
560 "name", g_value_get_string (name),
561 "content", g_value_get_string (content), NULL);
562 xmlAddChild (parent, node);
567 gst_cmml_parser_generic_error (void *ctx, const char *msg, ...)
569 #ifndef GST_DISABLE_GST_DEBUG
572 va_start (varargs, msg);
573 gst_debug_log_valist (GST_CAT_DEFAULT, GST_LEVEL_WARNING,
574 "", "", 0, NULL, msg, varargs);
576 #endif /* GST_DISABLE_GST_DEBUG */
579 /* sax handler called when an element start tag is found
580 * this is used to parse the cmml start tag
583 gst_cmml_parser_parse_start_element_ns (xmlParserCtxt * ctxt,
584 const xmlChar * name, const xmlChar * prefix, const xmlChar * URI,
585 int nb_preferences, const xmlChar ** namespaces,
586 int nb_attributes, int nb_defaulted, const xmlChar ** attributes)
588 GstCmmlParser *parser = (GstCmmlParser *) ctxt->_private;
590 xmlSAX2StartElementNs (ctxt, name, prefix, URI, nb_preferences, namespaces,
591 nb_attributes, nb_defaulted, attributes);
593 if (parser->mode == GST_CMML_PARSER_ENCODE)
594 if (!xmlStrcmp (name, (xmlChar *) "cmml"))
595 if (parser->preamble_callback)
596 /* FIXME: parse attributes */
597 gst_cmml_parser_parse_preamble (parser, NULL);
600 /* sax processing instruction handler
601 * used to parse the cmml processing instruction
604 gst_cmml_parser_parse_processing_instruction (xmlParserCtxtPtr ctxt,
605 const xmlChar * target, const xmlChar * data)
607 GstCmmlParser *parser = (GstCmmlParser *) ctxt->_private;
609 xmlSAX2ProcessingInstruction (ctxt, target, data);
611 if (parser->mode == GST_CMML_PARSER_DECODE)
612 if (!xmlStrcmp (target, (xmlChar *) "cmml"))
613 if (parser->preamble_callback)
614 gst_cmml_parser_parse_preamble (parser, data);
617 /* sax handler called when an xml end tag is found
618 * used to parse the stream, head and clip nodes
621 gst_cmml_parser_parse_end_element_ns (xmlParserCtxt * ctxt,
622 const xmlChar * name, const xmlChar * prefix, const xmlChar * URI)
625 GstCmmlParser *parser = (GstCmmlParser *) ctxt->_private;
627 xmlSAX2EndElementNs (ctxt, name, prefix, URI);
629 if (!xmlStrcmp (name, (xmlChar *) "clip")) {
630 if (parser->clip_callback) {
631 node = gst_cmml_parser_get_last_element (parser);
632 gst_cmml_parser_parse_clip (parser, node);
634 } else if (!xmlStrcmp (name, (xmlChar *) "cmml")) {
635 if (parser->cmml_end_callback)
636 parser->cmml_end_callback (parser->user_data);
637 } else if (!xmlStrcmp (name, (xmlChar *) "stream")) {
638 if (parser->stream_callback) {
639 node = gst_cmml_parser_get_last_element (parser);
640 gst_cmml_parser_parse_stream (parser, node);
642 } else if (!xmlStrcmp (name, (xmlChar *) "head")) {
643 if (parser->head_callback) {
644 node = gst_cmml_parser_get_last_element (parser);
645 gst_cmml_parser_parse_head (parser, node);