1 /*
2 * Multi-language String support
3 * Copyright (C) 2012 Adam Sutton
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <string.h>
20 #include <stdlib.h>
21
22 #include "redblack.h"
23 #include "lang_codes.h"
24 #include "lang_str.h"
25 #include "tvheadend.h"
26
27 SKEL_DECLARE(lang_str_ele_skel, lang_str_ele_t);
28
29 /* ************************************************************************
30 * Support
31 * ***********************************************************************/
32
33 /* Compare language codes */
_lang_cmp(void * a,void * b)34 static int _lang_cmp ( void *a, void *b )
35 {
36 return strcmp(((lang_str_ele_t*)a)->lang, ((lang_str_ele_t*)b)->lang);
37 }
38
39 /* ************************************************************************
40 * Language String
41 * ***********************************************************************/
42
43 /* Create new instance */
lang_str_create(void)44 lang_str_t *lang_str_create ( void )
45 {
46 return calloc(1, sizeof(lang_str_t));
47 }
48
lang_str_create2(const char * s,const char * lang)49 lang_str_t *lang_str_create2 ( const char *s, const char *lang )
50 {
51 lang_str_t *ls = lang_str_create();
52 if (ls)
53 lang_str_add(ls, s, lang, 0);
54 return ls;
55 }
56
57 /* Destroy (free memory) */
lang_str_destroy(lang_str_t * ls)58 void lang_str_destroy ( lang_str_t *ls )
59 {
60 lang_str_ele_t *e;
61 if (ls == NULL)
62 return;
63 while ((e = RB_FIRST(ls))) {
64 if (e->str) free(e->str);
65 RB_REMOVE(ls, e, link);
66 free(e);
67 }
68 free(ls);
69 }
70
71 /* Copy the lang_str instance */
lang_str_copy(const lang_str_t * ls)72 lang_str_t *lang_str_copy ( const lang_str_t *ls )
73 {
74 lang_str_t *ret = lang_str_create();
75 lang_str_ele_t *e;
76 RB_FOREACH(e, ls, link)
77 lang_str_add(ret, e->str, e->lang, 0);
78 return ret;
79 }
80
81 /* Get language element */
lang_str_get2(const lang_str_t * ls,const char * lang)82 lang_str_ele_t *lang_str_get2
83 ( const lang_str_t *ls, const char *lang )
84 {
85 int i;
86 const char **langs;
87 lang_str_ele_t skel, *e = NULL;
88
89 if (!ls) return NULL;
90
91 /* Check config/requested langs */
92 if ((langs = lang_code_split(lang))) {
93 i = 0;
94 while (langs[i]) {
95 skel.lang = langs[i];
96 if ((e = RB_FIND(ls, &skel, link, _lang_cmp)))
97 break;
98 i++;
99 }
100 free(langs);
101 }
102
103 /* Use first available */
104 if (!e) e = RB_FIRST(ls);
105
106 /* Return */
107 return e;
108 }
109
110 /* Get string */
lang_str_get(const lang_str_t * ls,const char * lang)111 const char *lang_str_get
112 ( const lang_str_t *ls, const char *lang )
113 {
114 lang_str_ele_t *e = lang_str_get2(ls, lang);
115 return e ? e->str : NULL;
116 }
117
118 /* Internal insertion routine */
_lang_str_add(lang_str_t * ls,const char * str,const char * lang,int update,int append)119 static int _lang_str_add
120 ( lang_str_t *ls, const char *str, const char *lang, int update, int append )
121 {
122 int save = 0;
123 lang_str_ele_t *e;
124
125 if (!str) return 0;
126
127 /* Get proper code */
128 if (!lang) lang = lang_code_preferred();
129 if (!(lang = lang_code_get(lang))) return 0;
130
131 /* Create skel */
132 SKEL_ALLOC(lang_str_ele_skel);
133 lang_str_ele_skel->lang = lang;
134
135 /* Create */
136 e = RB_INSERT_SORTED(ls, lang_str_ele_skel, link, _lang_cmp);
137 if (!e) {
138 lang_str_ele_skel->str = strdup(str);
139 SKEL_USED(lang_str_ele_skel);
140 save = 1;
141
142 /* Append */
143 } else if (append) {
144 e->str = realloc(e->str, strlen(e->str) + strlen(str) + 1);
145 strcat(e->str, str);
146 save = 1;
147
148 /* Update */
149 } else if (update && strcmp(str, e->str)) {
150 free(e->str);
151 e->str = strdup(str);
152 save = 1;
153 }
154
155 return save;
156 }
157
158 /* Add new string (or replace existing one) */
lang_str_add(lang_str_t * ls,const char * str,const char * lang,int update)159 int lang_str_add
160 ( lang_str_t *ls, const char *str, const char *lang, int update )
161 {
162 return _lang_str_add(ls, str, lang, update, 0);
163 }
164
165 /* Append to existing string (or add new one) */
lang_str_append(lang_str_t * ls,const char * str,const char * lang)166 int lang_str_append
167 ( lang_str_t *ls, const char *str, const char *lang )
168 {
169 return _lang_str_add(ls, str, lang, 0, 1);
170 }
171
172 /* Set new string with update check */
lang_str_set(lang_str_t ** dst,const char * str,const char * lang)173 int lang_str_set
174 ( lang_str_t **dst, const char *str, const char *lang )
175 {
176 lang_str_ele_t *e;
177 int found;
178 if (*dst == NULL) goto change1;
179 if (!lang) lang = lang_code_preferred();
180 if (!(lang = lang_code_get(lang))) return 0;
181 if (*dst) {
182 found = 0;
183 RB_FOREACH(e, *dst, link) {
184 if (found)
185 goto change;
186 found = strcmp(e->lang, lang) == 0 &&
187 strcmp(e->str, str) == 0;
188 if (!found)
189 goto change;
190 }
191 if (found)
192 return 0;
193 }
194 change:
195 lang_str_destroy(*dst);
196 change1:
197 *dst = lang_str_create();
198 lang_str_add(*dst, str, lang, 1);
199 return 1;
200 }
201
202 /* Set new strings with update check */
lang_str_set2(lang_str_t ** dst,lang_str_t * src)203 int lang_str_set2
204 ( lang_str_t **dst, lang_str_t *src )
205 {
206 if (*dst) {
207 if (!lang_str_compare(*dst, src))
208 return 0;
209 lang_str_destroy(*dst);
210 }
211 *dst = lang_str_copy(src);
212 return 1;
213 }
214
215 /* Serialize map */
lang_str_serialize_map(lang_str_t * ls)216 htsmsg_t *lang_str_serialize_map ( lang_str_t *ls )
217 {
218 lang_str_ele_t *e;
219 if (!ls) return NULL;
220 htsmsg_t *a = htsmsg_create_map();
221 RB_FOREACH(e, ls, link) {
222 htsmsg_add_str(a, e->lang, e->str);
223 }
224 return a;
225 }
226
227 /* Serialize */
lang_str_serialize(lang_str_t * ls,htsmsg_t * m,const char * f)228 void lang_str_serialize ( lang_str_t *ls, htsmsg_t *m, const char *f )
229 {
230 if (!ls) return;
231 htsmsg_add_msg(m, f, lang_str_serialize_map(ls));
232 }
233
234 /* De-serialize map */
lang_str_deserialize_map(htsmsg_t * map)235 lang_str_t *lang_str_deserialize_map ( htsmsg_t *map )
236 {
237 lang_str_t *ret = lang_str_create();
238 htsmsg_field_t *f;
239 const char *str;
240
241 HTSMSG_FOREACH(f, map) {
242 if ((str = htsmsg_field_get_string(f))) {
243 lang_str_add(ret, str, f->hmf_name, 0);
244 }
245 }
246 return ret;
247 }
248
249 /* De-serialize */
lang_str_deserialize(htsmsg_t * m,const char * n)250 lang_str_t *lang_str_deserialize ( htsmsg_t *m, const char *n )
251 {
252 htsmsg_t *a;
253 const char *str;
254
255 if ((a = htsmsg_get_map(m, n))) {
256 return lang_str_deserialize_map(a);
257 } else if ((str = htsmsg_get_str(m, n))) {
258 lang_str_t *ret = lang_str_create();
259 lang_str_add(ret, str, NULL, 0);
260 return ret;
261 }
262 return NULL;
263 }
264
265 /* Compare */
lang_str_compare(const lang_str_t * ls1,const lang_str_t * ls2)266 int lang_str_compare( const lang_str_t *ls1, const lang_str_t *ls2 )
267 {
268 lang_str_ele_t *e;
269 const char *s1, *s2;
270 int r;
271
272 if (ls1 == NULL && ls2)
273 return -1;
274 if (ls2 == NULL && ls1)
275 return 1;
276 if (ls1 == ls2)
277 return 0;
278 /* Note: may be optimized to not check languages twice */
279 RB_FOREACH(e, ls1, link) {
280 s1 = lang_str_get(ls1, e->lang);
281 s2 = lang_str_get(ls2, e->lang);
282 if (s1 == NULL && s2 != NULL)
283 return -1;
284 if (s2 == NULL && s1 != NULL)
285 return 1;
286 if (s1 == NULL || s2 == NULL)
287 continue;
288 r = strcmp(s1, s2);
289 if (r) return r;
290 }
291 RB_FOREACH(e, ls2, link) {
292 s1 = lang_str_get(ls1, e->lang);
293 s2 = lang_str_get(ls2, e->lang);
294 if (s1 == NULL && s2 != NULL)
295 return -1;
296 if (s2 == NULL && s1 != NULL)
297 return 1;
298 if (s1 == NULL || s2 == NULL)
299 continue;
300 r = strcmp(s1, s2);
301 if (r) return r;
302 }
303 return 0;
304 }
305
lang_str_size(const lang_str_t * ls)306 size_t lang_str_size(const lang_str_t *ls)
307 {
308 lang_str_ele_t *e;
309 size_t size;
310 if (!ls) return 0;
311 size = sizeof(*ls);
312 RB_FOREACH(e, ls, link) {
313 size += sizeof(*e);
314 size += tvh_strlen(e->str);
315 size += tvh_strlen(e->lang);
316 }
317 return size;
318 }
319
lang_str_done(void)320 void lang_str_done( void )
321 {
322 SKEL_FREE(lang_str_ele_skel);
323 }
324