]> git.ipfire.org Git - thirdparty/cups.git/blame - cups/language.c
Merge changes from CUPS 1.6svn-r10310.
[thirdparty/cups.git] / cups / language.c
CommitLineData
ef416fc2 1/*
75bd9771 2 * "$Id: language.c 7558 2008-05-12 23:46:44Z mike $"
ef416fc2 3 *
71e16022 4 * I18N/language support for CUPS.
ef416fc2 5 *
e60ec91f 6 * Copyright 2007-2011 by Apple Inc.
b86bc4cf 7 * Copyright 1997-2007 by Easy Software Products.
ef416fc2 8 *
9 * These coded instructions, statements, and computer programs are the
bc44d920 10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file. If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
ef416fc2 14 *
15 * This file is subject to the Apple OS-Developed Software exception.
16 *
17 * Contents:
18 *
e60ec91f
MS
19 * _cupsAppleLanguage() - Get the Apple language identifier associated with
20 * a locale ID.
21 * _cupsEncodingName() - Return the character encoding name string for the
22 * given encoding enumeration.
23 * cupsLangDefault() - Return the default language.
24 * cupsLangEncoding() - Return the character encoding (us-ascii, etc.) for
25 * the given language.
26 * cupsLangFlush() - Flush all language data out of the cache.
27 * cupsLangFree() - Free language data.
28 * cupsLangGet() - Get a language.
29 * _cupsLangString() - Get a message string.
30 * _cupsMessageFree() - Free a messages array.
31 * _cupsMessageLoad() - Load a .po file into a messages array.
32 * _cupsMessageLookup() - Lookup a message string.
33 * appleLangDefault() - Get the default locale string.
34 * appleMessageLoad() - Load a message catalog from a localizable bundle.
35 * cups_cache_lookup() - Lookup a language in the cache...
36 * cups_message_compare() - Compare two messages.
37 * cups_message_free() - Free a message.
9c80ffa2 38 * cups_message_load() - Load the message catalog for a language.
e60ec91f 39 * cups_unquote() - Unquote characters in strings...
ef416fc2 40 */
41
42/*
43 * Include necessary headers...
44 */
45
71e16022 46#include "cups-private.h"
ef416fc2 47#ifdef HAVE_LANGINFO_H
48# include <langinfo.h>
49#endif /* HAVE_LANGINFO_H */
50#ifdef WIN32
51# include <io.h>
52#else
53# include <unistd.h>
54#endif /* WIN32 */
fa73b229 55#ifdef HAVE_COREFOUNDATION_H
56# include <CoreFoundation/CoreFoundation.h>
57#endif /* HAVE_COREFOUNDATION_H */
ef416fc2 58
59
d6ae789d 60/*
61 * Local globals...
62 */
63
6d2f911b 64static _cups_mutex_t lang_mutex = _CUPS_MUTEX_INITIALIZER;
d6ae789d 65 /* Mutex to control access to cache */
d6ae789d 66static cups_lang_t *lang_cache = NULL;
67 /* Language string cache */
ef416fc2 68static const char * const lang_encodings[] =
69 { /* Encoding strings */
70 "us-ascii", "iso-8859-1",
71 "iso-8859-2", "iso-8859-3",
72 "iso-8859-4", "iso-8859-5",
73 "iso-8859-6", "iso-8859-7",
74 "iso-8859-8", "iso-8859-9",
75 "iso-8859-10", "utf-8",
76 "iso-8859-13", "iso-8859-14",
cc754834
MS
77 "iso-8859-15", "cp874",
78 "cp1250", "cp1251",
79 "cp1252", "cp1253",
80 "cp1254", "cp1255",
81 "cp1256", "cp1257",
82 "cp1258", "koi8-r",
ef416fc2 83 "koi8-u", "iso-8859-11",
a2326b5b 84 "iso-8859-16", "mac",
ef416fc2 85 "unknown", "unknown",
86 "unknown", "unknown",
87 "unknown", "unknown",
88 "unknown", "unknown",
89 "unknown", "unknown",
90 "unknown", "unknown",
91 "unknown", "unknown",
92 "unknown", "unknown",
93 "unknown", "unknown",
94 "unknown", "unknown",
95 "unknown", "unknown",
96 "unknown", "unknown",
97 "unknown", "unknown",
98 "unknown", "unknown",
99 "unknown", "unknown",
100 "unknown", "unknown",
101 "unknown", "unknown",
cc754834
MS
102 "cp932", "cp936",
103 "cp949", "cp950",
104 "cp1361", "unknown",
ef416fc2 105 "unknown", "unknown",
106 "unknown", "unknown",
107 "unknown", "unknown",
108 "unknown", "unknown",
109 "unknown", "unknown",
110 "unknown", "unknown",
111 "unknown", "unknown",
112 "unknown", "unknown",
113 "unknown", "unknown",
114 "unknown", "unknown",
115 "unknown", "unknown",
116 "unknown", "unknown",
117 "unknown", "unknown",
118 "unknown", "unknown",
119 "unknown", "unknown",
120 "unknown", "unknown",
121 "unknown", "unknown",
122 "unknown", "unknown",
123 "unknown", "unknown",
124 "unknown", "unknown",
125 "unknown", "unknown",
126 "unknown", "unknown",
127 "unknown", "unknown",
128 "unknown", "unknown",
129 "unknown", "unknown",
130 "unknown", "unknown",
131 "unknown", "unknown",
132 "unknown", "unknown",
133 "unknown", "unknown",
134 "euc-cn", "euc-jp",
4b3f67ff
MS
135 "euc-kr", "euc-tw",
136 "jis-x0213"
ef416fc2 137 };
138
0a682745
MS
139#ifdef __APPLE__
140typedef struct
141{
142 const char * const language; /* Language ID */
143 const char * const locale; /* Locale ID */
144} _apple_language_locale_t;
145
146static const _apple_language_locale_t apple_language_locale[] =
147{ /* Locale to language ID LUT */
0837b7e8
MS
148 { "en", "en_US" },
149 { "nb", "no" },
150 { "zh-Hans", "zh_CN" },
151 { "zh-Hant", "zh_TW" }
0a682745
MS
152};
153#endif /* __APPLE__ */
154
155
156/*
157 * Local functions...
158 */
159
82f97232 160
0a682745
MS
161#ifdef __APPLE__
162static const char *appleLangDefault(void);
0837b7e8 163# ifdef CUPS_BUNDLEDIR
82f97232
MS
164# ifndef CF_RETURNS_RETAINED
165# if __has_feature(attribute_cf_returns_retained)
166# define CF_RETURNS_RETAINED __attribute__((cf_returns_retained))
167# else
168# define CF_RETURNS_RETAINED
169# endif /* __has_feature(attribute_cf_returns_retained) */
170# endif /* !CF_RETURNED_RETAINED */
171static cups_array_t *appleMessageLoad(const char *locale)
172 CF_RETURNS_RETAINED;
0837b7e8 173# endif /* CUPS_BUNDLEDIR */
0a682745
MS
174#endif /* __APPLE__ */
175static cups_lang_t *cups_cache_lookup(const char *name,
176 cups_encoding_t encoding);
177static int cups_message_compare(_cups_message_t *m1,
178 _cups_message_t *m2);
0837b7e8 179static void cups_message_free(_cups_message_t *m);
9c80ffa2 180static void cups_message_load(cups_lang_t *lang);
0a682745
MS
181static void cups_unquote(char *d, const char *s);
182
183
184#ifdef __APPLE__
185/*
0837b7e8
MS
186 * '_cupsAppleLanguage()' - Get the Apple language identifier associated with a
187 * locale ID.
0a682745
MS
188 */
189
190const char * /* O - Language ID */
191_cupsAppleLanguage(const char *locale, /* I - Locale ID */
192 char *language,/* I - Language ID buffer */
193 size_t langsize) /* I - Size of language ID buffer */
194{
195 int i; /* Looping var */
196 CFStringRef localeid, /* CF locale identifier */
197 langid; /* CF language identifier */
198
199
200 /*
201 * Copy the locale name and convert, as needed, to the Apple-specific
202 * locale identifier...
203 */
204
205 switch (strlen(locale))
206 {
207 default :
208 /*
209 * Invalid locale...
210 */
211
212 strlcpy(language, "en", langsize);
213 break;
214
215 case 2 :
216 strlcpy(language, locale, langsize);
217 break;
218
219 case 5 :
220 strlcpy(language, locale, langsize);
221
222 if (language[2] == '-')
223 {
224 /*
225 * Convert ll-cc to ll_CC...
226 */
227
228 language[2] = '_';
229 language[3] = toupper(language[3] & 255);
230 language[4] = toupper(language[4] & 255);
231 }
232 break;
233 }
234
235 for (i = 0;
236 i < (int)(sizeof(apple_language_locale) /
237 sizeof(apple_language_locale[0]));
238 i ++)
239 if (!strcmp(locale, apple_language_locale[i].locale))
240 {
241 strlcpy(language, apple_language_locale[i].language, sizeof(language));
242 break;
243 }
244
245 /*
246 * Attempt to map the locale ID to a language ID...
247 */
248
249 if ((localeid = CFStringCreateWithCString(kCFAllocatorDefault, language,
250 kCFStringEncodingASCII)) != NULL)
251 {
252 if ((langid = CFLocaleCreateCanonicalLanguageIdentifierFromString(
253 kCFAllocatorDefault, localeid)) != NULL)
254 {
255 CFStringGetCString(langid, language, langsize, kCFStringEncodingASCII);
256 CFRelease(langid);
257 }
258
259 CFRelease(localeid);
260 }
261
262 /*
263 * Return what we got...
264 */
265
266 return (language);
267}
268#endif /* __APPLE__ */
269
ef416fc2 270
271/*
272 * '_cupsEncodingName()' - Return the character encoding name string
273 * for the given encoding enumeration.
274 */
275
276const char * /* O - Character encoding */
277_cupsEncodingName(
278 cups_encoding_t encoding) /* I - Encoding value */
279{
280 if (encoding < 0 ||
281 encoding >= (sizeof(lang_encodings) / sizeof(const char *)))
c7017ecc
MS
282 {
283 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = out of range (\"%s\")",
284 encoding, lang_encodings[0]));
ef416fc2 285 return (lang_encodings[0]);
c7017ecc 286 }
ef416fc2 287 else
c7017ecc
MS
288 {
289 DEBUG_printf(("1_cupsEncodingName(encoding=%d) = \"%s\"",
290 encoding, lang_encodings[encoding]));
ef416fc2 291 return (lang_encodings[encoding]);
c7017ecc 292 }
ef416fc2 293}
294
295
296/*
297 * 'cupsLangDefault()' - Return the default language.
298 */
299
300cups_lang_t * /* O - Language data */
301cupsLangDefault(void)
302{
303 return (cupsLangGet(NULL));
304}
305
306
307/*
308 * 'cupsLangEncoding()' - Return the character encoding (us-ascii, etc.)
309 * for the given language.
310 */
311
312const char * /* O - Character encoding */
313cupsLangEncoding(cups_lang_t *lang) /* I - Language data */
314{
315 if (lang == NULL)
316 return ((char*)lang_encodings[0]);
317 else
318 return ((char*)lang_encodings[lang->encoding]);
319}
320
321
322/*
323 * 'cupsLangFlush()' - Flush all language data out of the cache.
324 */
325
326void
327cupsLangFlush(void)
328{
329 cups_lang_t *lang, /* Current language */
330 *next; /* Next language */
ef416fc2 331
332
333 /*
334 * Free all languages in the cache...
335 */
336
6d2f911b 337 _cupsMutexLock(&lang_mutex);
d6ae789d 338
339 for (lang = lang_cache; lang != NULL; lang = next)
ef416fc2 340 {
341 /*
342 * Free all messages...
343 */
344
345 _cupsMessageFree(lang->strings);
346
347 /*
348 * Then free the language structure itself...
349 */
350
351 next = lang->next;
352 free(lang);
353 }
354
d6ae789d 355 lang_cache = NULL;
356
6d2f911b 357 _cupsMutexUnlock(&lang_mutex);
ef416fc2 358}
359
360
361/*
362 * 'cupsLangFree()' - Free language data.
363 *
5a738aea 364 * This does not actually free anything; use @link cupsLangFlush@ for that.
ef416fc2 365 */
366
367void
368cupsLangFree(cups_lang_t *lang) /* I - Language to free */
369{
6d2f911b 370 _cupsMutexLock(&lang_mutex);
d6ae789d 371
ef416fc2 372 if (lang != NULL && lang->used > 0)
373 lang->used --;
d6ae789d 374
6d2f911b 375 _cupsMutexUnlock(&lang_mutex);
ef416fc2 376}
377
378
379/*
380 * 'cupsLangGet()' - Get a language.
381 */
382
383cups_lang_t * /* O - Language data */
384cupsLangGet(const char *language) /* I - Language or locale */
385{
386 int i; /* Looping var */
480ef0fe 387#ifndef __APPLE__
388 char locale[255]; /* Copy of locale name */
389#endif /* !__APPLE__ */
390 char langname[16], /* Requested language name */
ef416fc2 391 country[16], /* Country code */
392 charset[16], /* Character set */
ef416fc2 393 *csptr, /* Pointer to CODESET string */
ef416fc2 394 *ptr, /* Pointer into language/charset */
0837b7e8 395 real[48]; /* Real language name */
ef416fc2 396 cups_encoding_t encoding; /* Encoding to use */
397 cups_lang_t *lang; /* Current language... */
ef416fc2 398 static const char * const locale_encodings[] =
399 { /* Locale charset names */
400 "ASCII", "ISO88591", "ISO88592", "ISO88593",
401 "ISO88594", "ISO88595", "ISO88596", "ISO88597",
402 "ISO88598", "ISO88599", "ISO885910", "UTF8",
403 "ISO885913", "ISO885914", "ISO885915", "CP874",
404 "CP1250", "CP1251", "CP1252", "CP1253",
405 "CP1254", "CP1255", "CP1256", "CP1257",
406 "CP1258", "KOI8R", "KOI8U", "ISO885911",
bd7854cb 407 "ISO885916", "MACROMAN", "", "",
ef416fc2 408
409 "", "", "", "",
410 "", "", "", "",
411 "", "", "", "",
412 "", "", "", "",
413 "", "", "", "",
414 "", "", "", "",
415 "", "", "", "",
416 "", "", "", "",
417
418 "CP932", "CP936", "CP949", "CP950",
419 "CP1361", "", "", "",
420 "", "", "", "",
421 "", "", "", "",
422 "", "", "", "",
423 "", "", "", "",
424 "", "", "", "",
425 "", "", "", "",
426
427 "", "", "", "",
428 "", "", "", "",
429 "", "", "", "",
430 "", "", "", "",
431 "", "", "", "",
432 "", "", "", "",
433 "", "", "", "",
434 "", "", "", "",
435
4b3f67ff
MS
436 "EUCCN", "EUCJP", "EUCKR", "EUCTW",
437 "SHIFT_JISX0213"
ef416fc2 438 };
439
440
f11a948a 441 DEBUG_printf(("2cupsLangGet(language=\"%s\")", language));
ef416fc2 442
443#ifdef __APPLE__
757d2cad 444 /*
445 * Set the character set to UTF-8...
446 */
447
448 strcpy(charset, "UTF8");
449
ef416fc2 450 /*
c8fef167 451 * Apple's setlocale doesn't give us the user's localization
ef416fc2 452 * preference so we have to look it up this way...
453 */
454
c9fc04c6
MS
455 if (!language)
456 {
b0f6947b 457 if (!getenv("SOFTWARE") || (language = getenv("LANG")) == NULL)
c9fc04c6
MS
458 language = appleLangDefault();
459
f11a948a 460 DEBUG_printf(("4cupsLangGet: language=\"%s\"", language));
c9fc04c6 461 }
757d2cad 462
ef416fc2 463#else
757d2cad 464 /*
465 * Set the charset to "unknown"...
466 */
467
468 charset[0] = '\0';
469
470 /*
471 * Use setlocale() to determine the currently set locale, and then
472 * fallback to environment variables to avoid setting the locale,
473 * since setlocale() is not thread-safe!
474 */
475
476 if (!language)
ef416fc2 477 {
478 /*
479 * First see if the locale has been set; if it is still "C" or
757d2cad 480 * "POSIX", use the environment to get the default...
ef416fc2 481 */
482
483# ifdef LC_MESSAGES
484 ptr = setlocale(LC_MESSAGES, NULL);
485# else
486 ptr = setlocale(LC_ALL, NULL);
487# endif /* LC_MESSAGES */
488
f11a948a 489 DEBUG_printf(("4cupsLangGet: current locale is \"%s\"", ptr));
ef416fc2 490
491 if (!ptr || !strcmp(ptr, "C") || !strcmp(ptr, "POSIX"))
ef416fc2 492 {
757d2cad 493 /*
494 * Get the character set from the LC_CTYPE locale setting...
495 */
496
497 if ((ptr = getenv("LC_CTYPE")) == NULL)
498 if ((ptr = getenv("LC_ALL")) == NULL)
499 if ((ptr = getenv("LANG")) == NULL)
500 ptr = "en_US";
501
502 if ((csptr = strchr(ptr, '.')) != NULL)
503 {
504 /*
505 * Extract the character set from the environment...
506 */
507
508 for (ptr = charset, csptr ++; *csptr; csptr ++)
7cf5915e 509 if (ptr < (charset + sizeof(charset) - 1) && _cups_isalnum(*csptr))
757d2cad 510 *ptr++ = *csptr;
511
512 *ptr = '\0';
513 }
757d2cad 514
515 /*
516 * Get the locale for messages from the LC_MESSAGES locale setting...
517 */
518
519 if ((ptr = getenv("LC_MESSAGES")) == NULL)
520 if ((ptr = getenv("LC_ALL")) == NULL)
521 if ((ptr = getenv("LANG")) == NULL)
522 ptr = "en_US";
ef416fc2 523 }
ef416fc2 524
525 if (ptr)
526 {
527 strlcpy(locale, ptr, sizeof(locale));
528 language = locale;
529
a41f09e2
MS
530 /*
531 * CUPS STR #2575: Map "nb" to "no" for back-compatibility...
532 */
533
534 if (!strncmp(locale, "nb", 2))
535 locale[1] = 'o';
536
f11a948a 537 DEBUG_printf(("4cupsLangGet: new language value is \"%s\"", language));
ef416fc2 538 }
539 }
540#endif /* __APPLE__ */
541
542 /*
543 * If "language" is NULL at this point, then chances are we are using
544 * a language that is not installed for the base OS.
545 */
546
547 if (!language)
548 {
549 /*
757d2cad 550 * Switch to the POSIX ("C") locale...
ef416fc2 551 */
552
757d2cad 553 language = "C";
ef416fc2 554 }
555
ef416fc2 556#ifdef CODESET
557 /*
558 * On systems that support the nl_langinfo(CODESET) call, use
559 * this value as the character set...
560 */
561
757d2cad 562 if (!charset[0] && (csptr = nl_langinfo(CODESET)) != NULL)
ef416fc2 563 {
564 /*
565 * Copy all of the letters and numbers in the CODESET string...
566 */
567
568 for (ptr = charset; *csptr; csptr ++)
7cf5915e 569 if (_cups_isalnum(*csptr) && ptr < (charset + sizeof(charset) - 1))
ef416fc2 570 *ptr++ = *csptr;
571
572 *ptr = '\0';
573
f11a948a 574 DEBUG_printf(("4cupsLangGet: charset set to \"%s\" via "
e07d4801 575 "nl_langinfo(CODESET)...", charset));
ef416fc2 576 }
577#endif /* CODESET */
578
07725fee 579 /*
580 * If we don't have a character set by now, default to UTF-8...
581 */
582
583 if (!charset[0])
584 strcpy(charset, "UTF8");
585
ef416fc2 586 /*
587 * Parse the language string passed in to a locale string. "C" is the
588 * standard POSIX locale and is copied unchanged. Otherwise the
589 * language string is converted from ll-cc[.charset] (language-country)
590 * to ll_CC[.CHARSET] to match the file naming convention used by all
591 * POSIX-compliant operating systems. Invalid language names are mapped
592 * to the POSIX locale.
593 */
594
595 country[0] = '\0';
596
597 if (language == NULL || !language[0] ||
598 !strcmp(language, "POSIX"))
599 strcpy(langname, "C");
600 else
601 {
602 /*
603 * Copy the parts of the locale string over safely...
604 */
605
606 for (ptr = langname; *language; language ++)
607 if (*language == '_' || *language == '-' || *language == '.')
608 break;
609 else if (ptr < (langname + sizeof(langname) - 1))
610 *ptr++ = tolower(*language & 255);
611
612 *ptr = '\0';
613
614 if (*language == '_' || *language == '-')
615 {
616 /*
617 * Copy the country code...
618 */
619
620 for (language ++, ptr = country; *language; language ++)
621 if (*language == '.')
622 break;
623 else if (ptr < (country + sizeof(country) - 1))
624 *ptr++ = toupper(*language & 255);
625
626 *ptr = '\0';
627 }
628
629 if (*language == '.' && !charset[0])
630 {
631 /*
632 * Copy the encoding...
633 */
634
635 for (language ++, ptr = charset; *language; language ++)
7cf5915e 636 if (_cups_isalnum(*language) && ptr < (charset + sizeof(charset) - 1))
ef416fc2 637 *ptr++ = toupper(*language & 255);
638
639 *ptr = '\0';
640 }
641
642 /*
643 * Force a POSIX locale for an invalid language name...
644 */
645
646 if (strlen(langname) != 2)
647 {
648 strcpy(langname, "C");
649 country[0] = '\0';
650 charset[0] = '\0';
651 }
652 }
653
f11a948a 654 DEBUG_printf(("4cupsLangGet: langname=\"%s\", country=\"%s\", charset=\"%s\"",
ef416fc2 655 langname, country, charset));
656
657 /*
658 * Figure out the desired encoding...
659 */
660
661 encoding = CUPS_AUTO_ENCODING;
662
663 if (charset[0])
664 {
bd7854cb 665 for (i = 0;
666 i < (int)(sizeof(locale_encodings) / sizeof(locale_encodings[0]));
667 i ++)
88f9aafc 668 if (!_cups_strcasecmp(charset, locale_encodings[i]))
ef416fc2 669 {
670 encoding = (cups_encoding_t)i;
671 break;
672 }
8ca02f3c 673
674 if (encoding == CUPS_AUTO_ENCODING)
675 {
676 /*
677 * Map alternate names for various character sets...
678 */
679
88f9aafc
MS
680 if (!_cups_strcasecmp(charset, "iso-2022-jp") ||
681 !_cups_strcasecmp(charset, "sjis"))
8ca02f3c 682 encoding = CUPS_WINDOWS_932;
88f9aafc 683 else if (!_cups_strcasecmp(charset, "iso-2022-cn"))
8ca02f3c 684 encoding = CUPS_WINDOWS_936;
88f9aafc 685 else if (!_cups_strcasecmp(charset, "iso-2022-kr"))
8ca02f3c 686 encoding = CUPS_WINDOWS_949;
88f9aafc 687 else if (!_cups_strcasecmp(charset, "big5"))
8ca02f3c 688 encoding = CUPS_WINDOWS_950;
689 }
ef416fc2 690 }
691
f11a948a 692 DEBUG_printf(("4cupsLangGet: encoding=%d(%s)", encoding,
ef416fc2 693 encoding == CUPS_AUTO_ENCODING ? "auto" :
694 lang_encodings[encoding]));
695
696 /*
697 * See if we already have this language/country loaded...
698 */
699
bd7854cb 700 if (country[0])
bd7854cb 701 snprintf(real, sizeof(real), "%s_%s", langname, country);
bd7854cb 702 else
d6ae789d 703 strcpy(real, langname);
d6ae789d 704
6d2f911b 705 _cupsMutexLock(&lang_mutex);
d6ae789d 706
c0e1af83 707 if ((lang = cups_cache_lookup(real, encoding)) != NULL)
d6ae789d 708 {
6d2f911b 709 _cupsMutexUnlock(&lang_mutex);
d6ae789d 710
f11a948a 711 DEBUG_printf(("3cupsLangGet: Using cached copy of \"%s\"...", real));
c9fc04c6 712
d6ae789d 713 return (lang);
714 }
ef416fc2 715
ef416fc2 716 /*
717 * See if there is a free language available; if so, use that
718 * record...
719 */
720
d6ae789d 721 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 722 if (lang->used == 0)
723 break;
724
725 if (lang == NULL)
726 {
727 /*
728 * Allocate memory for the language and add it to the cache.
729 */
730
731 if ((lang = calloc(sizeof(cups_lang_t), 1)) == NULL)
d6ae789d 732 {
6d2f911b 733 _cupsMutexUnlock(&lang_mutex);
d6ae789d 734
ef416fc2 735 return (NULL);
d6ae789d 736 }
ef416fc2 737
d6ae789d 738 lang->next = lang_cache;
739 lang_cache = lang;
ef416fc2 740 }
bd7854cb 741 else
742 {
743 /*
744 * Free all old strings as needed...
745 */
ef416fc2 746
bd7854cb 747 _cupsMessageFree(lang->strings);
9c80ffa2 748 lang->strings = NULL;
bd7854cb 749 }
ef416fc2 750
751 /*
752 * Then assign the language and encoding fields...
753 */
754
755 lang->used ++;
756 strlcpy(lang->language, real, sizeof(lang->language));
757
758 if (encoding != CUPS_AUTO_ENCODING)
759 lang->encoding = encoding;
760 else
761 lang->encoding = CUPS_UTF8;
762
ef416fc2 763 /*
764 * Return...
765 */
766
6d2f911b 767 _cupsMutexUnlock(&lang_mutex);
d6ae789d 768
ef416fc2 769 return (lang);
770}
771
772
773/*
774 * '_cupsLangString()' - Get a message string.
775 *
776 * The returned string is UTF-8 encoded; use cupsUTF8ToCharset() to
777 * convert the string to the language encoding.
778 */
779
780const char * /* O - Localized message */
781_cupsLangString(cups_lang_t *lang, /* I - Language */
782 const char *message) /* I - Message */
783{
6d2f911b
MS
784 const char *s; /* Localized message */
785
ef416fc2 786 /*
787 * Range check input...
788 */
789
790 if (!lang || !message)
791 return (message);
792
6d2f911b 793 _cupsMutexLock(&lang_mutex);
d6ae789d 794
9c80ffa2
MS
795 /*
796 * Load the message catalog if needed...
797 */
798
799 if (!lang->strings)
800 cups_message_load(lang);
801
6d2f911b 802 s = _cupsMessageLookup(lang->strings, message);
d6ae789d 803
6d2f911b 804 _cupsMutexUnlock(&lang_mutex);
d6ae789d 805
6d2f911b 806 return (s);
ef416fc2 807}
808
809
810/*
811 * '_cupsMessageFree()' - Free a messages array.
812 */
813
814void
815_cupsMessageFree(cups_array_t *a) /* I - Message array */
816{
0837b7e8
MS
817#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
818 /*
819 * Release the cups.strings dictionary as needed...
820 */
ef416fc2 821
0837b7e8
MS
822 if (cupsArrayUserData(a))
823 CFRelease((CFDictionaryRef)cupsArrayUserData(a));
824#endif /* __APPLE__ && CUPS_BUNDLEDIR */
ef416fc2 825
826 /*
827 * Free the array...
828 */
829
830 cupsArrayDelete(a);
831}
832
833
834/*
835 * '_cupsMessageLoad()' - Load a .po file into a messages array.
836 */
837
838cups_array_t * /* O - New message array */
8b116e60 839_cupsMessageLoad(const char *filename, /* I - Message catalog to load */
0837b7e8 840 int unquote) /* I - Unescape \foo in strings? */
ef416fc2 841{
842 cups_file_t *fp; /* Message file */
843 cups_array_t *a; /* Message array */
844 _cups_message_t *m; /* Current message */
845 char s[4096], /* String buffer */
846 *ptr, /* Pointer into buffer */
847 *temp; /* New string */
848 int length; /* Length of combined strings */
849
850
e07d4801 851 DEBUG_printf(("4_cupsMessageLoad(filename=\"%s\")", filename));
bd7854cb 852
ef416fc2 853 /*
854 * Create an array to hold the messages...
855 */
856
0837b7e8
MS
857 if ((a = cupsArrayNew3((cups_array_func_t)cups_message_compare, NULL,
858 (cups_ahash_func_t)NULL, 0,
859 (cups_acopy_func_t)NULL,
860 (cups_afree_func_t)cups_message_free)) == NULL)
c9fc04c6 861 {
e07d4801 862 DEBUG_puts("5_cupsMessageLoad: Unable to allocate array!");
ef416fc2 863 return (NULL);
c9fc04c6 864 }
ef416fc2 865
866 /*
867 * Open the message catalog file...
868 */
869
870 if ((fp = cupsFileOpen(filename, "r")) == NULL)
c9fc04c6 871 {
e07d4801 872 DEBUG_printf(("5_cupsMessageLoad: Unable to open file: %s",
c9fc04c6 873 strerror(errno)));
ef416fc2 874 return (a);
c9fc04c6 875 }
ef416fc2 876
877 /*
878 * Read messages from the catalog file until EOF...
879 *
880 * The format is the GNU gettext .po format, which is fairly simple:
881 *
882 * msgid "some text"
883 * msgstr "localized text"
884 *
bd7854cb 885 * The ID and localized text can span multiple lines using the form:
ef416fc2 886 *
bd7854cb 887 * msgid ""
888 * "some long text"
889 * msgstr ""
890 * "localized text spanning "
ef416fc2 891 * "multiple lines"
892 */
893
894 m = NULL;
895
896 while (cupsFileGets(fp, s, sizeof(s)) != NULL)
897 {
898 /*
899 * Skip blank and comment lines...
900 */
901
902 if (s[0] == '#' || !s[0])
903 continue;
904
905 /*
906 * Strip the trailing quote...
907 */
908
909 if ((ptr = strrchr(s, '\"')) == NULL)
910 continue;
911
912 *ptr = '\0';
913
914 /*
915 * Find start of value...
916 */
c8fef167 917
ef416fc2 918 if ((ptr = strchr(s, '\"')) == NULL)
919 continue;
920
921 ptr ++;
922
923 /*
924 * Unquote the text...
925 */
926
8b116e60
MS
927 if (unquote)
928 cups_unquote(ptr, ptr);
ef416fc2 929
930 /*
931 * Create or add to a message...
932 */
933
934 if (!strncmp(s, "msgid", 5))
935 {
bd7854cb 936 /*
937 * Add previous message as needed...
938 */
939
940 if (m)
941 cupsArrayAdd(a, m);
942
943 /*
944 * Create a new message with the given msgid string...
945 */
946
ef416fc2 947 if ((m = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
948 {
949 cupsFileClose(fp);
950 return (a);
951 }
952
91c84a35
MS
953 if ((m->id = strdup(ptr)) == NULL)
954 {
955 free(m);
956 cupsFileClose(fp);
957 return (a);
958 }
ef416fc2 959 }
bd7854cb 960 else if (s[0] == '\"' && m)
ef416fc2 961 {
bd7854cb 962 /*
963 * Append to current string...
964 */
ef416fc2 965
b86bc4cf 966 length = (int)strlen(m->str ? m->str : m->id);
ef416fc2 967
bd7854cb 968 if ((temp = realloc(m->str ? m->str : m->id,
969 length + strlen(ptr) + 1)) == NULL)
970 {
971 cupsFileClose(fp);
972 return (a);
973 }
ef416fc2 974
bd7854cb 975 if (m->str)
976 {
ef416fc2 977 /*
bd7854cb 978 * Copy the new portion to the end of the msgstr string - safe
979 * to use strcpy because the buffer is allocated to the correct
980 * size...
ef416fc2 981 */
982
bd7854cb 983 m->str = temp;
984
ef416fc2 985 strcpy(m->str + length, ptr);
986 }
987 else
988 {
989 /*
bd7854cb 990 * Copy the new portion to the end of the msgid string - safe
991 * to use strcpy because the buffer is allocated to the correct
992 * size...
ef416fc2 993 */
994
bd7854cb 995 m->id = temp;
996
997 strcpy(m->id + length, ptr);
ef416fc2 998 }
999 }
bd7854cb 1000 else if (!strncmp(s, "msgstr", 6) && m)
1001 {
1002 /*
1003 * Set the string...
1004 */
1005
91c84a35
MS
1006 if ((m->str = strdup(ptr)) == NULL)
1007 {
1008 cupsFileClose(fp);
1009 return (a);
1010 }
bd7854cb 1011 }
ef416fc2 1012 }
1013
bd7854cb 1014 /*
1015 * Add the last message string to the array as needed...
1016 */
1017
1018 if (m)
1019 cupsArrayAdd(a, m);
1020
ef416fc2 1021 /*
1022 * Close the message catalog file and return the new array...
1023 */
1024
1025 cupsFileClose(fp);
1026
e07d4801 1027 DEBUG_printf(("5_cupsMessageLoad: Returning %d messages...",
c9fc04c6
MS
1028 cupsArrayCount(a)));
1029
ef416fc2 1030 return (a);
1031}
1032
1033
1034/*
1035 * '_cupsMessageLookup()' - Lookup a message string.
1036 */
1037
1038const char * /* O - Localized message */
1039_cupsMessageLookup(cups_array_t *a, /* I - Message array */
1040 const char *m) /* I - Message */
1041{
1042 _cups_message_t key, /* Search key */
1043 *match; /* Matching message */
1044
1045
1046 /*
1047 * Lookup the message string; if it doesn't exist in the catalog,
1048 * then return the message that was passed to us...
1049 */
1050
1051 key.id = (char *)m;
1052 match = (_cups_message_t *)cupsArrayFind(a, &key);
1053
0837b7e8
MS
1054#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1055 if (!match && cupsArrayUserData(a))
1056 {
1057 /*
1058 * Try looking the string up in the cups.strings dictionary...
1059 */
1060
1061 CFDictionaryRef dict; /* cups.strings dictionary */
1062 CFStringRef cfm, /* Message as a CF string */
1063 cfstr; /* Localized text as a CF string */
1064
1065 dict = (CFDictionaryRef)cupsArrayUserData(a);
1066 cfm = CFStringCreateWithCString(kCFAllocatorDefault, m,
1067 kCFStringEncodingUTF8);
1068 match = calloc(1, sizeof(_cups_message_t));
1069 match->id = strdup(m);
1070 cfstr = cfm ? CFDictionaryGetValue(dict, cfm) : NULL;
1071
1072 if (cfstr)
1073 {
1074 char buffer[1024]; /* Message buffer */
1075
1076 CFStringGetCString(cfstr, buffer, sizeof(buffer), kCFStringEncodingUTF8);
1077 match->str = strdup(buffer);
c8fef167
MS
1078
1079 DEBUG_printf(("1_cupsMessageLookup: Found \"%s\" as \"%s\"...",
1080 m, buffer));
0837b7e8
MS
1081 }
1082 else
c8fef167 1083 {
0837b7e8
MS
1084 match->str = strdup(m);
1085
c8fef167
MS
1086 DEBUG_printf(("1_cupsMessageLookup: Did not find \"%s\"...", m));
1087 }
1088
0837b7e8 1089 cupsArrayAdd(a, match);
e60ec91f
MS
1090
1091 if (cfm)
1092 CFRelease(cfm);
0837b7e8
MS
1093 }
1094#endif /* __APPLE__ && CUPS_BUNDLEDIR */
1095
ef416fc2 1096 if (match && match->str)
1097 return (match->str);
1098 else
1099 return (m);
1100}
1101
1102
ef416fc2 1103#ifdef __APPLE__
ef416fc2 1104/*
1105 * 'appleLangDefault()' - Get the default locale string.
1106 */
1107
1108static const char * /* O - Locale string */
1109appleLangDefault(void)
1110{
b94498cf 1111 int i; /* Looping var */
db1f069b
MS
1112 CFBundleRef bundle; /* Main bundle (if any) */
1113 CFArrayRef bundleList; /* List of localizations in bundle */
ef416fc2 1114 CFPropertyListRef localizationList;
1115 /* List of localization data */
1116 CFStringRef languageName; /* Current name */
1117 CFStringRef localeName; /* Canonical from of name */
26d47ec6 1118 char *lang; /* LANG environment variable */
ef416fc2 1119 _cups_globals_t *cg = _cupsGlobals();
1120 /* Pointer to library globals */
1121
1122
b0f6947b
MS
1123 DEBUG_puts("2appleLangDefault()");
1124
ef416fc2 1125 /*
1126 * Only do the lookup and translation the first time.
1127 */
1128
1129 if (!cg->language[0])
1130 {
b0f6947b 1131 if (getenv("SOFTWARE") != NULL && (lang = getenv("LANG")) != NULL)
db1f069b 1132 {
26d47ec6 1133 strlcpy(cg->language, lang, sizeof(cg->language));
db1f069b
MS
1134 return (cg->language);
1135 }
1136 else if ((bundle = CFBundleGetMainBundle()) != NULL &&
1137 (bundleList = CFBundleCopyBundleLocalizations(bundle)) != NULL)
ef416fc2 1138 {
26d47ec6 1139 localizationList =
db1f069b 1140 CFBundleCopyPreferredLocalizationsFromArray(bundleList);
26d47ec6 1141
db1f069b
MS
1142 CFRelease(bundleList);
1143 }
1144 else
1145 localizationList =
1146 CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
1147 kCFPreferencesCurrentApplication);
1148
1149 if (localizationList)
1150 {
1151 if (CFGetTypeID(localizationList) == CFArrayGetTypeID() &&
1152 CFArrayGetCount(localizationList) > 0)
ef416fc2 1153 {
db1f069b
MS
1154 languageName = CFArrayGetValueAtIndex(localizationList, 0);
1155
1156 if (languageName &&
1157 CFGetTypeID(languageName) == CFStringGetTypeID())
26d47ec6 1158 {
db1f069b
MS
1159 localeName = CFLocaleCreateCanonicalLocaleIdentifierFromString(
1160 kCFAllocatorDefault, languageName);
ef416fc2 1161
db1f069b
MS
1162 if (localeName)
1163 {
1164 CFStringGetCString(localeName, cg->language, sizeof(cg->language),
1165 kCFStringEncodingASCII);
1166 CFRelease(localeName);
ef416fc2 1167
e07d4801 1168 DEBUG_printf(("9appleLangDefault: cg->language=\"%s\"",
db1f069b 1169 cg->language));
26d47ec6 1170
db1f069b
MS
1171 /*
1172 * Map new language identifiers to locales...
1173 */
b94498cf 1174
db1f069b 1175 for (i = 0;
0a682745
MS
1176 i < (int)(sizeof(apple_language_locale) /
1177 sizeof(apple_language_locale[0]));
1178 i ++)
db1f069b 1179 {
0a682745 1180 if (!strcmp(cg->language, apple_language_locale[i].language))
b94498cf 1181 {
e07d4801 1182 DEBUG_printf(("9appleLangDefault: mapping \"%s\" to \"%s\"...",
0a682745 1183 cg->language, apple_language_locale[i].locale));
c8fef167 1184 strlcpy(cg->language, apple_language_locale[i].locale,
db1f069b
MS
1185 sizeof(cg->language));
1186 break;
b94498cf 1187 }
db1f069b 1188 }
b94498cf 1189
db1f069b
MS
1190 /*
1191 * Convert language subtag into region subtag...
1192 */
b94498cf 1193
db1f069b
MS
1194 if (cg->language[2] == '-')
1195 cg->language[2] = '_';
b94498cf 1196
db1f069b
MS
1197 if (!strchr(cg->language, '.'))
1198 strlcat(cg->language, ".UTF-8", sizeof(cg->language));
1199 }
26d47ec6 1200 }
26d47ec6 1201 }
db1f069b
MS
1202
1203 CFRelease(localizationList);
ef416fc2 1204 }
c8fef167 1205
ef416fc2 1206 /*
1207 * If we didn't find the language, default to en_US...
1208 */
1209
1210 if (!cg->language[0])
1211 strlcpy(cg->language, "en_US.UTF-8", sizeof(cg->language));
1212 }
1213
1214 /*
1215 * Return the cached locale...
1216 */
1217
1218 return (cg->language);
1219}
0837b7e8
MS
1220
1221
1222# ifdef CUPS_BUNDLEDIR
1223/*
1224 * 'appleMessageLoad()' - Load a message catalog from a localizable bundle.
1225 */
1226
1227static cups_array_t * /* O - Message catalog */
1228appleMessageLoad(const char *locale) /* I - Locale ID */
1229{
1230 char filename[1024], /* Path to cups.strings file */
1231 applelang[256]; /* Apple language ID */
1232 CFURLRef url; /* URL to cups.strings file */
c8fef167
MS
1233 CFReadStreamRef stream = NULL; /* File stream */
1234 CFPropertyListRef plist = NULL; /* Localization file */
1235#ifdef DEBUG
1236 CFErrorRef error = NULL; /* Error when opening file */
1237#endif /* DEBUG */
1238
1239
1240 DEBUG_printf(("appleMessageLoad(locale=\"%s\")", locale));
0837b7e8
MS
1241
1242 /*
1243 * Load the cups.strings file...
1244 */
1245
1246 snprintf(filename, sizeof(filename),
1247 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings",
1248 _cupsAppleLanguage(locale, applelang, sizeof(applelang)));
c8fef167
MS
1249 DEBUG_printf(("1appleMessageLoad: filename=\"%s\"", filename));
1250
1251 if (access(filename, 0))
1252 {
1253 /*
1254 * Try alternate lproj directory names...
1255 */
1256
1257 if (!strncmp(locale, "en", 2))
1258 locale = "English";
1259 else if (!strncmp(locale, "nb", 2) || !strncmp(locale, "nl", 2))
1260 locale = "Dutch";
1261 else if (!strncmp(locale, "fr", 2))
1262 locale = "French";
1263 else if (!strncmp(locale, "de", 2))
1264 locale = "German";
1265 else if (!strncmp(locale, "it", 2))
1266 locale = "Italian";
1267 else if (!strncmp(locale, "ja", 2))
1268 locale = "Japanese";
1269 else if (!strncmp(locale, "es", 2))
1270 locale = "Spanish";
1271
1272 snprintf(filename, sizeof(filename),
1273 CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", locale);
1274 DEBUG_printf(("1appleMessageLoad: alternate filename=\"%s\"", filename));
1275 }
1276
1277 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
1278 (UInt8 *)filename,
1279 strlen(filename), false);
1280 if (url)
1281 {
1282 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
1283 if (stream)
1284 {
82f97232
MS
1285 /*
1286 * Read the property list containing the localization data.
1287 *
1288 * NOTE: This code currently generates a clang "potential leak"
1289 * warning, but the object is released in _cupsMessageFree().
1290 */
1291
c8fef167
MS
1292 CFReadStreamOpen(stream);
1293
1294#ifdef DEBUG
1295 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
1296 kCFPropertyListImmutable, NULL,
1297 &error);
1298 if (error)
1299 {
1300 CFStringRef msg = CFErrorCopyDescription(error);
1301 /* Error message */
1302
1303 CFStringGetCString(msg, filename, sizeof(filename),
1304 kCFStringEncodingUTF8);
1305 DEBUG_printf(("1appleMessageLoad: %s", filename));
1306
82f97232 1307 CFRelease(msg);
c8fef167
MS
1308 CFRelease(error);
1309 }
1310
1311#else
1312 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0,
1313 kCFPropertyListImmutable, NULL,
1314 NULL);
1315#endif /* DEBUG */
1316
1317 if (plist && CFGetTypeID(plist) != CFDictionaryGetTypeID())
1318 {
1319 CFRelease(plist);
1320 plist = NULL;
1321 }
1322
1323 CFRelease(stream);
1324 }
1325
1326 CFRelease(url);
1327 }
0837b7e8 1328
c8fef167
MS
1329 DEBUG_printf(("1appleMessageLoad: url=%p, stream=%p, plist=%p", url, stream,
1330 plist));
0837b7e8
MS
1331
1332 /*
1333 * Create and return an empty array to act as a cache for messages, passing the
c8fef167 1334 * plist as the user data.
0837b7e8
MS
1335 */
1336
c8fef167 1337 return (cupsArrayNew3((cups_array_func_t)cups_message_compare, (void *)plist,
0837b7e8
MS
1338 (cups_ahash_func_t)NULL, 0,
1339 (cups_acopy_func_t)NULL,
1340 (cups_afree_func_t)cups_message_free));
1341}
1342# endif /* CUPS_BUNDLEDIR */
ef416fc2 1343#endif /* __APPLE__ */
1344
1345
1346/*
1347 * 'cups_cache_lookup()' - Lookup a language in the cache...
1348 */
1349
1350static cups_lang_t * /* O - Language data or NULL */
0837b7e8
MS
1351cups_cache_lookup(
1352 const char *name, /* I - Name of locale */
1353 cups_encoding_t encoding) /* I - Encoding of locale */
ef416fc2 1354{
1355 cups_lang_t *lang; /* Current language */
1356
1357
e07d4801 1358 DEBUG_printf(("7cups_cache_lookup(name=\"%s\", encoding=%d(%s))", name,
ef416fc2 1359 encoding, encoding == CUPS_AUTO_ENCODING ? "auto" :
1360 lang_encodings[encoding]));
1361
1362 /*
1363 * Loop through the cache and return a match if found...
1364 */
1365
d6ae789d 1366 for (lang = lang_cache; lang != NULL; lang = lang->next)
ef416fc2 1367 {
e07d4801
MS
1368 DEBUG_printf(("9cups_cache_lookup: lang=%p, language=\"%s\", "
1369 "encoding=%d(%s)", lang, lang->language, lang->encoding,
ef416fc2 1370 lang_encodings[lang->encoding]));
1371
1372 if (!strcmp(lang->language, name) &&
1373 (encoding == CUPS_AUTO_ENCODING || encoding == lang->encoding))
1374 {
1375 lang->used ++;
1376
e07d4801 1377 DEBUG_puts("8cups_cache_lookup: returning match!");
ef416fc2 1378
1379 return (lang);
1380 }
1381 }
1382
e07d4801 1383 DEBUG_puts("8cups_cache_lookup: returning NULL!");
ef416fc2 1384
1385 return (NULL);
1386}
1387
1388
1389/*
1390 * 'cups_message_compare()' - Compare two messages.
1391 */
1392
1393static int /* O - Result of comparison */
1394cups_message_compare(
1395 _cups_message_t *m1, /* I - First message */
1396 _cups_message_t *m2) /* I - Second message */
1397{
1398 return (strcmp(m1->id, m2->id));
1399}
1400
1401
0837b7e8
MS
1402/*
1403 * 'cups_message_free()' - Free a message.
1404 */
1405
1406static void
1407cups_message_free(_cups_message_t *m) /* I - Message */
1408{
1409 if (m->id)
1410 free(m->id);
1411
1412 if (m->str)
1413 free(m->str);
1414
1415 free(m);
1416}
1417
1418
9c80ffa2
MS
1419/*
1420 * 'cups_message_load()' - Load the message catalog for a language.
1421 */
1422
1423static void
1424cups_message_load(cups_lang_t *lang) /* I - Language */
1425{
1426#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
1427 lang->strings = appleMessageLoad(lang->language);
1428
1429#else
1430 char filename[1024]; /* Filename for language locale file */
1431 _cups_globals_t *cg = _cupsGlobals();
1432 /* Pointer to library globals */
1433
1434
1435 snprintf(filename, sizeof(filename), "%s/%s/cups_%s.po", cg->localedir,
1436 lang->language, lang->language);
1437
1438 if (strchr(lang->language, '_') && access(filename, 0))
1439 {
1440 /*
1441 * Country localization not available, look for generic localization...
1442 */
1443
1444 snprintf(filename, sizeof(filename), "%s/%.2s/cups_%.2s.po", cg->localedir,
1445 lang->language, lang->language);
1446
1447 if (access(filename, 0))
1448 {
1449 /*
1450 * No generic localization, so use POSIX...
1451 */
1452
1453 DEBUG_printf(("4cups_message_load: access(\"%s\", 0): %s", filename,
1454 strerror(errno)));
1455
1456 snprintf(filename, sizeof(filename), "%s/C/cups_C.po", cg->localedir);
1457 }
1458 }
1459
1460 /*
1461 * Read the strings from the file...
1462 */
1463
1464 lang->strings = _cupsMessageLoad(filename, 1);
1465#endif /* __APPLE__ && CUPS_BUNDLEDIR */
1466}
1467
1468
ef416fc2 1469/*
1470 * 'cups_unquote()' - Unquote characters in strings...
1471 */
1472
1473static void
1474cups_unquote(char *d, /* O - Unquoted string */
1475 const char *s) /* I - Original string */
1476{
1477 while (*s)
1478 {
1479 if (*s == '\\')
1480 {
1481 s ++;
1482 if (isdigit(*s))
1483 {
1484 *d = 0;
1485
1486 while (isdigit(*s))
1487 {
1488 *d = *d * 8 + *s - '0';
1489 s ++;
1490 }
d09495fa 1491
1492 d ++;
ef416fc2 1493 }
1494 else
1495 {
1496 if (*s == 'n')
1497 *d ++ = '\n';
1498 else if (*s == 'r')
1499 *d ++ = '\r';
1500 else if (*s == 't')
1501 *d ++ = '\t';
1502 else
1503 *d++ = *s;
1504
1505 s ++;
1506 }
1507 }
1508 else
1509 *d++ = *s++;
1510 }
1511
1512 *d = '\0';
1513}
1514
1515
1516/*
75bd9771 1517 * End of "$Id: language.c 7558 2008-05-12 23:46:44Z mike $".
ef416fc2 1518 */