summaryrefslogtreecommitdiff
path: root/src/lang.c
blob: 7679b1f961281b724b5bb2eef455fb652136ed04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include "fileserv.h"
#include <string.h>

struct lang_pref {
	char const *lang;
	size_t len;
	float pref;
};

static int
lang_pref_cmp(const void *a, const void *b)
{
	struct lang_pref const *lpa = a;
	struct lang_pref const *lpb = b;

	if (lpa->pref < lpb->pref)
		return 1;
	if (lpa->pref > lpb->pref)
		return -1;
	return 0;
}

static int
parse_accepted_language(const char *str, struct lang_pref **lpv, size_t *lpc)
{
	size_t count = 1;
	const char *p;
	struct lang_pref *lp;
	size_t len;
	size_t i;
	
	for (p = str; *p; p++)
		if (*p == ',')
			++count;
	lp = calloc(count, sizeof(lp[0]));
	if (!lp) {
		alloc_warn();
		return -1;
	}
	i = 0;
	while (i < count) {
		while (*str && (*str == ' ' || *str == '\t'))
			++str;
		if (!*str)
			break;
		len = strcspn(str, ",;");
		lp[i].lang = str;
		lp[i].len = len;
		str += len;
		if (*str == ';') {
			while (*++str && (*str == ' ' || *str == '\t'))
				;
			if (str[0] == 'q' && str[1] == '=') {
				lp[i].pref = strtod(str + 2, (char**) &str);
				while (*str && (*str == ' ' || *str == '\t'))
					++str;
			}
		} else {
			lp[i].pref = 1.0;
		}
		if (*str == ',') {
			++str;
			++i;
		} else {
			if (*str == 0)
				i++;
			break;
		}
	}
	qsort(lp, i, sizeof(lp[0]), lang_pref_cmp);
	
	*lpv = lp;
	*lpc = i;

	return 0;
}

static ssize_t
match_lang(struct lang_pref const *lpv, size_t lpc, char const *lang)
{
	size_t llen = strlen(lang);
	ssize_t i, wildcard = -1;
	
	for (i = 0; i < lpc; i++) {
		if (lpv[i].len == 1 && lpv[i].lang[0] == '*')
			wildcard = i;
		else if ((lpv[i].len == llen
			  || (lpv[i].len > llen && lpv[i].lang[llen] == '-'))
			 && memcmp(lpv[i].lang, lang, llen) == 0)
			return i;
	}
	return wildcard;
}

LANG_VAR *
select_language(LANG_VAR_LIST *lst, char const *acc)
{
	struct lang_pref *lpv;
	size_t lpc;
	LANG_VAR *res = NULL;
	ssize_t n;
	LANG_VAR *lv;
	
	if (parse_accepted_language(acc ? acc : "en,*;q=0.5", &lpv, &lpc)
	    == 0) {
		STAILQ_FOREACH(lv, lst, next) {
			ssize_t j = match_lang(lpv, lpc, lv->lang);
			if (j >= 0
			    && (!res || lpv[j].pref > lpv[n].pref)) {
				res = lv;
				n = j;
			}
		}
		free(lpv);
	}

	if (res == NULL) {
		res = STAILQ_FIRST(lst);
		STAILQ_FOREACH(lv, lst, next) {
			if (strcmp(lv->lang, "en") == 0) {
				res = lv;
				break;
			}
		}
	}
	return res;
}

Return to:

Send suggestions and report system problems to the System administrator.