/src/cups/cups/ppd-localize.c
Line | Count | Source |
1 | | /* |
2 | | * PPD localization routines for CUPS. |
3 | | * |
4 | | * Copyright 2007-2018 by Apple Inc. |
5 | | * Copyright 1997-2007 by Easy Software Products, all rights reserved. |
6 | | * |
7 | | * These coded instructions, statements, and computer programs are the |
8 | | * property of Apple Inc. and are protected by Federal copyright |
9 | | * law. Distribution and use rights are outlined in the file "LICENSE.txt" |
10 | | * which should have been included with this file. If this file is |
11 | | * missing or damaged, see the license at "http://www.cups.org/". |
12 | | * |
13 | | * PostScript is a trademark of Adobe Systems, Inc. |
14 | | * |
15 | | * This code and any derivative of it may be used and distributed |
16 | | * freely under the terms of the GNU General Public License when |
17 | | * used with GNU Ghostscript or its derivatives. Use of the code |
18 | | * (or any derivative of it) with software other than GNU |
19 | | * GhostScript (or its derivatives) is governed by the CUPS license |
20 | | * agreement. |
21 | | * |
22 | | * This file is subject to the Apple OS-Developed Software exception. |
23 | | */ |
24 | | |
25 | | /* |
26 | | * Include necessary headers. |
27 | | */ |
28 | | |
29 | | #include "cups-private.h" |
30 | | #include "ppd-private.h" |
31 | | |
32 | | |
33 | | /* |
34 | | * Local functions... |
35 | | */ |
36 | | |
37 | | static cups_lang_t *ppd_ll_CC(char *ll_CC, size_t ll_CC_size); |
38 | | |
39 | | |
40 | | /* |
41 | | * 'ppdLocalize()' - Localize the PPD file to the current locale. |
42 | | * |
43 | | * All groups, options, and choices are localized, as are ICC profile |
44 | | * descriptions, printer presets, and custom option parameters. Each |
45 | | * localized string uses the UTF-8 character encoding. |
46 | | * |
47 | | * @since CUPS 1.2/macOS 10.5@ |
48 | | */ |
49 | | |
50 | | int /* O - 0 on success, -1 on error */ |
51 | | ppdLocalize(ppd_file_t *ppd) /* I - PPD file */ |
52 | 0 | { |
53 | 0 | int i, j, k; /* Looping vars */ |
54 | 0 | ppd_group_t *group; /* Current group */ |
55 | 0 | ppd_option_t *option; /* Current option */ |
56 | 0 | ppd_choice_t *choice; /* Current choice */ |
57 | 0 | ppd_coption_t *coption; /* Current custom option */ |
58 | 0 | ppd_cparam_t *cparam; /* Current custom parameter */ |
59 | 0 | ppd_attr_t *attr, /* Current attribute */ |
60 | 0 | *locattr; /* Localized attribute */ |
61 | 0 | char ckeyword[PPD_MAX_NAME], /* Custom keyword */ |
62 | 0 | ll_CC[6]; /* Language + country locale */ |
63 | | |
64 | | |
65 | | /* |
66 | | * Range check input... |
67 | | */ |
68 | |
|
69 | 0 | DEBUG_printf(("ppdLocalize(ppd=%p)", ppd)); |
70 | |
|
71 | 0 | if (!ppd) |
72 | 0 | return (-1); |
73 | | |
74 | | /* |
75 | | * Get the default language... |
76 | | */ |
77 | | |
78 | 0 | ppd_ll_CC(ll_CC, sizeof(ll_CC)); |
79 | | |
80 | | /* |
81 | | * Now lookup all of the groups, options, choices, etc. |
82 | | */ |
83 | |
|
84 | 0 | for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++) |
85 | 0 | { |
86 | 0 | if ((locattr = _ppdLocalizedAttr(ppd, "Translation", group->name, |
87 | 0 | ll_CC)) != NULL) |
88 | 0 | strlcpy(group->text, locattr->text, sizeof(group->text)); |
89 | |
|
90 | 0 | for (j = group->num_options, option = group->options; j > 0; j --, option ++) |
91 | 0 | { |
92 | 0 | if ((locattr = _ppdLocalizedAttr(ppd, "Translation", option->keyword, |
93 | 0 | ll_CC)) != NULL) |
94 | 0 | strlcpy(option->text, locattr->text, sizeof(option->text)); |
95 | |
|
96 | 0 | for (k = option->num_choices, choice = option->choices; |
97 | 0 | k > 0; |
98 | 0 | k --, choice ++) |
99 | 0 | { |
100 | 0 | if (strcmp(choice->choice, "Custom") || |
101 | 0 | !ppdFindCustomOption(ppd, option->keyword)) |
102 | 0 | locattr = _ppdLocalizedAttr(ppd, option->keyword, choice->choice, |
103 | 0 | ll_CC); |
104 | 0 | else |
105 | 0 | { |
106 | 0 | snprintf(ckeyword, sizeof(ckeyword), "Custom%s", option->keyword); |
107 | |
|
108 | 0 | locattr = _ppdLocalizedAttr(ppd, ckeyword, "True", ll_CC); |
109 | 0 | } |
110 | |
|
111 | 0 | if (locattr) |
112 | 0 | strlcpy(choice->text, locattr->text, sizeof(choice->text)); |
113 | 0 | } |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | | /* |
118 | | * Translate any custom parameters... |
119 | | */ |
120 | |
|
121 | 0 | for (coption = (ppd_coption_t *)cupsArrayFirst(ppd->coptions); |
122 | 0 | coption; |
123 | 0 | coption = (ppd_coption_t *)cupsArrayNext(ppd->coptions)) |
124 | 0 | { |
125 | 0 | for (cparam = (ppd_cparam_t *)cupsArrayFirst(coption->params); |
126 | 0 | cparam; |
127 | 0 | cparam = (ppd_cparam_t *)cupsArrayNext(coption->params)) |
128 | 0 | { |
129 | 0 | snprintf(ckeyword, sizeof(ckeyword), "ParamCustom%s", coption->keyword); |
130 | |
|
131 | 0 | if ((locattr = _ppdLocalizedAttr(ppd, ckeyword, cparam->name, |
132 | 0 | ll_CC)) != NULL) |
133 | 0 | strlcpy(cparam->text, locattr->text, sizeof(cparam->text)); |
134 | 0 | } |
135 | 0 | } |
136 | | |
137 | | /* |
138 | | * Translate ICC profile names... |
139 | | */ |
140 | |
|
141 | 0 | if ((attr = ppdFindAttr(ppd, "APCustomColorMatchingName", NULL)) != NULL) |
142 | 0 | { |
143 | 0 | if ((locattr = _ppdLocalizedAttr(ppd, "APCustomColorMatchingName", |
144 | 0 | attr->spec, ll_CC)) != NULL) |
145 | 0 | strlcpy(attr->text, locattr->text, sizeof(attr->text)); |
146 | 0 | } |
147 | |
|
148 | 0 | for (attr = ppdFindAttr(ppd, "cupsICCProfile", NULL); |
149 | 0 | attr; |
150 | 0 | attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL)) |
151 | 0 | { |
152 | 0 | cupsArraySave(ppd->sorted_attrs); |
153 | |
|
154 | 0 | if ((locattr = _ppdLocalizedAttr(ppd, "cupsICCProfile", attr->spec, |
155 | 0 | ll_CC)) != NULL) |
156 | 0 | strlcpy(attr->text, locattr->text, sizeof(attr->text)); |
157 | |
|
158 | 0 | cupsArrayRestore(ppd->sorted_attrs); |
159 | 0 | } |
160 | | |
161 | | /* |
162 | | * Translate printer presets... |
163 | | */ |
164 | |
|
165 | 0 | for (attr = ppdFindAttr(ppd, "APPrinterPreset", NULL); |
166 | 0 | attr; |
167 | 0 | attr = ppdFindNextAttr(ppd, "APPrinterPreset", NULL)) |
168 | 0 | { |
169 | 0 | cupsArraySave(ppd->sorted_attrs); |
170 | |
|
171 | 0 | if ((locattr = _ppdLocalizedAttr(ppd, "APPrinterPreset", attr->spec, |
172 | 0 | ll_CC)) != NULL) |
173 | 0 | strlcpy(attr->text, locattr->text, sizeof(attr->text)); |
174 | |
|
175 | 0 | cupsArrayRestore(ppd->sorted_attrs); |
176 | 0 | } |
177 | |
|
178 | 0 | return (0); |
179 | 0 | } |
180 | | |
181 | | |
182 | | /* |
183 | | * 'ppdLocalizeAttr()' - Localize an attribute. |
184 | | * |
185 | | * This function uses the current locale to find the localized attribute for |
186 | | * the given main and option keywords. If no localized version of the |
187 | | * attribute exists for the current locale, the unlocalized version is returned. |
188 | | */ |
189 | | |
190 | | ppd_attr_t * /* O - Localized attribute or @code NULL@ if none exists */ |
191 | | ppdLocalizeAttr(ppd_file_t *ppd, /* I - PPD file */ |
192 | | const char *keyword, /* I - Main keyword */ |
193 | | const char *spec) /* I - Option keyword or @code NULL@ for none */ |
194 | 0 | { |
195 | 0 | ppd_attr_t *locattr; /* Localized attribute */ |
196 | 0 | char ll_CC[6]; /* Language + country locale */ |
197 | | |
198 | | |
199 | | /* |
200 | | * Get the default language... |
201 | | */ |
202 | |
|
203 | 0 | ppd_ll_CC(ll_CC, sizeof(ll_CC)); |
204 | | |
205 | | /* |
206 | | * Find the localized attribute... |
207 | | */ |
208 | |
|
209 | 0 | if (spec) |
210 | 0 | locattr = _ppdLocalizedAttr(ppd, keyword, spec, ll_CC); |
211 | 0 | else |
212 | 0 | locattr = _ppdLocalizedAttr(ppd, "Translation", keyword, ll_CC); |
213 | |
|
214 | 0 | if (!locattr) |
215 | 0 | locattr = ppdFindAttr(ppd, keyword, spec); |
216 | |
|
217 | 0 | return (locattr); |
218 | 0 | } |
219 | | |
220 | | |
221 | | /* |
222 | | * 'ppdLocalizeIPPReason()' - Get the localized version of a cupsIPPReason |
223 | | * attribute. |
224 | | * |
225 | | * This function uses the current locale to find the corresponding reason |
226 | | * text or URI from the attribute value. If "scheme" is NULL or "text", |
227 | | * the returned value contains human-readable (UTF-8) text from the translation |
228 | | * string or attribute value. Otherwise the corresponding URI is returned. |
229 | | * |
230 | | * If no value of the requested scheme can be found, NULL is returned. |
231 | | * |
232 | | * @since CUPS 1.3/macOS 10.5@ |
233 | | */ |
234 | | |
235 | | const char * /* O - Value or NULL if not found */ |
236 | | ppdLocalizeIPPReason( |
237 | | ppd_file_t *ppd, /* I - PPD file */ |
238 | | const char *reason, /* I - IPP reason keyword to look up */ |
239 | | const char *scheme, /* I - URI scheme or NULL for text */ |
240 | | char *buffer, /* I - Value buffer */ |
241 | | size_t bufsize) /* I - Size of value buffer */ |
242 | 0 | { |
243 | 0 | cups_lang_t *lang; /* Current language */ |
244 | 0 | ppd_attr_t *locattr; /* Localized attribute */ |
245 | 0 | char ll_CC[6], /* Language + country locale */ |
246 | 0 | *bufptr, /* Pointer into buffer */ |
247 | 0 | *bufend, /* Pointer to end of buffer */ |
248 | 0 | *valptr; /* Pointer into value */ |
249 | 0 | int ch; /* Hex-encoded character */ |
250 | 0 | size_t schemelen; /* Length of scheme name */ |
251 | | |
252 | | |
253 | | /* |
254 | | * Range check input... |
255 | | */ |
256 | |
|
257 | 0 | if (buffer) |
258 | 0 | *buffer = '\0'; |
259 | |
|
260 | 0 | if (!ppd || !reason || (scheme && !*scheme) || |
261 | 0 | !buffer || bufsize < PPD_MAX_TEXT) |
262 | 0 | return (NULL); |
263 | | |
264 | | /* |
265 | | * Get the default language... |
266 | | */ |
267 | | |
268 | 0 | lang = ppd_ll_CC(ll_CC, sizeof(ll_CC)); |
269 | | |
270 | | /* |
271 | | * Find the localized attribute... |
272 | | */ |
273 | |
|
274 | 0 | if ((locattr = _ppdLocalizedAttr(ppd, "cupsIPPReason", reason, |
275 | 0 | ll_CC)) == NULL) |
276 | 0 | locattr = ppdFindAttr(ppd, "cupsIPPReason", reason); |
277 | |
|
278 | 0 | if (!locattr) |
279 | 0 | { |
280 | 0 | if (lang && (!scheme || !strcmp(scheme, "text")) && strcmp(reason, "none")) |
281 | 0 | { |
282 | | /* |
283 | | * Try to localize a standard printer-state-reason keyword... |
284 | | */ |
285 | |
|
286 | 0 | const char *message = NULL; /* Localized message */ |
287 | |
|
288 | 0 | if (!strncmp(reason, "media-needed", 12)) |
289 | 0 | message = _("Load paper."); |
290 | 0 | else if (!strncmp(reason, "media-jam", 9)) |
291 | 0 | message = _("Paper jam."); |
292 | 0 | else if (!strncmp(reason, "offline", 7) || |
293 | 0 | !strncmp(reason, "shutdown", 8)) |
294 | 0 | message = _("The printer is not connected."); |
295 | 0 | else if (!strncmp(reason, "toner-low", 9)) |
296 | 0 | message = _("The printer is low on toner."); |
297 | 0 | else if (!strncmp(reason, "toner-empty", 11)) |
298 | 0 | message = _("The printer may be out of toner."); |
299 | 0 | else if (!strncmp(reason, "cover-open", 10)) |
300 | 0 | message = _("The printer's cover is open."); |
301 | 0 | else if (!strncmp(reason, "interlock-open", 14)) |
302 | 0 | message = _("The printer's interlock is open."); |
303 | 0 | else if (!strncmp(reason, "door-open", 9)) |
304 | 0 | message = _("The printer's door is open."); |
305 | 0 | else if (!strncmp(reason, "input-tray-missing", 18)) |
306 | 0 | message = _("Paper tray is missing."); |
307 | 0 | else if (!strncmp(reason, "media-low", 9)) |
308 | 0 | message = _("Paper tray is almost empty."); |
309 | 0 | else if (!strncmp(reason, "media-empty", 11)) |
310 | 0 | message = _("Paper tray is empty."); |
311 | 0 | else if (!strncmp(reason, "output-tray-missing", 19)) |
312 | 0 | message = _("Output bin is missing."); |
313 | 0 | else if (!strncmp(reason, "output-area-almost-full", 23)) |
314 | 0 | message = _("Output bin is almost full."); |
315 | 0 | else if (!strncmp(reason, "output-area-full", 16)) |
316 | 0 | message = _("Output bin is full."); |
317 | 0 | else if (!strncmp(reason, "marker-supply-low", 17)) |
318 | 0 | message = _("The printer is low on ink."); |
319 | 0 | else if (!strncmp(reason, "marker-supply-empty", 19)) |
320 | 0 | message = _("The printer may be out of ink."); |
321 | 0 | else if (!strncmp(reason, "marker-waste-almost-full", 24)) |
322 | 0 | message = _("The printer's waste bin is almost full."); |
323 | 0 | else if (!strncmp(reason, "marker-waste-full", 17)) |
324 | 0 | message = _("The printer's waste bin is full."); |
325 | 0 | else if (!strncmp(reason, "fuser-over-temp", 15)) |
326 | 0 | message = _("The fuser's temperature is high."); |
327 | 0 | else if (!strncmp(reason, "fuser-under-temp", 16)) |
328 | 0 | message = _("The fuser's temperature is low."); |
329 | 0 | else if (!strncmp(reason, "opc-near-eol", 12)) |
330 | 0 | message = _("The optical photoconductor will need to be replaced soon."); |
331 | 0 | else if (!strncmp(reason, "opc-life-over", 13)) |
332 | 0 | message = _("The optical photoconductor needs to be replaced."); |
333 | 0 | else if (!strncmp(reason, "developer-low", 13)) |
334 | 0 | message = _("The developer unit will need to be replaced soon."); |
335 | 0 | else if (!strncmp(reason, "developer-empty", 15)) |
336 | 0 | message = _("The developer unit needs to be replaced."); |
337 | |
|
338 | 0 | if (message) |
339 | 0 | { |
340 | 0 | strlcpy(buffer, _cupsLangString(lang, message), bufsize); |
341 | 0 | return (buffer); |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | 0 | return (NULL); |
346 | 0 | } |
347 | | |
348 | | /* |
349 | | * Now find the value we need... |
350 | | */ |
351 | | |
352 | 0 | bufend = buffer + bufsize - 1; |
353 | |
|
354 | 0 | if (!scheme || !strcmp(scheme, "text")) |
355 | 0 | { |
356 | | /* |
357 | | * Copy a text value (either the translation text or text:... URIs from |
358 | | * the value... |
359 | | */ |
360 | |
|
361 | 0 | strlcpy(buffer, locattr->text, bufsize); |
362 | |
|
363 | 0 | for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;) |
364 | 0 | { |
365 | 0 | if (!strncmp(valptr, "text:", 5)) |
366 | 0 | { |
367 | | /* |
368 | | * Decode text: URI and add to the buffer... |
369 | | */ |
370 | |
|
371 | 0 | valptr += 5; |
372 | |
|
373 | 0 | while (*valptr && !_cups_isspace(*valptr) && bufptr < bufend) |
374 | 0 | { |
375 | 0 | if (*valptr == '%' && isxdigit(valptr[1] & 255) && |
376 | 0 | isxdigit(valptr[2] & 255)) |
377 | 0 | { |
378 | | /* |
379 | | * Pull a hex-encoded character from the URI... |
380 | | */ |
381 | |
|
382 | 0 | valptr ++; |
383 | |
|
384 | 0 | if (isdigit(*valptr & 255)) |
385 | 0 | ch = (*valptr - '0') << 4; |
386 | 0 | else |
387 | 0 | ch = (tolower(*valptr) - 'a' + 10) << 4; |
388 | 0 | valptr ++; |
389 | |
|
390 | 0 | if (isdigit(*valptr & 255)) |
391 | 0 | *bufptr++ = (char)(ch | (*valptr - '0')); |
392 | 0 | else |
393 | 0 | *bufptr++ = (char)(ch | (tolower(*valptr) - 'a' + 10)); |
394 | 0 | valptr ++; |
395 | 0 | } |
396 | 0 | else if (*valptr == '+') |
397 | 0 | { |
398 | 0 | *bufptr++ = ' '; |
399 | 0 | valptr ++; |
400 | 0 | } |
401 | 0 | else |
402 | 0 | *bufptr++ = *valptr++; |
403 | 0 | } |
404 | 0 | } |
405 | 0 | else |
406 | 0 | { |
407 | | /* |
408 | | * Skip this URI... |
409 | | */ |
410 | |
|
411 | 0 | while (*valptr && !_cups_isspace(*valptr)) |
412 | 0 | valptr++; |
413 | 0 | } |
414 | | |
415 | | /* |
416 | | * Skip whitespace... |
417 | | */ |
418 | |
|
419 | 0 | while (_cups_isspace(*valptr)) |
420 | 0 | valptr ++; |
421 | 0 | } |
422 | |
|
423 | 0 | if (bufptr > buffer) |
424 | 0 | *bufptr = '\0'; |
425 | |
|
426 | 0 | return (buffer); |
427 | 0 | } |
428 | 0 | else |
429 | 0 | { |
430 | | /* |
431 | | * Copy a URI... |
432 | | */ |
433 | |
|
434 | 0 | schemelen = strlen(scheme); |
435 | 0 | if (scheme[schemelen - 1] == ':') /* Force scheme to be just the name */ |
436 | 0 | schemelen --; |
437 | |
|
438 | 0 | for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;) |
439 | 0 | { |
440 | 0 | if ((!strncmp(valptr, scheme, schemelen) && valptr[schemelen] == ':') || |
441 | 0 | (*valptr == '/' && !strcmp(scheme, "file"))) |
442 | 0 | { |
443 | | /* |
444 | | * Copy URI... |
445 | | */ |
446 | |
|
447 | 0 | while (*valptr && !_cups_isspace(*valptr) && bufptr < bufend) |
448 | 0 | *bufptr++ = *valptr++; |
449 | |
|
450 | 0 | *bufptr = '\0'; |
451 | |
|
452 | 0 | return (buffer); |
453 | 0 | } |
454 | 0 | else |
455 | 0 | { |
456 | | /* |
457 | | * Skip this URI... |
458 | | */ |
459 | |
|
460 | 0 | while (*valptr && !_cups_isspace(*valptr)) |
461 | 0 | valptr++; |
462 | 0 | } |
463 | | |
464 | | /* |
465 | | * Skip whitespace... |
466 | | */ |
467 | | |
468 | 0 | while (_cups_isspace(*valptr)) |
469 | 0 | valptr ++; |
470 | 0 | } |
471 | | |
472 | 0 | return (NULL); |
473 | 0 | } |
474 | 0 | } |
475 | | |
476 | | |
477 | | /* |
478 | | * 'ppdLocalizeMarkerName()' - Get the localized version of a marker-names |
479 | | * attribute value. |
480 | | * |
481 | | * This function uses the current locale to find the corresponding name |
482 | | * text from the attribute value. If no localized text for the requested |
483 | | * name can be found, @code NULL@ is returned. |
484 | | * |
485 | | * @since CUPS 1.4/macOS 10.6@ |
486 | | */ |
487 | | |
488 | | const char * /* O - Value or @code NULL@ if not found */ |
489 | | ppdLocalizeMarkerName( |
490 | | ppd_file_t *ppd, /* I - PPD file */ |
491 | | const char *name) /* I - Marker name to look up */ |
492 | 0 | { |
493 | 0 | ppd_attr_t *locattr; /* Localized attribute */ |
494 | 0 | char ll_CC[6]; /* Language + country locale */ |
495 | | |
496 | | |
497 | | /* |
498 | | * Range check input... |
499 | | */ |
500 | |
|
501 | 0 | if (!ppd || !name) |
502 | 0 | return (NULL); |
503 | | |
504 | | /* |
505 | | * Get the default language... |
506 | | */ |
507 | | |
508 | 0 | ppd_ll_CC(ll_CC, sizeof(ll_CC)); |
509 | | |
510 | | /* |
511 | | * Find the localized attribute... |
512 | | */ |
513 | |
|
514 | 0 | if ((locattr = _ppdLocalizedAttr(ppd, "cupsMarkerName", name, |
515 | 0 | ll_CC)) == NULL) |
516 | 0 | locattr = ppdFindAttr(ppd, "cupsMarkerName", name); |
517 | |
|
518 | 0 | return (locattr ? locattr->text : NULL); |
519 | 0 | } |
520 | | |
521 | | |
522 | | /* |
523 | | * '_ppdFreeLanguages()' - Free an array of languages from _ppdGetLanguages. |
524 | | */ |
525 | | |
526 | | void |
527 | | _ppdFreeLanguages( |
528 | | cups_array_t *languages) /* I - Languages array */ |
529 | 0 | { |
530 | 0 | char *language; /* Current language */ |
531 | | |
532 | |
|
533 | 0 | for (language = (char *)cupsArrayFirst(languages); |
534 | 0 | language; |
535 | 0 | language = (char *)cupsArrayNext(languages)) |
536 | 0 | free(language); |
537 | |
|
538 | 0 | cupsArrayDelete(languages); |
539 | 0 | } |
540 | | |
541 | | |
542 | | /* |
543 | | * '_ppdGetLanguages()' - Get an array of languages from a PPD file. |
544 | | */ |
545 | | |
546 | | cups_array_t * /* O - Languages array */ |
547 | | _ppdGetLanguages(ppd_file_t *ppd) /* I - PPD file */ |
548 | 0 | { |
549 | 0 | cups_array_t *languages; /* Languages array */ |
550 | 0 | ppd_attr_t *attr; /* cupsLanguages attribute */ |
551 | 0 | char *value, /* Copy of attribute value */ |
552 | 0 | *start, /* Start of current language */ |
553 | 0 | *ptr; /* Pointer into languages */ |
554 | | |
555 | | |
556 | | /* |
557 | | * See if we have a cupsLanguages attribute... |
558 | | */ |
559 | |
|
560 | 0 | if ((attr = ppdFindAttr(ppd, "cupsLanguages", NULL)) == NULL || !attr->value) |
561 | 0 | return (NULL); |
562 | | |
563 | | /* |
564 | | * Yes, load the list... |
565 | | */ |
566 | | |
567 | 0 | if ((languages = cupsArrayNew((cups_array_func_t)strcmp, NULL)) == NULL) |
568 | 0 | return (NULL); |
569 | | |
570 | 0 | if ((value = strdup(attr->value)) == NULL) |
571 | 0 | { |
572 | 0 | cupsArrayDelete(languages); |
573 | 0 | return (NULL); |
574 | 0 | } |
575 | | |
576 | 0 | for (ptr = value; *ptr;) |
577 | 0 | { |
578 | | /* |
579 | | * Skip leading whitespace... |
580 | | */ |
581 | |
|
582 | 0 | while (_cups_isspace(*ptr)) |
583 | 0 | ptr ++; |
584 | |
|
585 | 0 | if (!*ptr) |
586 | 0 | break; |
587 | | |
588 | | /* |
589 | | * Find the end of this language name... |
590 | | */ |
591 | | |
592 | 0 | for (start = ptr; *ptr && !_cups_isspace(*ptr); ptr ++); |
593 | |
|
594 | 0 | if (*ptr) |
595 | 0 | *ptr++ = '\0'; |
596 | |
|
597 | 0 | if (!strcmp(start, "en")) |
598 | 0 | continue; |
599 | | |
600 | 0 | cupsArrayAdd(languages, strdup(start)); |
601 | 0 | } |
602 | | |
603 | | /* |
604 | | * Free the temporary string and return either an array with one or more |
605 | | * values or a NULL pointer... |
606 | | */ |
607 | |
|
608 | 0 | free(value); |
609 | |
|
610 | 0 | if (cupsArrayCount(languages) == 0) |
611 | 0 | { |
612 | 0 | cupsArrayDelete(languages); |
613 | 0 | return (NULL); |
614 | 0 | } |
615 | 0 | else |
616 | 0 | return (languages); |
617 | 0 | } |
618 | | |
619 | | |
620 | | /* |
621 | | * '_ppdHashName()' - Generate a hash value for a device or profile name. |
622 | | * |
623 | | * This function is primarily used on macOS, but is generally accessible |
624 | | * since cupstestppd needs to check for profile name collisions in PPD files... |
625 | | */ |
626 | | |
627 | | unsigned /* O - Hash value */ |
628 | | _ppdHashName(const char *name) /* I - Name to hash */ |
629 | 0 | { |
630 | 0 | unsigned mult, /* Multiplier */ |
631 | 0 | hash = 0; /* Hash value */ |
632 | | |
633 | |
|
634 | 0 | for (mult = 1; *name && mult <= 128; mult ++, name ++) |
635 | 0 | hash += (*name & 255) * mult; |
636 | |
|
637 | 0 | return (hash); |
638 | 0 | } |
639 | | |
640 | | |
641 | | /* |
642 | | * '_ppdLocalizedAttr()' - Find a localized attribute. |
643 | | */ |
644 | | |
645 | | ppd_attr_t * /* O - Localized attribute or NULL */ |
646 | | _ppdLocalizedAttr(ppd_file_t *ppd, /* I - PPD file */ |
647 | | const char *keyword, /* I - Main keyword */ |
648 | | const char *spec, /* I - Option keyword */ |
649 | | const char *ll_CC) /* I - Language + country locale */ |
650 | 0 | { |
651 | 0 | char lkeyword[PPD_MAX_NAME]; /* Localization keyword */ |
652 | 0 | ppd_attr_t *attr; /* Current attribute */ |
653 | | |
654 | |
|
655 | 0 | DEBUG_printf(("4_ppdLocalizedAttr(ppd=%p, keyword=\"%s\", spec=\"%s\", " |
656 | 0 | "ll_CC=\"%s\")", ppd, keyword, spec, ll_CC)); |
657 | | |
658 | | /* |
659 | | * Look for Keyword.ll_CC, then Keyword.ll... |
660 | | */ |
661 | |
|
662 | 0 | snprintf(lkeyword, sizeof(lkeyword), "%s.%s", ll_CC, keyword); |
663 | 0 | if ((attr = ppdFindAttr(ppd, lkeyword, spec)) == NULL) |
664 | 0 | { |
665 | | /* |
666 | | * <rdar://problem/22130168> |
667 | | * |
668 | | * Multiple locales need special handling... Sigh... |
669 | | */ |
670 | |
|
671 | 0 | if (!strcmp(ll_CC, "zh_HK")) |
672 | 0 | { |
673 | 0 | snprintf(lkeyword, sizeof(lkeyword), "zh_TW.%s", keyword); |
674 | 0 | attr = ppdFindAttr(ppd, lkeyword, spec); |
675 | 0 | } |
676 | |
|
677 | 0 | if (!attr) |
678 | 0 | { |
679 | 0 | snprintf(lkeyword, sizeof(lkeyword), "%2.2s.%s", ll_CC, keyword); |
680 | 0 | attr = ppdFindAttr(ppd, lkeyword, spec); |
681 | 0 | } |
682 | |
|
683 | 0 | if (!attr) |
684 | 0 | { |
685 | 0 | if (!strncmp(ll_CC, "ja", 2)) |
686 | 0 | { |
687 | | /* |
688 | | * Due to a bug in the CUPS DDK 1.1.0 ppdmerge program, Japanese |
689 | | * PPD files were incorrectly assigned "jp" as the locale name |
690 | | * instead of "ja". Support both the old (incorrect) and new |
691 | | * locale names for Japanese... |
692 | | */ |
693 | |
|
694 | 0 | snprintf(lkeyword, sizeof(lkeyword), "jp.%s", keyword); |
695 | 0 | attr = ppdFindAttr(ppd, lkeyword, spec); |
696 | 0 | } |
697 | 0 | else if (!strncmp(ll_CC, "nb", 2)) |
698 | 0 | { |
699 | | /* |
700 | | * Norway has two languages, "Bokmal" (the primary one) |
701 | | * and "Nynorsk" (new Norwegian); this code maps from the (currently) |
702 | | * recommended "nb" to the previously recommended "no"... |
703 | | */ |
704 | |
|
705 | 0 | snprintf(lkeyword, sizeof(lkeyword), "no.%s", keyword); |
706 | 0 | attr = ppdFindAttr(ppd, lkeyword, spec); |
707 | 0 | } |
708 | 0 | else if (!strncmp(ll_CC, "no", 2)) |
709 | 0 | { |
710 | | /* |
711 | | * Norway has two languages, "Bokmal" (the primary one) |
712 | | * and "Nynorsk" (new Norwegian); we map "no" to "nb" here as |
713 | | * recommended by the locale folks... |
714 | | */ |
715 | |
|
716 | 0 | snprintf(lkeyword, sizeof(lkeyword), "nb.%s", keyword); |
717 | 0 | attr = ppdFindAttr(ppd, lkeyword, spec); |
718 | 0 | } |
719 | 0 | } |
720 | 0 | } |
721 | |
|
722 | | #ifdef DEBUG |
723 | | if (attr) |
724 | | DEBUG_printf(("5_ppdLocalizedAttr: *%s %s/%s: \"%s\"\n", attr->name, |
725 | | attr->spec, attr->text, attr->value ? attr->value : "")); |
726 | | else |
727 | | DEBUG_puts("5_ppdLocalizedAttr: NOT FOUND"); |
728 | | #endif /* DEBUG */ |
729 | |
|
730 | 0 | return (attr); |
731 | 0 | } |
732 | | |
733 | | |
734 | | /* |
735 | | * 'ppd_ll_CC()' - Get the current locale names. |
736 | | */ |
737 | | |
738 | | static cups_lang_t * /* O - Current language */ |
739 | | ppd_ll_CC(char *ll_CC, /* O - Country-specific locale name */ |
740 | | size_t ll_CC_size) /* I - Size of country-specific name */ |
741 | 0 | { |
742 | 0 | cups_lang_t *lang; /* Current language */ |
743 | | |
744 | | |
745 | | /* |
746 | | * Get the current locale... |
747 | | */ |
748 | |
|
749 | 0 | if ((lang = cupsLangDefault()) == NULL) |
750 | 0 | { |
751 | 0 | strlcpy(ll_CC, "en_US", ll_CC_size); |
752 | 0 | return (NULL); |
753 | 0 | } |
754 | | |
755 | | /* |
756 | | * Copy the locale name... |
757 | | */ |
758 | | |
759 | 0 | strlcpy(ll_CC, lang->language, ll_CC_size); |
760 | |
|
761 | 0 | if (strlen(ll_CC) == 2) |
762 | 0 | { |
763 | | /* |
764 | | * Map "ll" to primary/origin country locales to have the best |
765 | | * chance of finding a match... |
766 | | */ |
767 | |
|
768 | 0 | if (!strcmp(ll_CC, "cs")) |
769 | 0 | strlcpy(ll_CC, "cs_CZ", ll_CC_size); |
770 | 0 | else if (!strcmp(ll_CC, "en")) |
771 | 0 | strlcpy(ll_CC, "en_US", ll_CC_size); |
772 | 0 | else if (!strcmp(ll_CC, "ja")) |
773 | 0 | strlcpy(ll_CC, "ja_JP", ll_CC_size); |
774 | 0 | else if (!strcmp(ll_CC, "sv")) |
775 | 0 | strlcpy(ll_CC, "sv_SE", ll_CC_size); |
776 | 0 | else if (!strcmp(ll_CC, "zh")) /* Simplified Chinese */ |
777 | 0 | strlcpy(ll_CC, "zh_CN", ll_CC_size); |
778 | 0 | } |
779 | |
|
780 | 0 | DEBUG_printf(("8ppd_ll_CC: lang->language=\"%s\", ll_CC=\"%s\"...", |
781 | 0 | lang->language, ll_CC)); |
782 | 0 | return (lang); |
783 | 0 | } |