GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/db/unitsdb.cpp Lines: 80 162 49.4 %
Date: 2017-11-29 Branches: 79 264 29.9 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2017  The ManaPlus Developers
6
 *
7
 *  This file is part of The ManaPlus Client.
8
 *
9
 *  This program is free software; you can redistribute it and/or modify
10
 *  it under the terms of the GNU General Public License as published by
11
 *  the Free Software Foundation; either version 2 of the License, or
12
 *  any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include "resources/db/unitsdb.h"
24
25
#include "configuration.h"
26
27
#include "const/resources/currency.h"
28
29
#include "utils/cast.h"
30
#include "utils/checkutils.h"
31
#include "utils/stdmove.h"
32
33
#include "resources/beingcommon.h"
34
35
#include <climits>
36
37
#include "debug.h"
38
39
namespace
40
{
41
22890
    struct UnitLevel final
42
    {
43
        A_DEFAULT_COPY(UnitLevel)
44
45
        std::string symbol;
46
        int count;
47
        int round;
48
        std::string separator;
49
    };
50
51



3052
    struct UnitDescription final
52
    {
53
        A_DEFAULT_COPY(UnitDescription)
54
55
        STD_VECTOR<struct UnitLevel> levels;
56
        double conversion;
57
        bool mix;
58
    };
59
60
2
    UnitDescription defaultCurrency;
61
2
    UnitDescription defaultWeight;
62
63
2
    std::map<std::string, UnitDescription> mCurrencies;
64
}  // namespace
65
66
static std::string formatUnit(const int value,
67
                              const UnitDescription &ud);
68
69
static std::string splitNumber(std::string str,
70
                               const std::string &separator);
71
72
152
void UnitsDb::load()
73
{
74
152
    logger->log1("Initializing unit database...");
75
    {   // Setup default weight
76
304
        UnitDescription ud;
77
78
152
        ud.conversion = 1.0;
79
152
        ud.mix = false;
80
81
152
        const UnitLevel bu =
82
        {
83
            "g",
84
            1,
85
            0,
86
            ""
87

912
        };
88
152
        ud.levels.push_back(bu);
89
90
152
        const UnitLevel ul =
91
        {
92
            "kg",
93
            1000,
94
            2,
95
            ""
96

912
        };
97
152
        ud.levels.push_back(ul);
98
99
152
        defaultWeight = ud;
100
    }
101
102
    {   // Setup default currency
103
304
        UnitDescription ud;
104
105
152
        ud.conversion = 1.0;
106
152
        ud.mix = false;
107
108

912
        const UnitLevel bu = {"¤", 1, 0, ""};
109
152
        ud.levels.push_back(bu);
110
111
152
        defaultCurrency = ud;
112
    }
113
114

760
    loadXmlFile(paths.getStringValue("unitsFile"), SkipError_false);
115

760
    loadXmlFile(paths.getStringValue("unitsPatchFile"), SkipError_true);
116



2128
    loadXmlDir("unitsPatchDir", loadXmlFile);
117
152
}
118
119
536
void UnitsDb::unload()
120
{
121
536
    logger->log1("Unloading unit database...");
122
536
    mCurrencies.clear();
123
536
}
124
125
304
static UnitDescription loadUnit(XmlNodePtr node)
126
{
127
304
    UnitDescription ud;
128
304
    int level = 1;
129
304
    ud.conversion = XML::getProperty(node, "conversion", 1);
130

1824
    ud.mix = XML::getProperty(node, "mix", "no") == "yes";
131
132
608
    UnitLevel bu;
133

1520
    bu.symbol = XML::getProperty(node, "base", "¤");
134
304
    bu.count = 1;
135
304
    bu.round = XML::getProperty(node, "round", 2);
136

1520
    bu.separator = XML::getProperty(node, "separator", " ");
137
138
304
    ud.levels.push_back(bu);
139
140
760
    for_each_xml_child_node(uLevel, node)
141
    {
142

456
        if (xmlNameEqual(uLevel, "level"))
143
        {
144
            const UnitLevel ul =
145
            {
146
                XML::getProperty(uLevel, "symbol",
147
304
                                 strprintf("¤%d", level)),
148
152
                XML::getProperty(uLevel, "count", -1),
149
152
                XML::getProperty(uLevel, "round", bu.round),
150
                XML::getProperty(uLevel, "separator", bu.separator)
151

608
            };
152
153
152
            if (ul.count > 0)
154
            {
155
152
                ud.levels.push_back(ul);
156
152
                level++;
157
            }
158
            else
159
            {
160
                logger->log("Error bad unit count: %d for %s in %s",
161
                    ul.count,
162
                    ul.symbol.c_str(),
163
                    bu.symbol.c_str());
164
            }
165
        }
166
    }
167
168
    // Add one more level for saftey
169
304
    const UnitLevel lev =
170
    {
171
        "",
172
        INT_MAX,
173
        0,
174
        ""
175

1824
    };
176
304
    ud.levels.push_back(lev);
177
304
    return ud;
178
}
179
180
static void loadCurrencies(XmlNodePtr parentNode)
181
{
182
    for_each_xml_child_node(node, parentNode)
183
    {
184
        if (xmlNameEqual(node, "unit"))
185
        {
186
            const std::string name = XML::getProperty(node, "name", "");
187
            if (name.empty())
188
            {
189
                reportAlways("Error: unknown currency name.");
190
                continue;
191
            }
192
            mCurrencies[name] = loadUnit(node);
193
            if (name == DEFAULT_CURRENCY)
194
                defaultCurrency = mCurrencies[name];
195
        }
196
    }
197
}
198
199
304
void UnitsDb::loadXmlFile(const std::string &fileName,
200
                          const SkipError skipError)
201
{
202
456
    XML::Document doc(fileName, UseVirtFs_true, skipError);
203
304
    XmlNodeConstPtrConst root = doc.rootNode();
204
205

304
    if ((root == nullptr) || !xmlNameEqual(root, "units"))
206
    {
207
304
        logger->log("Error loading unit definition file: "
208

1064
            + paths.getStringValue("unitsFile"));
209
152
        return;
210
    }
211
212
1824
    for_each_xml_child_node(node, root)
213
    {
214

760
        if (xmlNameEqual(node, "include"))
215
        {
216
            const std::string name = XML::getProperty(node, "name", "");
217
            if (!name.empty())
218
                loadXmlFile(name, skipError);
219
            continue;
220
        }
221

760
        else if (xmlNameEqual(node, "unit"))
222
        {
223

1520
            const std::string type = XML::getProperty(node, "type", "");
224
608
            UnitDescription ud = loadUnit(node);
225
304
            if (type == "weight")
226
            {
227
                defaultWeight = ud;
228
            }
229
152
            else if (type == "currency")
230
            {
231
152
                defaultCurrency = ud;
232
152
                mCurrencies[DEFAULT_CURRENCY] = ud;
233
            }
234
            else
235
            {
236
                logger->log("Error unknown unit type: %s", type.c_str());
237
            }
238
        }
239

456
        else if (xmlNameEqual(node, "currency"))
240
        {
241
            loadCurrencies(node);
242
        }
243
    }
244
}
245
246
6
static std::string formatUnit(const int value,
247
                              const UnitDescription &ud)
248
{
249
12
    UnitLevel ul;
250
251
    // Shortcut for 0; do the same for values less than 0  (for now)
252
6
    if (value <= 0)
253
    {
254
6
        ul = ud.levels[0];
255
6
        return strprintf("0%s", ul.symbol.c_str());
256
    }
257
258
    double amount = ud.conversion * value;
259
    const unsigned int sz = CAST_U32(ud.levels.size());
260
261
    // If only the first level is needed, act like mix if false
262
    if (ud.mix && !ud.levels.empty() && ud.levels[1].count < amount)
263
    {
264
        std::string output;
265
        UnitLevel pl = ud.levels[0];
266
        ul = ud.levels[1];
267
        int levelAmount = CAST_S32(amount);
268
        int nextAmount = 0;
269
270
        if (ul.count != 0)
271
            levelAmount /= ul.count;
272
273
        amount -= static_cast<double>(levelAmount * ul.count);
274
275
        if (amount > 0)
276
        {
277
            output = splitNumber(strprintf("%.*f", pl.round,
278
                amount), pl.separator).append(pl.symbol);
279
        }
280
281
        for (unsigned int i = 2; i < sz; i++)
282
        {
283
            pl = ul;
284
            ul = ud.levels[i];
285
286
            if (ul.count != 0)
287
            {
288
                nextAmount = levelAmount / ul.count;
289
                levelAmount %= ul.count;
290
            }
291
292
            if (levelAmount > 0)
293
            {
294
                output = splitNumber(strprintf("%d", levelAmount),
295
                    pl.separator).append(pl.symbol).append(output);
296
            }
297
298
            if (nextAmount == 0)
299
                break;
300
            levelAmount = nextAmount;
301
        }
302
303
        return output;
304
    }
305
306
    ul.round = 0;
307
    for (unsigned int i = 0; i < sz; i++)
308
    {
309
        ul = ud.levels[i];
310
        if (amount < ul.count && ul.count > 0)
311
        {
312
            ul = ud.levels[i - 1];
313
            break;
314
        }
315
        if (ul.count != 0)
316
            amount /= ul.count;
317
    }
318
319
    return splitNumber(strprintf("%.*f", ul.round, amount),
320
        ul.separator).append(ul.symbol);
321
}
322
323
6
std::string UnitsDb::formatCurrency(const int value)
324
{
325
6
    return formatUnit(value, defaultCurrency);
326
}
327
328
std::string UnitsDb::formatCurrency64(const int64_t value)
329
{
330
    return formatUnit(CAST_S32(value),
331
        defaultCurrency);
332
}
333
334
std::string UnitsDb::formatCurrency(std::string name,
335
                                    const int value)
336
{
337
    if (mCurrencies.find(name) == mCurrencies.end())
338
        name = DEFAULT_CURRENCY;
339
    return formatUnit(value, mCurrencies[name]);
340
}
341
342
std::string UnitsDb::formatCurrency64(std::string name,
343
                                      const int64_t value)
344
{
345
    if (mCurrencies.find(name) == mCurrencies.end())
346
        name = DEFAULT_CURRENCY;
347
    return formatUnit(CAST_S32(value),
348
        mCurrencies[name]);
349
}
350
351
std::string UnitsDb::formatWeight(const int value)
352
{
353
    return formatUnit(value, defaultWeight);
354
}
355
356
bool UnitsDb::existsCurrency(const std::string &name)
357
{
358
    return mCurrencies.find(name) != mCurrencies.end();
359
}
360
361
static std::string splitNumber(std::string str,
362
                               const std::string &separator)
363
{
364
    std::string lastPart;
365
    const size_t point = str.find('.');
366
    if (point != std::string::npos)
367
    {
368
        lastPart = str.substr(point);
369
        str = str.substr(0, point);
370
    }
371
    std::string result;
372
373
    if (!str.empty())
374
    {
375
        size_t sz = str.size();
376
        while (sz >= 3)
377
        {
378
            if (sz >= 6)
379
            {
380
                result = std::string(separator).append(str.substr(
381
                    sz - 3)).append(result);
382
            }
383
            else
384
            {
385
                result = str.substr(sz - 3).append(result);
386
            }
387
            str = str.substr(0, str.size() - 3);
388
            sz = str.size();
389
        }
390
        if (!str.empty())
391
        {
392
            if (!result.empty())
393
                result = std::string(str).append(separator).append(result);
394
            else
395
                result = STD_MOVE(str);
396
        }
397
    }
398
399
    return result + lastPart;
400

6
}