Coverage Report

Created: 2025-08-29 07:11

/src/PROJ/src/iso19111/internal.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  ISO19111:2019 implementation
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
33
#include "proj/internal/internal.hpp"
34
35
#include <cmath>
36
#include <cstdint>
37
#include <cstring>
38
#ifdef _MSC_VER
39
#include <string.h>
40
#else
41
#include <strings.h>
42
#endif
43
#include <exception>
44
#include <iomanip> // std::setprecision
45
#include <locale>
46
#include <sstream> // std::istringstream and std::ostringstream
47
#include <string>
48
49
#include "sqlite3.h"
50
51
NS_PROJ_START
52
53
namespace internal {
54
55
// ---------------------------------------------------------------------------
56
57
/**
58
 * Replace all occurrences of before with after.
59
 */
60
std::string replaceAll(const std::string &str, const std::string &before,
61
280k
                       const std::string &after) {
62
280k
    std::string ret(str);
63
280k
    const size_t nBeforeSize = before.size();
64
280k
    const size_t nAfterSize = after.size();
65
280k
    if (nBeforeSize) {
66
280k
        size_t nStartPos = 0;
67
413k
        while ((nStartPos = ret.find(before, nStartPos)) != std::string::npos) {
68
132k
            ret.replace(nStartPos, nBeforeSize, after);
69
132k
            nStartPos += nAfterSize;
70
132k
        }
71
280k
    }
72
280k
    return ret;
73
280k
}
74
75
// ---------------------------------------------------------------------------
76
77
1.26G
inline static bool EQUALN(const char *a, const char *b, size_t size) {
78
#ifdef _MSC_VER
79
    return _strnicmp(a, b, size) == 0;
80
#else
81
1.26G
    return strncasecmp(a, b, size) == 0;
82
1.26G
#endif
83
1.26G
}
84
85
/**
86
 * Case-insensitive equality test
87
 */
88
8.65M
bool ci_equal(const std::string &a, const std::string &b) noexcept {
89
8.65M
    const auto size = a.size();
90
8.65M
    if (size != b.size()) {
91
2.43M
        return false;
92
2.43M
    }
93
6.21M
    return EQUALN(a.c_str(), b.c_str(), size);
94
8.65M
}
95
96
5.54M
bool ci_equal(const std::string &a, const char *b) noexcept {
97
5.54M
    const auto size = a.size();
98
5.54M
    if (size != strlen(b)) {
99
3.76M
        return false;
100
3.76M
    }
101
1.77M
    return EQUALN(a.c_str(), b, size);
102
5.54M
}
103
104
3.16k
bool ci_equal(const char *a, const char *b) noexcept {
105
3.16k
    const auto size = strlen(a);
106
3.16k
    if (size != strlen(b)) {
107
743
        return false;
108
743
    }
109
2.41k
    return EQUALN(a, b, size);
110
3.16k
}
111
112
// ---------------------------------------------------------------------------
113
114
20.2k
bool ci_less(const std::string &a, const std::string &b) noexcept {
115
#ifdef _MSC_VER
116
    return _stricmp(a.c_str(), b.c_str()) < 0;
117
#else
118
20.2k
    return strcasecmp(a.c_str(), b.c_str()) < 0;
119
20.2k
#endif
120
20.2k
}
121
122
// ---------------------------------------------------------------------------
123
124
/**
125
 * Convert to lower case.
126
 */
127
128
std::string tolower(const std::string &str)
129
130
32.3k
{
131
32.3k
    std::string ret(str);
132
32.3k
    for (char &ch : ret)
133
185k
        ch =
134
185k
            (ch >= 'A' && ch <= 'Z') ? static_cast<char>(ch + ('a' - 'A')) : ch;
135
32.3k
    return ret;
136
32.3k
}
137
138
// ---------------------------------------------------------------------------
139
140
/**
141
 * Convert to upper case.
142
 */
143
144
std::string toupper(const std::string &str)
145
146
215
{
147
215
    std::string ret(str);
148
215
    for (char &ch : ret)
149
2.40k
        ch =
150
2.40k
            (ch >= 'a' && ch <= 'z') ? static_cast<char>(ch - ('a' - 'A')) : ch;
151
215
    return ret;
152
215
}
153
154
// ---------------------------------------------------------------------------
155
156
/** Strip leading and trailing double quote characters */
157
89.3k
std::string stripQuotes(const std::string &str) {
158
89.3k
    if (str.size() >= 2 && str[0] == '"' && str.back() == '"') {
159
10.1k
        return str.substr(1, str.size() - 2);
160
10.1k
    }
161
79.2k
    return str;
162
89.3k
}
163
164
// ---------------------------------------------------------------------------
165
166
4.64M
size_t ci_find(const std::string &str, const char *needle) noexcept {
167
4.64M
    const size_t needleSize = strlen(needle);
168
171M
    for (size_t i = 0; i + needleSize <= str.size(); i++) {
169
167M
        if (EQUALN(str.c_str() + i, needle, needleSize)) {
170
262k
            return i;
171
262k
        }
172
167M
    }
173
4.38M
    return std::string::npos;
174
4.64M
}
175
176
// ---------------------------------------------------------------------------
177
178
size_t ci_find(const std::string &str, const std::string &needle,
179
76.9M
               size_t startPos) noexcept {
180
76.9M
    const size_t needleSize = needle.size();
181
1.16G
    for (size_t i = startPos; i + needleSize <= str.size(); i++) {
182
1.08G
        if (EQUALN(str.c_str() + i, needle.c_str(), needleSize)) {
183
10.6k
            return i;
184
10.6k
        }
185
1.08G
    }
186
76.8M
    return std::string::npos;
187
76.9M
}
188
189
// ---------------------------------------------------------------------------
190
191
/*
192
bool starts_with(const std::string &str, const std::string &prefix) noexcept {
193
    if (str.size() < prefix.size()) {
194
        return false;
195
    }
196
    return std::memcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0;
197
}
198
*/
199
200
// ---------------------------------------------------------------------------
201
202
/*
203
bool starts_with(const std::string &str, const char *prefix) noexcept {
204
    const size_t prefixSize = std::strlen(prefix);
205
    if (str.size() < prefixSize) {
206
        return false;
207
    }
208
    return std::memcmp(str.c_str(), prefix, prefixSize) == 0;
209
}
210
*/
211
212
// ---------------------------------------------------------------------------
213
214
105k
bool ci_starts_with(const char *str, const char *prefix) noexcept {
215
105k
    const auto str_size = strlen(str);
216
105k
    const auto prefix_size = strlen(prefix);
217
105k
    if (str_size < prefix_size) {
218
10.2k
        return false;
219
10.2k
    }
220
95.1k
    return EQUALN(str, prefix, prefix_size);
221
105k
}
222
223
// ---------------------------------------------------------------------------
224
225
bool ci_starts_with(const std::string &str,
226
2.13M
                    const std::string &prefix) noexcept {
227
2.13M
    if (str.size() < prefix.size()) {
228
390k
        return false;
229
390k
    }
230
1.74M
    return EQUALN(str.c_str(), prefix.c_str(), prefix.size());
231
2.13M
}
232
233
// ---------------------------------------------------------------------------
234
235
1.81M
bool ends_with(const std::string &str, const std::string &suffix) noexcept {
236
1.81M
    if (str.size() < suffix.size()) {
237
38.8k
        return false;
238
38.8k
    }
239
1.77M
    return std::memcmp(str.c_str() + str.size() - suffix.size(), suffix.c_str(),
240
1.77M
                       suffix.size()) == 0;
241
1.81M
}
242
243
// ---------------------------------------------------------------------------
244
245
28.9M
double c_locale_stod(const std::string &s) {
246
28.9M
    bool success;
247
28.9M
    double val = c_locale_stod(s, success);
248
28.9M
    if (!success) {
249
885
        throw std::invalid_argument("non double value");
250
885
    }
251
28.9M
    return val;
252
28.9M
}
253
254
28.9M
double c_locale_stod(const std::string &s, bool &success) {
255
256
28.9M
    success = true;
257
28.9M
    const auto s_size = s.size();
258
    // Fast path
259
28.9M
    if (s_size > 0 && s_size < 15) {
260
28.9M
        std::int64_t acc = 0;
261
28.9M
        std::int64_t div = 1;
262
28.9M
        bool afterDot = false;
263
28.9M
        size_t i = 0;
264
28.9M
        if (s[0] == '-') {
265
1.26M
            ++i;
266
1.26M
            div = -1;
267
27.6M
        } else if (s[0] == '+') {
268
1.01k
            ++i;
269
1.01k
        }
270
287M
        for (; i < s_size; ++i) {
271
258M
            const auto ch = s[i];
272
258M
            if (ch >= '0' && ch <= '9') {
273
229M
                acc = acc * 10 + ch - '0';
274
229M
                if (afterDot) {
275
50.0M
                    div *= 10;
276
50.0M
                }
277
229M
            } else if (ch == '.') {
278
28.8M
                afterDot = true;
279
28.8M
            } else {
280
5.43k
                div = 0;
281
5.43k
            }
282
258M
        }
283
28.9M
        if (div) {
284
28.9M
            return static_cast<double>(acc) / div;
285
28.9M
        }
286
28.9M
    }
287
288
18.7k
    std::istringstream iss(s);
289
18.7k
    iss.imbue(std::locale::classic());
290
18.7k
    double d;
291
18.7k
    iss >> d;
292
18.7k
    if (!iss.eof() || iss.fail()) {
293
2.78k
        success = false;
294
2.78k
        d = 0;
295
2.78k
    }
296
18.7k
    return d;
297
28.9M
}
298
299
// ---------------------------------------------------------------------------
300
301
233k
std::vector<std::string> split(const std::string &str, char separator) {
302
233k
    std::vector<std::string> res;
303
233k
    size_t lastPos = 0;
304
233k
    size_t newPos = 0;
305
511k
    while ((newPos = str.find(separator, lastPos)) != std::string::npos) {
306
278k
        res.push_back(str.substr(lastPos, newPos - lastPos));
307
278k
        lastPos = newPos + 1;
308
278k
    }
309
233k
    res.push_back(str.substr(lastPos));
310
233k
    return res;
311
233k
}
312
313
// ---------------------------------------------------------------------------
314
315
std::vector<std::string> split(const std::string &str,
316
91.0k
                               const std::string &separator) {
317
91.0k
    std::vector<std::string> res;
318
91.0k
    size_t lastPos = 0;
319
91.0k
    size_t newPos = 0;
320
136k
    while ((newPos = str.find(separator, lastPos)) != std::string::npos) {
321
45.8k
        res.push_back(str.substr(lastPos, newPos - lastPos));
322
45.8k
        lastPos = newPos + separator.size();
323
45.8k
    }
324
91.0k
    res.push_back(str.substr(lastPos));
325
91.0k
    return res;
326
91.0k
}
327
328
// ---------------------------------------------------------------------------
329
330
#ifdef _WIN32
331
332
// For some reason, sqlite3_snprintf() in the sqlite3 builds used on AppVeyor
333
// doesn't round identically to the Unix builds, and thus breaks a number of
334
// unit test. So to avoid this, use the stdlib formatting
335
336
std::string toString(int val) {
337
    std::ostringstream buffer;
338
    buffer.imbue(std::locale::classic());
339
    buffer << val;
340
    return buffer.str();
341
}
342
343
std::string toString(double val, int precision) {
344
    std::ostringstream buffer;
345
    buffer.imbue(std::locale::classic());
346
    buffer << std::setprecision(precision);
347
    buffer << val;
348
    auto str = buffer.str();
349
    if (precision == 15 && str.find("9999999999") != std::string::npos) {
350
        buffer.str("");
351
        buffer.clear();
352
        buffer << std::setprecision(14);
353
        buffer << val;
354
        return buffer.str();
355
    }
356
    return str;
357
}
358
359
#else
360
361
942k
std::string toString(int val) {
362
    // use sqlite3 API that is slightly faster than std::ostringstream
363
    // with forcing the C locale. sqlite3_snprintf() emulates a C locale.
364
942k
    constexpr int BUF_SIZE = 16;
365
942k
    char szBuffer[BUF_SIZE];
366
942k
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%d", val);
367
942k
    return szBuffer;
368
942k
}
369
370
1.69M
std::string toString(double val, int precision) {
371
    // use sqlite3 API that is slightly faster than std::ostringstream
372
    // with forcing the C locale. sqlite3_snprintf() emulates a C locale.
373
1.69M
    constexpr int BUF_SIZE = 32;
374
1.69M
    char szBuffer[BUF_SIZE];
375
1.69M
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%.*g", precision, val);
376
1.69M
    if (precision == 15 && strstr(szBuffer, "9999999999")) {
377
3.35k
        sqlite3_snprintf(BUF_SIZE, szBuffer, "%.14g", val);
378
3.35k
    }
379
1.69M
    return szBuffer;
380
1.69M
}
381
382
#endif
383
384
// ---------------------------------------------------------------------------
385
386
525
std::string concat(const char *a, const std::string &b) {
387
525
    std::string res(a);
388
525
    res += b;
389
525
    return res;
390
525
}
391
392
244k
std::string concat(const char *a, const std::string &b, const char *c) {
393
244k
    std::string res(a);
394
244k
    res += b;
395
244k
    res += c;
396
244k
    return res;
397
244k
}
398
399
// ---------------------------------------------------------------------------
400
401
// Avoid rounding issues due to year -> second (SI unit) -> year roundtrips
402
43
double getRoundedEpochInDecimalYear(double year) {
403
    // Try to see if the value is close to xxxx.yyy decimal year.
404
43
    if (std::fabs(1000 * year - std::round(1000 * year)) <= 1e-3) {
405
43
        year = std::round(1000 * year) / 1000.0;
406
43
    }
407
43
    return year;
408
43
}
409
410
// ---------------------------------------------------------------------------
411
412
} // namespace internal
413
414
NS_PROJ_END