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