1 /* GXPSFile
2  *
3  * Copyright (C) 2010  Carlos Garcia Campos <carlosgc@gnome.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #include <config.h>
21 
22 #include <string.h>
23 
24 #include "gxps-file.h"
25 #include "gxps-archive.h"
26 #include "gxps-private.h"
27 #include "gxps-error.h"
28 #include "gxps-debug.h"
29 
30 /**
31  * SECTION:gxps-file
32  * @Short_description: XPS Files
33  * @Title: GXPSFile
34  * @See_also: #GXPSDocument, #GXPSLinkTarget
35  *
36  * #GXPSFile represents a XPS file. A #GXPSFile is a set of one or more
37  * documents, you can get the amount of documents contained in the set
38  * with gxps_file_get_n_documents(). Documents can be retrieved by their
39  * index in the set with gxps_file_get_document().
40  */
41 
42 enum {
43 	PROP_0,
44 	PROP_FILE
45 };
46 
47 struct _GXPSFilePrivate {
48 	GFile       *file;
49 	GXPSArchive *zip;
50 	GPtrArray   *docs;
51 
52 	gboolean     initialized;
53 	GError      *init_error;
54 
55 	gchar       *fixed_repr;
56 	gchar       *thumbnail;
57 	gchar       *core_props;
58 };
59 
60 static void initable_iface_init (GInitableIface *initable_iface);
61 
G_DEFINE_TYPE_WITH_CODE(GXPSFile,gxps_file,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,initable_iface_init))62 G_DEFINE_TYPE_WITH_CODE (GXPSFile, gxps_file, G_TYPE_OBJECT,
63 			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
64 
65 GQuark
66 gxps_file_error_quark (void)
67 {
68 	return g_quark_from_static_string ("gxps-file-error-quark");
69 }
70 
71 #define REL_METATADA_CORE_PROPS  "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
72 #define REL_METATADA_THUMBNAIL   "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"
73 #define REL_FIXED_REPRESENTATION "http://schemas.microsoft.com/xps/2005/06/fixedrepresentation"
74 #define REL_OXPS_FIXED_REPRESENTATION "http://schemas.openxps.org/oxps/v1.0/fixedrepresentation"
75 
76 /* Relationship parser */
77 static void
rels_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)78 rels_start_element (GMarkupParseContext  *context,
79 		    const gchar          *element_name,
80 		    const gchar         **names,
81 		    const gchar         **values,
82 		    gpointer              user_data,
83 		    GError              **error)
84 {
85 	GXPSFile *xps = GXPS_FILE (user_data);
86 
87 	if (strcmp (element_name, "Relationship") == 0) {
88 		const gchar *type = NULL;
89 		const gchar *target = NULL;
90 		gint         i;
91 
92 		for (i = 0; names[i]; i++) {
93 			if (strcmp (names[i], "Type") == 0) {
94 				type = values[i];
95 			} else if (strcmp (names[i], "Target") == 0) {
96 				target = values[i];
97 			} else if (strcmp (names[i], "Id") == 0) {
98 				/* Ignore ids for now */
99 			}
100 		}
101 
102 		if (!type || !target) {
103 			gxps_parse_error (context,
104 					  "_rels/.rels",
105 					  G_MARKUP_ERROR_MISSING_ATTRIBUTE,
106 					  element_name,
107 					  !type ? "Type" : "Target",
108 					  NULL, error);
109 			return;
110 		}
111 
112 		if (strcmp (type, REL_FIXED_REPRESENTATION) == 0 ||
113 		    strcmp (type, REL_OXPS_FIXED_REPRESENTATION) == 0) {
114 			xps->priv->fixed_repr = g_strdup (target);
115 		} else if (strcmp (type, REL_METATADA_THUMBNAIL) == 0) {
116 			xps->priv->thumbnail = g_strdup (target);
117 		} else if (strcmp (type, REL_METATADA_CORE_PROPS) == 0) {
118 			xps->priv->core_props = g_strdup (target);
119 		} else {
120 			GXPS_DEBUG (g_debug ("Unsupported attribute of %s, %s=%s",
121                                              element_name, type, target));
122 		}
123 	} else if (strcmp (element_name, "Relationships") == 0) {
124 		/* Nothing to do */
125 	} else {
126 		gxps_parse_error (context,
127 				  "_rels/.rels",
128 				  G_MARKUP_ERROR_UNKNOWN_ELEMENT,
129 				  element_name, NULL, NULL, error);
130 	}
131 }
132 
133 static const GMarkupParser rels_parser = {
134 	rels_start_element,
135 	NULL,
136 	NULL,
137 	NULL,
138 	NULL
139 };
140 
141 static gboolean
gxps_file_parse_rels(GXPSFile * xps,GError ** error)142 gxps_file_parse_rels (GXPSFile *xps,
143 		      GError  **error)
144 {
145 	GInputStream        *stream;
146 	GMarkupParseContext *ctx;
147 
148 	stream = gxps_archive_open (xps->priv->zip, "_rels/.rels");
149 	if (!stream) {
150 		g_set_error_literal (error,
151 				     GXPS_ERROR,
152 				     GXPS_ERROR_SOURCE_NOT_FOUND,
153 				     "Source _rels/.rels not found in archive");
154 		return FALSE;
155 	}
156 
157 	ctx = g_markup_parse_context_new (&rels_parser, 0, xps, NULL);
158 	gxps_parse_stream (ctx, stream, error);
159 	g_object_unref (stream);
160 	g_markup_parse_context_free (ctx);
161 
162 	return (*error != NULL) ? FALSE : TRUE;
163 }
164 
165 /* FixedRepresentation parser */
166 static void
fixed_repr_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)167 fixed_repr_start_element (GMarkupParseContext  *context,
168 			  const gchar          *element_name,
169 			  const gchar         **names,
170 			  const gchar         **values,
171 			  gpointer              user_data,
172 			  GError              **error)
173 {
174 	GXPSFile *xps = GXPS_FILE (user_data);
175 
176 	if (strcmp (element_name, "DocumentReference") == 0) {
177 		gint i;
178 
179 		for (i = 0; names[i]; i++) {
180 			if (strcmp (names[i], "Source") == 0) {
181 				g_ptr_array_add (xps->priv->docs,
182 						 gxps_resolve_relative_path (xps->priv->fixed_repr, values[i]));
183 			}
184 		}
185 	} else if (strcmp (element_name, "FixedDocumentSequence") == 0) {
186 		/* Nothing to do */
187 	} else {
188 		gxps_parse_error (context,
189 				  xps->priv->fixed_repr,
190 				  G_MARKUP_ERROR_UNKNOWN_ELEMENT,
191 				  element_name, NULL, NULL, error);
192 	}
193 }
194 
195 static const GMarkupParser fixed_repr_parser = {
196 	fixed_repr_start_element,
197 	NULL,
198 	NULL,
199 	NULL,
200 	NULL
201 };
202 
203 static gboolean
gxps_file_parse_fixed_repr(GXPSFile * xps,GError ** error)204 gxps_file_parse_fixed_repr (GXPSFile *xps,
205 			    GError  **error)
206 {
207 	GInputStream        *stream;
208 	GMarkupParseContext *ctx;
209 
210 	stream = gxps_archive_open (xps->priv->zip,
211 				    xps->priv->fixed_repr);
212 	if (!stream) {
213 		g_set_error_literal (error,
214 				     GXPS_FILE_ERROR,
215 				     GXPS_FILE_ERROR_INVALID,
216 				     "Invalid XPS File: cannot open fixedrepresentation");
217 		return FALSE;
218 	}
219 
220 	ctx = g_markup_parse_context_new (&fixed_repr_parser, 0, xps, NULL);
221 	gxps_parse_stream (ctx, stream, error);
222 	g_object_unref (stream);
223 	g_markup_parse_context_free (ctx);
224 
225 	return (*error != NULL) ? FALSE : TRUE;
226 }
227 
228 static void
gxps_file_finalize(GObject * object)229 gxps_file_finalize (GObject *object)
230 {
231 	GXPSFile *xps = GXPS_FILE (object);
232 
233 	g_clear_object (&xps->priv->zip);
234 	g_clear_object (&xps->priv->file);
235 	g_clear_pointer (&xps->priv->docs, g_ptr_array_unref);
236 	g_clear_pointer (&xps->priv->fixed_repr, g_free);
237 	g_clear_pointer (&xps->priv->thumbnail, g_free);
238 	g_clear_pointer (&xps->priv->core_props, g_free);
239 	g_clear_error (&xps->priv->init_error);
240 
241 	G_OBJECT_CLASS (gxps_file_parent_class)->finalize (object);
242 }
243 
244 static void
gxps_file_init(GXPSFile * xps)245 gxps_file_init (GXPSFile *xps)
246 {
247 	xps->priv = G_TYPE_INSTANCE_GET_PRIVATE (xps,
248 						 GXPS_TYPE_FILE,
249 						 GXPSFilePrivate);
250 }
251 
252 static void
gxps_file_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)253 gxps_file_set_property (GObject      *object,
254 			guint         prop_id,
255 			const GValue *value,
256 			GParamSpec   *pspec)
257 {
258 	GXPSFile *xps = GXPS_FILE (object);
259 
260 	switch (prop_id) {
261 	case PROP_FILE:
262 		xps->priv->file = g_value_dup_object (value);
263 		break;
264 	default:
265 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
266 		break;
267 	}
268 }
269 
270 static void
gxps_file_class_init(GXPSFileClass * klass)271 gxps_file_class_init (GXPSFileClass *klass)
272 {
273 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
274 
275 	object_class->set_property = gxps_file_set_property;
276 	object_class->finalize = gxps_file_finalize;
277 
278 	g_object_class_install_property (object_class,
279 					 PROP_FILE,
280 					 g_param_spec_object ("file",
281 							      "File",
282 							      "The file file",
283 							      G_TYPE_FILE,
284 							      G_PARAM_WRITABLE |
285 							      G_PARAM_CONSTRUCT_ONLY));
286 
287 	g_type_class_add_private (klass, sizeof (GXPSFilePrivate));
288 }
289 
290 static gboolean
gxps_file_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)291 gxps_file_initable_init (GInitable     *initable,
292 			 GCancellable  *cancellable,
293 			 GError       **error)
294 {
295 	GXPSFile *xps = GXPS_FILE (initable);
296 
297 	if (xps->priv->initialized) {
298 		if (xps->priv->init_error) {
299 			g_propagate_error (error, g_error_copy (xps->priv->init_error));
300 			return FALSE;
301 		}
302 
303 		return TRUE;
304 	}
305 
306 	xps->priv->initialized = TRUE;
307 
308 	xps->priv->docs = g_ptr_array_new_with_free_func (g_free);
309 
310 	xps->priv->zip = gxps_archive_new (xps->priv->file, &xps->priv->init_error);
311 	if (!xps->priv->zip) {
312 		g_propagate_error (error, g_error_copy (xps->priv->init_error));
313 		return FALSE;
314 	}
315 
316 	if (!gxps_file_parse_rels (xps, &xps->priv->init_error)) {
317 		g_propagate_error (error, g_error_copy (xps->priv->init_error));
318 		return FALSE;
319 	}
320 
321 	if (!xps->priv->fixed_repr) {
322 		g_set_error_literal (&xps->priv->init_error,
323 				     GXPS_FILE_ERROR,
324 				     GXPS_FILE_ERROR_INVALID,
325 				     "Invalid XPS File: fixedrepresentation not found");
326 		g_propagate_error (error, g_error_copy (xps->priv->init_error));
327 		return FALSE;
328 	}
329 
330 	if (!gxps_file_parse_fixed_repr (xps, &xps->priv->init_error)) {
331 		g_propagate_error (error, g_error_copy (xps->priv->init_error));
332 		return FALSE;
333 	}
334 
335 	if (xps->priv->docs->len == 0) {
336 		g_set_error_literal (&xps->priv->init_error,
337 				     GXPS_FILE_ERROR,
338 				     GXPS_FILE_ERROR_INVALID,
339 				     "Invalid XPS File: no documents found");
340 		g_propagate_error (error, g_error_copy (xps->priv->init_error));
341 		return FALSE;
342 	}
343 
344 	return TRUE;
345 }
346 
347 static void
initable_iface_init(GInitableIface * initable_iface)348 initable_iface_init (GInitableIface *initable_iface)
349 {
350 	initable_iface->init = gxps_file_initable_init;
351 }
352 
353 /**
354  * gxps_file_new:
355  * @filename: a #GFile
356  * @error: #GError for error reporting, or %NULL to ignore
357  *
358  * Creates a new #GXPSFile for the given #GFile.
359  *
360  * Returns: a #GXPSFile or %NULL on error.
361  */
362 GXPSFile *
gxps_file_new(GFile * filename,GError ** error)363 gxps_file_new (GFile   *filename,
364 	       GError **error)
365 {
366 	g_return_val_if_fail (G_IS_FILE (filename), NULL);
367 
368 	return g_initable_new (GXPS_TYPE_FILE,
369 			       NULL, error,
370 			       "file", filename,
371 			       NULL);
372 }
373 
374 /**
375  * gxps_file_get_n_documents:
376  * @xps: a #GXPSFile
377  *
378  * Gets the number of documents in @xps.
379  *
380  * Returns: the number of documents.
381  */
382 guint
gxps_file_get_n_documents(GXPSFile * xps)383 gxps_file_get_n_documents (GXPSFile *xps)
384 {
385 	g_return_val_if_fail (GXPS_IS_FILE (xps), 0);
386 
387 	return xps->priv->docs->len;
388 }
389 
390 /**
391  * gxps_file_get_document:
392  * @xps: a #GXPSFile
393  * @n_doc: the index of the document to get
394  * @error: #GError for error reporting, or %NULL to ignore
395  *
396  * Creates a new #GXPSDocument representing the document at
397  * index @n_doc in @xps file.
398  *
399  * Returns: (transfer full): a new #GXPSDocument or %NULL on error.
400  *     Free the returned object with g_object_unref().
401  */
402 GXPSDocument *
gxps_file_get_document(GXPSFile * xps,guint n_doc,GError ** error)403 gxps_file_get_document (GXPSFile *xps,
404 			guint     n_doc,
405 			GError  **error)
406 {
407 	const gchar  *source;
408 
409 	g_return_val_if_fail (GXPS_IS_FILE (xps), NULL);
410 	g_return_val_if_fail (n_doc < xps->priv->docs->len, NULL);
411 
412 	source = g_ptr_array_index (xps->priv->docs, n_doc);
413 	g_assert (source != NULL);
414 
415 	return _gxps_document_new (xps->priv->zip, source, error);
416 }
417 
418 /**
419  * gxps_file_get_document_for_link_target:
420  * @xps: a #GXPSFile
421  * @target: a #GXPSLinkTarget
422  *
423  * Gets the index of the document in @xps pointed by @target.
424  * If the #GXPSLinkTarget does not reference a document, or
425  * referenced document is not found in @xps file -1 will be
426  * returned. In this case you can look for the page pointed by
427  * the link target by calling gxps_document_get_page_for_anchor()
428  * with the anchor of the #GXPSLinkTarget for every document in
429  * @xps.
430  *
431  * Returns: the index of the document pointed by the given
432  *     #GXPSLinkTarget or -1.
433  */
434 gint
gxps_file_get_document_for_link_target(GXPSFile * xps,GXPSLinkTarget * target)435 gxps_file_get_document_for_link_target (GXPSFile       *xps,
436 					GXPSLinkTarget *target)
437 {
438 	guint        i;
439 	const gchar *uri;
440 
441         g_return_val_if_fail (GXPS_IS_FILE (xps), -1);
442         g_return_val_if_fail (target != NULL, -1);
443 
444 	uri = gxps_link_target_get_uri (target);
445 	for (i = 0; i < xps->priv->docs->len; ++i) {
446 		if (g_ascii_strcasecmp (uri, (gchar *)xps->priv->docs->pdata[i]) == 0)
447 			return i;
448 	}
449 
450 	return -1;
451 }
452 
453 /**
454  * gxps_file_get_core_properties:
455  * @xps: a #GXPSFile
456  * @error: #GError for error reporting, or %NULL to ignore
457  *
458  * Create a #GXPSCoreProperties object containing the metadata
459  * of @xpsm, or %NULL in case of error or if the #GXPSFile
460  * doesn't contain core properties.
461  *
462  * Returns: (transfer full): a new #GXPSCoreProperties or %NULL.
463  *    Free the returned object with g_object_unref().
464  */
465 GXPSCoreProperties *
gxps_file_get_core_properties(GXPSFile * xps,GError ** error)466 gxps_file_get_core_properties (GXPSFile *xps,
467                                GError  **error)
468 {
469         g_return_val_if_fail (GXPS_IS_FILE (xps), NULL);
470 
471         if (!xps->priv->core_props)
472                 return NULL;
473 
474         return _gxps_core_properties_new (xps->priv->zip,
475                                           xps->priv->core_props,
476                                           error);
477 }
478