2 * Copyright (C) 2007, 2008 OpenedHand Ltd.
3 * Copyright (C) 2012 Intel Corporation.
5 * Authors: Jorn Baayen <jorn@openedhand.com>
6 * Jens Georg <jensg@openismus.com>
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., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
25 * SECTION:gupnp-didl-lite-writer
26 * @short_description: DIDL-Lite fragment writer
28 * #GUPnPDIDLLiteWriter is a helper class for writing DIDL-Lite fragments.
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"
41 G_DEFINE_TYPE (GUPnPDIDLLiteWriter,
42 gupnp_didl_lite_writer,
45 struct _GUPnPDIDLLiteWriterPrivate {
56 gboolean dlna_attr_present;
66 compare_prop (const char *a, xmlAttr *attr)
74 attr_name = g_strjoin (":", attr->ns->prefix, attr->name, NULL);
76 attr_name = g_strdup ((const char *) attr->name);
78 if (attr->parent->ns != NULL)
79 parent_name = g_strjoin (":",
80 attr->parent->ns->prefix,
84 parent_name = g_strdup ((const char *) attr->parent->name);
89 /* Top-level property */
90 ret = strcmp (a + 1, attr_name);
92 ret = strncmp (a, parent_name, p - a) ||
93 strcmp (p + 1, attr_name);
95 ret = strcmp (a, attr_name);
104 is_attribute_forbidden (xmlAttr *attr,
107 return g_list_find_custom (allowed,
109 (GCompareFunc) compare_prop) == NULL;
113 compare_node_name (const char *a, const char *b)
119 /* Filter is for top-level property */
124 /* Compare only the string before '@' */
129 result = strncmp (a, b, len);
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;
141 is_node_forbidden (xmlNode *node,
149 name = g_strjoin (":", ns, node->name, NULL);
151 name = g_strdup ((const char *) node->name);
153 ret = g_list_find_custom (allowed,
155 (GCompareFunc) compare_node_name) == NULL;
163 is_container_standard_prop (const char *name,
164 const char *namespace,
165 const char *upnp_class)
167 return g_strcmp0 (upnp_class, "object.container.storageFolder") == 0 &&
168 g_strcmp0 (namespace, "upnp") == 0 &&
169 strcmp (name, "storageUsed") == 0;
173 is_standard_prop (const char *name,
174 const char *namespace,
175 const char *parent_name)
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);
189 filter_attributes (xmlNode *node,
193 GList *forbidden = NULL;
196 /* Find forbidden properties */
197 for (attr = node->properties; attr != NULL; attr = attr->next)
198 if (!is_standard_prop ((const char *) attr->name,
200 (const char *) attr->parent->name) &&
201 is_attribute_forbidden (attr, allowed))
202 forbidden = g_list_append (forbidden, attr);
204 /* Now unset forbidden properties */
205 for (l = forbidden; l != NULL; l = l->next)
206 xmlRemoveProp ((xmlAttr *) l->data);
208 g_list_free (forbidden);
212 check_dlna_attr (xmlNode *node,
213 GUPnPDIDLLiteWriter *writer)
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;
225 filter_node (xmlNode *node,
227 GUPnPDIDLLiteWriter *writer,
231 GList *forbidden = NULL;
233 gboolean is_container = FALSE;
234 const char *container_class = NULL;
237 filter_attributes (node, allowed);
239 // Check if dlna namespace attribute is present
240 if (!writer->priv->dlna_attr_present)
241 check_dlna_attr (node, writer);
243 if (strcmp ((const char *) node->name, "container") == 0) {
245 container_class = xml_util_get_child_element_content (node,
250 for (child = node->children; child != NULL; child = child->next) {
251 const char *ns = NULL;
253 if (xmlNodeIsText (child))
256 if (child->ns != NULL)
257 ns = (const char *) child->ns->prefix;
259 if (!(is_container && is_container_standard_prop
260 ((const char *) child->name,
263 !is_standard_prop ((const char *) child->name,
265 (const char *) node->name) &&
266 is_node_forbidden (child, allowed, ns))
267 forbidden = g_list_append (forbidden, child);
270 /* Now remove the forbidden nodes */
271 for (l = forbidden; l != NULL; l = l->next) {
274 n = (xmlNode *) l->data;
280 g_list_free (forbidden);
283 for (child = node->children; child != NULL; child = child->next)
284 if (!xmlNodeIsText (child))
285 filter_node (child, allowed, writer, tags_only);
289 apply_filter (GUPnPDIDLLiteWriter *writer,
294 GList *allowed = NULL;
298 g_return_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer));
299 g_return_if_fail (filter != NULL);
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 */
309 tokens = g_strsplit (filter, ",", -1);
310 g_return_if_fail (tokens != NULL);
312 for (i = 0; tokens[i] != NULL; i++)
313 allowed = g_list_append (allowed, tokens[i]);
315 for (node = writer->priv->xml_node->children;
318 filter_node (node, allowed, writer, tags_only);
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);
326 g_list_free (allowed);
332 gupnp_didl_lite_writer_init (GUPnPDIDLLiteWriter *writer)
334 writer->priv = G_TYPE_INSTANCE_GET_PRIVATE (writer,
335 GUPNP_TYPE_DIDL_LITE_WRITER,
336 GUPnPDIDLLiteWriterPrivate);
340 gupnp_didl_lite_writer_set_property (GObject *object,
346 GUPnPDIDLLiteWriter *writer;
348 writer = GUPNP_DIDL_LITE_WRITER (object);
350 switch (property_id) {
352 writer->priv->language = g_value_dup_string (value);
355 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
361 gupnp_didl_lite_writer_get_property (GObject *object,
366 GUPnPDIDLLiteWriter *writer;
368 writer = GUPNP_DIDL_LITE_WRITER (object);
370 switch (property_id) {
373 (value, gupnp_didl_lite_writer_get_xml_node (writer));
377 (value, gupnp_didl_lite_writer_get_language (writer));
380 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
386 gupnp_didl_lite_writer_constructed (GObject *object)
388 GObjectClass *object_class;
389 GUPnPDIDLLiteWriterPrivate *priv;
392 priv = GUPNP_DIDL_LITE_WRITER (object)->priv;
394 doc = xmlNewDoc ((unsigned char *) "1.0");
395 priv->xml_doc = gupnp_xml_doc_new (doc);
397 priv->xml_node = xmlNewDocNode (priv->xml_doc->doc,
399 (unsigned char *) "DIDL-Lite",
401 xmlDocSetRootElement (priv->xml_doc->doc, priv->xml_node);
402 priv->dc_ns = xmlNewNs (priv->xml_node,
404 "http://purl.org/dc/elements/1.1/",
406 GUPNP_DIDL_LITE_WRITER_NAMESPACE_DC);
407 priv->upnp_ns = xmlNewNs (priv->xml_node,
409 "urn:schemas-upnp-org:metadata-1-0/upnp/",
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,
417 "urn:schemas-dlna-org:metadata-1-0/",
419 GUPNP_DIDL_LITE_WRITER_NAMESPACE_DLNA);
420 priv->pv_ns = xmlNewNs (priv->xml_node,
422 "http://www.pv.com/pvns/",
424 GUPNP_DIDL_LITE_WRITER_NAMESPACE_PV);
425 xmlNewNs (priv->xml_node,
427 "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/",
431 xmlSetProp (priv->xml_node,
432 (unsigned char *) "lang",
433 (unsigned char *) priv->language);
435 priv->dlna_attr_present = FALSE;
437 object_class = G_OBJECT_CLASS (gupnp_didl_lite_writer_parent_class);
438 if (object_class->constructed != NULL)
439 object_class->constructed (object);
443 gupnp_didl_lite_writer_dispose (GObject *object)
445 GObjectClass *object_class;
446 GUPnPDIDLLiteWriterPrivate *priv;
448 priv = GUPNP_DIDL_LITE_WRITER (object)->priv;
451 g_object_unref (priv->xml_doc);
452 priv->xml_doc = NULL;
455 object_class = G_OBJECT_CLASS (gupnp_didl_lite_writer_parent_class);
456 object_class->dispose (object);
460 gupnp_didl_lite_writer_finalize (GObject *object)
462 GObjectClass *object_class;
463 GUPnPDIDLLiteWriterPrivate *priv;
465 priv = GUPNP_DIDL_LITE_WRITER (object)->priv;
468 g_free (priv->language);
470 object_class = G_OBJECT_CLASS (gupnp_didl_lite_writer_parent_class);
471 object_class->finalize (object);
475 gupnp_didl_lite_writer_class_init (GUPnPDIDLLiteWriterClass *klass)
477 GObjectClass *object_class;
479 object_class = G_OBJECT_CLASS (klass);
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;
487 g_type_class_add_private (klass, sizeof (GUPnPDIDLLiteWriterPrivate));
490 * GUPnPDIDLLiteWriter:xml-node:
492 * The pointer to root node in XML document.
494 g_object_class_install_property
497 g_param_spec_pointer ("xml-node",
499 "The pointer to root node in XML"
502 G_PARAM_STATIC_NAME |
503 G_PARAM_STATIC_NICK |
504 G_PARAM_STATIC_BLURB));
507 * GUPnPDIDLLiteWriter:language:
509 * The language the DIDL-Lite fragment is in.
512 g_object_class_install_property
515 g_param_spec_string ("language",
517 "The language the DIDL-Lite fragment"
520 G_PARAM_CONSTRUCT_ONLY |
522 G_PARAM_STATIC_NAME |
523 G_PARAM_STATIC_NICK |
524 G_PARAM_STATIC_BLURB));
528 * gupnp_didl_lite_writer_new:
529 * @language: (allow-none):The language the DIDL-Lite fragment is in, or %NULL
531 * Note: @language should always be set to %NULL, DLNA does not support the
532 * language parameter.
534 * Return value: A new #GUPnPDIDLLiteWriter object.
536 GUPnPDIDLLiteWriter *
537 gupnp_didl_lite_writer_new (const char *language)
539 return g_object_new (GUPNP_TYPE_DIDL_LITE_WRITER,
540 "language", language,
545 * gupnp_didl_lite_writer_add_item:
546 * @writer: A #GUPnPDIDLLiteWriter
548 * Creates a new item, attaches it to @writer and returns it.
550 * Returns: (transfer full): A new #GUPnPDIDLLiteItem object. Unref after usage.
553 gupnp_didl_lite_writer_add_item (GUPnPDIDLLiteWriter *writer)
556 GUPnPDIDLLiteObject *object;
558 g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
560 item_node = xmlNewChild (writer->priv->xml_node,
562 (unsigned char *) "item",
565 object = gupnp_didl_lite_object_new_from_xml (item_node,
566 writer->priv->xml_doc,
567 writer->priv->upnp_ns,
569 writer->priv->dlna_ns,
570 writer->priv->pv_ns);
571 return GUPNP_DIDL_LITE_ITEM (object);
575 * gupnp_didl_lite_writer_add_container_child_item:
576 * @writer: #GUPnPDIDLLiteWriter
577 * @container: #GUPnPDIDLLiteContainer
579 * Add a child item to a container. This is only useful in DIDL_S playlist
582 * Returns: (transfer full): A new #GUPnPDIDLLiteItem object. Unref after
586 gupnp_didl_lite_writer_add_container_child_item
587 (GUPnPDIDLLiteWriter *writer,
588 GUPnPDIDLLiteContainer *container)
590 xmlNode *item_node, *container_node;
591 GUPnPDIDLLiteObject *object;
593 g_return_val_if_fail (GUPNP_IS_DIDL_LITE_CONTAINER (container), NULL);
595 object = GUPNP_DIDL_LITE_OBJECT (container);
596 container_node = gupnp_didl_lite_object_get_xml_node (object);
598 item_node = xmlNewChild (container_node,
603 object = gupnp_didl_lite_object_new_from_xml (item_node,
604 writer->priv->xml_doc,
605 writer->priv->upnp_ns,
607 writer->priv->dlna_ns,
608 writer->priv->pv_ns);
609 return GUPNP_DIDL_LITE_ITEM (object);
613 * gupnp_didl_lite_writer_add_container:
614 * @writer: A #GUPnPDIDLLiteWriter
616 * Creates a new container, attaches it to @writer and returns it.
618 * Returns: (transfer full): A new #GUPnPDIDLLiteContainer object. Unref after usage.
620 GUPnPDIDLLiteContainer *
621 gupnp_didl_lite_writer_add_container (GUPnPDIDLLiteWriter *writer)
623 xmlNode *container_node;
624 GUPnPDIDLLiteObject *object;
626 g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
628 container_node = xmlNewChild (writer->priv->xml_node,
630 (unsigned char *) "container",
633 object = gupnp_didl_lite_object_new_from_xml (container_node,
634 writer->priv->xml_doc,
635 writer->priv->upnp_ns,
637 writer->priv->dlna_ns,
638 writer->priv->pv_ns);
639 return GUPNP_DIDL_LITE_CONTAINER (object);
643 * gupnp_didl_lite_writer_add_descriptor:
644 * @writer: A #GUPnPDIDLLiteWriter
646 * Creates a new descriptor, attaches it to @object and returns it.
648 * Returns: (transfer full): A new #GUPnPDIDLLiteDescriptor object. Unref after usage.
650 GUPnPDIDLLiteDescriptor *
651 gupnp_didl_lite_writer_add_descriptor (GUPnPDIDLLiteWriter *writer)
655 g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
657 desc_node = xmlNewChild (writer->priv->xml_node,
659 (unsigned char *) "desc",
662 return gupnp_didl_lite_descriptor_new_from_xml (desc_node,
663 writer->priv->xml_doc);
667 * gupnp_didl_lite_writer_get_string:
668 * @writer: A #GUPnPDIDLLiteWriter
670 * Creates a string representation of the DIDL-Lite XML document.
672 * Return value: The DIDL-Lite XML string, or %NULL. #g_free after usage.
675 gupnp_didl_lite_writer_get_string (GUPnPDIDLLiteWriter *writer)
680 g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
682 buffer = xmlBufferCreate ();
684 writer->priv->xml_doc->doc,
685 writer->priv->xml_node,
688 ret = g_strndup ((char *) xmlBufferContent (buffer),
689 xmlBufferLength (buffer));
690 xmlBufferFree (buffer);
696 * gupnp_didl_lite_writer_get_xml_node:
697 * @writer: The #GUPnPDIDLLiteWriter
699 * Get the pointer to root node in XML document.
701 * Returns: (transfer none): The pointer to root node in XML document.
704 gupnp_didl_lite_writer_get_xml_node (GUPnPDIDLLiteWriter *writer)
706 g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
708 return writer->priv->xml_node;
712 * gupnp_didl_lite_writer_get_language:
713 * @writer: #GUPnPDIDLLiteWriter
715 * Get the language the DIDL-Lite fragment is in.
717 * Returns: (transfer none): The language of the @writer, or %NULL.
720 gupnp_didl_lite_writer_get_language (GUPnPDIDLLiteWriter *writer)
722 g_return_val_if_fail (GUPNP_IS_DIDL_LITE_WRITER (writer), NULL);
724 return writer->priv->language;
728 * gupnp_didl_lite_writer_filter:
729 * @writer: A #GUPnPDIDLLiteWriter
730 * @filter: A filter string
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.
738 * Return value: None.
741 gupnp_didl_lite_writer_filter (GUPnPDIDLLiteWriter *writer,
744 apply_filter (writer, filter, FALSE);
748 * gupnp_didl_lite_writer_filter_tags:
749 * @writer: A #GUPnPDIDLLiteWriter
750 * @filter: A filter string
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.
758 * In contrast to gupnp_didl_lite_writer_filter(), this function only removes
759 * unwanted tags but leaves all attributes in-place.
761 * Return value: None.
764 gupnp_didl_lite_writer_filter_tags (GUPnPDIDLLiteWriter *writer,
767 apply_filter (writer, filter, TRUE);