Branch data Line data Source code
1 : : /********************************************************************
2 : : * gnc-icu-locale.cpp -- Localization with ICU. *
3 : : * *
4 : : * Copyright (C) 2025 John Ralls <jralls@ceridwen.us *
5 : : * *
6 : : * This program is free software; you can redistribute it and/or *
7 : : * modify it under the terms of the GNU General Public License as *
8 : : * published by the Free Software Foundation; either version 2 of *
9 : : * the License, or (at your option) any later version. *
10 : : * *
11 : : * This program is distributed in the hope that it will be useful, *
12 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 : : * GNU General Public License for more details. *
15 : : * *
16 : : * You should have received a copy of the GNU General Public License*
17 : : * along with this program; if not, contact: *
18 : : * *
19 : : * Free Software Foundation Voice: +1-617-542-5942 *
20 : : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 : : * Boston, MA 02110-1301, USA gnu@gnu.org *
22 : : ********************************************************************/
23 : :
24 : : #include "gnc-unicode.h"
25 : :
26 : : #include <memory>
27 : : #include <unicode/stsearch.h>
28 : : #include <unicode/tblcoll.h>
29 : : #include <unicode/coll.h>
30 : : #include "gnc-locale-utils.h"
31 : : #include <glib-2.0/glib.h>
32 : :
33 : : constexpr const char *logdomain{"gnc.locale"};
34 : :
35 : : enum class CompareStrength {
36 : : PRIMARY,
37 : : SECONDARY,
38 : : TERTIARY,
39 : : QUATERNARY,
40 : : IDENTICAL
41 : : };
42 : :
43 : : static void
44 : 3503 : collator_set_strength(icu::Collator* collator, CompareStrength strength)
45 : : {
46 : 3503 : switch (strength)
47 : : {
48 : 0 : case CompareStrength::PRIMARY:
49 : 0 : collator->setStrength(icu::Collator::PRIMARY);
50 : 0 : break;
51 : 0 : case CompareStrength::SECONDARY:
52 : 0 : collator->setStrength(icu::Collator::SECONDARY);
53 : 0 : break;
54 : 3503 : case CompareStrength::TERTIARY:
55 : 3503 : collator->setStrength(icu::Collator::TERTIARY);
56 : 3503 : break;
57 : 0 : case CompareStrength::QUATERNARY:
58 : 0 : collator->setStrength(icu::Collator::QUATERNARY);
59 : 0 : break;
60 : 0 : case CompareStrength::IDENTICAL:
61 : 0 : collator->setStrength(icu::Collator::IDENTICAL);
62 : 0 : break;
63 : : }
64 : 3503 : }
65 : :
66 : : static bool
67 : 0 : unicode_has_substring_internal(const char* needle, const char* haystack,
68 : : int* position, int* length,
69 : : CompareStrength strength)
70 : : {
71 : 0 : UErrorCode status{U_ZERO_ERROR};
72 : 0 : auto locale{gnc_locale_name()};
73 : 0 : auto u_needle{icu::UnicodeString::fromUTF8(needle)};
74 : 0 : auto u_haystack{icu::UnicodeString::fromUTF8(haystack)};
75 : 0 : icu::StringSearch search(u_needle, u_haystack, locale, nullptr, status);
76 : 0 : g_free(locale);
77 : :
78 : 0 : if (U_SUCCESS(status))
79 : : {
80 : 0 : auto collator = search.getCollator();
81 : 0 : collator_set_strength(collator, strength);
82 : 0 : search.reset();
83 : : }
84 : :
85 : 0 : if (U_FAILURE(status))
86 : : {
87 : 0 : g_log(logdomain, G_LOG_LEVEL_ERROR,
88 : : "StringSearch creation failed for %s", haystack);
89 : 0 : return false;
90 : : }
91 : :
92 : 0 : auto pos{search.first(status)};
93 : 0 : if (U_FAILURE(status))
94 : : {
95 : 0 : g_log(logdomain, G_LOG_LEVEL_ERROR,
96 : : "StringSearch encountered an error finding %s in %s",
97 : : needle, haystack);
98 : 0 : return false;
99 : : }
100 : 0 : if (pos == USEARCH_DONE)
101 : : {
102 : 0 : g_log(logdomain, G_LOG_LEVEL_DEBUG, "%s not found in %s",
103 : : needle, haystack);
104 : 0 : return false;
105 : : }
106 : :
107 : 0 : if (position && length)
108 : : {
109 : 0 : *position = pos;
110 : 0 : *length = search.getMatchedLength();
111 : : }
112 : :
113 : 0 : g_log(logdomain, G_LOG_LEVEL_DEBUG, "%s found in %s at index %d",
114 : : needle, haystack, pos);
115 : 0 : return true;
116 : 0 : }
117 : :
118 : : bool
119 : 0 : gnc_unicode_has_substring_base_chars(const char* needle,
120 : : const char* haystack,
121 : : int* position,
122 : : int* length)
123 : : {
124 : 0 : return unicode_has_substring_internal(needle, haystack, position, length,
125 : 0 : CompareStrength::PRIMARY);
126 : : }
127 : :
128 : : bool
129 : 0 : gnc_unicode_has_substring_accented_chars(const char* needle,
130 : : const char* haystack,
131 : : int* position,
132 : : int* length)
133 : : {
134 : 0 : return unicode_has_substring_internal(needle, haystack, position, length,
135 : 0 : CompareStrength::SECONDARY);
136 : : }
137 : :
138 : : bool
139 : 0 : gnc_unicode_has_substring_accented_case_sensitive(const char* needle,
140 : : const char* haystack,
141 : : int* position,
142 : : int* length)
143 : : {
144 : 0 : return unicode_has_substring_internal(needle, haystack, position, length,
145 : 0 : CompareStrength::TERTIARY);
146 : : }
147 : :
148 : : bool
149 : 0 : gnc_unicode_has_substring_identical(const char* needle,
150 : : const char*haystack,
151 : : int* position,
152 : : int* length)
153 : : {
154 : 0 : auto location = strstr(haystack, needle);
155 : 0 : if (location && location != haystack)
156 : : {
157 : 0 : *position = static_cast<int>(location - haystack);
158 : 0 : *length = strlen(needle);
159 : 0 : return true;
160 : : }
161 : 0 : return false;
162 : : }
163 : :
164 : : static int
165 : 3503 : unicode_compare_internal(const char* one, const char* two,
166 : : CompareStrength strength)
167 : : {
168 : 3503 : UErrorCode status{U_ZERO_ERROR};
169 : 3503 : auto locale{gnc_locale_name()};
170 : : std::unique_ptr<icu::Collator> coll(
171 : 3503 : icu::Collator::createInstance(icu::Locale(locale), status));
172 : :
173 : 3503 : if (U_SUCCESS(status))
174 : 3503 : collator_set_strength(coll.get(), strength);
175 : :
176 : 3503 : if (U_FAILURE(status))
177 : : {
178 : 0 : g_log(logdomain, G_LOG_LEVEL_ERROR,
179 : : "Failed to create collator for locale %s: %s",
180 : : locale, u_errorName(status));
181 : 0 : g_free(locale);
182 : 0 : return -99;
183 : : }
184 : :
185 : 3503 : auto result = coll->compare(one, two, status);
186 : :
187 : 3503 : if (U_FAILURE(status))
188 : : {
189 : 0 : g_log(logdomain, G_LOG_LEVEL_ERROR,
190 : : "Comparison of %s and %s in locale %s failed: %s",
191 : : one, two, locale, u_errorName(status));
192 : 0 : g_free(locale);
193 : 0 : return -99;
194 : : }
195 : :
196 : 3503 : g_free(locale);
197 : 3503 : return result == UCOL_LESS ? -1 : result == UCOL_EQUAL ? 0 : 1;
198 : 3503 : }
199 : :
200 : : int
201 : 0 : gnc_unicode_compare_base_chars(const char* one, const char* two)
202 : : {
203 : 0 : return unicode_compare_internal(one, two, CompareStrength::PRIMARY);
204 : : }
205 : :
206 : : int
207 : 0 : gnc_unicode_compare_accented_chars(const char* one, const char* two)
208 : : {
209 : 0 : return unicode_compare_internal(one, two, CompareStrength::SECONDARY);
210 : : }
211 : :
212 : : int
213 : 3503 : gnc_unicode_compare_accented_case_sensitive(const char* one, const char* two)
214 : : {
215 : 3503 : return unicode_compare_internal(one, two, CompareStrength::TERTIARY);
216 : : }
217 : :
218 : : int
219 : 0 : gnc_unicode_compare_identical(const char* one, const char* two)
220 : : {
221 : 0 : return unicode_compare_internal(one, two, CompareStrength::IDENTICAL);
222 : :
223 : : }
|