/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * gsf-open-pkg-utils.c: Utilities for handling Open Package zip files * from MS Office 2007 or XPS. * * Copyright (C) 2006-2007 Jody Goldberg (jody@gnome.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2.1 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ #include #include #include #include #include #include #include #include #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "libgsf:open_pkg" enum { OPEN_PKG_NS_REL }; static GsfXMLInNS const open_pkg_ns[] = { GSF_XML_IN_NS (OPEN_PKG_NS_REL, "http://schemas.openxmlformats.org/package/2006/relationships"), GSF_XML_IN_NS_END }; struct _GsfOpenPkgRel { char *id, *type, *target; gboolean is_extern; }; struct _GsfOpenPkgRels { GHashTable *by_id; GHashTable *by_type; }; static void gsf_open_pkg_rels_free (GsfOpenPkgRels *rels) { g_hash_table_destroy (rels->by_id); g_hash_table_destroy (rels->by_type); g_free (rels); } static void gsf_open_pkg_rel_free (GsfOpenPkgRel *rel) { g_free (rel->id); rel->id = NULL; g_free (rel->type); rel->type = NULL; g_free (rel->target); rel->target = NULL; g_free (rel); } static void open_pkg_rel_begin (GsfXMLIn *xin, xmlChar const **attrs) { GsfOpenPkgRels *rels = xin->user_state; GsfOpenPkgRel *rel; xmlChar const *id = NULL; xmlChar const *type = NULL; xmlChar const *target = NULL; gboolean is_extern = FALSE; for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) if (0 == strcmp (attrs[0], "Id")) id = attrs[1]; else if (0 == strcmp (attrs[0], "Type")) type = attrs[1]; else if (0 == strcmp (attrs[0], "Target")) target = attrs[1]; else if (0 == strcmp (attrs[0], "TargetMode")) is_extern = 0 == strcmp (attrs[1], "External"); g_return_if_fail (id != NULL); g_return_if_fail (type != NULL); g_return_if_fail (target != NULL); rel = g_new0 (GsfOpenPkgRel, 1); rel->id = g_strdup (id); rel->type = g_strdup (type); rel->target = g_strdup (target); rel->is_extern = is_extern; g_hash_table_replace (rels->by_id, rel->id, rel); g_hash_table_replace (rels->by_type, rel->type, rel); } static GsfXMLInNode const open_pkg_rel_dtd[] = { GSF_XML_IN_NODE_FULL (START, START, -1, NULL, GSF_XML_NO_CONTENT, FALSE, TRUE, NULL, NULL, 0), GSF_XML_IN_NODE_FULL (START, RELS, OPEN_PKG_NS_REL, "Relationships", GSF_XML_NO_CONTENT, FALSE, TRUE, NULL, NULL, 0), GSF_XML_IN_NODE (RELS, REL, OPEN_PKG_NS_REL, "Relationship", GSF_XML_NO_CONTENT, open_pkg_rel_begin, NULL), GSF_XML_IN_NODE_END }; /** * gsf_open_pkg_rel_is_extern : * @rel : #GsfOpenPkgRel * * Returns: %TRUE if @rel has mode 'External' **/ gboolean gsf_open_pkg_rel_is_extern (GsfOpenPkgRel const *rel) { g_return_val_if_fail (rel != NULL, FALSE); return rel->is_extern; } /** * gsf_open_pkg_rel_get_target : * @rel : #GsfOpenPkgRel * * Returns: const pointer to @rel's target. **/ char const * gsf_open_pkg_rel_get_target (GsfOpenPkgRel const *rel) { g_return_val_if_fail (rel != NULL, NULL); return rel->target; } /** * gsf_open_pkg_rel_get_type : * @rel : #GsfOpenPkgRel * * Returns: const pointer to @rel's type. **/ char const * gsf_open_pkg_rel_get_type (GsfOpenPkgRel const *rel) { g_return_val_if_fail (rel != NULL, NULL); return rel->type; } /** * gsf_open_pkg_get_rels : * @in : #GsfInput * * Returns: a hashtable of the relationships associated with @in **/ static GsfOpenPkgRels * gsf_open_pkg_get_rels (GsfInput *in) { GsfOpenPkgRels *rels; g_return_val_if_fail (in != NULL, NULL); if (NULL == (rels = g_object_get_data (G_OBJECT (in), "OpenPkgRels"))) { char const *part_name = gsf_input_name (in); GsfXMLInDoc *rel_doc; GsfInput *rel_stream; if (NULL != part_name) { GsfInfile *container = gsf_input_container (in); char *rel_name; g_return_val_if_fail (container != NULL, NULL); rel_name = g_strconcat (part_name, ".rels", NULL); rel_stream = gsf_infile_child_by_vname (container, "_rels", rel_name, NULL); g_free (rel_name); } else /* the root */ rel_stream = gsf_infile_child_by_vname (GSF_INFILE (in), "_rels", ".rels", NULL); g_return_val_if_fail (rel_stream != NULL, NULL); rels = g_new (GsfOpenPkgRels, 1); rels->by_id = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)gsf_open_pkg_rel_free); rels->by_type = g_hash_table_new (g_str_hash, g_str_equal); rel_doc = gsf_xml_in_doc_new (open_pkg_rel_dtd, open_pkg_ns); (void) gsf_xml_in_doc_parse (rel_doc, rel_stream, rels); gsf_xml_in_doc_free (rel_doc); g_object_unref (G_OBJECT (rel_stream)); g_object_set_data_full (G_OBJECT (in), "OpenPkgRels", rels, (GDestroyNotify) gsf_open_pkg_rels_free); } return rels; } static GsfInput * gsf_open_pkg_open_rel (GsfInput *in, GsfOpenPkgRel *rel, G_GNUC_UNUSED GError **err /* just in case we need it one day */ ) { GsfInfile *container, *prev; gchar **elems; unsigned i; g_return_val_if_fail (rel != NULL, NULL); g_return_val_if_fail (in != NULL, NULL); container = gsf_input_name (in) ? gsf_input_container (in) : GSF_INFILE (in); /* parts can not have '/' in their names ? TODO : PROVE THIS * right now the only test is that worksheets can not have it * in their names */ elems = g_strsplit (rel->target, "/", 0); for (i = 0 ; elems[i] ; i++) { prev = container; if (0 == strcmp (elems[i], "..")) { container = gsf_input_container (GSF_INPUT (container)); g_return_val_if_fail (container != NULL, NULL); g_object_ref (container); in = NULL; } else if (0 == strcmp (elems[i], ".")) { in = NULL; /* Be pedantic and ignore '.' */ continue; } else { in = gsf_infile_child_by_name (container, elems[i]); if (NULL != elems[i+1]) { g_return_val_if_fail (GSF_IS_INFILE (in), NULL); container = GSF_INFILE (in); } } if (i > 0) g_object_unref (G_OBJECT (prev)); } g_strfreev (elems); return in; } /** * gsf_open_pkg_lookup_rel_by_type : * @in : #GsfInput * @type : * * New in 1.14.6 * * Finds _a_ relation of @in with @type (no order is guaranteed) * * Returns: A #GsfOpenPkgRel or %NULL **/ GsfOpenPkgRel * gsf_open_pkg_lookup_rel_by_type (GsfInput *in, char const *type) { GsfOpenPkgRels *rels = gsf_open_pkg_get_rels (in); g_return_val_if_fail (rels != NULL, NULL); return g_hash_table_lookup (rels->by_type, type); } /** * gsf_open_pkg_open_rel_by_id : * @in : #GsfInput * @id : * * New in 1.14.6 * * Finds @in's relation with @id * * Returns: A #GsfOpenPkgRel or %NULL **/ GsfOpenPkgRel * gsf_open_pkg_lookup_rel_by_id (GsfInput *in, char const *id) { GsfOpenPkgRels *rels = gsf_open_pkg_get_rels (in); g_return_val_if_fail (rels != NULL, NULL); return g_hash_table_lookup (rels->by_id, id); } /** * gsf_open_pkg_open_rel_by_id : * @in : #GsfInput * @id : * @err : optionally %NULL * * New in 1.15.0 * * Open @in's relation @id * * Returns: A new GsfInput or %NULL, and sets @err if possible. **/ GsfInput * gsf_open_pkg_open_rel_by_id (GsfInput *in, char const *id, GError **err) { GsfOpenPkgRel *rel = NULL; GsfOpenPkgRels *rels = gsf_open_pkg_get_rels (in); g_return_val_if_fail (rels != NULL, NULL); if (NULL != (rel = g_hash_table_lookup (rels->by_id, id))) return gsf_open_pkg_open_rel (in, rel, err); if (err) *err = g_error_new (gsf_input_error_id(), gsf_open_pkg_error_id (), _("Unable to find part id='%s' for '%s'"), id, gsf_input_name (in) ); return NULL; } /** * gsf_open_pkg_open_rel_by_type : * @in : #GsfInput * @type : * @err : optionally %NULL * * New in 1.15.0 * * Open one of @in's relationships with type=@type. * * Returns: A new GsfInput or %NULL, and sets @err if possible. **/ GsfInput * gsf_open_pkg_open_rel_by_type (GsfInput *in, char const *type, GError **err) { GsfOpenPkgRel *rel = NULL; GsfOpenPkgRels *rels = gsf_open_pkg_get_rels (in); g_return_val_if_fail (rels != NULL, NULL); if (NULL != (rel = g_hash_table_lookup (rels->by_type, type))) return gsf_open_pkg_open_rel (in, rel, err); if (err) *err = g_error_new (gsf_input_error_id(), gsf_open_pkg_error_id (), _("Unable to find part with type='%s' for '%s'"), type, gsf_input_name (in) ); return NULL; } /** * gsf_open_pkg_parse_rel_by_id : * @xin : #GsfXMLIn * @id : * @dtd : #GsfXMLInNode * @ns : #GsfXMLInNS * * Convenience function to parse a related part. * * Returns: NULL on success or a GError which callerss need to free on failure. **/ GError * gsf_open_pkg_parse_rel_by_id (GsfXMLIn *xin, char const *id, GsfXMLInNode const *dtd, GsfXMLInNS const *ns) { GError *res = NULL; GsfInput *cur_stream, *part_stream; g_return_val_if_fail (xin != NULL, NULL); cur_stream = gsf_xml_in_get_input (xin); if (NULL == id) return g_error_new (gsf_input_error_id(), gsf_open_pkg_error_id (), _("Missing id for part in '%s'"), gsf_input_name (cur_stream) ); part_stream = gsf_open_pkg_open_rel_by_id (cur_stream, id, &res); if (NULL != part_stream) { GsfXMLInDoc *doc = gsf_xml_in_doc_new (dtd, ns); if (!gsf_xml_in_doc_parse (doc, part_stream, xin->user_state)) res = g_error_new (gsf_input_error_id(), gsf_open_pkg_error_id (), _("Part '%s' in '%s' from '%s' is corrupt!"), id, gsf_input_name (part_stream), gsf_input_name (cur_stream) ); gsf_xml_in_doc_free (doc); g_object_unref (G_OBJECT (part_stream)); } return res; } /* DEPRECATED in 1.14.6 */ GsfInput *gsf_open_pkg_get_rel_by_type (GsfInput *in, char const *type) { return gsf_open_pkg_open_rel_by_type (in, type, NULL); } GsfInput *gsf_open_pkg_get_rel_by_id (GsfInput *in, char const *id) { return gsf_open_pkg_open_rel_by_id (in, id, NULL); } /*************************************************************/ struct _GsfOutfileOpenPkg { GsfOutfile parent; GsfOutput *sink; gboolean is_dir; char *content_type; GSList *children; GSList *relations; }; typedef GsfOutfileClass GsfOutfileOpenPkgClass; enum { PROP_0, PROP_SINK, PROP_CONTENT_TYPE, PROP_IS_DIR, PROP_IS_ROOT }; static GObjectClass *parent_class; static void gsf_outfile_open_pkg_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GsfOutfileOpenPkg *open_pkg = (GsfOutfileOpenPkg *)object; switch (property_id) { case PROP_SINK: g_value_set_object (value, open_pkg->sink); break; case PROP_CONTENT_TYPE: g_value_set_string (value, open_pkg->content_type); break; case PROP_IS_DIR: g_value_set_boolean (value, open_pkg->is_dir); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gsf_outfile_open_pkg_set_property (GObject *object, guint property_id, GValue const *value, GParamSpec *pspec) { GsfOutfileOpenPkg *open_pkg = (GsfOutfileOpenPkg *)object; switch (property_id) { case PROP_SINK: gsf_outfile_open_pkg_set_sink (open_pkg, g_value_get_object (value)); break; case PROP_CONTENT_TYPE: gsf_outfile_open_pkg_set_content_type (open_pkg, g_value_get_string (value)); break; case PROP_IS_DIR: open_pkg->is_dir = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gsf_outfile_open_pkg_init (GObject *obj) { GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (obj); open_pkg->sink = NULL; open_pkg->content_type = NULL; open_pkg->is_dir = FALSE; open_pkg->children = NULL; open_pkg->relations = NULL; } static void gsf_outfile_open_pkg_finalize (GObject *obj) { GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (obj); GSList *ptr; if (open_pkg->sink != NULL) { g_object_unref (open_pkg->sink); open_pkg->sink = NULL; } g_free (open_pkg->content_type); open_pkg->content_type = NULL; for (ptr = open_pkg->children ; ptr != NULL ; ptr = ptr->next) g_object_unref (ptr->data); g_slist_free (open_pkg->children); parent_class->finalize (obj); } static gboolean gsf_outfile_open_pkg_write (GsfOutput *output, size_t num_bytes, guint8 const *data) { GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (output); return gsf_output_write (open_pkg->sink, num_bytes, data); } static gboolean gsf_outfile_open_pkg_seek (GsfOutput *output, gsf_off_t offset, GSeekType whence) { GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (output); return gsf_output_seek (open_pkg->sink, offset, whence); } static GsfOutput * gsf_outfile_open_pkg_new_child (GsfOutfile *parent, char const *name, gboolean is_dir, char const *first_property_name, va_list args) { GsfOutfileOpenPkg *child, *open_pkg = GSF_OUTFILE_OPEN_PKG (parent); GsfOutput *sink; if (!open_pkg->is_dir) return NULL; child = (GsfOutfileOpenPkg *)g_object_new_valist ( GSF_OUTFILE_OPEN_PKG_TYPE, first_property_name, args); gsf_output_set_name (GSF_OUTPUT (child), name); gsf_output_set_container (GSF_OUTPUT (child), parent); child->is_dir = is_dir; sink = gsf_outfile_new_child (GSF_OUTFILE (open_pkg->sink), name, is_dir); gsf_outfile_open_pkg_set_sink (child, sink); g_object_unref (sink); open_pkg->children = g_slist_prepend (open_pkg->children, child); g_object_ref (child); return GSF_OUTPUT (child); } static void gsf_open_pkg_write_content_default (GsfXMLOut *xml, char const *ext, char const *type) { gsf_xml_out_start_element (xml, "Default"); gsf_xml_out_add_cstr (xml, "Extension", ext); gsf_xml_out_add_cstr (xml, "ContentType", type); gsf_xml_out_end_element (xml); /* */ } static void gsf_open_pkg_write_content_override (GsfOutfileOpenPkg const *open_pkg, char const *base, GsfXMLOut *xml) { GsfOutfileOpenPkg const *child; char *path; GSList *ptr; for (ptr = open_pkg->children ; ptr != NULL ; ptr = ptr->next) { child = ptr->data; if (child->is_dir) { path = g_strconcat (base, gsf_output_name (GSF_OUTPUT (child)), "/", NULL); gsf_open_pkg_write_content_override (child, path, xml); } else { path = g_strconcat (base, gsf_output_name (GSF_OUTPUT (child)), NULL); /* rels files do need content types, the defaults handle them */ if (NULL != child->content_type) { gsf_xml_out_start_element (xml, "Override"); gsf_xml_out_add_cstr (xml, "PartName", path); gsf_xml_out_add_cstr (xml, "ContentType", child->content_type); gsf_xml_out_end_element (xml); /* */ } } g_free (path); } } static gboolean gsf_outfile_open_pkg_close (GsfOutput *output) { GsfOutfileOpenPkg *open_pkg = GSF_OUTFILE_OPEN_PKG (output); GsfOutput *dir; gboolean res = FALSE; char *rels_name; if (NULL == open_pkg->sink || gsf_output_is_closed (open_pkg->sink)) return TRUE; /* Generate [Content_types].xml when we close the root dir */ if (NULL == gsf_output_name (output)) { GsfOutput *out = gsf_outfile_new_child (GSF_OUTFILE (open_pkg->sink), "[Content_Types].xml", FALSE); GsfXMLOut *xml = gsf_xml_out_new (out); gsf_xml_out_start_element (xml, "Types"); gsf_xml_out_add_cstr_unchecked (xml, "xmlns", "http://schemas.openxmlformats.org/package/2006/content-types"); gsf_open_pkg_write_content_default (xml, "rels", "application/vnd.openxmlformats-package.relationships+xml"); gsf_open_pkg_write_content_default (xml, "xlbin", "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"); gsf_open_pkg_write_content_default (xml, "xml", "application/xml"); gsf_open_pkg_write_content_override (open_pkg, "/", xml); gsf_xml_out_end_element (xml); /* */ g_object_unref (xml); gsf_output_close (out); g_object_unref (out); dir = open_pkg->sink; rels_name = g_strdup (".rels"); } else { res = gsf_output_close (open_pkg->sink); dir = (GsfOutput *)gsf_output_container (open_pkg->sink); rels_name = g_strconcat (gsf_output_name (output), ".rels", NULL); } if (NULL != open_pkg->relations) { GsfOutput *rels; GsfXMLOut *xml; GsfOpenPkgRel *rel; GSList *ptr; dir = gsf_outfile_new_child (GSF_OUTFILE (dir), "_rels", TRUE); rels = gsf_outfile_new_child (GSF_OUTFILE (dir), rels_name, FALSE); xml = gsf_xml_out_new (rels); gsf_xml_out_start_element (xml, "Relationships"); gsf_xml_out_add_cstr_unchecked (xml, "xmlns", "http://schemas.openxmlformats.org/package/2006/relationships"); for (ptr = open_pkg->relations ; ptr != NULL ; ptr = ptr->next) { rel = ptr->data; gsf_xml_out_start_element (xml, "Relationship"); gsf_xml_out_add_cstr (xml, "Id", rel->id); gsf_xml_out_add_cstr (xml, "Type", rel->type); gsf_xml_out_add_cstr (xml, "Target", rel->target); if (rel->is_extern) gsf_xml_out_add_cstr_unchecked (xml, "TargetMode", "External"); gsf_xml_out_end_element (xml); /* */ g_free (rel->id); g_free (rel->type); g_free (rel->target); g_free (rel); } g_slist_free (open_pkg->relations); gsf_xml_out_end_element (xml); /* */ g_object_unref (xml); gsf_output_close (rels); g_object_unref (rels); g_object_unref (dir); } g_free (rels_name); /* close the container */ if (NULL == gsf_output_name (output)) return gsf_output_close (open_pkg->sink); return res; } static void gsf_outfile_open_pkg_class_init (GObjectClass *gobject_class) { GsfOutputClass *output_class = GSF_OUTPUT_CLASS (gobject_class); GsfOutfileClass *outfile_class = GSF_OUTFILE_CLASS (gobject_class); gobject_class->finalize = gsf_outfile_open_pkg_finalize; gobject_class->get_property = gsf_outfile_open_pkg_get_property; gobject_class->set_property = gsf_outfile_open_pkg_set_property; output_class->Write = gsf_outfile_open_pkg_write; output_class->Seek = gsf_outfile_open_pkg_seek; output_class->Close = gsf_outfile_open_pkg_close; outfile_class->new_child = gsf_outfile_open_pkg_new_child; parent_class = g_type_class_peek_parent (gobject_class); g_object_class_install_property (gobject_class, PROP_SINK, g_param_spec_object ("sink", "Sink", "The GsfOutput that stores the Open Package content.", GSF_OUTFILE_TYPE, GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE, g_param_spec_string ("content-type", "ContentType", "The ContentType stored in the root [Content_Types].xml file.", "", GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (gobject_class, PROP_IS_DIR, g_param_spec_boolean ("is-dir", "IsDir", "Can the outfile have children.", FALSE, GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } GSF_CLASS (GsfOutfileOpenPkg, gsf_outfile_open_pkg, gsf_outfile_open_pkg_class_init, gsf_outfile_open_pkg_init, GSF_OUTFILE_TYPE) /** * gsf_outfile_open_pkg_new : * @sink : #GsfOutfile * * Convenience routine to create a GsfOutfileOpenPkg inside @sink. * * Returns: a GsfOutfile that the caller is responsible for. **/ GsfOutfile * gsf_outfile_open_pkg_new (GsfOutfile *sink) { return g_object_new (GSF_OUTFILE_OPEN_PKG_TYPE, "sink", sink, "is-dir", TRUE, NULL); } /** * gsf_outfile_open_pkg_set_sink : * @open_pkg : #GsfOutfileOpenPkg * @sink : #GsfOutput * * Assigns a GsfOutput (@sink) to store the package into. **/ void gsf_outfile_open_pkg_set_sink (GsfOutfileOpenPkg *open_pkg, GsfOutput *sink) { if (sink) g_object_ref (sink); if (open_pkg->sink) g_object_unref (open_pkg->sink); open_pkg->sink = sink; } /** * gsf_outfile_open_pkg_set_content_type : * @open_pkg : #GsfOutfileOpenPkg * @content_type : * **/ void gsf_outfile_open_pkg_set_content_type (GsfOutfileOpenPkg *open_pkg, char const *content_type) { g_return_if_fail (content_type != NULL); if (open_pkg->content_type != content_type) { g_free (open_pkg->content_type); open_pkg->content_type = g_strdup (content_type); } } static char const * gsf_outfile_open_pkg_create_rel (GsfOutfileOpenPkg *parent, char *target, char const *type, gboolean is_extern) { GsfOpenPkgRel *rel = g_new0 (GsfOpenPkgRel, 1); rel->target = target; rel->type = g_strdup (type); rel->id = g_strdup_printf ("rId%u", g_slist_length (parent->relations) + 1); rel->is_extern = is_extern; parent->relations = g_slist_prepend (parent->relations, rel); return rel->id; } /** * gsf_outfile_open_pkg_relate: * @child : #GsfOutfileOpenPkg * @parent : #GsfOutfileOpenPkg * @type : * * Create a relationship between @child and @parent of @type. * * Returns: the relID which the caller does not own but will live as long as * @parent. **/ char const * gsf_outfile_open_pkg_relate (GsfOutfileOpenPkg *child, GsfOutfileOpenPkg *parent, char const *type) { GString *path; int up = -1; GsfOutfile *child_dir, *parent_dir; /* Calculate the path from @child to @parent */ parent_dir = parent->is_dir ? GSF_OUTFILE (parent) : gsf_output_container (GSF_OUTPUT (parent)); do { up++; child_dir = GSF_OUTFILE (child); while (NULL != (child_dir = gsf_output_container (GSF_OUTPUT (child_dir)))) if (child_dir == parent_dir) goto found; /* break out of both loops */ } while (NULL != (parent_dir = gsf_output_container (GSF_OUTPUT (parent_dir)))); found: /* yes prepend is slow, this will never be preformance critical */ path = g_string_new (gsf_output_name (GSF_OUTPUT (child))); child_dir = GSF_OUTFILE (child); while (NULL != (child_dir = gsf_output_container (GSF_OUTPUT (child_dir))) && NULL != gsf_output_name (GSF_OUTPUT (child_dir)) && child_dir != parent_dir) { g_string_prepend_c (path, '/'); g_string_prepend (path, gsf_output_name (GSF_OUTPUT (child_dir))); } while (up--) g_string_prepend (path, "../"); return gsf_outfile_open_pkg_create_rel (parent, g_string_free (path, FALSE), type, FALSE); } /** * gsf_outfile_open_pkg_add_rel: * @dir : #GsfOutfile * @name : * @content_type : * @parent : #GsfOutfile * @type : * * A convenience wrapper to create a child in @dir of @content_type then create * a @type relation to @parent * * Returns: the new part. **/ GsfOutput * gsf_outfile_open_pkg_add_rel (GsfOutfile *dir, char const *name, char const *content_type, GsfOutfile *parent, char const *type) { GsfOutput *part = gsf_outfile_new_child_full (dir, name, FALSE, "content-type", content_type, NULL); (void) gsf_outfile_open_pkg_relate (GSF_OUTFILE_OPEN_PKG (part), GSF_OUTFILE_OPEN_PKG (parent), type); return part; } /** * gsf_outfile_open_pkg_add_extern_rel : * @parent : #GsfOutfileOpenPkg * @target : * @content_type : * * Add an external relation to @parent. * * Returns: The id of the relation. The string is managed by the parent and * should not be changed or freed by the caller. **/ char const * gsf_outfile_open_pkg_add_extern_rel (GsfOutfileOpenPkg *parent, char const *target, char const *content_type) { return gsf_outfile_open_pkg_create_rel (parent, g_strdup (target), content_type, TRUE); } gint gsf_open_pkg_error_id (void) { return 42; /* something arbitrary */ }