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