Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : * unicode_case.c
3 : : * Unicode case mapping and case conversion.
4 : : *
5 : : * Portions Copyright (c) 2017-2023, PostgreSQL Global Development Group
6 : : *
7 : : * IDENTIFICATION
8 : : * src/common/unicode_case.c
9 : : *
10 : : *-------------------------------------------------------------------------
11 : : */
12 : : #ifndef FRONTEND
13 : : #include "postgres.h"
14 : : #else
15 : : #include "postgres_fe.h"
16 : : #endif
17 : :
18 : : #include "common/unicode_case.h"
19 : : #include "common/unicode_case_table.h"
20 : : #include "common/unicode_category.h"
21 : : #include "mb/pg_wchar.h"
22 : :
23 : : static const pg_case_map *find_case_map(pg_wchar ucs);
24 : : static size_t convert_case(char *dst, size_t dstsize, const char *src, ssize_t srclen,
25 : : CaseKind str_casekind, WordBoundaryNext wbnext,
26 : : void *wbstate);
27 : :
28 : : pg_wchar
38 jdavis@postgresql.or 29 :GNC 186 : unicode_lowercase_simple(pg_wchar code)
30 : : {
31 : 186 : const pg_case_map *map = find_case_map(code);
32 : :
33 [ + - ]: 186 : return map ? map->simplemap[CaseLower] : code;
34 : : }
35 : :
36 : : pg_wchar
38 jdavis@postgresql.or 37 :UNC 0 : unicode_titlecase_simple(pg_wchar code)
38 : : {
39 : 0 : const pg_case_map *map = find_case_map(code);
40 : :
41 [ # # ]: 0 : return map ? map->simplemap[CaseTitle] : code;
42 : : }
43 : :
44 : : pg_wchar
38 jdavis@postgresql.or 45 :GNC 186 : unicode_uppercase_simple(pg_wchar code)
46 : : {
47 : 186 : const pg_case_map *map = find_case_map(code);
48 : :
49 [ + - ]: 186 : return map ? map->simplemap[CaseUpper] : code;
50 : : }
51 : :
52 : : /*
53 : : * unicode_strlower()
54 : : *
55 : : * Convert src to lowercase, and return the result length (not including
56 : : * terminating NUL).
57 : : *
58 : : * String src must be encoded in UTF-8. If srclen < 0, src must be
59 : : * NUL-terminated.
60 : : *
61 : : * Result string is stored in dst, truncating if larger than dstsize. If
62 : : * dstsize is greater than the result length, dst will be NUL-terminated;
63 : : * otherwise not.
64 : : *
65 : : * If dstsize is zero, dst may be NULL. This is useful for calculating the
66 : : * required buffer size before allocating.
67 : : */
68 : : size_t
37 69 : 1009 : unicode_strlower(char *dst, size_t dstsize, const char *src, ssize_t srclen)
70 : : {
16 71 : 1009 : return convert_case(dst, dstsize, src, srclen, CaseLower, NULL, NULL);
72 : : }
73 : :
74 : : /*
75 : : * unicode_strtitle()
76 : : *
77 : : * Convert src to titlecase, and return the result length (not including
78 : : * terminating NUL).
79 : : *
80 : : * String src must be encoded in UTF-8. If srclen < 0, src must be
81 : : * NUL-terminated.
82 : : *
83 : : * Result string is stored in dst, truncating if larger than dstsize. If
84 : : * dstsize is greater than the result length, dst will be NUL-terminated;
85 : : * otherwise not.
86 : : *
87 : : * If dstsize is zero, dst may be NULL. This is useful for calculating the
88 : : * required buffer size before allocating.
89 : : *
90 : : * Titlecasing requires knowledge about word boundaries, which is provided by
91 : : * the callback wbnext. A word boundary is the offset of the start of a word
92 : : * or the offset of the character immediately following a word.
93 : : *
94 : : * The caller is expected to initialize and free the callback state
95 : : * wbstate. The callback should first return offset 0 for the first boundary;
96 : : * then the offset of each subsequent word boundary; then the total length of
97 : : * the string to indicate the final boundary.
98 : : */
99 : : size_t
100 : 43 : unicode_strtitle(char *dst, size_t dstsize, const char *src, ssize_t srclen,
101 : : WordBoundaryNext wbnext, void *wbstate)
102 : : {
103 : 43 : return convert_case(dst, dstsize, src, srclen, CaseTitle, wbnext,
104 : : wbstate);
105 : : }
106 : :
107 : : /*
108 : : * unicode_strupper()
109 : : *
110 : : * Convert src to uppercase, and return the result length (not including
111 : : * terminating NUL).
112 : : *
113 : : * String src must be encoded in UTF-8. If srclen < 0, src must be
114 : : * NUL-terminated.
115 : : *
116 : : * Result string is stored in dst, truncating if larger than dstsize. If
117 : : * dstsize is greater than the result length, dst will be NUL-terminated;
118 : : * otherwise not.
119 : : *
120 : : * If dstsize is zero, dst may be NULL. This is useful for calculating the
121 : : * required buffer size before allocating.
122 : : */
123 : : size_t
37 124 : 158393 : unicode_strupper(char *dst, size_t dstsize, const char *src, ssize_t srclen)
125 : : {
16 126 : 158393 : return convert_case(dst, dstsize, src, srclen, CaseUpper, NULL, NULL);
127 : : }
128 : :
129 : : /*
130 : : * If str_casekind is CaseLower or CaseUpper, map each character in the string
131 : : * for which a mapping is available.
132 : : *
133 : : * If str_casekind is CaseTitle, maps characters found on a word boundary to
134 : : * uppercase and other characters to lowercase.
135 : : */
136 : : static size_t
37 137 : 159445 : convert_case(char *dst, size_t dstsize, const char *src, ssize_t srclen,
138 : : CaseKind str_casekind, WordBoundaryNext wbnext, void *wbstate)
139 : : {
140 : : /* character CaseKind varies while titlecasing */
16 141 : 159445 : CaseKind chr_casekind = str_casekind;
38 142 : 159445 : size_t srcoff = 0;
143 : 159445 : size_t result_len = 0;
16 144 : 159445 : size_t boundary = 0;
145 : :
146 [ + + + - : 159445 : Assert((str_casekind == CaseTitle && wbnext && wbstate) ||
- + + - +
- - + ]
147 : : (str_casekind != CaseTitle && !wbnext && !wbstate));
148 : :
149 [ + + ]: 159445 : if (str_casekind == CaseTitle)
150 : : {
151 : 43 : boundary = wbnext(wbstate);
152 [ - + ]: 43 : Assert(boundary == 0); /* start of text is always a boundary */
153 : : }
154 : :
21 155 [ + - + + : 459317 : while ((srclen < 0 || srcoff < srclen) && src[srcoff] != '\0')
+ - ]
156 : : {
38 157 : 299872 : pg_wchar u1 = utf8_to_unicode((unsigned char *) src + srcoff);
158 : 299872 : int u1len = unicode_utf8len(u1);
159 : 299872 : const pg_case_map *casemap = find_case_map(u1);
160 : :
16 161 [ + + ]: 299872 : if (str_casekind == CaseTitle)
162 : : {
163 [ + + ]: 333 : if (srcoff == boundary)
164 : : {
165 : 129 : chr_casekind = CaseUpper;
166 : 129 : boundary = wbnext(wbstate);
167 : : }
168 : : else
169 : 204 : chr_casekind = CaseLower;
170 : : }
171 : :
172 : : /* perform mapping, update result_len, and write to dst */
38 173 [ + + ]: 299872 : if (casemap)
174 : : {
16 175 : 299824 : pg_wchar u2 = casemap->simplemap[chr_casekind];
38 176 : 299824 : pg_wchar u2len = unicode_utf8len(u2);
177 : :
21 178 [ + + ]: 299824 : if (result_len + u2len <= dstsize)
38 179 : 299812 : unicode_to_utf8(u2, (unsigned char *) dst + result_len);
180 : :
181 : 299824 : result_len += u2len;
182 : : }
183 : : else
184 : : {
185 : : /* no mapping; copy bytes from src */
21 186 [ + - ]: 48 : if (result_len + u1len <= dstsize)
38 187 : 48 : memcpy(dst + result_len, src + srcoff, u1len);
188 : :
189 : 48 : result_len += u1len;
190 : : }
191 : :
192 : 299872 : srcoff += u1len;
193 : : }
194 : :
195 [ + + ]: 159445 : if (result_len < dstsize)
196 : 159427 : dst[result_len] = '\0';
197 : :
198 : 159445 : return result_len;
199 : : }
200 : :
201 : : /* find entry in simple case map, if any */
202 : : static const pg_case_map *
203 : 300244 : find_case_map(pg_wchar ucs)
204 : : {
205 : : int min;
206 : : int mid;
207 : : int max;
208 : :
209 : : /* all chars <= 0x80 are stored in array for fast lookup */
210 : : Assert(lengthof(case_map) >= 0x80);
211 [ + + ]: 300244 : if (ucs < 0x80)
212 : : {
213 : 299734 : const pg_case_map *map = &case_map[ucs];
214 : :
215 [ - + ]: 299734 : Assert(map->codepoint == ucs);
216 : 299734 : return map;
217 : : }
218 : :
219 : : /* otherwise, binary search */
220 : 510 : min = 0x80;
221 : 510 : max = lengthof(case_map) - 1;
222 [ + + ]: 5268 : while (max >= min)
223 : : {
224 : 5220 : mid = (min + max) / 2;
225 [ + + ]: 5220 : if (ucs > case_map[mid].codepoint)
226 : 1575 : min = mid + 1;
227 [ + + ]: 3645 : else if (ucs < case_map[mid].codepoint)
228 : 3183 : max = mid - 1;
229 : : else
230 : 462 : return &case_map[mid];
231 : : }
232 : :
233 : 48 : return NULL;
234 : : }
|