1 /* GXPSDocument
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 <stdlib.h>
23 #include <string.h>
24 
25 #include "gxps-document.h"
26 #include "gxps-archive.h"
27 #include "gxps-links.h"
28 #include "gxps-private.h"
29 #include "gxps-error.h"
30 
31 /**
32  * SECTION:gxps-document
33  * @Short_description: XPS Documents
34  * @Title: GXPSDocument
35  * @See_also: #GXPSFile, #GXPSPage, #GXPSDocumentStructure
36  *
37  * #GXPSDocument represents a document in a #GXPSFile. #GXPSDocument
38  * objects can not be created directly, they are retrieved from a
39  * #GXPSFile with gxps_file_get_document().
40  */
41 
42 enum {
43 	PROP_0,
44 	PROP_ARCHIVE,
45 	PROP_SOURCE
46 };
47 
48 typedef struct _Page {
49 	gchar *source;
50 	gint   width;
51 	gint   height;
52 	GList *links;
53 } Page;
54 
55 struct _GXPSDocumentPrivate {
56 	GXPSArchive *zip;
57 	gchar       *source;
58 	gboolean     has_rels;
59 	gchar       *structure;
60 
61 	gboolean     initialized;
62 	GError      *init_error;
63 
64 	Page       **pages;
65 	guint        n_pages;
66 };
67 
68 static void initable_iface_init (GInitableIface *initable_iface);
69 
G_DEFINE_TYPE_WITH_CODE(GXPSDocument,gxps_document,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,initable_iface_init))70 G_DEFINE_TYPE_WITH_CODE (GXPSDocument, gxps_document, G_TYPE_OBJECT,
71 			 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
72 
73 #define REL_DOCUMENT_STRUCTURE "http://schemas.microsoft.com/xps/2005/06/documentstructure"
74 
75 static Page *
76 page_new (void)
77 {
78 	return g_slice_new0 (Page);
79 }
80 
81 static void
page_free(Page * page)82 page_free (Page *page)
83 {
84 	g_free (page->source);
85 	g_list_foreach (page->links, (GFunc)g_free, NULL);
86 	g_list_free (page->links);
87 
88 	g_slice_free (Page, page);
89 }
90 
91 /* FixedDoc parser */
92 typedef struct _FixedDocParserData {
93 	GXPSDocument *doc;
94 	Page         *page;
95 	guint         n_pages;
96 	GList        *pages;
97 } FixedDocParserData;
98 
99 static void
fixed_doc_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)100 fixed_doc_start_element (GMarkupParseContext  *context,
101 			 const gchar          *element_name,
102 			 const gchar         **names,
103 			 const gchar         **values,
104 			 gpointer              user_data,
105 			 GError              **error)
106 {
107 	FixedDocParserData *data = (FixedDocParserData *)user_data;
108 	gint                i;
109 
110 	if (strcmp (element_name, "PageContent") == 0) {
111 		gchar  *source = NULL;
112 		gdouble width = -1, height = -1;
113 
114 		for (i = 0; names[i]; i++) {
115 			if (strcmp (names[i], "Source") == 0) {
116 				source = gxps_resolve_relative_path (data->doc->priv->source,
117 								     values[i]);
118 			} else if (strcmp (names[i], "Width") == 0) {
119 				if (!gxps_value_get_double_positive (values[i], &width))
120 					width = 0;
121 			} else if (strcmp (names[i], "Height") == 0) {
122 				if (!gxps_value_get_double_positive (values[i], &height))
123 					height = 0;
124 			}
125 		}
126 
127 		if (!source) {
128 			gxps_parse_error (context,
129 					  data->doc->priv->source,
130 					  G_MARKUP_ERROR_MISSING_ATTRIBUTE,
131 					  element_name, "Source", NULL, error);
132 			return;
133 		}
134 
135 		data->page = page_new ();
136 		data->page->source = source;
137 		data->page->width = width;
138 		data->page->height = height;
139 	} else if (strcmp (element_name, "LinkTarget") == 0) {
140 		if (!data->page) {
141 			/* TODO: error */
142 			return;
143 		}
144 
145 		for (i = 0; names[i]; i++) {
146 			if (strcmp (names[i], "Name") == 0) {
147 				data->page->links = g_list_prepend (data->page->links, g_strdup (values[i]));
148 			}
149 		}
150 	} else if (strcmp (element_name, "PageContent.LinkTargets") == 0) {
151 	} else if (strcmp (element_name, "FixedDocument") == 0) {
152 		/* Nothing to do */
153 	} else {
154 		gxps_parse_error (context,
155 				  data->doc->priv->source,
156 				  G_MARKUP_ERROR_UNKNOWN_ELEMENT,
157 				  element_name, NULL, NULL, error);
158 	}
159 }
160 
161 static void
fixed_doc_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)162 fixed_doc_end_element (GMarkupParseContext  *context,
163 		       const gchar          *element_name,
164 		       gpointer              user_data,
165 		       GError              **error)
166 {
167 	FixedDocParserData *data = (FixedDocParserData *)user_data;
168 
169 	if (strcmp (element_name, "PageContent") == 0) {
170 		data->n_pages++;
171 		data->pages = g_list_prepend (data->pages, data->page);
172 		data->page = NULL;
173 	} else if (strcmp (element_name, "PageContent.LinkTargets") == 0) {
174 		if (!data->page) {
175 			/* TODO: error */
176 			return;
177 		}
178 		data->page->links = g_list_reverse (data->page->links);
179 	} else if (strcmp (element_name, "FixedDocument") == 0) {
180 		GList *l;
181 
182 		data->doc->priv->n_pages = data->n_pages;
183                 if (data->doc->priv->n_pages > 0) {
184                         data->doc->priv->pages = g_new (Page *, data->n_pages);
185 
186                         for (l = data->pages; l; l = g_list_next (l))
187                                 data->doc->priv->pages[--data->n_pages] = (Page *)l->data;
188                 }
189                 g_list_free (data->pages);
190 	} else if (strcmp (element_name, "LinkTarget") == 0) {
191 		/* Do Nothing */
192 	} else {
193 		gxps_parse_error (context,
194 				  data->doc->priv->source,
195 				  G_MARKUP_ERROR_UNKNOWN_ELEMENT,
196 				  element_name, NULL, NULL, error);
197 	}
198 }
199 
200 static const GMarkupParser fixed_doc_parser = {
201 	fixed_doc_start_element,
202 	fixed_doc_end_element,
203 	NULL,
204 	NULL,
205 	NULL
206 };
207 
208 static gboolean
gxps_document_parse_fixed_doc(GXPSDocument * doc,GError ** error)209 gxps_document_parse_fixed_doc (GXPSDocument *doc,
210 			       GError      **error)
211 {
212 	GInputStream        *stream;
213 	GMarkupParseContext *ctx;
214 	FixedDocParserData  *parser_data;
215 
216 	stream = gxps_archive_open (doc->priv->zip,
217 				    doc->priv->source);
218 	if (!stream) {
219 		g_set_error (error,
220 			     GXPS_ERROR,
221 			     GXPS_ERROR_SOURCE_NOT_FOUND,
222 			     "Document source %s not found in archive",
223 			     doc->priv->source);
224 		return FALSE;
225 	}
226 
227 	parser_data = g_new0 (FixedDocParserData, 1);
228 	parser_data->doc = doc;
229 
230 	ctx = g_markup_parse_context_new (&fixed_doc_parser, 0, parser_data, NULL);
231 	gxps_parse_stream (ctx, stream, error);
232 	g_object_unref (stream);
233 
234 	g_free (parser_data);
235 	g_markup_parse_context_free (ctx);
236 
237 	return (*error != NULL) ? FALSE : TRUE;
238 }
239 
240 static void
doc_rels_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)241 doc_rels_start_element (GMarkupParseContext  *context,
242 			const gchar          *element_name,
243 			const gchar         **names,
244 			const gchar         **values,
245 			gpointer              user_data,
246 			GError              **error)
247 {
248 	GXPSDocument *doc = GXPS_DOCUMENT (user_data);
249 
250 	if (strcmp (element_name, "Relationship") == 0) {
251 		const gchar *type = NULL;
252 		const gchar *target = NULL;
253 		gint         i;
254 
255 		for (i = 0; names[i]; i++) {
256 			if (strcmp (names[i], "Type") == 0) {
257 				type = values[i];
258 			} else if (strcmp (names[i], "Target") == 0) {
259 				target = values[i];
260 			} else if (strcmp (names[i], "Id") == 0) {
261 				/* Ignore ids for now */
262 			}
263 		}
264 
265 		if (g_strcmp0 (type, REL_DOCUMENT_STRUCTURE) == 0) {
266 			doc->priv->structure = target ? gxps_resolve_relative_path (doc->priv->source, target) : NULL;
267 		}
268 	}
269 }
270 
271 static const GMarkupParser doc_rels_parser = {
272 	doc_rels_start_element,
273 	NULL,
274 	NULL,
275 	NULL,
276 	NULL
277 };
278 
279 static gboolean
gxps_document_parse_rels(GXPSDocument * doc,GError ** error)280 gxps_document_parse_rels (GXPSDocument *doc,
281 			  GError      **error)
282 {
283 	GInputStream        *stream;
284 	GMarkupParseContext *ctx;
285 	gchar               *filename;
286 	gchar               *rels, *doc_rels;
287 	gboolean             retval;
288 
289 	if (!doc->priv->has_rels)
290 		return FALSE;
291 
292 	filename = g_path_get_basename (doc->priv->source);
293 	rels = g_strconcat ("_rels/", filename, ".rels", NULL);
294 	doc_rels = gxps_resolve_relative_path (doc->priv->source, rels);
295 	g_free (filename);
296 	g_free (rels);
297 
298 	stream = gxps_archive_open (doc->priv->zip, doc_rels);
299 	if (!stream) {
300 		doc->priv->has_rels = FALSE;
301 		g_free (doc_rels);
302 
303 		return FALSE;
304 	}
305 
306 	ctx = g_markup_parse_context_new (&doc_rels_parser, 0, doc, NULL);
307 	retval = gxps_parse_stream (ctx, stream, error);
308 	g_object_unref (stream);
309 	g_free (doc_rels);
310 
311 	g_markup_parse_context_free (ctx);
312 
313 	return retval;
314 }
315 
316 static void
gxps_document_finalize(GObject * object)317 gxps_document_finalize (GObject *object)
318 {
319 	GXPSDocument *doc = GXPS_DOCUMENT (object);
320 
321 	g_clear_object (&doc->priv->zip);
322 	g_clear_pointer (&doc->priv->source, g_free);
323 	g_clear_pointer (&doc->priv->structure, g_free);
324 
325 	if (doc->priv->pages) {
326 		gint i;
327 
328 		for (i = 0; i < doc->priv->n_pages; i++)
329 			page_free (doc->priv->pages[i]);
330 		g_free (doc->priv->pages);
331 		doc->priv->pages = NULL;
332 	}
333 
334 	g_clear_error (&doc->priv->init_error);
335 
336 	G_OBJECT_CLASS (gxps_document_parent_class)->finalize (object);
337 }
338 
339 static void
gxps_document_init(GXPSDocument * doc)340 gxps_document_init (GXPSDocument *doc)
341 {
342 	doc->priv = G_TYPE_INSTANCE_GET_PRIVATE (doc,
343 						 GXPS_TYPE_DOCUMENT,
344 						 GXPSDocumentPrivate);
345 	doc->priv->has_rels = TRUE;
346 }
347 
348 static void
gxps_document_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)349 gxps_document_set_property (GObject      *object,
350 			    guint         prop_id,
351 			    const GValue *value,
352 			    GParamSpec   *pspec)
353 {
354 	GXPSDocument *doc = GXPS_DOCUMENT (object);
355 
356 	switch (prop_id) {
357 	case PROP_ARCHIVE:
358 		doc->priv->zip = g_value_dup_object (value);
359 		break;
360 	case PROP_SOURCE:
361 		doc->priv->source = g_value_dup_string (value);
362 		break;
363 	default:
364 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
365 		break;
366 	}
367 }
368 
369 static void
gxps_document_class_init(GXPSDocumentClass * klass)370 gxps_document_class_init (GXPSDocumentClass *klass)
371 {
372 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
373 
374 	object_class->set_property = gxps_document_set_property;
375 	object_class->finalize = gxps_document_finalize;
376 
377 	g_object_class_install_property (object_class,
378 					 PROP_ARCHIVE,
379 					 g_param_spec_object ("archive",
380 							      "Archive",
381 							      "The document archive",
382 							      GXPS_TYPE_ARCHIVE,
383 							      G_PARAM_WRITABLE |
384 							      G_PARAM_CONSTRUCT_ONLY));
385 	g_object_class_install_property (object_class,
386 					 PROP_SOURCE,
387 					 g_param_spec_string ("source",
388 							      "Source",
389 							      "The Document Source File",
390 							      NULL,
391 							      G_PARAM_WRITABLE |
392 							      G_PARAM_CONSTRUCT_ONLY));
393 
394 	g_type_class_add_private (klass, sizeof (GXPSDocumentPrivate));
395 }
396 
397 static gboolean
gxps_document_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)398 gxps_document_initable_init (GInitable     *initable,
399 			     GCancellable  *cancellable,
400 			     GError       **error)
401 {
402 	GXPSDocument *doc = GXPS_DOCUMENT (initable);
403 
404 	if (doc->priv->initialized) {
405 		if (doc->priv->init_error) {
406 			g_propagate_error (error, g_error_copy (doc->priv->init_error));
407 
408 			return FALSE;
409 		}
410 
411 		return TRUE;
412 	}
413 
414 	doc->priv->initialized = TRUE;
415 
416 	if (!gxps_document_parse_fixed_doc (doc, &doc->priv->init_error)) {
417 		g_propagate_error (error, g_error_copy (doc->priv->init_error));
418 		return FALSE;
419 	}
420 
421 	return TRUE;
422 }
423 
424 static void
initable_iface_init(GInitableIface * initable_iface)425 initable_iface_init (GInitableIface *initable_iface)
426 {
427 	initable_iface->init = gxps_document_initable_init;
428 }
429 
430 GXPSDocument *
_gxps_document_new(GXPSArchive * zip,const gchar * source,GError ** error)431 _gxps_document_new (GXPSArchive *zip,
432 		    const gchar *source,
433 		    GError     **error)
434 {
435 	return g_initable_new (GXPS_TYPE_DOCUMENT,
436 			       NULL, error,
437 			       "archive", zip,
438 			       "source", source,
439 			       NULL);
440 }
441 
442 /**
443  * gxps_document_get_n_pages:
444  * @doc: a #GXPSDocument
445  *
446  * Gets the number of pages in @doc.
447  *
448  * Returns: the number of pages.
449  */
450 guint
gxps_document_get_n_pages(GXPSDocument * doc)451 gxps_document_get_n_pages (GXPSDocument *doc)
452 {
453 	g_return_val_if_fail (GXPS_IS_DOCUMENT (doc), 0);
454 
455 	return doc->priv->n_pages;
456 }
457 
458 /**
459  * gxps_document_get_page:
460  * @doc: a #GXPSDocument
461  * @n_page: the index of the page to get
462  * @error: #GError for error reporting, or %NULL to ignore
463  *
464  * Creates a new #GXPSPage representing the page at
465  * index @n_doc in @doc document.
466  *
467  * Returns: (transfer full): a new #GXPSPage or %NULL on error.
468  *     Free the returned object with g_object_unref().
469  */
470 GXPSPage *
gxps_document_get_page(GXPSDocument * doc,guint n_page,GError ** error)471 gxps_document_get_page (GXPSDocument *doc,
472 			guint         n_page,
473 			GError      **error)
474 {
475 	const gchar *source;
476 
477 	g_return_val_if_fail (GXPS_IS_DOCUMENT (doc), NULL);
478 	g_return_val_if_fail (n_page < doc->priv->n_pages, NULL);
479 
480 	source = doc->priv->pages[n_page]->source;
481 	g_assert (source != NULL);
482 
483 	return _gxps_page_new (doc->priv->zip, source, error);
484 }
485 
486 /**
487  * gxps_document_get_page_size:
488  * @doc: a #GXPSDocument
489  * @n_page: the index of a page in @doc
490  * @width: (out) (allow-none): return location for the width of @n_page
491  * @height: (out) (allow-none): return location for the height of @n_page
492  *
493  * Gets the typical size of the page at index @n_page in @doc document.
494  * This function is useful to get the advisory size of pages in a document
495  * without creating #GXPSPage objects. This page size might be different than
496  * the actual page size so page dimension might need to be updated once the
497  * page is loaded. Advisory page sizes are not always available in @doc,
498  * in which case this function returns %FALSE.
499  * To get the authoritative size of a page you should use gxps_page_get_size()
500  * instead.
501  *
502  * Returns: %TRUE if the page size information is available in @doc,
503  *     %FALSE otherwise.
504  */
505 gboolean
gxps_document_get_page_size(GXPSDocument * doc,guint n_page,gdouble * width,gdouble * height)506 gxps_document_get_page_size (GXPSDocument *doc,
507 			     guint         n_page,
508 			     gdouble      *width,
509 			     gdouble      *height)
510 {
511 	Page *page;
512 
513 	g_return_val_if_fail (GXPS_IS_DOCUMENT (doc), FALSE);
514 	g_return_val_if_fail (n_page < doc->priv->n_pages, FALSE);
515 
516 	page = doc->priv->pages[n_page];
517 	if (page->width == 0 || page->height == 0)
518 		return FALSE;
519 
520 	if (width)
521 		*width = page->width;
522 	if (height)
523 		*height = page->height;
524 
525 	return TRUE;
526 }
527 
528 /**
529  * gxps_document_get_page_for_anchor:
530  * @doc: a #GXPSDocument
531  * @anchor: the name of an anchor
532  *
533  * Gets the index of the page in @doc where the given
534  * anchor is.
535  *
536  * Returns: the page index of the given anchor.
537  */
538 gint
gxps_document_get_page_for_anchor(GXPSDocument * doc,const gchar * anchor)539 gxps_document_get_page_for_anchor (GXPSDocument *doc,
540 				   const gchar  *anchor)
541 {
542 	guint i;
543 
544 	g_return_val_if_fail (GXPS_IS_DOCUMENT (doc), -1);
545 	g_return_val_if_fail (anchor != NULL, -1);
546 
547 	for (i = 0; i < doc->priv->n_pages; i++) {
548 		if (g_list_find_custom (doc->priv->pages[i]->links, anchor, (GCompareFunc)strcmp))
549 			return i;
550 	}
551 
552 	return -1;
553 }
554 
555 /**
556  * gxps_document_get_structure:
557  * @doc: a a #GXPSDocument
558  *
559  * Creates a new #GXPSDocumentStructure representing the document
560  * structure of @doc.
561  *
562  * Returns: (transfer full): a new #GXPSDocumentStructure or %NULL if document doesn't have a structure.
563  *     Free the returned object with g_object_unref().
564  */
565 GXPSDocumentStructure *
gxps_document_get_structure(GXPSDocument * doc)566 gxps_document_get_structure (GXPSDocument *doc)
567 {
568 	g_return_val_if_fail (GXPS_IS_DOCUMENT (doc), NULL);
569 
570 	if (!doc->priv->structure) {
571 		if (!gxps_document_parse_rels (doc, NULL))
572 			return NULL;
573 	}
574 
575 	if (!doc->priv->structure)
576 		return NULL;
577 
578 	if (!gxps_archive_has_entry (doc->priv->zip, doc->priv->structure))
579 		return NULL;
580 
581 	return _gxps_document_structure_new (doc->priv->zip,
582 					     doc->priv->structure);
583 }
584 
585