Update to gupnp-av-0.12.4
[profile/ivi/GUPnP-AV.git] / libgupnp-av / gupnp-didl-lite-writer.c
1 /*
2  * Copyright (C) 2007, 2008 OpenedHand Ltd.
3  * Copyright (C) 2012 Intel Corporation.
4  *
5  * Authors: Jorn Baayen <jorn@openedhand.com>
6  *          Jens Georg <jensg@openismus.com>
7  *
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.
12  *
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.
17  *
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., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 /**
25  * SECTION:gupnp-didl-lite-writer
26  * @short_description: DIDL-Lite fragment writer
27  *
28  * #GUPnPDIDLLiteWriter is a helper class for writing DIDL-Lite fragments.
29  */
30
31 #include <string.h>
32
33 #include "gupnp-didl-lite-writer.h"
34 #include "gupnp-didl-lite-object.h"
35 #include "gupnp-didl-lite-object-private.h"
36 #include "gupnp-didl-lite-descriptor-private.h"
37 #include "gupnp-didl-lite-writer-private.h"
38
39 #include "xml-util.h"
40
41 G_DEFINE_TYPE (GUPnPDIDLLiteWriter,
42                gupnp_didl_lite_writer,
43                G_TYPE_OBJECT);
44
45 struct _GUPnPDIDLLiteWriterPrivate {
46         xmlNode     *xml_node;
47         GUPnPXMLDoc *xml_doc;
48
49         xmlNs       *upnp_ns;
50         xmlNs       *dc_ns;
51         xmlNs       *dlna_ns;
52         xmlNs       *pv_ns;
53
54         char        *language;
55
56         gboolean    dlna_attr_present;
57 };
58
59 enum {
60         PROP_0,
61         PROP_XML_NODE,
62         PROP_LANGUAGE,
63 };
64
65 static int
66 compare_prop (const char *a, xmlAttr *attr)
67 {
68         const char *p;
69         char *parent_name;
70         char *attr_name;
71         int ret = -1;
72
73         if (attr->ns != NULL)
74                 attr_name = g_strjoin (":", attr->ns->prefix, attr->name, NULL);
75         else
76                 attr_name = g_strdup ((const char *) attr->name);
77
78         if (attr->parent->ns != NULL)
79                 parent_name = g_strjoin (":",
80                                          attr->parent->ns->prefix,
81                                          attr->parent->name,
82                                          NULL);
83         else
84                 parent_name = g_strdup ((const char *) attr->parent->name);
85
86         p = strstr (a, "@");
87         if (p)
88                 if (p == a)
89                         /* Top-level property */
90                         ret = strcmp (a + 1, attr_name);
91                 else
92                         ret = strncmp (a, parent_name, p - a) ||
93                               strcmp (p + 1, attr_name);
94         else
95                 ret = strcmp (a, attr_name);
96
97         g_free (attr_name);
98         g_free (parent_name);
99
100         return ret;
101 }
102
103 static gboolean
104 is_attribute_forbidden (xmlAttr *attr,
105                         GList   *allowed)
106 {
107         return g_list_find_custom (allowed,
108                                    attr,
109                                    (GCompareFunc) compare_prop) == NULL;
110 }
111
112 static int
113 compare_node_name (const char *a, const char *b)
114 {
115         const char *p;
116         int len, result;
117
118         if (a[0] == '@')
119                 /* Filter is for top-level property */
120                 return -1;
121
122         p = strstr (a, "@");
123         if (p != NULL)
124                 /* Compare only the string before '@' */
125                 len = p - a;
126         else
127                 len = strlen (a);
128
129         result = strncmp (a, b, len);
130
131         if (result == 0) {
132             /* Avoid that we return a match although only prefixes match like
133              * in upnp:album and upnp:albumArtUri, cf. bgo#687462 */
134             return strlen (b) - len;
135         }
136
137         return result;
138 }
139
140 static gboolean
141 is_node_forbidden (xmlNode     *node,
142                     GList      *allowed,
143                     const char *ns)
144 {
145         char *name;
146         gboolean ret;
147
148         if (ns != NULL)
149                 name = g_strjoin (":", ns, node->name, NULL);
150         else
151                 name = g_strdup ((const char *) node->name);
152
153         ret = g_list_find_custom (allowed,
154                                   name,
155                                   (GCompareFunc) compare_node_name) == NULL;
156
157         g_free (name);
158
159         return ret;
160 }
161
162 static gboolean
163 is_container_standard_prop (const char *name,
164                             const char *namespace,
165                             const char *upnp_class)
166 {
167         return g_strcmp0 (upnp_class, "object.container.storageFolder") == 0 &&
168                g_strcmp0 (namespace, "upnp") == 0 &&
169                strcmp (name, "storageUsed") == 0;
170 }
171
172 static gboolean
173 is_standard_prop (const char *name,
174                   const char *namespace,
175                   const char *parent_name)
176 {
177         return strcmp (name, "id") == 0 ||
178                strcmp (name, "parentID") == 0 ||
179                strcmp (name, "restricted") == 0 ||
180                (g_strcmp0 (namespace, "dc") == 0 &&
181                 strcmp (name, "title") == 0) ||
182                (g_strcmp0 (namespace, "upnp") == 0 &&
183                 strcmp (name, "class") == 0) ||
184                (g_strcmp0 (parent_name, "res") == 0 &&
185                 strcmp (name, "protocolInfo") == 0);
186 }
187
188 static void
189 filter_attributes (xmlNode             *node,
190                    GList               *allowed)
191 {
192         xmlAttr *attr;
193         GList   *forbidden = NULL;
194         GList   *l;
195
196         /* Find forbidden properties */
197         for (attr = node->properties; attr != NULL; attr = attr->next)
198                 if (!is_standard_prop ((const char *) attr->name,
199                                         NULL,
200                                         (const char *) attr->parent->name) &&
201                     is_attribute_forbidden (attr, allowed))
202                         forbidden = g_list_append (forbidden, attr);
203
204         /* Now unset forbidden properties */
205         for (l = forbidden; l != NULL; l = l->next)
206                 xmlRemoveProp ((xmlAttr *) l->data);
207
208         g_list_free (forbidden);
209 }
210
211 static void
212 check_dlna_attr (xmlNode             *node,
213                  GUPnPDIDLLiteWriter *writer)
214 {
215         xmlAttr *attr;
216
217         /* check if dlna prefix is present for a node */
218         for (attr = node->properties; attr != NULL; attr = attr->next) {
219                 if (attr->ns && g_strcmp0 (attr->ns->prefix, "dlna") == 0)
220                         writer->priv->dlna_attr_present = TRUE;
221         }
222 }
223
224 static void
225 filter_node (xmlNode             *node,
226              GList               *allowed,
227              GUPnPDIDLLiteWriter *writer,
228              gboolean             tags_only)
229 {
230         xmlNode *child;
231         GList   *forbidden = NULL;
232         GList   *l;
233         gboolean is_container = FALSE;
234         const char *container_class = NULL;
235
236         if (!tags_only)
237                 filter_attributes (node, allowed);
238
239         // Check if dlna namespace attribute is present
240         if (!writer->priv->dlna_attr_present)
241                 check_dlna_attr (node, writer);
242
243         if (strcmp ((const char *) node->name, "container") == 0) {
244                 is_container = TRUE;
245                 container_class = xml_util_get_child_element_content (node,
246                                                                       "class");
247         }
248
249         forbidden = NULL;
250         for (child = node->children; child != NULL; child = child->next) {
251                 const char *ns = NULL;
252
253                 if (xmlNodeIsText (child))
254                         continue;
255
256                 if (child->ns != NULL)
257                         ns = (const char *) child->ns->prefix;
258
259                 if (!(is_container && is_container_standard_prop
260                                             ((const char *) child->name,
261                                              ns,
262                                              container_class)) &&
263                     !is_standard_prop ((const char *) child->name,
264                                        ns,
265                                        (const char *)  node->name) &&
266                     is_node_forbidden (child, allowed, ns))
267                         forbidden = g_list_append (forbidden, child);
268         }
269
270         /* Now remove the forbidden nodes */
271         for (l = forbidden; l != NULL; l = l->next) {
272                 xmlNode *n;
273
274                 n = (xmlNode *) l->data;
275
276                 xmlUnlinkNode (n);
277                 xmlFreeNode (n);
278         }
279
280         g_list_free (forbidden);
281
282         /* Recurse */
283         for (child = node->children; child != NULL; child = child->next)
284                 if (!xmlNodeIsText (child))
285                         filter_node (child, allowed, writer, tags_only);
286 }
287
288 static void
289 apply_filter (GUPnPDIDLLiteWriter *writer,
290               const char          *filter,
291               gboolean             tags_only)
292 {
293         char **tokens;
294         GList *allowed = NULL;
295         unsigned short i;
296         xmlNode *node;
297
298         g_return_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer));
299         g_return_if_fail (filter != NULL);
300
301         if (filter[0] == '*') {
302                 /* Create DLNA namespace as we include anything anyway */
303                 xmlNewNs (writer->priv->xml_node,
304                           writer->priv->dlna_ns->href,
305                           writer->priv->dlna_ns->prefix);
306                 return;         /* Wildcard */
307         }
308
309         tokens = g_strsplit (filter, ",", -1);
310         g_return_if_fail (tokens != NULL);
311
312         for (i = 0; tokens[i] != NULL; i++)
313                 allowed = g_list_append (allowed, tokens[i]);
314
315         for (node = writer->priv->xml_node->children;
316              node != NULL;
317              node = node->next)
318                 filter_node (node, allowed, writer, tags_only);
319
320         if (writer->priv->dlna_attr_present) {
321                 xmlNewNs (writer->priv->xml_node,
322                           writer->priv->dlna_ns->href,
323                           writer->priv->dlna_ns->prefix);
324         }
325
326         g_list_free (allowed);
327         g_strfreev (tokens);
328 }
329
330
331 static void
332 gupnp_didl_lite_writer_init (GUPnPDIDLLiteWriter *writer)
333 {
334         writer->priv = G_TYPE_INSTANCE_GET_PRIVATE (writer,
335                                                     GUPNP_TYPE_DIDL_LITE_WRITER,
336                                                     GUPnPDIDLLiteWriterPrivate);
337 }
338
339 static void
340 gupnp_didl_lite_writer_set_property (GObject      *object,
341                                      guint         property_id,
342                                      const GValue *value,
343                                      GParamSpec   *pspec)
344
345 {
346         GUPnPDIDLLiteWriter *writer;
347
348         writer = GUPNP_DIDL_LITE_WRITER (object);
349
350         switch (property_id) {
351         case PROP_LANGUAGE:
352                 writer->priv->language = g_value_dup_string (value);
353                 break;
354         default:
355                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
356                 break;
357         }
358 }
359
360 static void
361 gupnp_didl_lite_writer_get_property (GObject    *object,
362                                      guint       property_id,
363                                      GValue     *value,
364                                      GParamSpec *pspec)
365 {
366         GUPnPDIDLLiteWriter *writer;
367
368         writer = GUPNP_DIDL_LITE_WRITER (object);
369
370         switch (property_id) {
371         case PROP_XML_NODE:
372                 g_value_set_pointer
373                         (value, gupnp_didl_lite_writer_get_xml_node (writer));
374                 break;
375         case PROP_LANGUAGE:
376                 g_value_set_string
377                         (value, gupnp_didl_lite_writer_get_language (writer));
378                 break;
379         default:
380                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
381                 break;
382         }
383 }
384
385 static void
386 gupnp_didl_lite_writer_constructed (GObject *object)
387 {
388         GObjectClass               *object_class;
389         GUPnPDIDLLiteWriterPrivate *priv;
390         xmlDoc                     *doc;
391
392         priv = GUPNP_DIDL_LITE_WRITER (object)->priv;
393
394         doc = xmlNewDoc ((unsigned char *) "1.0");
395         priv->xml_doc = gupnp_xml_doc_new (doc);
396
397         priv->xml_node = xmlNewDocNode (priv->xml_doc->doc,
398                                         NULL,
399                                         (unsigned char *) "DIDL-Lite",
400                                         NULL);
401         xmlDocSetRootElement (priv->xml_doc->doc, priv->xml_node);
402         priv->dc_ns = xmlNewNs (priv->xml_node,
403                                 (unsigned char *)
404                                 "http://purl.org/dc/elements/1.1/",
405                                 (unsigned char *)
406                                 GUPNP_DIDL_LITE_WRITER_NAMESPACE_DC);
407         priv->upnp_ns = xmlNewNs (priv->xml_node,
408                                   (unsigned char *)
409                                   "urn:schemas-upnp-org:metadata-1-0/upnp/",
410                                   (unsigned char *)
411                                   GUPNP_DIDL_LITE_WRITER_NAMESPACE_UPNP);
412         /* Not adding dlna namespace declaration to any node yet.
413            Add the namespace to Didl-Lite element only if any of the child
414            nodes have dlna namespace prefix attributes */
415         priv->dlna_ns = xmlNewNs (NULL,
416                                   (unsigned char *)
417                                   "urn:schemas-dlna-org:metadata-1-0/",
418                                   (unsigned char *)
419                                   GUPNP_DIDL_LITE_WRITER_NAMESPACE_DLNA);
420         priv->pv_ns = xmlNewNs (priv->xml_node,
421                                  (unsigned char *)
422                                  "http://www.pv.com/pvns/",
423                                  (unsigned char *)
424                                  GUPNP_DIDL_LITE_WRITER_NAMESPACE_PV);
425         xmlNewNs (priv->xml_node,
426                   (unsigned char *)
427                   "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/",
428                   NULL);
429
430         if (priv->language)
431                 xmlSetProp (priv->xml_node,
432                             (unsigned char *) "lang",
433                             (unsigned char *) priv->language);
434
435         priv->dlna_attr_present = FALSE;
436
437         object_class = G_OBJECT_CLASS (gupnp_didl_lite_writer_parent_class);
438         if (object_class->constructed != NULL)
439                 object_class->constructed (object);
440 }
441
442 static void
443 gupnp_didl_lite_writer_dispose (GObject *object)
444 {
445         GObjectClass               *object_class;
446         GUPnPDIDLLiteWriterPrivate *priv;
447
448         priv = GUPNP_DIDL_LITE_WRITER (object)->priv;
449
450         if (priv->xml_doc) {
451                 g_object_unref (priv->xml_doc);
452                 priv->xml_doc = NULL;
453         }
454
455         object_class = G_OBJECT_CLASS (gupnp_didl_lite_writer_parent_class);
456         object_class->dispose (object);
457 }
458
459 static void
460 gupnp_didl_lite_writer_finalize (GObject *object)
461 {
462         GObjectClass               *object_class;
463         GUPnPDIDLLiteWriterPrivate *priv;
464
465         priv = GUPNP_DIDL_LITE_WRITER (object)->priv;
466
467         if (priv->language)
468                 g_free (priv->language);
469
470         object_class = G_OBJECT_CLASS (gupnp_didl_lite_writer_parent_class);
471         object_class->finalize (object);
472 }
473
474 static void
475 gupnp_didl_lite_writer_class_init (GUPnPDIDLLiteWriterClass *klass)
476 {
477         GObjectClass *object_class;
478
479         object_class = G_OBJECT_CLASS (klass);
480
481         object_class->set_property = gupnp_didl_lite_writer_set_property;
482         object_class->get_property = gupnp_didl_lite_writer_get_property;
483         object_class->constructed = gupnp_didl_lite_writer_constructed;
484         object_class->dispose = gupnp_didl_lite_writer_dispose;
485         object_class->finalize = gupnp_didl_lite_writer_finalize;
486
487         g_type_class_add_private (klass, sizeof (GUPnPDIDLLiteWriterPrivate));
488
489         /**
490          * GUPnPDIDLLiteWriter:xml-node:
491          *
492          * The pointer to root node in XML document.
493          **/
494         g_object_class_install_property
495                 (object_class,
496                  PROP_XML_NODE,
497                  g_param_spec_pointer ("xml-node",
498                                        "XMLNode",
499                                        "The pointer to root node in XML"
500                                        " document.",
501                                        G_PARAM_READABLE |
502                                        G_PARAM_STATIC_NAME |
503                                        G_PARAM_STATIC_NICK |
504                                        G_PARAM_STATIC_BLURB));
505
506         /**
507          * GUPnPDIDLLiteWriter:language:
508          *
509          * The language the DIDL-Lite fragment is in.
510          *
511          **/
512         g_object_class_install_property
513                 (object_class,
514                  PROP_LANGUAGE,
515                  g_param_spec_string ("language",
516                                       "Language",
517                                       "The language the DIDL-Lite fragment"
518                                       " is in.",
519                                       NULL,
520                                       G_PARAM_CONSTRUCT_ONLY |
521                                       G_PARAM_READWRITE |
522                                       G_PARAM_STATIC_NAME |
523                                       G_PARAM_STATIC_NICK |
524                                       G_PARAM_STATIC_BLURB));
525 }
526
527 /**
528  * gupnp_didl_lite_writer_new:
529  * @language: (allow-none):The language the DIDL-Lite fragment is in, or %NULL
530  *
531  * Note: @language should always be set to %NULL, DLNA does not support the
532  * language parameter.
533  *
534  * Return value: A new #GUPnPDIDLLiteWriter object.
535  **/
536 GUPnPDIDLLiteWriter *
537 gupnp_didl_lite_writer_new (const char *language)
538 {
539         return g_object_new (GUPNP_TYPE_DIDL_LITE_WRITER,
540                              "language", language,
541                              NULL);
542 }
543
544 /**
545  * gupnp_didl_lite_writer_add_item:
546  * @writer: A #GUPnPDIDLLiteWriter
547  *
548  * Creates a new item, attaches it to @writer and returns it.
549  *
550  * Returns: (transfer full): A new #GUPnPDIDLLiteItem object. Unref after usage.
551  **/
552 GUPnPDIDLLiteItem *
553 gupnp_didl_lite_writer_add_item (GUPnPDIDLLiteWriter *writer)
554 {
555         xmlNode *item_node;
556         GUPnPDIDLLiteObject *object;
557
558         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
559
560         item_node = xmlNewChild (writer->priv->xml_node,
561                                 NULL,
562                                 (unsigned char *) "item",
563                                 NULL);
564
565         object = gupnp_didl_lite_object_new_from_xml (item_node,
566                                                       writer->priv->xml_doc,
567                                                       writer->priv->upnp_ns,
568                                                       writer->priv->dc_ns,
569                                                       writer->priv->dlna_ns,
570                                                       writer->priv->pv_ns);
571         return GUPNP_DIDL_LITE_ITEM (object);
572 }
573
574 /**
575  * gupnp_didl_lite_writer_add_container_child_item:
576  * @writer: #GUPnPDIDLLiteWriter
577  * @container: #GUPnPDIDLLiteContainer
578  *
579  * Add a child item to a container. This is only useful in DIDL_S playlist
580  * creation.
581  *
582  * Returns: (transfer full): A new #GUPnPDIDLLiteItem object. Unref after
583  * usage.
584  **/
585 GUPnPDIDLLiteItem *
586 gupnp_didl_lite_writer_add_container_child_item
587                                         (GUPnPDIDLLiteWriter    *writer,
588                                          GUPnPDIDLLiteContainer *container)
589 {
590         xmlNode *item_node, *container_node;
591         GUPnPDIDLLiteObject *object;
592
593         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_CONTAINER (container), NULL);
594
595         object = GUPNP_DIDL_LITE_OBJECT (container);
596         container_node = gupnp_didl_lite_object_get_xml_node (object);
597
598         item_node = xmlNewChild (container_node,
599                                  NULL,
600                                  (xmlChar *) "item",
601                                  NULL);
602
603         object = gupnp_didl_lite_object_new_from_xml (item_node,
604                                                       writer->priv->xml_doc,
605                                                       writer->priv->upnp_ns,
606                                                       writer->priv->dc_ns,
607                                                       writer->priv->dlna_ns,
608                                                       writer->priv->pv_ns);
609         return GUPNP_DIDL_LITE_ITEM (object);
610 }
611
612 /**
613  * gupnp_didl_lite_writer_add_container:
614  * @writer: A #GUPnPDIDLLiteWriter
615  *
616  * Creates a new container, attaches it to @writer and returns it.
617  *
618  * Returns: (transfer full): A new #GUPnPDIDLLiteContainer object. Unref after usage.
619  **/
620 GUPnPDIDLLiteContainer *
621 gupnp_didl_lite_writer_add_container (GUPnPDIDLLiteWriter *writer)
622 {
623         xmlNode *container_node;
624         GUPnPDIDLLiteObject *object;
625
626         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
627
628         container_node = xmlNewChild (writer->priv->xml_node,
629                                       NULL,
630                                       (unsigned char *) "container",
631                                       NULL);
632
633         object = gupnp_didl_lite_object_new_from_xml (container_node,
634                                                       writer->priv->xml_doc,
635                                                       writer->priv->upnp_ns,
636                                                       writer->priv->dc_ns,
637                                                       writer->priv->dlna_ns,
638                                                       writer->priv->pv_ns);
639         return GUPNP_DIDL_LITE_CONTAINER (object);
640 }
641
642 /**
643  * gupnp_didl_lite_writer_add_descriptor:
644  * @writer: A #GUPnPDIDLLiteWriter
645  *
646  * Creates a new descriptor, attaches it to @object and returns it.
647  *
648  * Returns: (transfer full): A new #GUPnPDIDLLiteDescriptor object. Unref after usage.
649  **/
650 GUPnPDIDLLiteDescriptor *
651 gupnp_didl_lite_writer_add_descriptor (GUPnPDIDLLiteWriter *writer)
652 {
653         xmlNode *desc_node;
654
655         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
656
657         desc_node = xmlNewChild (writer->priv->xml_node,
658                                  NULL,
659                                  (unsigned char *) "desc",
660                                  NULL);
661
662         return gupnp_didl_lite_descriptor_new_from_xml (desc_node,
663                                                         writer->priv->xml_doc);
664 }
665
666 /**
667  * gupnp_didl_lite_writer_get_string:
668  * @writer: A #GUPnPDIDLLiteWriter
669  *
670  * Creates a string representation of the DIDL-Lite XML document.
671  *
672  * Return value: The DIDL-Lite XML string, or %NULL. #g_free after usage.
673  **/
674 char *
675 gupnp_didl_lite_writer_get_string (GUPnPDIDLLiteWriter *writer)
676 {
677         xmlBuffer *buffer;
678         char      *ret;
679
680         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
681
682         buffer = xmlBufferCreate ();
683         xmlNodeDump (buffer,
684                      writer->priv->xml_doc->doc,
685                      writer->priv->xml_node,
686                      0,
687                      0);
688         ret = g_strndup ((char *) xmlBufferContent (buffer),
689                          xmlBufferLength (buffer));
690         xmlBufferFree (buffer);
691
692         return ret;
693 }
694
695 /**
696  * gupnp_didl_lite_writer_get_xml_node:
697  * @writer: The #GUPnPDIDLLiteWriter
698  *
699  * Get the pointer to root node in XML document.
700  *
701  * Returns: (transfer none): The pointer to root node in XML document.
702  **/
703 xmlNode *
704 gupnp_didl_lite_writer_get_xml_node (GUPnPDIDLLiteWriter *writer)
705 {
706         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
707
708         return writer->priv->xml_node;
709 }
710
711 /**
712  * gupnp_didl_lite_writer_get_language:
713  * @writer: #GUPnPDIDLLiteWriter
714  *
715  * Get the language the DIDL-Lite fragment is in.
716  *
717  * Returns: (transfer none): The language of the @writer, or %NULL.
718  **/
719 const char *
720 gupnp_didl_lite_writer_get_language (GUPnPDIDLLiteWriter *writer)
721 {
722         g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
723
724         return writer->priv->language;
725 }
726
727 /**
728  * gupnp_didl_lite_writer_filter:
729  * @writer: A #GUPnPDIDLLiteWriter
730  * @filter: A filter string
731  *
732  * Clears the DIDL-Lite XML document of the properties not specified in the
733  * @filter. The passed filter string would typically come from the 'Filter'
734  * argument of Browse or Search actions from a ContentDirectory control point.
735  * Please refer to Section 2.3.15 of UPnP AV ContentDirectory version 3
736  * specification for details on this string.
737  *
738  * Return value: None.
739  **/
740 void
741 gupnp_didl_lite_writer_filter (GUPnPDIDLLiteWriter *writer,
742                                const char          *filter)
743 {
744         apply_filter (writer, filter, FALSE);
745 }
746
747 /**
748  * gupnp_didl_lite_writer_filter_tags:
749  * @writer: A #GUPnPDIDLLiteWriter
750  * @filter: A filter string
751  *
752  * Clears the DIDL-Lite XML document of the properties not specified in the
753  * @filter. The passed filter string would typically come from the 'Filter'
754  * argument of Browse or Search actions from a ContentDirectory control point.
755  * Please refer to Section 2.3.15 of UPnP AV ContentDirectory version 3
756  * specification for details on this string.
757  *
758  * In contrast to gupnp_didl_lite_writer_filter(), this function only removes
759  * unwanted tags but leaves all attributes in-place.
760  *
761  * Return value: None.
762  **/
763 void
764 gupnp_didl_lite_writer_filter_tags (GUPnPDIDLLiteWriter *writer,
765                                     const char          *filter)
766 {
767         apply_filter (writer, filter, TRUE);
768 }