1 /* Time-stamp: <2006-05-01 15:04:54 jcs>
2 |
3 | Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
4 | Part of the gtkpod project.
5 |
6 | URL: http://www.gtkpod.org/
7 | URL: http://gtkpod.sourceforge.net/
8 |
9 | This program is free software; you can redistribute it and/or modify
10 | it under the terms of the GNU General Public License as published by
11 | the Free Software Foundation; either version 2 of the License, or
12 | (at your option) any later version.
13 |
14 | This program is distributed in the hope that it will be useful,
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | GNU General Public License for more details.
18 |
19 | You should have received a copy of the GNU General Public License
20 | along with this program; if not, write to the Free Software
21 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 |
23 | iTunes and iPod are trademarks of Apple
24 |
25 | This product is not supported/written/published by Apple!
26 |
27 | $Id$
28 */
29
30 #ifdef HAVE_CONFIG_H
31 # include <config.h>
32 #endif
33
34 #include <string.h>
35 #include <gtk/gtk.h>
36 #include "prefs.h"
37 #include "charset.h"
38 #include "misc.h"
39
40 /* If Japanese auto-conversion is being used, this variable is being
41 set with each call of charset_to_utf8(). You can get a copy of its
42 value by calling charset_get_auto().
43 This variable will only be reset by calling charset_reset_auto(). */
44 static gchar *auto_charset = NULL;
45
46 typedef struct {
47 const gchar *descr;
48 const gchar *name;
49 } CharsetInfo;
50
51
52 static const CharsetInfo charset_info[] = {
53 {N_("Arabic (IBM-864)"), "IBM864" },
54 {N_("Arabic (ISO-8859-6)"), "ISO-8859-6" },
55 {N_("Arabic (Windows-1256)"), "windows-1256" },
56 {N_("Baltic (ISO-8859-13)"), "ISO-8859-13" },
57 {N_("Baltic (ISO-8859-4)"), "ISO-8859-4" },
58 {N_("Baltic (Windows-1257)"), "windows-1257" },
59 {N_("Celtic (ISO-8859-14)"), "ISO-8859-14" },
60 {N_("Central European (IBM-852)"), "IBM852" },
61 {N_("Central European (ISO-8859-2)"), "ISO-8859-2" },
62 {N_("Central European (Windows-1250)"), "windows-1250" },
63 {N_("Chinese Simplified (GB18030)"), "gb18030" },
64 {N_("Chinese Simplified (GB2312)"), "GB2312" },
65 {N_("Chinese Traditional (Big5)"), "Big5" },
66 {N_("Chinese Traditional (Big5-HKSCS)"), "Big5-HKSCS" },
67 {N_("Cyrillic (IBM-855)"), "IBM855" },
68 {N_("Cyrillic (ISO-8859-5)"), "ISO-8859-5" },
69 {N_("Cyrillic (ISO-IR-111)"), "ISO-IR-111" },
70 {N_("Cyrillic (KOI8-R)"), "KOI8-R" },
71 {N_("Cyrillic (Windows-1251)"), "windows-1251" },
72 {N_("Cyrillic/Russian (CP-866)"), "IBM866" },
73 {N_("Cyrillic/Ukrainian (KOI8-U)"), "KOI8-U" },
74 {N_("English (US-ASCII)"), "us-ascii" },
75 {N_("Greek (ISO-8859-7)"), "ISO-8859-7" },
76 {N_("Greek (Windows-1253)"), "windows-1253" },
77 {N_("Hebrew (IBM-862)"), "IBM862" },
78 {N_("Hebrew (Windows-1255)"), "windows-1255" },
79 {N_("Japanese (automatic detection)"), GTKPOD_JAPAN_AUTOMATIC},
80 {N_("Japanese (EUC-JP)"), "EUC-JP" },
81 {N_("Japanese (ISO-2022-JP)"), "ISO-2022-JP" },
82 {N_("Japanese (Shift_JIS)"), "Shift_JIS" },
83 {N_("Korean (EUC-KR)"), "EUC-KR" },
84 {N_("Nordic (ISO-8859-10)"), "ISO-8859-10" },
85 {N_("South European (ISO-8859-3)"), "ISO-8859-3" },
86 {N_("Thai (TIS-620)"), "TIS-620" },
87 {N_("Turkish (IBM-857)"), "IBM857" },
88 {N_("Turkish (ISO-8859-9)"), "ISO-8859-9" },
89 {N_("Turkish (Windows-1254)"), "windows-1254" },
90 {N_("Unicode (UTF-7)"), "UTF-7" },
91 {N_("Unicode (UTF-8)"), "UTF-8" },
92 {N_("Unicode (UTF-16BE)"), "UTF-16BE" },
93 {N_("Unicode (UTF-16LE)"), "UTF-16LE" },
94 {N_("Unicode (UTF-32BE)"), "UTF-32BE" },
95 {N_("Unicode (UTF-32LE)"), "UTF-32LE" },
96 {N_("Vietnamese (VISCII)"), "VISCII" },
97 {N_("Vietnamese (Windows-1258)"), "windows-1258" },
98 {N_("Visual Hebrew (ISO-8859-8)"), "ISO-8859-8" },
99 {N_("Western (IBM-850)"), "IBM850" },
100 {N_("Western (ISO-8859-1)"), "ISO-8859-1" },
101 {N_("Western (ISO-8859-15)"), "ISO-8859-15" },
102 {N_("Western (Windows-1252)"), "windows-1252" },
103 /*
104 * From this point, character sets aren't supported by iconv
105 */
106 /* {N_("Arabic (IBM-864-I)"), "IBM864i" },
107 {N_("Arabic (ISO-8859-6-E)"), "ISO-8859-6-E" },
108 {N_("Arabic (ISO-8859-6-I)"), "ISO-8859-6-I" },
109 {N_("Arabic (MacArabic)"), "x-mac-arabic" },
110 {N_("Armenian (ARMSCII-8)"), "armscii-8" },
111 {N_("Central European (MacCE)"), "x-mac-ce" },
112 {N_("Chinese Simplified (GBK)"), "x-gbk" },
113 {N_("Chinese Simplified (HZ)"), "HZ-GB-2312" },
114 {N_("Chinese Traditional (EUC-TW)"), "x-euc-tw" },
115 {N_("Croatian (MacCroatian)"), "x-mac-croatian" },
116 {N_("Cyrillic (MacCyrillic)"), "x-mac-cyrillic" },
117 {N_("Cyrillic/Ukrainian (MacUkrainian)"), "x-mac-ukrainian" },
118 {N_("Farsi (MacFarsi)"), "x-mac-farsi"},
119 {N_("Greek (MacGreek)"), "x-mac-greek" },
120 {N_("Gujarati (MacGujarati)"), "x-mac-gujarati" },
121 {N_("Gurmukhi (MacGurmukhi)"), "x-mac-gurmukhi" },
122 {N_("Hebrew (ISO-8859-8-E)"), "ISO-8859-8-E" },
123 {N_("Hebrew (ISO-8859-8-I)"), "ISO-8859-8-I" },
124 {N_("Hebrew (MacHebrew)"), "x-mac-hebrew" },
125 {N_("Hindi (MacDevanagari)"), "x-mac-devanagari" },
126 {N_("Icelandic (MacIcelandic)"), "x-mac-icelandic" },
127 {N_("Korean (JOHAB)"), "x-johab" },
128 {N_("Korean (UHC)"), "x-windows-949" },
129 {N_("Romanian (MacRomanian)"), "x-mac-romanian" },
130 {N_("Turkish (MacTurkish)"), "x-mac-turkish" },
131 {N_("User Defined"), "x-user-defined" },
132 {N_("Vietnamese (TCVN)"), "x-viet-tcvn5712" },
133 {N_("Vietnamese (VPS)"), "x-viet-vps" },
134 {N_("Western (MacRoman)"), "x-mac-roman" },
135 // charsets whithout posibly translatable names
136 {"T61.8bit", "T61.8bit" },
137 {"x-imap4-modified-utf7", "x-imap4-modified-utf7"},
138 {"x-u-escaped", "x-u-escaped" },
139 {"windows-936", "windows-936" }
140 */
141 {NULL, NULL}
142 };
143
144
145
146 /* Sets up the charsets to choose from in the "combo". It presets the
147 charset stored in cfg->charset (or "System Charset" if none is set
148 there
149
150 DEPRECATED - use charset_init_combo_box with GtkComboBox
151 */
charset_init_combo(GtkCombo * combo)152 void charset_init_combo (GtkCombo *combo)
153 {
154 gchar *current_charset;
155 gchar *description;
156 const CharsetInfo *ci;
157 static GList *charsets = NULL; /* list with choices -- takes a while to
158 * initialize, so we only do it once */
159
160 current_charset = prefs_get_string("charset");
161 if ((current_charset == NULL) || (strlen (current_charset) == 0))
162 {
163 description = g_strdup (_("System Charset"));
164 }
165 else
166 {
167 description = charset_to_description (current_charset);
168 }
169 if (charsets == NULL)
170 { /* set up list with charsets */
171 FILE *fp;
172
173 charsets = g_list_append (charsets, _("System Charset"));
174 /* now add all the charset descriptions in the list above */
175 ci=charset_info;
176 while (ci->descr != NULL)
177 {
178 charsets = g_list_append (charsets, _(ci->descr));
179 ++ci;
180 }
181 /* let's add all available charsets returned by "iconv -l" */
182 /* The code assumes that "iconv -l" returns a list with the
183 name of one charset in each line, each valid line being
184 terminated by "//". */
185 fp = popen ("iconv -l", "r");
186 if (fp)
187 {
188 gchar buf[PATH_MAX];
189 /* read one line of output at a time */
190 while (fgets (buf, PATH_MAX, fp))
191 {
192 /* only consider lines ending on "//" */
193 gchar *bufp = g_strrstr (buf, "//\n");
194 if (bufp)
195 { /* add everything before "//" to our charset list */
196 gchar *bufpp = buf;
197 *bufp = 0; /* shorten string */
198 while ((*bufpp == ' ') || (*bufpp == 0x09))
199 ++bufpp; /* skip whitespace */
200 if (*bufpp)
201 charsets = g_list_append (charsets, g_strdup (bufpp));
202 }
203 }
204 pclose (fp);
205 }
206 }
207 /* set pull down items */
208 gtk_combo_set_popdown_strings (GTK_COMBO (combo), charsets);
209 /* set standard entry */
210 gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (combo)->entry), description);
211 g_free (description);
212 g_free(current_charset);
213 }
214
215 /* Sets up the charsets to choose from in the "combo". It presets the
216 charset stored in cfg->charset (or "System Charset" if none is set
217 there */
charset_init_combo_box(GtkComboBox * combo)218 void charset_init_combo_box (GtkComboBox *combo)
219 {
220 gchar *current_charset;
221 gchar *description;
222 const CharsetInfo *ci;
223 GtkCellRenderer *renderer;
224 GtkTreeIter use_iter;
225 static GtkListStore *charsets = NULL; /* list with choices -- takes a while to
226 * initialize, so we only do it once */
227
228 current_charset = prefs_get_string("charset");
229
230 if ((current_charset == NULL) || (strlen (current_charset) == 0))
231 {
232 description = g_strdup (_("System Charset"));
233 }
234 else
235 {
236 description = charset_to_description (current_charset);
237 }
238
239 if (charsets == NULL)
240 { /* set up list with charsets */
241 FILE *fp;
242 GtkTreeIter iter;
243
244 charsets = gtk_list_store_new (1, G_TYPE_STRING);
245 /* now add all the charset descriptions in the list above */
246
247 gtk_list_store_append (charsets, &iter);
248 gtk_list_store_set (charsets, &iter, 0, _("System Charset"), -1);
249
250 for (ci = charset_info; ci->descr; ci++)
251 {
252 gtk_list_store_append (charsets, &iter);
253 gtk_list_store_set (charsets, &iter, 0, _(ci->descr), -1);
254 }
255
256 /* let's add all available charsets returned by "iconv -l" */
257 /* The code assumes that "iconv -l" returns a list with the
258 name of one charset in each line, each valid line being
259 terminated by "//". */
260 fp = popen ("iconv -l", "r");
261
262 if (fp)
263 {
264 gchar buf[PATH_MAX];
265
266 /* read one line of output at a time */
267 while (fgets (buf, PATH_MAX, fp))
268 {
269 /* only consider lines ending on "//" */
270 gchar *bufp = g_strrstr (buf, "//\n");
271
272 if (bufp)
273 {
274 /* add everything before "//" to our charset list */
275 gchar *bufpp = buf;
276 *bufp = 0; /* shorten string */
277
278 while ((*bufpp == ' ') || (*bufpp == 0x09))
279 bufpp++; /* skip whitespace */
280
281 if (*bufpp)
282 {
283 gtk_list_store_append (charsets, &iter);
284 gtk_list_store_set (charsets, &iter, 0, bufpp, -1);
285
286 }
287 }
288 }
289
290 pclose (fp);
291 }
292 }
293 /* set pull down items */
294 renderer = gtk_cell_renderer_text_new ();
295
296 gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (charsets));
297 gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo));
298 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, FALSE);
299 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), renderer, "text", 0);
300
301 for (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (charsets), &use_iter);
302 gtk_list_store_iter_is_valid (charsets, &use_iter);
303 gtk_tree_model_iter_next (GTK_TREE_MODEL (charsets), &use_iter))
304 {
305 gchar *cur_desc;
306 gtk_tree_model_get (GTK_TREE_MODEL (charsets), &use_iter, 0, &cur_desc, -1);
307
308 if(!g_utf8_collate(description, cur_desc))
309 {
310 g_free (cur_desc);
311 break;
312 }
313
314 g_free (cur_desc);
315 }
316
317 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &use_iter);
318 g_free (description);
319 g_free (current_charset);
320 }
321
322 /* returns the charset name belonging to the description "descr"
323 * chosen from the combo. Return "NULL" when it could not be found, or
324 * if it is the System Default Charset (locale). You must g_free
325 * the charset received (unlike g_get_charset ()) */
charset_from_description(gchar * descr)326 gchar *charset_from_description (gchar *descr)
327 {
328 const CharsetInfo *ci;
329
330 if (!descr) return NULL; /* sanity! */
331 /* check for "System Charset" and return NULL */
332 if (g_utf8_collate (descr, _("System Charset")) == 0) return NULL;
333 /* check if description matches one of the descriptions in
334 * charset_info[], and if so, return the charset name */
335 ci = charset_info;
336 while (ci->descr != NULL)
337 {
338 if (g_utf8_collate (descr, _(ci->descr)) == 0)
339 {
340 return g_strdup (ci->name);
341 }
342 ++ci;
343 }
344 /* OK, it was not in the charset_info[] list. Therefore it must be
345 * from the "iconv -l" list. We just return a copy of it */
346 return g_strdup (descr);
347 }
348
349
350 /* Returns the description belonging to the charset "charset"
351 * Returns the description (if found) or just a copy of "charset"
352 * You must g_free the charset received */
charset_to_description(gchar * charset)353 gchar *charset_to_description (gchar *charset)
354 {
355 const CharsetInfo *ci;
356
357 if (!charset) return NULL; /* sanity! */
358 /* check if charset matches one of the charsets in
359 * charset_info[], and if so, return the description */
360 ci = charset_info;
361 while (ci->descr != NULL)
362 {
363 if (compare_string_case_insensitive (charset, ci->name) == 0)
364 {
365 return g_strdup (_(ci->descr));
366 }
367 ++ci;
368 }
369 /* OK, it was not in the charset_info[] list. Therefore it must be
370 * from the "iconv -l" list. We just return a copy of it */
371 return g_strdup (charset);
372 }
373
374
375 /* code for automatic detection of Japanese char-subset donated by
376 Hiroshi Kawashima */
charset_check_k_code(const gchar * p2)377 static const gchar *charset_check_k_code (const gchar *p2)
378 {
379 static const char* charsets[] = {
380 "UTF-8",
381 "EUC-JP",
382 "CP932",
383 "ISO-2022-JP",
384 NULL
385 };
386 int i;
387 gchar *ret;
388 gssize len;
389
390 if (p2 == NULL) return NULL;
391
392 len = strlen ((gchar*)p2);
393 for (i=0; charsets[i]; i++) {
394 ret = g_convert ((gchar*)p2, /* string to convert */
395 len, /* length of string */
396 "UTF-8", /* to_codeset */
397 charsets[i], /* from_codeset */
398 NULL, /* *bytes_read */
399 NULL, /* *bytes_written */
400 NULL); /* GError **error */
401 if (ret != NULL) {
402 g_free(ret);
403 return charsets[i];
404 }
405 }
406 return (NULL);
407 }
408
409 /* same as check_k_code, but defaults to "UTF-8" if no match is found */
charset_check_k_code_with_default(const guchar * p)410 static const gchar *charset_check_k_code_with_default (const guchar *p)
411 {
412 const gchar *result=NULL;
413
414 if (p) result = charset_check_k_code (p);
415 if (!result) result = "UTF-8";
416 return result;
417 }
418
419 /* return the charset actually used for the "auto detection"
420 * feature. So far only Japanese Auto Detecion is implemented */
charset_check_auto(const gchar * str)421 static gchar *charset_check_auto (const gchar *str)
422 {
423 gchar *charset;
424 gchar *result;
425
426 if (str == NULL) return NULL; /* sanity */
427
428 charset = prefs_get_string("charset");
429
430 if (charset && (strcmp (charset, GTKPOD_JAPAN_AUTOMATIC) == 0))
431 result = g_strdup(charset_check_k_code (str));
432 else
433 result = NULL;
434
435 g_free(charset);
436 return result;
437 }
438
439 /* See description at the definition of gchar *auto_charset; for
440 details */
charset_get_auto(void)441 gchar *charset_get_auto (void)
442 {
443 return g_strdup (auto_charset);
444 }
445
charset_reset_auto(void)446 void charset_reset_auto (void)
447 {
448 auto_charset = NULL;
449 }
450
451
452 /* Convert "str" (in the charset specified in cfg->charset) to
453 * utf8. If cfg->charset is NULL, "str" is assumed to be in the
454 * current locale charset */
455 /* Must free the returned string yourself */
charset_to_utf8(const gchar * str)456 gchar *charset_to_utf8 (const gchar *str)
457 {
458 gchar *charset; /* From prefs */
459 const gchar *locale_charset; /* Used if prefs doesn't have a charset */
460 gchar *result;
461
462 if (str == NULL) return NULL; /* sanity */
463 charset = charset_check_auto (str);
464 if (charset)
465 {
466 g_free(auto_charset);
467 auto_charset = g_strdup(charset);
468 }
469 else
470 {
471 charset = prefs_get_string("charset");
472 if (!charset || !strlen (charset))
473 { /* use standard locale charset */
474 g_free(charset);
475 g_get_charset (&locale_charset);
476 charset = g_strdup(locale_charset);
477 }
478 }
479
480 result = charset_to_charset (charset, "UTF-8", str);
481 g_free(charset);
482 return result;
483 }
484
485
486 /* Convert "str" from utf8 to the charset specified in
487 * cfg->charset. If cfg->charset is NULL, "str" is converted to the
488 * current locale charset */
489 /* Must free the returned string yourself */
charset_from_utf8(const gchar * str)490 gchar *charset_from_utf8 (const gchar *str)
491 {
492 gchar *charset;
493 const gchar *locale_charset;
494 gchar *result;
495
496 if (str == NULL) return NULL; /* sanity */
497 charset = prefs_get_string("charset");
498 if (!charset || !strlen (charset))
499 {
500 /* use standard locale charset */
501 g_free(charset);
502 g_get_charset (&locale_charset);
503 charset = g_strdup(locale_charset);
504 }
505
506 result = charset_to_charset ("UTF-8", charset, str);
507 g_free(charset);
508 return result;
509 }
510
511 /* Convert "str" from utf8 to the charset specified in @s->charset. If
512 * this is NULL, try cfg->charset. If cfg->charset is also NULL, "str"
513 * is converted to the current locale charset */
514 /* Must free the returned string yourself */
charset_track_charset_from_utf8(Track * s,const gchar * str)515 gchar *charset_track_charset_from_utf8 (Track *s, const gchar *str)
516 {
517 gchar *charset;
518 const gchar *locale_charset;
519 gchar *result;
520 ExtraTrackData *etd;
521
522 g_return_val_if_fail (s, NULL);
523 g_return_val_if_fail (s->userdata, NULL);
524
525 if (str == NULL) return NULL; /* sanity */
526
527 etd = s->userdata;
528
529 if (etd->charset && strlen (etd->charset))
530 charset = g_strdup(etd->charset);
531 else
532 charset = prefs_get_string("charset");
533
534 if (!charset || !strlen (charset))
535 { /* use standard locale charset */
536 g_free(charset);
537 g_get_charset (&locale_charset);
538 charset = g_strdup(locale_charset);
539 }
540
541 result = charset_to_charset ("UTF-8", charset, str);
542 g_free(charset);
543 return result;
544 }
545
546 /* Convert "str" from "from_charset" to "to_charset", trying to skip
547 illegal character as best as possible */
548 /* Must free the returned string yourself */
charset_to_charset(const gchar * from_charset,const gchar * to_charset,const gchar * str)549 gchar *charset_to_charset (const gchar *from_charset,
550 const gchar *to_charset,
551 const gchar *str)
552 {
553 gchar *ret;
554 gssize len;
555 gsize bytes_read;
556
557 if (!str) return NULL;
558
559 /* Handle automatic selection of Japanese charset */
560 if (from_charset && (strcmp (from_charset, GTKPOD_JAPAN_AUTOMATIC) == 0))
561 from_charset = charset_check_k_code_with_default (str);
562 /* Automatic selection of Japanese charset when encoding to
563 Japanese is a bit of a problem... we simply fall back on EUC-JP
564 (defined in check_k_code_with_default ) if this situation
565 occurs. */
566 if (to_charset && (strcmp (to_charset, GTKPOD_JAPAN_AUTOMATIC) == 0))
567 to_charset = charset_check_k_code_with_default (NULL);
568
569 len = strlen (str);
570 /* do the conversion! */
571 ret = g_convert (str, /* string to convert */
572 len, /* length of string */
573 to_charset, /* to_codeset */
574 from_charset, /* from_codeset */
575 &bytes_read, /* *bytes_read */
576 NULL, /* *bytes_written */
577 NULL); /* GError **error */
578 if (ret == NULL)
579 { /* We probably had illegal characters. We try to convert as much
580 * as possible. "bytes_read" tells how many chars were converted
581 * successfully, and we try to fill up with spaces 0x20, which
582 * will work for most charsets (except those using 16 Bit or 32
583 * Bit representation (0x2020 or 0x20202020 != 0x0020 or
584 * 0x00000020) */
585 gchar *strc = g_strdup (str);
586 gsize br0 = bytes_read;
587 gsize br;
588 while (!ret && (bytes_read < len))
589 {
590 strc[bytes_read] = 0x20;
591 br = bytes_read;
592 ret = g_convert (strc, /* string to convert */
593 len, /* length of string */
594 to_charset, /* to_codeset */
595 from_charset, /* from_codeset */
596 &bytes_read, /* *bytes_read */
597 NULL, /* *bytes_written */
598 NULL); /* GError **error */
599 /* max. nr. of converted Bytes */
600 if (bytes_read > br0) br0 = bytes_read;
601 /* don't do an infinite loop */
602 if (bytes_read <= br) bytes_read = br + 1;
603 }
604 if (ret == NULL)
605 { /* still no valid string */
606 if (br0 != 0)
607 { /* ok, at least something we can use! */
608 ret = g_convert (strc, /* string to convert */
609 br0, /* length of string */
610 to_charset, /* to_codeset */
611 from_charset, /* from_codeset */
612 &bytes_read, /* *bytes_read */
613 NULL, /* *bytes_written */
614 NULL); /* GError **error */
615 }
616 if (ret == NULL)
617 { /* Well... what do you think we should return? */
618 /* ret = g_strdup (_("gtkpod: Invalid Conversion")); */
619 ret = g_strdup ("");
620 }
621 }
622 g_free (strc);
623 }
624 return ret;
625 }
626