"Initial commit to Gerrit"
[profile/ivi/libgsf.git] / gsf / gsf-open-pkg-utils.c
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * gsf-open-pkg-utils.c: Utilities for handling Open Package zip files
4  *                      from MS Office 2007 or XPS.
5  *
6  * Copyright (C) 2006-2007 Jody Goldberg (jody@gnome.org)
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of version 2.1 of the GNU Lesser General Public
10  * License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22
23 #include <gsf-config.h>
24 #include <gsf/gsf-open-pkg-utils.h>
25 #include <gsf/gsf-input.h>
26 #include <gsf/gsf-infile.h>
27 #include <gsf/gsf-outfile-impl.h>
28 #include <gsf/gsf-impl-utils.h>
29
30 #include <glib/gi18n-lib.h>
31 #include <string.h>
32
33 #undef G_LOG_DOMAIN
34 #define G_LOG_DOMAIN "libgsf:open_pkg"
35
36 enum {
37         OPEN_PKG_NS_REL
38 };
39
40 static GsfXMLInNS const open_pkg_ns[] = {
41         GSF_XML_IN_NS (OPEN_PKG_NS_REL,   "http://schemas.openxmlformats.org/package/2006/relationships"),
42         GSF_XML_IN_NS_END
43 };
44
45 struct _GsfOpenPkgRel {
46         char *id, *type, *target;
47         gboolean is_extern;
48 };
49
50 struct _GsfOpenPkgRels {
51         GHashTable      *by_id;
52         GHashTable      *by_type;
53 };
54
55 static void
56 gsf_open_pkg_rels_free (GsfOpenPkgRels *rels)
57 {
58         g_hash_table_destroy (rels->by_id);
59         g_hash_table_destroy (rels->by_type);
60         g_free (rels);
61 }
62
63 static void
64 gsf_open_pkg_rel_free (GsfOpenPkgRel *rel)
65 {
66         g_free (rel->id);       rel->id = NULL;
67         g_free (rel->type);     rel->type = NULL;
68         g_free (rel->target);   rel->target = NULL;
69         g_free (rel);
70 }
71
72 static void
73 open_pkg_rel_begin (GsfXMLIn *xin, xmlChar const **attrs)
74 {
75         GsfOpenPkgRels *rels = xin->user_state;
76         GsfOpenPkgRel *rel;
77         xmlChar const *id = NULL;
78         xmlChar const *type = NULL;
79         xmlChar const *target = NULL;
80         gboolean is_extern = FALSE;
81
82         for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2)
83                 if (0 == strcmp (attrs[0], "Id"))
84                         id = attrs[1];
85                 else if (0 == strcmp (attrs[0], "Type"))
86                         type = attrs[1];
87                 else if (0 == strcmp (attrs[0], "Target"))
88                         target = attrs[1];
89                 else if (0 == strcmp (attrs[0], "TargetMode"))
90                         is_extern = 0 == strcmp (attrs[1], "External");
91
92         g_return_if_fail (id != NULL);
93         g_return_if_fail (type != NULL);
94         g_return_if_fail (target != NULL);
95
96         rel = g_new0 (GsfOpenPkgRel, 1);
97         rel->id         = g_strdup (id);
98         rel->type       = g_strdup (type);
99         rel->target     = g_strdup (target);
100         rel->is_extern  = is_extern;
101
102         g_hash_table_replace (rels->by_id, rel->id, rel);
103         g_hash_table_replace (rels->by_type, rel->type, rel);
104 }
105
106 static GsfXMLInNode const open_pkg_rel_dtd[] = {
107 GSF_XML_IN_NODE_FULL (START, START, -1, NULL, GSF_XML_NO_CONTENT, FALSE, TRUE, NULL, NULL, 0),
108 GSF_XML_IN_NODE_FULL (START, RELS, OPEN_PKG_NS_REL, "Relationships", GSF_XML_NO_CONTENT, FALSE, TRUE, NULL, NULL, 0),
109   GSF_XML_IN_NODE (RELS, REL, OPEN_PKG_NS_REL, "Relationship", GSF_XML_NO_CONTENT, open_pkg_rel_begin, NULL),
110
111 GSF_XML_IN_NODE_END
112 };
113
114 /**
115  * gsf_open_pkg_rel_is_extern :
116  * @rel : #GsfOpenPkgRel
117  *
118  * Returns: %TRUE if @rel has mode 'External'
119  **/
120 gboolean
121 gsf_open_pkg_rel_is_extern (GsfOpenPkgRel const *rel)
122 {
123         g_return_val_if_fail (rel != NULL, FALSE);
124         return rel->is_extern;
125 }
126
127 /**
128  * gsf_open_pkg_rel_get_target :
129  * @rel : #GsfOpenPkgRel
130  *
131  * Returns: const pointer to @rel's target.
132  **/
133 char const *
134 gsf_open_pkg_rel_get_target (GsfOpenPkgRel const *rel)
135 {
136         g_return_val_if_fail (rel != NULL, NULL);
137         return rel->target;
138 }
139
140 /**
141  * gsf_open_pkg_rel_get_type :
142  * @rel : #GsfOpenPkgRel
143  *
144  * Returns: const pointer to @rel's type.
145  **/
146 char const *
147 gsf_open_pkg_rel_get_type (GsfOpenPkgRel const *rel)
148 {
149         g_return_val_if_fail (rel != NULL, NULL);
150         return rel->type;
151 }
152
153 static GsfOpenPkgRels *
154 gsf_open_pkg_get_rels (GsfInput *opkg)
155 {
156         GsfOpenPkgRels *rels = NULL;
157
158         g_return_val_if_fail (opkg != NULL, NULL);
159
160         if (NULL == (rels = g_object_get_data (G_OBJECT (opkg), "OpenPkgRels"))) {
161                 char const *part_name = gsf_input_name (opkg);
162                 GsfXMLInDoc *rel_doc;
163                 GsfInput *rel_stream;
164
165                 if (NULL != part_name) {
166                         GsfInfile *container = gsf_input_container (opkg);
167                         char *rel_name;
168
169                         g_return_val_if_fail (container != NULL, NULL);
170
171                         rel_name = g_strconcat (part_name, ".rels", NULL);
172                         rel_stream = gsf_infile_child_by_vname (container, "_rels", rel_name, NULL);
173                         g_free (rel_name);
174                 } else /* the root */
175                         rel_stream = gsf_infile_child_by_vname (GSF_INFILE (opkg), "_rels", ".rels", NULL);
176
177                 if (NULL != rel_stream) {
178                         rels = g_new (GsfOpenPkgRels, 1);
179                         rels->by_id = g_hash_table_new_full (g_str_hash, g_str_equal,
180                                 NULL, (GDestroyNotify)gsf_open_pkg_rel_free);
181                         rels->by_type = g_hash_table_new (g_str_hash, g_str_equal);
182
183                         rel_doc = gsf_xml_in_doc_new (open_pkg_rel_dtd, open_pkg_ns);
184                         (void) gsf_xml_in_doc_parse (rel_doc, rel_stream, rels);
185
186                         gsf_xml_in_doc_free (rel_doc);
187                         g_object_unref (G_OBJECT (rel_stream));
188                 }
189
190                 g_object_set_data_full (G_OBJECT (opkg), "OpenPkgRels", rels,
191                         (GDestroyNotify) gsf_open_pkg_rels_free);
192         }
193
194         return rels;
195 }
196
197 /**
198  * gsf_open_pkg_open_rel :
199  * @opkg : #GsfInput
200  * @rel : #GsfOpenPkgRel
201  * @err : #GError.
202  *
203  * Returns: a new #GsfInput which the called needs to unref, or %NULL and sets @err
204  **/
205 GsfInput *
206 gsf_open_pkg_open_rel (GsfInput *opkg, GsfOpenPkgRel const *rel,
207                        G_GNUC_UNUSED GError **err /* just in case we need it one day */ )
208 {
209         GsfInput *res = NULL;
210         GsfInfile *parent, *prev_parent;
211         gchar **elems;
212         unsigned i;
213
214         g_return_val_if_fail (rel != NULL, NULL);
215         g_return_val_if_fail (opkg != NULL, NULL);
216
217         /* References from the root use children of opkg
218          * References from a child are relative to siblings of opkg */
219         parent = gsf_input_name (opkg)
220                 ? gsf_input_container (opkg) : GSF_INFILE (opkg);
221         g_object_ref (parent);
222
223         elems = g_strsplit (rel->target, "/", 0);
224         for (i = 0 ; elems[i] && NULL != parent ; i++) {
225                 if (0 == strcmp (elems[i], ".") || '\0' == *elems[i])
226                         continue; /* ignore '.' and empty */
227
228                 prev_parent = parent;
229                 if (0 == strcmp (elems[i], "..")) {
230                         parent = gsf_input_container (GSF_INPUT (parent));
231                         res = NULL;     /* only return newly created children */
232                         if (NULL != parent) {
233                                 /* check for attempt to gain access outside the zip file */
234                                 if (G_OBJECT_TYPE (parent) == G_OBJECT_TYPE (prev_parent))
235                                         g_object_ref (G_OBJECT (parent));
236                                 else
237                                         parent = NULL;
238                         }
239                 } else {
240                         res = gsf_infile_child_by_name (parent, elems[i]);
241                         if (NULL != elems[i+1]) {
242                                 g_return_val_if_fail (GSF_IS_INFILE (res), NULL);
243                                 parent = GSF_INFILE (res);
244                         }
245                 }
246                 g_object_unref (G_OBJECT (prev_parent));
247         }
248         g_strfreev (elems);
249
250         return res;
251 }
252
253 /**
254  * gsf_open_pkg_lookup_rel_by_type :
255  * @opkg : #GsfInput
256  * @type : target
257  *
258  * New in 1.14.6
259  *
260  * Finds _a_ relation of @opkg with @type (no order is guaranteed)
261  *
262  * Returns: A #GsfOpenPkgRel or %NULL
263  **/
264 GsfOpenPkgRel *
265 gsf_open_pkg_lookup_rel_by_type (GsfInput *opkg, char const *type)
266 {
267         GsfOpenPkgRels *rels = gsf_open_pkg_get_rels (opkg);
268         return rels ? g_hash_table_lookup (rels->by_type, type) : NULL;
269 }
270
271 /**
272  * gsf_open_pkg_open_rel_by_id :
273  * @opkg : #GsfInput
274  * @id :
275  *
276  * New in 1.14.6
277  *
278  * Finds @opkg's relation with @id
279  *
280  * Returns: A #GsfOpenPkgRel or %NULL
281  **/
282 GsfOpenPkgRel *
283 gsf_open_pkg_lookup_rel_by_id (GsfInput *opkg, char const *id)
284 {
285         GsfOpenPkgRels *rels = gsf_open_pkg_get_rels (opkg);
286         return rels ? g_hash_table_lookup (rels->by_id, id) : NULL;
287 }
288
289 struct pkg_iter_data {
290         GsfInput *opkg;
291         GsfOpenPkgIter func;
292         gpointer user_data;
293 };
294
295 static void
296 cb_foreach_rel (G_GNUC_UNUSED gpointer id,
297                 GsfOpenPkgRel *rel,
298                 struct pkg_iter_data *dat)
299 {
300         (*dat->func) (dat->opkg, rel, dat->user_data);
301 }
302
303 /**
304  * gsf_open_pkg_foreach_rel:
305  * @opkg : #GsfInput
306  * @func : #GsfOpenPkgIter
307  * @user_data : gpointer
308  *
309  * New in 1.14.9
310  *
311  * Walks each relationship associated with @opkg and calls @func with @user_data.
312  **/
313 void
314 gsf_open_pkg_foreach_rel (GsfInput *opkg,
315                           GsfOpenPkgIter func,
316                           gpointer       user_data)
317 {
318         GsfOpenPkgRels *rels = gsf_open_pkg_get_rels (opkg);
319         struct pkg_iter_data dat;
320
321         if (NULL != rels) {
322                 dat.opkg = opkg;
323                 dat.func = func;
324                 dat.user_data = user_data;
325                 g_hash_table_foreach (rels->by_id, (GHFunc)&cb_foreach_rel, &dat);
326         }
327 }
328
329 /**
330  * gsf_open_pkg_open_rel_by_id :
331  * @opkg : #GsfInput
332  * @id : target id
333  * @err : optionally %NULL
334  *
335  * New in 1.14.7
336  *
337  * Open @opkg's relation @id
338  *
339  * Returns: A new GsfInput or %NULL, and sets @err if possible.
340  **/
341 GsfInput *
342 gsf_open_pkg_open_rel_by_id (GsfInput *opkg, char const *id, GError **err)
343 {
344         GsfOpenPkgRel *rel = NULL;
345         GsfOpenPkgRels *rels = gsf_open_pkg_get_rels (opkg);
346
347         if (NULL != rels && NULL != (rel = g_hash_table_lookup (rels->by_id, id)))
348                 return gsf_open_pkg_open_rel (opkg, rel, err);
349         if (err)
350                 *err = g_error_new (gsf_input_error_id(), gsf_open_pkg_error_id (),
351                         _("Unable to find part id='%s' for '%s'"),
352                         id, gsf_input_name (opkg) );
353         return NULL;
354 }
355
356 /**
357  * gsf_open_pkg_open_rel_by_type :
358  * @opkg : #GsfInput
359  * @type : target type
360  * @err : optionally %NULL
361  *
362  * New in 1.14.9
363  *
364  * Open one of @opkg's relationships with type=@type.
365  *
366  * Returns: A new GsfInput or %NULL, and sets @err if possible.
367  **/
368 GsfInput *
369 gsf_open_pkg_open_rel_by_type (GsfInput *opkg, char const *type, GError **err)
370 {
371         GsfOpenPkgRel *rel = NULL;
372         GsfOpenPkgRels *rels = gsf_open_pkg_get_rels (opkg);
373
374         if (NULL != rels && NULL != (rel = g_hash_table_lookup (rels->by_type, type)))
375                 return gsf_open_pkg_open_rel (opkg, rel, err);
376
377         if (err)
378                 *err = g_error_new (gsf_input_error_id(), gsf_open_pkg_error_id (),
379                         _("Unable to find part with type='%s' for '%s'"),
380                         type, gsf_input_name (opkg) );
381         return NULL;
382 }
383
384 /**
385  * gsf_open_pkg_parse_rel_by_id :
386  * @xin : #GsfXMLIn
387  * @id : target id
388  * @dtd : #GsfXMLInNode
389  * @ns : #GsfXMLInNS
390  *
391  * Convenience function to parse a related part.
392  *
393  * Returns: NULL on success or a GError which callerss need to free on failure.
394  **/
395 GError *
396 gsf_open_pkg_parse_rel_by_id (GsfXMLIn *xin, char const *id,
397                               GsfXMLInNode const *dtd,
398                               GsfXMLInNS const *ns)
399 {
400         GError *res = NULL;
401         GsfInput *cur_stream, *part_stream;
402
403         g_return_val_if_fail (xin != NULL, NULL);
404
405         cur_stream = gsf_xml_in_get_input (xin);
406
407         if (NULL == id)
408                 return g_error_new (gsf_input_error_id(), gsf_open_pkg_error_id (),
409                         _("Missing id for part in '%s'"),
410                         gsf_input_name (cur_stream) );
411
412         part_stream = gsf_open_pkg_open_rel_by_id (cur_stream, id, &res);
413         if (NULL != part_stream) {
414                 GsfXMLInDoc *doc = gsf_xml_in_doc_new (dtd, ns);
415
416                 if (!gsf_xml_in_doc_parse (doc, part_stream, xin->user_state))
417                         res = g_error_new (gsf_input_error_id(), gsf_open_pkg_error_id (),
418                                 _("Part '%s' in '%s' from '%s' is corrupt!"),
419                                 id,
420                                 gsf_input_name (part_stream),
421                                 gsf_input_name (cur_stream) );
422                 gsf_xml_in_doc_free (doc);
423
424                 g_object_unref (G_OBJECT (part_stream));
425         }
426         return res;
427 }
428
429 /* DEPRECATED in 1.14.6 */
430 GsfInput *gsf_open_pkg_get_rel_by_type (GsfInput *opkg, char const *type) { return gsf_open_pkg_open_rel_by_type (opkg, type, NULL); }
431 GsfInput *gsf_open_pkg_get_rel_by_id   (GsfInput *opkg, char const *id)   { return gsf_open_pkg_open_rel_by_id (opkg, id, NULL); }
432
433 /*************************************************************/
434
435 struct _GsfOutfileOpenPkg {
436         GsfOutfile  parent;
437
438         GsfOutput  *sink;
439         gboolean    is_dir;
440         char       *content_type;
441         GSList     *children;
442         GSList     *relations;
443 };
444
445 typedef GsfOutfileClass GsfOutfileOpenPkgClass;
446
447 enum {
448         PROP_0,
449         PROP_SINK,
450         PROP_CONTENT_TYPE,
451         PROP_IS_DIR,
452         PROP_IS_ROOT
453 };
454
455 static GObjectClass *parent_class;
456
457 static void
458 gsf_outfile_open_pkg_get_property (GObject     *object,
459                                    guint        property_id,
460                                    GValue      *value,
461                                    GParamSpec  *pspec)
462 {
463         GsfOutfileOpenPkg *open_pkg = (GsfOutfileOpenPkg *)object;
464
465         switch (property_id) {
466         case PROP_SINK:
467                 g_value_set_object (value, open_pkg->sink);
468                 break;
469         case PROP_CONTENT_TYPE:
470                 g_value_set_string (value, open_pkg->content_type);
471                 break;
472         case PROP_IS_DIR:
473                 g_value_set_boolean (value, open_pkg->is_dir);
474                 break;
475         default:
476                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
477                 break;
478         }
479 }
480
481 static void
482 gsf_outfile_open_pkg_set_property (GObject      *object,
483                                    guint         property_id,
484                                    GValue const *value,
485                                    GParamSpec   *pspec)
486 {
487         GsfOutfileOpenPkg *open_pkg = (GsfOutfileOpenPkg *)object;
488
489         switch (property_id) {
490         case PROP_SINK:
491                 gsf_outfile_open_pkg_set_sink (open_pkg, g_value_get_object (value));
492                 break;
493         case PROP_CONTENT_TYPE:
494                 gsf_outfile_open_pkg_set_content_type (open_pkg, g_value_get_string (value));
495                 break;
496         case PROP_IS_DIR:
497                 open_pkg->is_dir = g_value_get_boolean (value);
498                 break;
499         default:
500                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
501                 break;
502         }
503 }
504
505 static void
506 gsf_outfile_open_pkg_init (GObject *obj)
507 {
508         GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (obj);
509
510         open_pkg->sink = NULL;
511         open_pkg->content_type = NULL;
512         open_pkg->is_dir = FALSE;
513         open_pkg->children = NULL;
514         open_pkg->relations = NULL;
515 }
516
517 static void
518 gsf_outfile_open_pkg_finalize (GObject *obj)
519 {
520         GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (obj);
521         GSList *ptr;
522
523         if (open_pkg->sink != NULL) {
524                 g_object_unref (open_pkg->sink);
525                 open_pkg->sink = NULL;
526         }
527         g_free (open_pkg->content_type);
528         open_pkg->content_type = NULL;
529
530         for (ptr = open_pkg->children ; ptr != NULL ; ptr = ptr->next)
531                 g_object_unref (ptr->data);
532         g_slist_free (open_pkg->children);
533         parent_class->finalize (obj);
534 }
535
536 static gboolean
537 gsf_outfile_open_pkg_write (GsfOutput *output, size_t num_bytes, guint8 const *data)
538 {
539         GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (output);
540         return gsf_output_write (open_pkg->sink, num_bytes, data);
541 }
542 static gboolean
543 gsf_outfile_open_pkg_seek (GsfOutput *output, gsf_off_t offset, GSeekType whence)
544 {
545         GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (output);
546         return gsf_output_seek (open_pkg->sink, offset, whence);
547 }
548 static GsfOutput *
549 gsf_outfile_open_pkg_new_child (GsfOutfile *parent,
550                                 char const *name, gboolean is_dir,
551                                 char const *first_property_name, va_list args)
552 {
553         GsfOutfileOpenPkg *child, *open_pkg = GSF_OUTFILE_OPEN_PKG (parent);
554         GsfOutput *sink;
555
556         if (!open_pkg->is_dir)
557                 return NULL;
558
559         child = (GsfOutfileOpenPkg *)g_object_new_valist (
560                 GSF_OUTFILE_OPEN_PKG_TYPE, first_property_name, args);
561         gsf_output_set_name (GSF_OUTPUT (child), name);
562         gsf_output_set_container (GSF_OUTPUT (child), parent);
563         child->is_dir = is_dir;
564
565         sink = gsf_outfile_new_child (GSF_OUTFILE (open_pkg->sink), name, is_dir);
566         gsf_outfile_open_pkg_set_sink (child, sink);
567         g_object_unref (sink);
568
569         open_pkg->children = g_slist_prepend (open_pkg->children, child);
570         g_object_ref (child);
571
572         return GSF_OUTPUT (child);
573 }
574
575 static void
576 gsf_open_pkg_write_content_default (GsfXMLOut *xml, char const *ext, char const *type)
577 {
578         gsf_xml_out_start_element (xml, "Default");
579         gsf_xml_out_add_cstr (xml, "Extension", ext);
580         gsf_xml_out_add_cstr (xml, "ContentType", type);
581         gsf_xml_out_end_element (xml); /* </Default> */
582 }
583 static void
584 gsf_open_pkg_write_content_override (GsfOutfileOpenPkg const *open_pkg,
585                                      char const *base,
586                                      GsfXMLOut *xml)
587 {
588         GsfOutfileOpenPkg const *child;
589         char   *path;
590         GSList *ptr;
591
592         for (ptr = open_pkg->children ; ptr != NULL ; ptr = ptr->next) {
593                 child = ptr->data;
594                 if (child->is_dir) {
595                         path = g_strconcat (base, gsf_output_name (GSF_OUTPUT (child)), "/", NULL);
596                         gsf_open_pkg_write_content_override (child, path, xml);
597                 } else {
598                         path = g_strconcat (base, gsf_output_name (GSF_OUTPUT (child)), NULL);
599                         /* rels files do need content types, the defaults handle them */
600                         if (NULL != child->content_type) {
601                                 gsf_xml_out_start_element (xml, "Override");
602                                 gsf_xml_out_add_cstr (xml, "PartName", path);
603                                 gsf_xml_out_add_cstr (xml, "ContentType", child->content_type);
604                                 gsf_xml_out_end_element (xml); /* </Override> */
605                         }
606                 }
607                 g_free (path);
608         }
609 }
610
611 static gboolean
612 gsf_outfile_open_pkg_close (GsfOutput *output)
613 {
614         GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (output);
615         GsfOutput *dir;
616         gboolean res = FALSE;
617         char *rels_name;
618
619         if (NULL == open_pkg->sink || gsf_output_is_closed (open_pkg->sink))
620                 return TRUE;
621
622         /* Generate [Content_types].xml when we close the root dir */
623         if (NULL == gsf_output_name (output)) {
624                 GsfOutput *out = gsf_outfile_new_child (GSF_OUTFILE (open_pkg->sink),
625                         "[Content_Types].xml", FALSE);
626                 GsfXMLOut *xml = gsf_xml_out_new (out);
627
628                 gsf_xml_out_start_element (xml, "Types");
629                 gsf_xml_out_add_cstr_unchecked (xml, "xmlns",
630                         "http://schemas.openxmlformats.org/package/2006/content-types");
631                 gsf_open_pkg_write_content_default (xml, "rels",
632                         "application/vnd.openxmlformats-package.relationships+xml");
633                 gsf_open_pkg_write_content_default (xml, "xlbin",
634                         "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings");
635                 gsf_open_pkg_write_content_default (xml, "xml",
636                         "application/xml");
637                 gsf_open_pkg_write_content_override (open_pkg, "/", xml);
638                 gsf_xml_out_end_element (xml); /* </Types> */
639                 g_object_unref (xml);
640
641                 gsf_output_close (out);
642                 g_object_unref (out);
643
644                 dir = open_pkg->sink;
645                 rels_name = g_strdup (".rels");
646         } else {
647                 res = gsf_output_close (open_pkg->sink);
648
649                 dir = (GsfOutput *)gsf_output_container (open_pkg->sink);
650                 rels_name = g_strconcat (gsf_output_name (output), ".rels", NULL);
651         }
652
653         if (NULL != open_pkg->relations) {
654                 GsfOutput *rels;
655                 GsfXMLOut *xml;
656                 GsfOpenPkgRel *rel;
657                 GSList *ptr;
658
659                 dir = gsf_outfile_new_child (GSF_OUTFILE (dir), "_rels", TRUE);
660                 rels = gsf_outfile_new_child (GSF_OUTFILE (dir), rels_name, FALSE);
661                 xml = gsf_xml_out_new (rels);
662
663                 gsf_xml_out_start_element (xml, "Relationships");
664                 gsf_xml_out_add_cstr_unchecked (xml, "xmlns",
665                         "http://schemas.openxmlformats.org/package/2006/relationships");
666
667                 for (ptr = open_pkg->relations ; ptr != NULL ; ptr = ptr->next) {
668                         rel = ptr->data;
669                         gsf_xml_out_start_element (xml, "Relationship");
670                         gsf_xml_out_add_cstr (xml, "Id", rel->id);
671                         gsf_xml_out_add_cstr (xml, "Type", rel->type);
672                         gsf_xml_out_add_cstr (xml, "Target", rel->target);
673                         if (rel->is_extern)
674                                 gsf_xml_out_add_cstr_unchecked (xml, "TargetMode", "External");
675                         gsf_xml_out_end_element (xml); /* </Relationship> */
676
677                         g_free (rel->id);
678                         g_free (rel->type);
679                         g_free (rel->target);
680                         g_free (rel);
681                 }
682                 g_slist_free (open_pkg->relations);
683
684                 gsf_xml_out_end_element (xml); /* </Relationships> */
685                 g_object_unref (xml);
686                 gsf_output_close (rels);
687                 g_object_unref (rels);
688                 g_object_unref (dir);
689         }
690         g_free (rels_name);
691
692         /* close the container */
693         if (NULL == gsf_output_name (output))
694                 return gsf_output_close (open_pkg->sink);
695         return res;
696 }
697
698 static void
699 gsf_outfile_open_pkg_class_init (GObjectClass *gobject_class)
700 {
701         GsfOutputClass  *output_class  = GSF_OUTPUT_CLASS (gobject_class);
702         GsfOutfileClass *outfile_class = GSF_OUTFILE_CLASS (gobject_class);
703
704         gobject_class->finalize         = gsf_outfile_open_pkg_finalize;
705         gobject_class->get_property     = gsf_outfile_open_pkg_get_property;
706         gobject_class->set_property     = gsf_outfile_open_pkg_set_property;
707
708         output_class->Write             = gsf_outfile_open_pkg_write;
709         output_class->Seek              = gsf_outfile_open_pkg_seek;
710         output_class->Close             = gsf_outfile_open_pkg_close;
711         outfile_class->new_child        = gsf_outfile_open_pkg_new_child;
712
713         parent_class = g_type_class_peek_parent (gobject_class);
714
715         g_object_class_install_property (gobject_class, PROP_SINK,
716                  g_param_spec_object ("sink", "Sink", "The GsfOutput that stores the Open Package content.",
717                         GSF_OUTFILE_TYPE, GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
718         g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE,
719                  g_param_spec_string ("content-type", "ContentType", "The ContentType stored in the root [Content_Types].xml file.",
720                         "", GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
721         g_object_class_install_property (gobject_class, PROP_IS_DIR,
722                  g_param_spec_boolean ("is-dir", "IsDir", "Can the outfile have children.",
723                         FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
724 }
725
726 GSF_CLASS (GsfOutfileOpenPkg, gsf_outfile_open_pkg,
727            gsf_outfile_open_pkg_class_init, gsf_outfile_open_pkg_init,
728            GSF_OUTFILE_TYPE)
729
730 /**
731  * gsf_outfile_open_pkg_new :
732  * @sink : #GsfOutfile
733  *
734  * Convenience routine to create a GsfOutfileOpenPkg inside @sink.
735  *
736  * Returns: a GsfOutfile that the caller is responsible for.
737  **/
738 GsfOutfile *
739 gsf_outfile_open_pkg_new (GsfOutfile *sink)
740 {
741         return g_object_new (GSF_OUTFILE_OPEN_PKG_TYPE,
742                 "sink", sink,   "is-dir", TRUE,
743                 NULL);
744 }
745
746 /**
747  * gsf_outfile_open_pkg_set_sink :
748  * @open_pkg : #GsfOutfileOpenPkg
749  * @sink : #GsfOutput
750  *
751  * Assigns a GsfOutput (@sink) to store the package into.
752  **/
753 void
754 gsf_outfile_open_pkg_set_sink (GsfOutfileOpenPkg *open_pkg, GsfOutput *sink)
755 {
756         if (sink)
757                 g_object_ref (sink);
758         if (open_pkg->sink)
759                 g_object_unref (open_pkg->sink);
760         open_pkg->sink = sink;
761 }
762
763 /**
764  * gsf_outfile_open_pkg_set_content_type :
765  * @open_pkg : #GsfOutfileOpenPkg
766  * @content_type : 
767  *
768  **/
769 void
770 gsf_outfile_open_pkg_set_content_type (GsfOutfileOpenPkg *open_pkg,
771                                        char const *content_type)
772 {
773         g_return_if_fail (content_type != NULL);
774
775         if (open_pkg->content_type != content_type) {
776                 g_free (open_pkg->content_type);
777                 open_pkg->content_type = g_strdup (content_type);
778         }
779 }
780
781 static char const *
782 gsf_outfile_open_pkg_create_rel (GsfOutfileOpenPkg *parent,
783                                  char *target,
784                                  char const *type,
785                                  gboolean is_extern)
786 {
787         GsfOpenPkgRel *rel = g_new0 (GsfOpenPkgRel, 1);
788         rel->target = target;
789         rel->type   = g_strdup (type);
790         rel->id     = g_strdup_printf ("rId%u", g_slist_length (parent->relations) + 1);
791         rel->is_extern = is_extern;
792         parent->relations = g_slist_prepend (parent->relations, rel);
793         return rel->id;
794 }
795
796 /**
797  * gsf_outfile_open_pkg_relate:
798  * @child : #GsfOutfileOpenPkg
799  * @parent : #GsfOutfileOpenPkg
800  * @type : target type
801  *
802  * Create a relationship between @child and @parent of @type.
803  *
804  * Returns: the relID which the caller does not own but will live as long as
805  *      @parent.
806  **/
807 char const *
808 gsf_outfile_open_pkg_relate (GsfOutfileOpenPkg *child,
809                              GsfOutfileOpenPkg *parent,
810                              char const *type)
811 {
812         GString *path;
813         int up = -1;
814         GsfOutfile *child_dir, *parent_dir;
815         
816         /* Calculate the path from @child to @parent */
817         parent_dir = parent->is_dir ? GSF_OUTFILE (parent)
818                 : gsf_output_container (GSF_OUTPUT (parent));
819         do {
820                 up++;
821                 child_dir  = GSF_OUTFILE (child);
822                 while (NULL != (child_dir = gsf_output_container (GSF_OUTPUT (child_dir))))
823                        if (child_dir == parent_dir)
824                                goto found; /* break out of both loops */
825         } while (NULL != (parent_dir = gsf_output_container (GSF_OUTPUT (parent_dir))));
826
827 found:
828         /* yes prepend is slow, this will never be preformance critical */
829         path = g_string_new (gsf_output_name (GSF_OUTPUT (child)));
830         child_dir  = GSF_OUTFILE (child);
831         while (NULL != (child_dir = gsf_output_container (GSF_OUTPUT (child_dir))) &&
832                NULL != gsf_output_name (GSF_OUTPUT (child_dir)) &&
833                child_dir != parent_dir) {
834                 g_string_prepend_c (path, '/');
835                 g_string_prepend (path, gsf_output_name (GSF_OUTPUT (child_dir)));
836         }
837         while (up--)
838                 g_string_prepend (path, "../");
839
840         return gsf_outfile_open_pkg_create_rel (parent,
841                 g_string_free (path, FALSE), type, FALSE);
842 }
843
844 /**
845  * gsf_outfile_open_pkg_add_rel:
846  * @dir : #GsfOutfile
847  * @name : target name
848  * @content_type : non-%NULL content type
849  * @parent : #GsfOutfile
850  * @type : target type
851  *
852  * A convenience wrapper to create a child in @dir of @content_type then create
853  * a @type relation to @parent
854  *
855  * Returns: the new part.
856  **/
857 GsfOutput *
858 gsf_outfile_open_pkg_add_rel (GsfOutfile *dir,
859                               char const *name,
860                               char const *content_type,
861                               GsfOutfile *parent,
862                               char const *type)
863 {
864         GsfOutput *part = gsf_outfile_new_child_full (dir, name, FALSE,
865                 "content-type", content_type,
866                 NULL);
867         (void) gsf_outfile_open_pkg_relate (GSF_OUTFILE_OPEN_PKG (part),
868                 GSF_OUTFILE_OPEN_PKG (parent), type);
869         return part;
870 }
871
872 /**
873  * gsf_outfile_open_pkg_add_extern_rel :
874  * @parent : #GsfOutfileOpenPkg
875  * @target : target type
876  * @content_type : target content
877  *
878  * Add an external relation to @parent.
879  *
880  * Returns: The id of the relation.  The string is managed by the parent and
881  *      should not be changed or freed by the caller.
882  **/
883 char const *
884 gsf_outfile_open_pkg_add_extern_rel (GsfOutfileOpenPkg *parent,
885                                      char const *target,
886                                      char const *content_type)
887 {
888         return gsf_outfile_open_pkg_create_rel (parent,
889                 g_strdup (target), content_type, TRUE);
890 }
891
892 gint
893 gsf_open_pkg_error_id (void)
894 {
895         return 42;      /* something arbitrary */
896 }
897