Coverage Report

Created: 2025-06-13 06:18

/src/proj/src/iso19111/internal.cpp
Line
Count
Source (jump to first uncovered line)
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
0
                       const std::string &after) {
62
0
    std::string ret(str);
63
0
    const size_t nBeforeSize = before.size();
64
0
    const size_t nAfterSize = after.size();
65
0
    if (nBeforeSize) {
66
0
        size_t nStartPos = 0;
67
0
        while ((nStartPos = ret.find(before, nStartPos)) != std::string::npos) {
68
0
            ret.replace(nStartPos, nBeforeSize, after);
69
0
            nStartPos += nAfterSize;
70
0
        }
71
0
    }
72
0
    return ret;
73
0
}
74
75
// ---------------------------------------------------------------------------
76
77
0
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
0
    return strncasecmp(a, b, size) == 0;
82
0
#endif
83
0
}
84
85
/**
86
 * Case-insensitive equality test
87
 */
88
0
bool ci_equal(const std::string &a, const std::string &b) noexcept {
89
0
    const auto size = a.size();
90
0
    if (size != b.size()) {
91
0
        return false;
92
0
    }
93
0
    return EQUALN(a.c_str(), b.c_str(), size);
94
0
}
95
96
0
bool ci_equal(const std::string &a, const char *b) noexcept {
97
0
    const auto size = a.size();
98
0
    if (size != strlen(b)) {
99
0
        return false;
100
0
    }
101
0
    return EQUALN(a.c_str(), b, size);
102
0
}
103
104
0
bool ci_equal(const char *a, const char *b) noexcept {
105
0
    const auto size = strlen(a);
106
0
    if (size != strlen(b)) {
107
0
        return false;
108
0
    }
109
0
    return EQUALN(a, b, size);
110
0
}
111
112
// ---------------------------------------------------------------------------
113
114
0
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
0
    return strcasecmp(a.c_str(), b.c_str()) < 0;
119
0
#endif
120
0
}
121
122
// ---------------------------------------------------------------------------
123
124
/**
125
 * Convert to lower case.
126
 */
127
128
std::string tolower(const std::string &str)
129
130
98
{
131
98
    std::string ret(str);
132
942
    for (size_t i = 0; i < ret.size(); i++)
133
844
        ret[i] = static_cast<char>(::tolower(ret[i]));
134
98
    return ret;
135
98
}
136
137
// ---------------------------------------------------------------------------
138
139
/**
140
 * Convert to upper case.
141
 */
142
143
std::string toupper(const std::string &str)
144
145
0
{
146
0
    std::string ret(str);
147
0
    for (size_t i = 0; i < ret.size(); i++)
148
0
        ret[i] = static_cast<char>(::toupper(ret[i]));
149
0
    return ret;
150
0
}
151
152
// ---------------------------------------------------------------------------
153
154
/** Strip leading and trailing double quote characters */
155
0
std::string stripQuotes(const std::string &str) {
156
0
    if (str.size() >= 2 && str[0] == '"' && str.back() == '"') {
157
0
        return str.substr(1, str.size() - 2);
158
0
    }
159
0
    return str;
160
0
}
161
162
// ---------------------------------------------------------------------------
163
164
0
size_t ci_find(const std::string &str, const char *needle) noexcept {
165
0
    const size_t needleSize = strlen(needle);
166
0
    for (size_t i = 0; i + needleSize <= str.size(); i++) {
167
0
        if (EQUALN(str.c_str() + i, needle, needleSize)) {
168
0
            return i;
169
0
        }
170
0
    }
171
0
    return std::string::npos;
172
0
}
173
174
// ---------------------------------------------------------------------------
175
176
size_t ci_find(const std::string &str, const std::string &needle,
177
0
               size_t startPos) noexcept {
178
0
    const size_t needleSize = needle.size();
179
0
    for (size_t i = startPos; i + needleSize <= str.size(); i++) {
180
0
        if (EQUALN(str.c_str() + i, needle.c_str(), needleSize)) {
181
0
            return i;
182
0
        }
183
0
    }
184
0
    return std::string::npos;
185
0
}
186
187
// ---------------------------------------------------------------------------
188
189
/*
190
bool starts_with(const std::string &str, const std::string &prefix) noexcept {
191
    if (str.size() < prefix.size()) {
192
        return false;
193
    }
194
    return std::memcmp(str.c_str(), prefix.c_str(), prefix.size()) == 0;
195
}
196
*/
197
198
// ---------------------------------------------------------------------------
199
200
/*
201
bool starts_with(const std::string &str, const char *prefix) noexcept {
202
    const size_t prefixSize = std::strlen(prefix);
203
    if (str.size() < prefixSize) {
204
        return false;
205
    }
206
    return std::memcmp(str.c_str(), prefix, prefixSize) == 0;
207
}
208
*/
209
210
// ---------------------------------------------------------------------------
211
212
0
bool ci_starts_with(const char *str, const char *prefix) noexcept {
213
0
    const auto str_size = strlen(str);
214
0
    const auto prefix_size = strlen(prefix);
215
0
    if (str_size < prefix_size) {
216
0
        return false;
217
0
    }
218
0
    return EQUALN(str, prefix, prefix_size);
219
0
}
220
221
// ---------------------------------------------------------------------------
222
223
bool ci_starts_with(const std::string &str,
224
0
                    const std::string &prefix) noexcept {
225
0
    if (str.size() < prefix.size()) {
226
0
        return false;
227
0
    }
228
0
    return EQUALN(str.c_str(), prefix.c_str(), prefix.size());
229
0
}
230
231
// ---------------------------------------------------------------------------
232
233
0
bool ends_with(const std::string &str, const std::string &suffix) noexcept {
234
0
    if (str.size() < suffix.size()) {
235
0
        return false;
236
0
    }
237
0
    return std::memcmp(str.c_str() + str.size() - suffix.size(), suffix.c_str(),
238
0
                       suffix.size()) == 0;
239
0
}
240
241
// ---------------------------------------------------------------------------
242
243
0
double c_locale_stod(const std::string &s) {
244
0
    bool success;
245
0
    double val = c_locale_stod(s, success);
246
0
    if (!success) {
247
0
        throw std::invalid_argument("non double value");
248
0
    }
249
0
    return val;
250
0
}
251
252
0
double c_locale_stod(const std::string &s, bool &success) {
253
254
0
    success = true;
255
0
    const auto s_size = s.size();
256
    // Fast path
257
0
    if (s_size > 0 && s_size < 15) {
258
0
        std::int64_t acc = 0;
259
0
        std::int64_t div = 1;
260
0
        bool afterDot = false;
261
0
        size_t i = 0;
262
0
        if (s[0] == '-') {
263
0
            ++i;
264
0
            div = -1;
265
0
        } else if (s[0] == '+') {
266
0
            ++i;
267
0
        }
268
0
        for (; i < s_size; ++i) {
269
0
            const auto ch = s[i];
270
0
            if (ch >= '0' && ch <= '9') {
271
0
                acc = acc * 10 + ch - '0';
272
0
                if (afterDot) {
273
0
                    div *= 10;
274
0
                }
275
0
            } else if (ch == '.') {
276
0
                afterDot = true;
277
0
            } else {
278
0
                div = 0;
279
0
            }
280
0
        }
281
0
        if (div) {
282
0
            return static_cast<double>(acc) / div;
283
0
        }
284
0
    }
285
286
0
    std::istringstream iss(s);
287
0
    iss.imbue(std::locale::classic());
288
0
    double d;
289
0
    iss >> d;
290
0
    if (!iss.eof() || iss.fail()) {
291
0
        success = false;
292
0
        d = 0;
293
0
    }
294
0
    return d;
295
0
}
296
297
// ---------------------------------------------------------------------------
298
299
0
std::vector<std::string> split(const std::string &str, char separator) {
300
0
    std::vector<std::string> res;
301
0
    size_t lastPos = 0;
302
0
    size_t newPos = 0;
303
0
    while ((newPos = str.find(separator, lastPos)) != std::string::npos) {
304
0
        res.push_back(str.substr(lastPos, newPos - lastPos));
305
0
        lastPos = newPos + 1;
306
0
    }
307
0
    res.push_back(str.substr(lastPos));
308
0
    return res;
309
0
}
310
311
// ---------------------------------------------------------------------------
312
313
std::vector<std::string> split(const std::string &str,
314
0
                               const std::string &separator) {
315
0
    std::vector<std::string> res;
316
0
    size_t lastPos = 0;
317
0
    size_t newPos = 0;
318
0
    while ((newPos = str.find(separator, lastPos)) != std::string::npos) {
319
0
        res.push_back(str.substr(lastPos, newPos - lastPos));
320
0
        lastPos = newPos + separator.size();
321
0
    }
322
0
    res.push_back(str.substr(lastPos));
323
0
    return res;
324
0
}
325
326
// ---------------------------------------------------------------------------
327
328
#ifdef _WIN32
329
330
// For some reason, sqlite3_snprintf() in the sqlite3 builds used on AppVeyor
331
// doesn't round identically to the Unix builds, and thus breaks a number of
332
// unit test. So to avoid this, use the stdlib formatting
333
334
std::string toString(int val) {
335
    std::ostringstream buffer;
336
    buffer.imbue(std::locale::classic());
337
    buffer << val;
338
    return buffer.str();
339
}
340
341
std::string toString(double val, int precision) {
342
    std::ostringstream buffer;
343
    buffer.imbue(std::locale::classic());
344
    buffer << std::setprecision(precision);
345
    buffer << val;
346
    auto str = buffer.str();
347
    if (precision == 15 && str.find("9999999999") != std::string::npos) {
348
        buffer.str("");
349
        buffer.clear();
350
        buffer << std::setprecision(14);
351
        buffer << val;
352
        return buffer.str();
353
    }
354
    return str;
355
}
356
357
#else
358
359
32
std::string toString(int val) {
360
    // use sqlite3 API that is slightly faster than std::ostringstream
361
    // with forcing the C locale. sqlite3_snprintf() emulates a C locale.
362
32
    constexpr int BUF_SIZE = 16;
363
32
    char szBuffer[BUF_SIZE];
364
32
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%d", val);
365
32
    return szBuffer;
366
32
}
367
368
0
std::string toString(double val, int precision) {
369
    // use sqlite3 API that is slightly faster than std::ostringstream
370
    // with forcing the C locale. sqlite3_snprintf() emulates a C locale.
371
0
    constexpr int BUF_SIZE = 32;
372
0
    char szBuffer[BUF_SIZE];
373
0
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%.*g", precision, val);
374
0
    if (precision == 15 && strstr(szBuffer, "9999999999")) {
375
0
        sqlite3_snprintf(BUF_SIZE, szBuffer, "%.14g", val);
376
0
    }
377
0
    return szBuffer;
378
0
}
379
380
#endif
381
382
// ---------------------------------------------------------------------------
383
384
0
std::string concat(const char *a, const std::string &b) {
385
0
    std::string res(a);
386
0
    res += b;
387
0
    return res;
388
0
}
389
390
0
std::string concat(const char *a, const std::string &b, const char *c) {
391
0
    std::string res(a);
392
0
    res += b;
393
0
    res += c;
394
0
    return res;
395
0
}
396
397
// ---------------------------------------------------------------------------
398
399
// Avoid rounding issues due to year -> second (SI unit) -> year roundtrips
400
0
double getRoundedEpochInDecimalYear(double year) {
401
    // Try to see if the value is close to xxxx.yyy decimal year.
402
0
    if (std::fabs(1000 * year - std::round(1000 * year)) <= 1e-3) {
403
0
        year = std::round(1000 * year) / 1000.0;
404
0
    }
405
0
    return year;
406
0
}
407
408
// ---------------------------------------------------------------------------
409
410
} // namespace internal
411
412
NS_PROJ_END