/src/ghostpdl/xps/xpsdoc.c
Line | Count | Source |
1 | | /* Copyright (C) 2001-2026 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
13 | | CA 94129, USA, for further information. |
14 | | */ |
15 | | |
16 | | |
17 | | /* XPS interpreter - document parsing */ |
18 | | |
19 | | #include "ghostxps.h" |
20 | | |
21 | | #include <expat.h> |
22 | | |
23 | | xps_part_t * |
24 | | xps_new_part(xps_context_t *ctx, const char *name, int size) |
25 | 96 | { |
26 | 96 | xps_part_t *part; |
27 | | |
28 | 96 | part = xps_alloc(ctx, sizeof(xps_part_t)); |
29 | 96 | if (!part) { |
30 | 0 | gs_throw(gs_error_VMerror, "out of memory: xps_new_part\n"); |
31 | 0 | return NULL; |
32 | 0 | } |
33 | 96 | part->name = xps_strdup(ctx, name); |
34 | 96 | part->size = size; |
35 | 96 | part->data = xps_alloc(ctx, size); |
36 | 96 | if (!part->data) { |
37 | 0 | xps_free(ctx, part); |
38 | 0 | gs_throw(gs_error_VMerror, "out of memory: xps_new_part\n"); |
39 | 0 | return NULL; |
40 | 0 | } |
41 | | |
42 | 96 | return part; |
43 | 96 | } |
44 | | |
45 | | void |
46 | | xps_free_part(xps_context_t *ctx, xps_part_t *part) |
47 | 94 | { |
48 | 94 | if (part == NULL) |
49 | 0 | return; |
50 | 94 | xps_free(ctx, part->name); |
51 | 94 | xps_free(ctx, part->data); |
52 | 94 | xps_free(ctx, part); |
53 | 94 | } |
54 | | |
55 | | /* |
56 | | * The FixedDocumentSequence and FixedDocument parts determine |
57 | | * which parts correspond to actual pages, and the page order. |
58 | | */ |
59 | | |
60 | | void |
61 | | xps_debug_fixdocseq(xps_context_t *ctx) |
62 | 0 | { |
63 | 0 | xps_document_t *fixdoc = ctx->first_fixdoc; |
64 | 0 | xps_page_t *page = ctx->first_page; |
65 | |
|
66 | 0 | if (ctx->start_part) |
67 | 0 | dmprintf1(ctx->memory, "start part %s\n", ctx->start_part); |
68 | |
|
69 | 0 | while (fixdoc) |
70 | 0 | { |
71 | 0 | dmprintf1(ctx->memory, "fixdoc %s\n", fixdoc->name); |
72 | 0 | fixdoc = fixdoc->next; |
73 | 0 | } |
74 | |
|
75 | 0 | while (page) |
76 | 0 | { |
77 | 0 | dmprintf3(ctx->memory, "page %s w=%d h=%d\n", page->name, page->width, page->height); |
78 | 0 | page = page->next; |
79 | 0 | } |
80 | 0 | } |
81 | | |
82 | | static void |
83 | | xps_add_fixed_document(xps_context_t *ctx, char *name) |
84 | 23 | { |
85 | 23 | xps_document_t *fixdoc; |
86 | | |
87 | | /* Check for duplicates first */ |
88 | 23 | for (fixdoc = ctx->first_fixdoc; fixdoc; fixdoc = fixdoc->next) |
89 | 0 | if (!strcmp(fixdoc->name, name)) |
90 | 0 | return; |
91 | | |
92 | 23 | if_debug1m('|', ctx->memory, "doc: adding fixdoc %s\n", name); |
93 | | |
94 | 23 | fixdoc = xps_alloc(ctx, sizeof(xps_document_t)); |
95 | 23 | if (!fixdoc) { |
96 | 0 | gs_throw(gs_error_VMerror, "out of memory: xps_add_fixed_document\n"); |
97 | 0 | return; |
98 | 0 | } |
99 | 23 | fixdoc->name = xps_strdup(ctx, name); |
100 | 23 | fixdoc->next = NULL; |
101 | | |
102 | 23 | if (!ctx->first_fixdoc) |
103 | 23 | { |
104 | 23 | ctx->first_fixdoc = fixdoc; |
105 | 23 | ctx->last_fixdoc = fixdoc; |
106 | 23 | } |
107 | 0 | else |
108 | 0 | { |
109 | 0 | ctx->last_fixdoc->next = fixdoc; |
110 | 0 | ctx->last_fixdoc = fixdoc; |
111 | 0 | } |
112 | 23 | } |
113 | | |
114 | | void |
115 | | xps_free_fixed_documents(xps_context_t *ctx) |
116 | 90 | { |
117 | 90 | xps_document_t *node = ctx->first_fixdoc; |
118 | 113 | while (node) |
119 | 23 | { |
120 | 23 | xps_document_t *next = node->next; |
121 | 23 | xps_free(ctx, node->name); |
122 | 23 | xps_free(ctx, node); |
123 | 23 | node = next; |
124 | 23 | } |
125 | 90 | ctx->first_fixdoc = NULL; |
126 | 90 | ctx->last_fixdoc = NULL; |
127 | 90 | } |
128 | | |
129 | | static void |
130 | | xps_add_fixed_page(xps_context_t *ctx, char *name, int width, int height) |
131 | 22 | { |
132 | 22 | xps_page_t *page; |
133 | | |
134 | | /* Check for duplicates first */ |
135 | 22 | for (page = ctx->first_page; page; page = page->next) |
136 | 0 | if (!strcmp(page->name, name)) |
137 | 0 | return; |
138 | | |
139 | 22 | if (ctx->page_range && !ctx->page_range->page_list) |
140 | 0 | { |
141 | 0 | ctx->page_range->current++; |
142 | |
|
143 | 0 | if (ctx->page_range->reverse) |
144 | 0 | { |
145 | 0 | if (ctx->page_range->current < ctx->page_range->last || |
146 | 0 | ctx->page_range->current > ctx->page_range->first) |
147 | 0 | return; |
148 | 0 | } |
149 | 0 | else |
150 | 0 | { |
151 | 0 | if ((ctx->page_range->first != 0 && ctx->page_range->current < ctx->page_range->first) || |
152 | 0 | (ctx->page_range->last != 0 && ctx->page_range->current > ctx->page_range->last)) |
153 | 0 | return; |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | 22 | if_debug1m('|', ctx->memory, "doc: adding page %s\n", name); |
158 | | |
159 | 22 | page = xps_alloc(ctx, sizeof(xps_page_t)); |
160 | 22 | if (!page) { |
161 | 0 | gs_throw(gs_error_VMerror, "out of memory: xps_add_fixed_page\n"); |
162 | 0 | return; |
163 | 0 | } |
164 | 22 | page->name = xps_strdup(ctx, name); |
165 | 22 | page->width = width; |
166 | 22 | page->height = height; |
167 | 22 | page->next = NULL; |
168 | | |
169 | 22 | if (!ctx->first_page) |
170 | 22 | { |
171 | 22 | ctx->first_page = page; |
172 | 22 | ctx->last_page = page; |
173 | 22 | } |
174 | 0 | else |
175 | 0 | { |
176 | | /* FirstPage < LastPage? */ |
177 | 0 | if (ctx->page_range && ctx->page_range->reverse) |
178 | 0 | { |
179 | 0 | page->next = ctx->first_page; |
180 | 0 | ctx->first_page = page; |
181 | 0 | } |
182 | 0 | else |
183 | 0 | { |
184 | 0 | ctx->last_page->next = page; |
185 | 0 | ctx->last_page = page; |
186 | 0 | } |
187 | 0 | } |
188 | 22 | } |
189 | | |
190 | | void |
191 | | xps_free_fixed_pages(xps_context_t *ctx) |
192 | 90 | { |
193 | 90 | xps_page_t *node = ctx->first_page; |
194 | 112 | while (node) |
195 | 22 | { |
196 | 22 | xps_page_t *next = node->next; |
197 | 22 | xps_free(ctx, node->name); |
198 | 22 | xps_free(ctx, node); |
199 | 22 | node = next; |
200 | 22 | } |
201 | 90 | ctx->first_page = NULL; |
202 | 90 | ctx->last_page = NULL; |
203 | 90 | } |
204 | | |
205 | | /* |
206 | | * Parse the fixed document sequence structure and _rels/.rels to find the |
207 | | * start part. We hook up unique expat handlers for this, since we don't need |
208 | | * the full document model. |
209 | | */ |
210 | | |
211 | | static void |
212 | | xps_parse_metadata_imp(void *zp, char *name, char **atts) |
213 | 162 | { |
214 | 162 | xps_context_t *ctx = zp; |
215 | 162 | int i; |
216 | | |
217 | 162 | if (!strcmp(name, "Relationship")) |
218 | 48 | { |
219 | 48 | char tgtbuf[1024]; |
220 | 48 | char *target = NULL; |
221 | 48 | char *type = NULL; |
222 | 48 | char *id = NULL; |
223 | | |
224 | 192 | for (i = 0; atts[i]; i += 2) |
225 | 144 | { |
226 | 144 | if (!strcmp(atts[i], "Target")) |
227 | 48 | target = atts[i + 1]; |
228 | 144 | if (!strcmp(atts[i], "Type")) |
229 | 48 | type = atts[i + 1]; |
230 | 144 | if (!strcmp(atts[i], "Id")) |
231 | 48 | id = atts[i + 1]; |
232 | 144 | } |
233 | | |
234 | 48 | if (target && type) |
235 | 48 | { |
236 | 48 | xps_absolute_path(tgtbuf, ctx->base_uri, target, sizeof tgtbuf); |
237 | 48 | if (!strcmp(type, REL_START_PART) || |
238 | 24 | !strcmp(type, REL_START_PART_OXPS)) |
239 | 24 | ctx->start_part = xps_strdup(ctx, tgtbuf); |
240 | 48 | if (!id) |
241 | 0 | gs_warn1("missing relationship Id for %s", target); |
242 | 48 | } |
243 | 48 | } |
244 | | |
245 | 162 | if (!strcmp(name, "DocumentReference")) |
246 | 23 | { |
247 | 23 | char *source = NULL; |
248 | 23 | char srcbuf[1024]; |
249 | | |
250 | 46 | for (i = 0; atts[i]; i += 2) |
251 | 23 | { |
252 | 23 | if (!strcmp(atts[i], "Source")) |
253 | 23 | source = atts[i + 1]; |
254 | 23 | } |
255 | | |
256 | 23 | if (source) |
257 | 23 | { |
258 | 23 | xps_absolute_path(srcbuf, ctx->base_uri, source, sizeof srcbuf); |
259 | 23 | xps_add_fixed_document(ctx, srcbuf); |
260 | 23 | } |
261 | 23 | } |
262 | | |
263 | 162 | if (!strcmp(name, "PageContent")) |
264 | 22 | { |
265 | 22 | char *source = NULL; |
266 | 22 | char srcbuf[1024]; |
267 | 22 | int width = 0; |
268 | 22 | int height = 0; |
269 | | |
270 | 44 | for (i = 0; atts[i]; i += 2) |
271 | 22 | { |
272 | 22 | if (!strcmp(atts[i], "Source")) |
273 | 22 | source = atts[i + 1]; |
274 | 22 | if (!strcmp(atts[i], "Width")) |
275 | 0 | width = atoi(atts[i + 1]); |
276 | 22 | if (!strcmp(atts[i], "Height")) |
277 | 0 | height = atoi(atts[i + 1]); |
278 | 22 | } |
279 | | |
280 | 22 | if (source) |
281 | 22 | { |
282 | 22 | xps_absolute_path(srcbuf, ctx->base_uri, source, sizeof srcbuf); |
283 | 22 | xps_add_fixed_page(ctx, srcbuf, width, height); |
284 | 22 | } |
285 | 22 | } |
286 | 162 | } |
287 | | |
288 | | int |
289 | | xps_parse_metadata(xps_context_t *ctx, xps_part_t *part) |
290 | 69 | { |
291 | 69 | XML_Parser xp; |
292 | 69 | int code; |
293 | 69 | char buf[1024]; |
294 | 69 | char *s; |
295 | | |
296 | | /* Save directory name part */ |
297 | 69 | gs_strlcpy(buf, part->name, sizeof buf); |
298 | 69 | s = strrchr(buf, '/'); |
299 | 69 | if (s) |
300 | 69 | s[0] = 0; |
301 | | |
302 | | /* _rels parts are voodoo: their URI references are from |
303 | | * the part they are associated with, not the actual _rels |
304 | | * part being parsed. |
305 | | */ |
306 | 69 | s = strstr(buf, "/_rels"); |
307 | 69 | if (s) |
308 | 24 | *s = 0; |
309 | | |
310 | 69 | ctx->base_uri = buf; |
311 | 69 | ctx->part_uri = part->name; |
312 | | |
313 | 69 | xp = XML_ParserCreate(NULL); |
314 | 69 | if (!xp) |
315 | 0 | return gs_throw(-1, "cannot create XML parser"); |
316 | | |
317 | 69 | XML_SetUserData(xp, ctx); |
318 | 69 | XML_SetParamEntityParsing(xp, XML_PARAM_ENTITY_PARSING_NEVER); |
319 | 69 | XML_SetStartElementHandler(xp, (XML_StartElementHandler)xps_parse_metadata_imp); |
320 | | |
321 | 69 | code = XML_Parse(xp, (char*)part->data, part->size, 1); |
322 | | |
323 | 69 | XML_ParserFree(xp); |
324 | | |
325 | 69 | ctx->base_uri = NULL; |
326 | 69 | ctx->part_uri = NULL; |
327 | | |
328 | 69 | if (code == 0) |
329 | 0 | return gs_throw1(-1, "cannot parse XML in part: %s", part->name); |
330 | | |
331 | 69 | return 0; |
332 | 69 | } |