Initial submission of GUPnP-AV to Tizen IVI
[profile/ivi/GUPnP-AV.git] / libgupnp-av / gupnp-didl-lite-writer.c
1 /*
2  * Copyright (C) 2007, 2008 OpenedHand Ltd.
3  *
4  * Authors: Jorn Baayen <jorn@openedhand.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /**
23  * SECTION:gupnp-didl-lite-writer
24  * @short_description: DIDL-Lite fragment writer
25  *
26  * #GUPnPDIDLLiteWriter is a helper class for writing DIDL-Lite fragments.
27  */
28
29 #include <string.h>
30
31 #include "gupnp-didl-lite-writer.h"
32 #include "gupnp-didl-lite-object.h"
33 #include "gupnp-didl-lite-object-private.h"
34 #include "gupnp-didl-lite-descriptor-private.h"
35
36 #include "xml-util.h"
37
38 G_DEFINE_TYPE (GUPnPDIDLLiteWriter,
39                gupnp_didl_lite_writer,
40                G_TYPE_OBJECT);
41
42 struct _GUPnPDIDLLiteWriterPrivate {
43         xmlNode     *xml_node;
44         GUPnPXMLDoc *xml_doc;
45
46         xmlNs       *upnp_ns;
47         xmlNs       *dc_ns;
48         xmlNs       *dlna_ns;
49
50         char        *language;
51 };
52
53 enum {
54         PROP_0,
55         PROP_XML_NODE,
56         PROP_LANGUAGE,
57 };
58
59 static int
60 compare_prop (const char *a, xmlAttr *attr)
61 {
62         const char *p;
63         char *parent_name;
64         char *attr_name;
65         int ret = -1;
66
67         if (attr->ns != NULL)
68                 attr_name = g_strjoin (":", attr->ns->prefix, attr->name, NULL);
69         else
70                 attr_name = g_strdup ((const char *) attr->name);
71
72         if (attr->parent->ns != NULL)
73                 parent_name = g_strjoin (":",
74                                          attr->parent->ns->prefix,
75                                          attr->parent->name,
76                                          NULL);
77         else
78                 parent_name = g_strdup ((const char *) attr->parent->name);
79
80         p = strstr (a, "@");
81         if (p)
82                 if (p == a)
83                         /* Top-level property */
84                         ret = strcmp (a + 1, attr_name);
85                 else
86                         ret = strncmp (a, parent_name, p - a) ||
87                               strcmp (p + 1, attr_name);
88         else
89                 ret = strcmp (a, attr_name);
90
91         g_free (attr_name);
92         g_free (parent_name);
93
94         return ret;
95 }
96
97 static gboolean
98 is_attribute_forbidden (xmlAttr *attr,
99                         GList   *allowed)
100 {
101         return g_list_find_custom (allowed,
102                                    attr,
103                                    (GCompareFunc) compare_prop) == NULL;
104 }
105
106 static int
107 compare_node_name (const char *a, const char *b)
108 {
109         const char *p;
110         int len;
111
112         if (a[0] == '@')
113                 /* Filer is for top-level property */
114                 return -1;
115
116         p = strstr (a, "@");
117         if (p != NULL)
118                 /* Compare only the string before '@' */
119                 len = p - a;
120         else
121                 len = strlen (a);
122
123         return strncmp (a, b, len);
124 }
125
126 static gboolean
127 is_node_forbidden (xmlNode     *node,
128                     GList      *allowed,
129                     const char *ns)
130 {
131         char *name;
132         gboolean ret;
133
134         if (ns != NULL)
135                 name = g_strjoin (":", ns, node->name, NULL);
136         else
137                 name = g_strdup ((const char *) node->name);
138
139         ret = g_list_find_custom (allowed,
140                                   name,
141                                   (GCompareFunc) compare_node_name) == NULL;
142
143         g_free (name);
144
145         return ret;
146 }
147
148 static gboolean
149 is_container_standard_prop (const char *name,
150                             const char *namespace,
151                             const char *upnp_class)
152 {
153         return g_strcmp0 (upnp_class, "object.container.storageFolder") == 0 &&
154                g_strcmp0 (namespace, "upnp") == 0 &&
155                strcmp (name, "storageUsed") == 0;
156 }
157
158 static gboolean
159 is_standard_prop (const char *name,
160                   const char *namespace,
161                   const char *parent_name)
162 {
163         return strcmp (name, "id") == 0 ||
164                strcmp (name, "parentID") == 0 ||
165                strcmp (name, "restricted") == 0 ||
166                (g_strcmp0 (namespace, "dc") == 0 &&
167                 strcmp (name, "title") == 0) ||
168                (g_strcmp0 (namespace, "upnp") == 0 &&
169                 strcmp (name, "class") == 0) ||
170                (g_strcmp0 (parent_name, "res") == 0 &&
171                 strcmp (name, "protocolInfo") == 0);
172 }
173
174 static void
175 filter_attributes (xmlNode             *node,
176                    GList               *allowed,
177                    GUPnPDIDLLiteWriter *writer)
178 {
179         xmlAttr *attr;
180         GList   *forbidden = NULL;
181         GList   *l;
182
183         /* Find forbidden properties */
184         for (attr = node->properties; attr != NULL; attr = attr->next)
185                 if (!is_standard_prop ((const char *) attr->name,
186                                         NULL,
187                                         (const char *) attr->parent->name) &&
188                     is_attribute_forbidden (attr, allowed))
189                         forbidden = g_list_append (forbidden, attr);
190
191         /* Now unset forbidden properties */
192         for (l = forbidden; l != NULL; l = l->next)
193                 xmlRemoveProp ((xmlAttr *) l->data);
194
195         g_list_free (forbidden);
196 }
197
198 static void
199 filter_node (xmlNode             *node,
200              GList               *allowed,
201              GUPnPDIDLLiteWriter *writer)
202 {
203         xmlNode *child;
204         GList   *forbidden = NULL;
205         GList   *l;
206         gboolean is_container;
207         const char *container_class = NULL;
208
209         filter_attributes (node, allowed, writer);
210
211         if (strcmp ((const char *) node->name, "container") == 0) {
212             is_container = TRUE;
213             container_class = xml_util_get_child_element_content (node,
214                                                                   "class");
215         }
216
217         forbidden = NULL;
218         for (child = node->children; child != NULL; child = child->next) {
219                 const char *ns = NULL;
220
221                 if (xmlNodeIsText (child))
222                         continue;
223
224                 if (child->ns != NULL)
225                         ns = (const char *) child->ns->prefix;
226
227                 if (!(is_container && is_container_standard_prop
228                                             ((const char *) child->name,
229                                              ns,
230                                              container_class)) &&
231                     !is_standard_prop ((const char *) child->name,
232                                        ns,
233                                        (const char *)  node->name) &&
234                     is_node_forbidden (child, allowed, ns))
235                         forbidden = g_list_append (forbidden, child);
236         }
237
238         /* Now remove the forbidden nodes */
239         for (l = forbidden; l != NULL; l = l->next) {
240                 xmlNode *n;
241
242                 n = (xmlNode *) l->data;
243
244                 xmlUnlinkNode (n);
245                 xmlFreeNode (n);
246         }
247
248         g_list_free (forbidden);
249
250         /* Recurse */
251         for (child = node->children; child != NULL; child = child->next)
252                 if (!xmlNodeIsText (child))
253                         filter_node (child, allowed, writer);
254 }
255
256 static void
257 gupnp_didl_lite_writer_init (GUPnPDIDLLiteWriter *writer)
258 {
259         writer->priv = G_TYPE_INSTANCE_GET_PRIVATE (writer,
260                                                     GUPNP_TYPE_DIDL_LITE_WRITER,
261                                                     GUPnPDIDLLiteWriterPrivate);
262 }
263
264 static void
265 gupnp_didl_lite_writer_set_property (GObject      *object,
266                                      guint         property_id,
267                                      const GValue *value,
268                                      GParamSpec   *pspec)
269
270 {
271         GUPnPDIDLLiteWriter *writer;
272
273         writer = GUPNP_DIDL_LITE_WRITER (object);
274
275         switch (property_id) {
276         case PROP_LANGUAGE:
277                 writer->priv->language = g_value_dup_string (value);
278                 break;
279         default:
280                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
281                 break;
282         }
283 }
284
285 static void
286 gupnp_didl_lite_writer_get_property (GObject    *object,
287                                      guint       property_id,
288                                      GValue     *value,
289                                      GParamSpec *pspec)
290 {
291         GUPnPDIDLLiteWriter *writer;
292
293         writer = GUPNP_DIDL_LITE_WRITER (object);
294
295         switch (property_id) {
296         case PROP_XML_NODE:
297                 g_value_set_pointer
298                         (value, gupnp_didl_lite_writer_get_xml_node (writer));
299                 break;
300         case PROP_LANGUAGE:
301                 g_value_set_string
302                         (value, gupnp_didl_lite_writer_get_language (writer));
303                 break;
304         default:
305                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
306                 break;
307         }
308 }
309
310 static void
311 gupnp_didl_lite_writer_constructed (GObject *object)
312 {
313         GObjectClass               *object_class;
314         GUPnPDIDLLiteWriterPrivate *priv;
315         xmlDoc                     *doc;
316
317         priv = GUPNP_DIDL_LITE_WRITER (object)->priv;
318
319         doc = xmlNewDoc ((unsigned char *) "1.0");
320         priv->xml_doc = gupnp_xml_doc_new (doc);
321
322         priv->xml_node = xmlNewDocNode (priv->xml_doc->doc,
323                                         NULL,
324                                         (unsigned char *) "DIDL-Lite",
325                                         NULL);
326         xmlDocSetRootElement (priv->xml_doc->doc, priv->xml_node);
327         priv->dc_ns = xmlNewNs (priv->xml_node,
328                                 (unsigned char *)
329                                 "http://purl.org/dc/elements/1.1/",
330                                 (unsigned char *)
331                                 GUPNP_DIDL_LITE_WRITER_NAMESPACE_DC);
332         priv->upnp_ns = xmlNewNs (priv->xml_node,
333                                   (unsigned char *)
334                                   "urn:schemas-upnp-org:metadata-1-0/upnp/",
335                                   (unsigned char *)
336                                   GUPNP_DIDL_LITE_WRITER_NAMESPACE_UPNP);
337         priv->dlna_ns = xmlNewNs (priv->xml_node,
338                                   (unsigned char *)
339                                   "urn:schemas-dlna-org:metadata-1-0/",
340                                   (unsigned char *)
341                                   GUPNP_DIDL_LITE_WRITER_NAMESPACE_DLNA);
342         xmlNewNs (priv->xml_node,
343                   (unsigned char *)
344                   "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/",
345                   NULL);
346
347         if (priv->language)
348                 xmlSetProp (priv->xml_node,
349                             (unsigned char *) "lang",
350                             (unsigned char *) priv->language);
351
352         object_class = G_OBJECT_CLASS (gupnp_didl_lite_writer_parent_class);
353         if (object_class->constructed != NULL)
354                 object_class->constructed (object);
355 }
356
357 static void
358 gupnp_didl_lite_writer_dispose (GObject *object)
359 {
360         GObjectClass               *object_class;
361         GUPnPDIDLLiteWriterPrivate *priv;
362
363         priv = GUPNP_DIDL_LITE_WRITER (object)->priv;
364
365         if (priv->xml_doc) {
366                 g_object_unref (priv->xml_doc);
367                 priv->xml_doc = NULL;
368         }
369
370         object_class = G_OBJECT_CLASS (gupnp_didl_lite_writer_parent_class);
371         object_class->dispose (object);
372 }
373
374 static void
375 gupnp_didl_lite_writer_finalize (GObject *object)
376 {
377         GObjectClass               *object_class;
378         GUPnPDIDLLiteWriterPrivate *priv;
379
380         priv = GUPNP_DIDL_LITE_WRITER (object)->priv;
381
382         if (priv->language)
383                 g_free (priv->language);
384
385         object_class = G_OBJECT_CLASS (gupnp_didl_lite_writer_parent_class);
386         object_class->finalize (object);
387 }
388
389 static void
390 gupnp_didl_lite_writer_class_init (GUPnPDIDLLiteWriterClass *klass)
391 {
392         GObjectClass *object_class;
393
394         object_class = G_OBJECT_CLASS (klass);
395
396         object_class->set_property = gupnp_didl_lite_writer_set_property;
397         object_class->get_property = gupnp_didl_lite_writer_get_property;
398         object_class->constructed = gupnp_didl_lite_writer_constructed;
399         object_class->dispose = gupnp_didl_lite_writer_dispose;
400         object_class->finalize = gupnp_didl_lite_writer_finalize;
401
402         g_type_class_add_private (klass, sizeof (GUPnPDIDLLiteWriterPrivate));
403
404         /**
405          * GUPnPDIDLLiteWriter:xml-node:
406          *
407          * The pointer to root node in XML document.
408          **/
409         g_object_class_install_property
410                 (object_class,
411                  PROP_XML_NODE,
412                  g_param_spec_pointer ("xml-node",
413                                        "XMLNode",
414                                        "The pointer to root node in XML"
415                                        " document.",
416                                        G_PARAM_READABLE |
417                                        G_PARAM_STATIC_NAME |
418                                        G_PARAM_STATIC_NICK |
419                                        G_PARAM_STATIC_BLURB));
420
421         /**
422          * GUPnPDIDLLiteWriter:language:
423          *
424          * The language the DIDL-Lite fragment is in.
425          *
426          **/
427         g_object_class_install_property
428                 (object_class,
429                  PROP_LANGUAGE,
430                  g_param_spec_string ("language",
431                                       "Language",
432                                       "The language the DIDL-Lite fragment"
433                                       " is in.",
434                                       NULL,
435                                       G_PARAM_CONSTRUCT_ONLY |
436                                       G_PARAM_READWRITE |
437                                       G_PARAM_STATIC_NAME |
438                                       G_PARAM_STATIC_NICK |
439                                       G_PARAM_STATIC_BLURB));
440 }
441
442 /**
443  * gupnp_didl_lite_writer_new:
444  * @language: The language the DIDL-Lite fragment is in, or NULL
445  *
446  * Return value: A new #GUPnPDIDLLiteWriter object.
447  **/
448 GUPnPDIDLLiteWriter *
449 gupnp_didl_lite_writer_new (const char *language)
450 {
451         return g_object_new (GUPNP_TYPE_DIDL_LITE_WRITER,
452                              "language", language,
453                              NULL);
454 }
455
456 /**
457  * gupnp_didl_lite_writer_add_item:
458  * @writer: A #GUPnPDIDLLiteWriter
459  *
460  * Creates a new item, attaches it to @writer and returns it.
461  *
462  * Returns: (transfer full): A new #GUPnPDIDLLiteItem object. Unref after usage.
463  **/
464 GUPnPDIDLLiteItem *
465 gupnp_didl_lite_writer_add_item (GUPnPDIDLLiteWriter *writer)
466 {
467         xmlNode *item_node;
468         GUPnPDIDLLiteObject *object;
469
470         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
471
472         item_node = xmlNewChild (writer->priv->xml_node,
473                                 NULL,
474                                 (unsigned char *) "item",
475                                 NULL);
476
477         object = gupnp_didl_lite_object_new_from_xml (item_node,
478                                                       writer->priv->xml_doc,
479                                                       writer->priv->upnp_ns,
480                                                       writer->priv->dc_ns,
481                                                       writer->priv->dlna_ns);
482         return GUPNP_DIDL_LITE_ITEM (object);
483 }
484
485 /**
486  * gupnp_didl_lite_writer_add_container:
487  * @writer: A #GUPnPDIDLLiteWriter
488  *
489  * Creates a new container, attaches it to @writer and returns it.
490  *
491  * Returns: (transfer full): A new #GUPnPDIDLLiteContainer object. Unref after usage.
492  **/
493 GUPnPDIDLLiteContainer *
494 gupnp_didl_lite_writer_add_container (GUPnPDIDLLiteWriter *writer)
495 {
496         xmlNode *container_node;
497         GUPnPDIDLLiteObject *object;
498
499         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
500
501         container_node = xmlNewChild (writer->priv->xml_node,
502                                       NULL,
503                                       (unsigned char *) "container",
504                                       NULL);
505
506         object = gupnp_didl_lite_object_new_from_xml (container_node,
507                                                       writer->priv->xml_doc,
508                                                       writer->priv->upnp_ns,
509                                                       writer->priv->dc_ns,
510                                                       writer->priv->dlna_ns);
511         return GUPNP_DIDL_LITE_CONTAINER (object);
512 }
513
514 /**
515  * gupnp_didl_lite_writer_add_descriptor:
516  * @writer: A #GUPnPDIDLLiteWriter
517  *
518  * Creates a new descriptor, attaches it to @object and returns it.
519  *
520  * Returns: (transfer full): A new #GUPnPDIDLLiteDescriptor object. Unref after usage.
521  **/
522 GUPnPDIDLLiteDescriptor *
523 gupnp_didl_lite_writer_add_descriptor (GUPnPDIDLLiteWriter *writer)
524 {
525         xmlNode *desc_node;
526
527         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
528
529         desc_node = xmlNewChild (writer->priv->xml_node,
530                                  NULL,
531                                  (unsigned char *) "desc",
532                                  NULL);
533
534         return gupnp_didl_lite_descriptor_new_from_xml (desc_node,
535                                                         writer->priv->xml_doc);
536 }
537
538 /**
539  * gupnp_didl_lite_writer_get_string:
540  * @writer: A #GUPnPDIDLLiteWriter
541  *
542  * Creates a string representation of the DIDL-Lite XML document.
543  *
544  * Return value: The DIDL-Lite XML string, or %NULL. #g_free after usage.
545  **/
546 char *
547 gupnp_didl_lite_writer_get_string (GUPnPDIDLLiteWriter *writer)
548 {
549         xmlBuffer *buffer;
550         char      *ret;
551
552         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
553
554         buffer = xmlBufferCreate ();
555         xmlNodeDump (buffer,
556                      writer->priv->xml_doc->doc,
557                      writer->priv->xml_node,
558                      0,
559                      0);
560         ret = g_strndup ((char *) xmlBufferContent (buffer),
561                          xmlBufferLength (buffer));
562         xmlBufferFree (buffer);
563
564         return ret;
565 }
566
567 /**
568  * gupnp_didl_lite_writer_get_xml_node:
569  * @writer: The #GUPnPDIDLLiteWriter
570  *
571  * Get the pointer to root node in XML document.
572  *
573  * Returns: (transfer none): The pointer to root node in XML document.
574  **/
575 xmlNode *
576 gupnp_didl_lite_writer_get_xml_node (GUPnPDIDLLiteWriter *writer)
577 {
578         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
579
580         return writer->priv->xml_node;
581 }
582
583 /**
584  * gupnp_didl_lite_writer_get_language:
585  * @writer: #GUPnPDIDLLiteWriter
586  *
587  * Get the language the DIDL-Lite fragment is in.
588  *
589  * Returns: (transfer none): The language of the @writer, or %NULL.
590  **/
591 const char *
592 gupnp_didl_lite_writer_get_language (GUPnPDIDLLiteWriter *writer)
593 {
594         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
595
596         return writer->priv->language;
597 }
598
599 /**
600  * gupnp_didl_lite_writer_filter:
601  * @writer: A #GUPnPDIDLLiteWriter
602  * @filter: A filter string
603  *
604  * Clears the DIDL-Lite XML document of the properties not specified in the
605  * @filter. The passed filter string would typically come from the 'Filter'
606  * argument of Browse or Search actions from a ContentDirectory control point.
607  * Please refer to Section 2.3.15 of UPnP AV ContentDirectory version 3
608  * specification for details on this string.
609  *
610  * Return value: None.
611  **/
612 void
613 gupnp_didl_lite_writer_filter (GUPnPDIDLLiteWriter *writer,
614                                const char          *filter)
615 {
616         char **tokens;
617         GList *allowed = NULL;
618         unsigned short i;
619         xmlNode *node;
620
621         g_return_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer));
622         g_return_if_fail (filter != NULL);
623
624         if (filter[0] == '*')
625                 return;         /* Wildcard */
626
627         tokens = g_strsplit (filter, ",", -1);
628         g_return_if_fail (tokens != NULL);
629
630         for (i = 0; tokens[i] != NULL; i++)
631                 allowed = g_list_append (allowed, tokens[i]);
632
633         for (node = writer->priv->xml_node->children;
634              node != NULL;
635              node = node->next)
636                 filter_node (node, allowed, writer);
637
638         g_list_free (allowed);
639         g_strfreev (tokens);
640 }