Branch data TLA Line data Source code
1 : : // Copyright (c) 2014-2022 Thomas Fussell
2 : : // Copyright (c) 2024-2025 xlnt-community
3 : : //
4 : : // Permission is hereby granted, free of charge, to any person obtaining a copy
5 : : // of this software and associated documentation files (the "Software"), to deal
6 : : // in the Software without restriction, including without limitation the rights
7 : : // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : : // copies of the Software, and to permit persons to whom the Software is
9 : : // furnished to do so, subject to the following conditions:
10 : : //
11 : : // The above copyright notice and this permission notice shall be included in
12 : : // all copies or substantial portions of the Software.
13 : : //
14 : : // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : : // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : : // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : : // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : : // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : : // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : : // THE SOFTWARE
21 : : //
22 : : // @license: http://www.opensource.org/licenses/mit-license.php
23 : : // @author: see AUTHORS file
24 : :
25 : : #include <algorithm>
26 : : #include <cctype>
27 : : #include <cmath>
28 : : #include <unordered_map>
29 : :
30 : : #include <xlnt/utils/exceptions.hpp>
31 : : #include <detail/default_case.hpp>
32 : : #include <detail/number_format/number_formatter.hpp>
33 : : #include <detail/serialization/parsers.hpp>
34 : : #include <detail/serialization/serialisation_helpers.hpp>
35 : :
36 : : #define FMT_HEADER_ONLY
37 : : #include <fmt/format.h>
38 : :
39 : : namespace {
40 : :
41 :CBC 4 : const std::unordered_map<int, std::string> known_locales()
42 : : {
43 : : static const std::unordered_map<int, std::string> all = std::unordered_map<int, std::string>(
44 : : {
45 :UBC 0 : {0x1, "Arabic"},
46 : 0 : {0x2, "Bulgarian"},
47 : 0 : {0x3, "Catalan"},
48 : 0 : {0x4, "Chinese (Simplified)"},
49 : 0 : {0x4, "Chinese (Simplified) Legacy"},
50 : 0 : {0x5, "Czech"},
51 : 0 : {0x6, "Danish"},
52 : 0 : {0x7, "German"},
53 : 0 : {0x8, "Greek"},
54 : 0 : {0x9, "English"},
55 : 0 : {0xA, "Spanish"},
56 : 0 : {0xB, "Finnish"},
57 : 0 : {0xC, "French"},
58 : 0 : {0xD, "Hebrew"},
59 : 0 : {0xE, "Hungarian"},
60 : 0 : {0xF, "Icelandic"},
61 : 0 : {0x10, "Italian"},
62 : 0 : {0x11, "Japanese"},
63 : 0 : {0x12, "Korean"},
64 : 0 : {0x13, "Dutch"},
65 : 0 : {0x14, "Norwegian"},
66 : 0 : {0x15, "Polish"},
67 : 0 : {0x16, "Portuguese"},
68 : 0 : {0x17, "Romansh"},
69 : 0 : {0x18, "Romanian"},
70 : 0 : {0x19, "Russian"},
71 : 0 : {0x1A, "Croatian"},
72 : 0 : {0x1B, "Slovak"},
73 : 0 : {0x1C, "Albanian"},
74 : 0 : {0x1D, "Swedish"},
75 : 0 : {0x1E, "Thai"},
76 : 0 : {0x1F, "Turkish"},
77 : 0 : {0x20, "Urdu"},
78 : 0 : {0x21, "Indonesian"},
79 : 0 : {0x22, "Ukrainian"},
80 : 0 : {0x23, "Belarusian"},
81 : 0 : {0x24, "Slovenian"},
82 : 0 : {0x25, "Estonian"},
83 : 0 : {0x26, "Latvian"},
84 : 0 : {0x27, "Lithuanian"},
85 : 0 : {0x28, "Tajik"},
86 : 0 : {0x29, "Persian"},
87 : 0 : {0x2A, "Vietnamese"},
88 : 0 : {0x2B, "Armenian"},
89 : 0 : {0x2C, "Azerbaijani"},
90 : 0 : {0x2D, "Basque"},
91 : 0 : {0x2E, "Upper Sorbian"},
92 : 0 : {0x2F, "Macedonian (FYROM)"},
93 : 0 : {0x30, "Southern Sotho"},
94 : 0 : {0x31, "Tsonga"},
95 : 0 : {0x32, "Setswana"},
96 : 0 : {0x33, "Venda"},
97 : 0 : {0x34, "isiXhosa"},
98 : 0 : {0x35, "isiZulu"},
99 : 0 : {0x36, "Afrikaans"},
100 : 0 : {0x37, "Georgian"},
101 : 0 : {0x38, "Faroese"},
102 : 0 : {0x39, "Hindi"},
103 : 0 : {0x3A, "Maltese"},
104 : 0 : {0x3B, "Sami (Northern)"},
105 : 0 : {0x3C, "Irish"},
106 : 0 : {0x3D, "Yiddish"},
107 : 0 : {0x3E, "Malay"},
108 : 0 : {0x3F, "Kazakh"},
109 : 0 : {0x40, "Kyrgyz"},
110 : 0 : {0x41, "Kiswahili"},
111 : 0 : {0x42, "Turkmen"},
112 : 0 : {0x43, "Uzbek"},
113 : 0 : {0x44, "Tatar"},
114 : 0 : {0x45, "Bangla"},
115 : 0 : {0x46, "Punjabi"},
116 : 0 : {0x47, "Gujarati"},
117 : 0 : {0x48, "Odia"},
118 : 0 : {0x49, "Tamil"},
119 : 0 : {0x4A, "Telugu"},
120 : 0 : {0x4B, "Kannada"},
121 : 0 : {0x4C, "Malayalam"},
122 : 0 : {0x4D, "Assamese"},
123 : 0 : {0x4E, "Marathi"},
124 : 0 : {0x4F, "Sanskrit"},
125 : 0 : {0x50, "Mongolian"},
126 : 0 : {0x51, "Tibetan"},
127 : 0 : {0x52, "Welsh"},
128 : 0 : {0x53, "Khmer"},
129 : 0 : {0x54, "Lao"},
130 : 0 : {0x55, "Burmese"},
131 : 0 : {0x56, "Galician"},
132 : 0 : {0x57, "Konkani"},
133 : 0 : {0x58, "Manipuri"},
134 : 0 : {0x59, "Sindhi"},
135 : 0 : {0x5A, "Syriac"},
136 : 0 : {0x5B, "Sinhala"},
137 : 0 : {0x5C, "Cherokee"},
138 : 0 : {0x5D, "Inuktitut"},
139 : 0 : {0x5E, "Amharic"},
140 : 0 : {0x5F, "Tamazight"},
141 : 0 : {0x60, "Kashmiri"},
142 : 0 : {0x61, "Nepali"},
143 : 0 : {0x62, "Frisian"},
144 : 0 : {0x63, "Pashto"},
145 : 0 : {0x64, "Filipino"},
146 : 0 : {0x65, "Divehi"},
147 : 0 : {0x66, "Edo"},
148 : 0 : {0x67, "Fulah"},
149 : 0 : {0x68, "Hausa"},
150 : 0 : {0x69, "Ibibio"},
151 : 0 : {0x6A, "Yoruba"},
152 : 0 : {0x6B, "Quechua"},
153 : 0 : {0x6C, "Sesotho sa Leboa"},
154 : 0 : {0x6D, "Bashkir"},
155 : 0 : {0x6E, "Luxembourgish"},
156 : 0 : {0x6F, "Greenlandic"},
157 : 0 : {0x70, "Igbo"},
158 : 0 : {0x71, "Kanuri"},
159 : 0 : {0x72, "Oromo"},
160 : 0 : {0x73, "Tigrinya"},
161 : 0 : {0x74, "Guarani"},
162 : 0 : {0x75, "Hawaiian"},
163 : 0 : {0x76, "Latin"},
164 : 0 : {0x77, "Somali"},
165 : 0 : {0x78, "Yi"},
166 : 0 : {0x79, "Papiamento"},
167 : 0 : {0x7A, "Mapudungun"},
168 : 0 : {0x7C, "Mohawk"},
169 : 0 : {0x7E, "Breton"},
170 : 0 : {0x7F, "Invariant Language (Invariant Country)"},
171 : 0 : {0x80, "Uyghur"},
172 : 0 : {0x81, "Maori"},
173 : : {0x82, "Occitan"},
174 : : {0x83, "Corsican"},
175 : : {0x84, "Alsatian"},
176 : : {0x85, "Sakha"},
177 : : {0x86, "K’iche’"},
178 : : {0x87, "Kinyarwanda"},
179 : : {0x88, "Wolof"},
180 : : {0x8C, "Dari"},
181 : : {0x91, "Scottish Gaelic"},
182 : : {0x92, "Central Kurdish"},
183 : : {0x401, "Arabic (Saudi Arabia)"},
184 : : {0x402, "Bulgarian (Bulgaria)"},
185 : : {0x403, "Catalan (Catalan)"},
186 : : {0x404, "Chinese (Traditional, Taiwan)"},
187 : : {0x405, "Czech (Czech Republic)"},
188 : : {0x406, "Danish (Denmark)"},
189 : : {0x407, "German (Germany)"},
190 : : {0x408, "Greek (Greece)"},
191 : : {0x409, "English (United States)"},
192 : : {0x40B, "Finnish (Finland)"},
193 : : {0x40C, "French (France)"},
194 : : {0x40D, "Hebrew (Israel)"},
195 : : {0x40E, "Hungarian (Hungary)"},
196 : : {0x40F, "Icelandic (Iceland)"},
197 : : {0x410, "Italian (Italy)"},
198 : : {0x411, "Japanese (Japan)"},
199 : : {0x412, "Korean (Korea)"},
200 : : {0x413, "Dutch (Netherlands)"},
201 : : {0x414, "Norwegian, Bokmål (Norway)"},
202 : : {0x415, "Polish (Poland)"},
203 : : {0x416, "Portuguese (Brazil)"},
204 : : {0x417, "Romansh (Switzerland)"},
205 : : {0x418, "Romanian (Romania)"},
206 : : {0x419, "Russian (Russia)"},
207 : : {0x41A, "Croatian (Croatia)"},
208 : : {0x41B, "Slovak (Slovakia)"},
209 : : {0x41C, "Albanian (Albania)"},
210 : : {0x41D, "Swedish (Sweden)"},
211 : : {0x41E, "Thai (Thailand)"},
212 : : {0x41F, "Turkish (Turkey)"},
213 : : {0x420, "Urdu (Islamic Republic of Pakistan)"},
214 : : {0x421, "Indonesian (Indonesia)"},
215 : : {0x422, "Ukrainian (Ukraine)"},
216 : : {0x423, "Belarusian (Belarus)"},
217 : : {0x424, "Slovenian (Slovenia)"},
218 : : {0x425, "Estonian (Estonia)"},
219 : : {0x426, "Latvian (Latvia)"},
220 : : {0x427, "Lithuanian (Lithuania)"},
221 : : {0x428, "Tajik (Cyrillic, Tajikistan)"},
222 : : {0x429, "Persian (Iran)"},
223 : : {0x42A, "Vietnamese (Vietnam)"},
224 : : {0x42B, "Armenian (Armenia)"},
225 : : {0x42C, "Azerbaijani (Latin, Azerbaijan)"},
226 : : {0x42D, "Basque (Basque)"},
227 : : {0x42E, "Upper Sorbian (Germany)"},
228 : : {0x42F, "Macedonian (Former Yugoslav Republic of Macedonia)"},
229 : : {0x430, "Southern Sotho (South Africa)"},
230 : : {0x431, "Tsonga (South Africa)"},
231 : : {0x432, "Setswana (South Africa)"},
232 : : {0x433, "Venda (South Africa)"},
233 : : {0x434, "isiXhosa (South Africa)"},
234 : : {0x435, "isiZulu (South Africa)"},
235 : : {0x436, "Afrikaans (South Africa)"},
236 : : {0x437, "Georgian (Georgia)"},
237 : : {0x438, "Faroese (Faroe Islands)"},
238 : : {0x439, "Hindi (India)"},
239 : : {0x43A, "Maltese (Malta)"},
240 : : {0x43B, "Sami, Northern (Norway)"},
241 : : {0x43D, "Yiddish (World)"},
242 : : {0x43E, "Malay (Malaysia)"},
243 : : {0x43F, "Kazakh (Kazakhstan)"},
244 : : {0x440, "Kyrgyz (Kyrgyzstan)"},
245 : : {0x441, "Kiswahili (Kenya)"},
246 : : {0x442, "Turkmen (Turkmenistan)"},
247 : : {0x443, "Uzbek (Latin, Uzbekistan)"},
248 : : {0x444, "Tatar (Russia)"},
249 : : {0x445, "Bangla (India)"},
250 : : {0x446, "Punjabi (India)"},
251 : : {0x447, "Gujarati (India)"},
252 : : {0x448, "Odia (India)"},
253 : : {0x449, "Tamil (India)"},
254 : : {0x44A, "Telugu (India)"},
255 : : {0x44B, "Kannada (India)"},
256 : : {0x44C, "Malayalam (India)"},
257 : : {0x44D, "Assamese (India)"},
258 : : {0x44E, "Marathi (India)"},
259 : : {0x44F, "Sanskrit (India)"},
260 : : {0x450, "Mongolian (Cyrillic, Mongolia)"},
261 : : {0x451, "Tibetan (PRC)"},
262 : : {0x452, "Welsh (United Kingdom)"},
263 : : {0x453, "Khmer (Cambodia)"},
264 : : {0x454, "Lao (Lao P.D.R.)"},
265 : : {0x455, "Burmese (Myanmar)"},
266 : : {0x456, "Galician (Galician)"},
267 : : {0x457, "Konkani (India)"},
268 : : {0x458, "Manipuri (India)"},
269 : : {0x459, "Sindhi (Devanagari, India)"},
270 : : {0x45A, "Syriac (Syria)"},
271 : : {0x45B, "Sinhala (Sri Lanka)"},
272 : : {0x45C, "Cherokee (Cherokee)"},
273 : : {0x45D, "Inuktitut (Syllabics, Canada)"},
274 : : {0x45E, "Amharic (Ethiopia)"},
275 : : {0x45F, "Central Atlas Tamazight (Arabic, Morocco)"},
276 : : {0x460, "Kashmiri (Perso-Arabic)"},
277 : : {0x461, "Nepali (Nepal)"},
278 : : {0x462, "Frisian (Netherlands)"},
279 : : {0x463, "Pashto (Afghanistan)"},
280 : : {0x464, "Filipino (Philippines)"},
281 : : {0x465, "Divehi (Maldives)"},
282 : : {0x466, "Edo (Nigeria)"},
283 : : {0x467, "Fulah (Nigeria)"},
284 : : {0x468, "Hausa (Latin, Nigeria)"},
285 : : {0x469, "Ibibio (Nigeria)"},
286 : : {0x46A, "Yoruba (Nigeria)"},
287 : : {0x46B, "Quechua (Bolivia)"},
288 : : {0x46C, "Sesotho sa Leboa (South Africa)"},
289 : : {0x46D, "Bashkir (Russia)"},
290 : : {0x46E, "Luxembourgish (Luxembourg)"},
291 : : {0x46F, "Greenlandic (Greenland)"},
292 : : {0x470, "Igbo (Nigeria)"},
293 : : {0x471, "Kanuri (Nigeria)"},
294 : : {0x472, "Oromo (Ethiopia)"},
295 : : {0x473, "Tigrinya (Ethiopia)"},
296 : : {0x474, "Guarani (Paraguay)"},
297 : : {0x475, "Hawaiian (United States)"},
298 : : {0x476, "Latin (World)"},
299 : : {0x477, "Somali (Somalia)"},
300 : : {0x478, "Yi (PRC)"},
301 : : {0x479, "Papiamento (Caribbean)"},
302 : : {0x47A, "Mapudungun (Chile)"},
303 : : {0x47C, "Mohawk (Mohawk)"},
304 : : {0x47E, "Breton (France)"},
305 : : {0x480, "Uyghur (PRC)"},
306 : : {0x481, "Maori (New Zealand)"},
307 : : {0x482, "Occitan (France)"},
308 : : {0x483, "Corsican (France)"},
309 : : {0x484, "Alsatian (France)"},
310 : : {0x485, "Sakha (Russia)"},
311 : : {0x486, "K’iche’ (Guatemala)"},
312 : : {0x487, "Kinyarwanda (Rwanda)"},
313 : : {0x488, "Wolof (Senegal)"},
314 : : {0x48C, "Dari (Afghanistan)"},
315 : : {0x491, "Scottish Gaelic (United Kingdom)"},
316 : : {0x492, "Central Kurdish (Iraq)"},
317 : : {0x801, "Arabic (Iraq)"},
318 : : {0x803, "Valencian (Spain)"},
319 : : {0x804, "Chinese (Simplified, PRC)"},
320 : : {0x807, "German (Switzerland)"},
321 : : {0x809, "English (United Kingdom)"},
322 : : {0x80A, "Spanish (Mexico)"},
323 : : {0x80C, "French (Belgium)"},
324 : : {0x810, "Italian (Switzerland)"},
325 : : {0x813, "Dutch (Belgium)"},
326 : : {0x814, "Norwegian, Nynorsk (Norway)"},
327 : : {0x816, "Portuguese (Portugal)"},
328 : : {0x818, "Romanian (Moldova)"},
329 : : {0x819, "Russian (Moldova)"},
330 : : {0x81D, "Swedish (Finland)"},
331 : : {0x820, "Urdu (India)"},
332 : : {0x82C, "Azerbaijani (Cyrillic, Azerbaijan)"},
333 : : {0x82E, "Lower Sorbian (Germany)"},
334 : : {0x832, "Setswana (Botswana)"},
335 : : {0x83B, "Sami, Northern (Sweden)"},
336 : : {0x83C, "Irish (Ireland)"},
337 : : {0x83E, "Malay (Brunei Darussalam)"},
338 : : {0x843, "Uzbek (Cyrillic, Uzbekistan)"},
339 : : {0x845, "Bangla (Bangladesh)"},
340 : : {0x846, "Punjabi (Islamic Republic of Pakistan)"},
341 : : {0x849, "Tamil (Sri Lanka)"},
342 : : {0x850, "Mongolian (Traditional Mongolian, PRC)"},
343 : : {0x859, "Sindhi (Islamic Republic of Pakistan)"},
344 : : {0x85D, "Inuktitut (Latin, Canada)"},
345 : : {0x85F, "Tamazight (Latin, Algeria)"},
346 : : {0x860, "Kashmiri (Devanagari, India)"},
347 : : {0x861, "Nepali (India)"},
348 : : {0x867, "Fulah (Latin, Senegal)"},
349 : : {0x86B, "Quechua (Ecuador)"},
350 : : {0x873, "Tigrinya (Eritrea)"},
351 : : {0xC01, "Arabic (Egypt)"},
352 : : {0xC04, "Chinese (Traditional, Hong Kong S.A.R.)"},
353 : : {0xC07, "German (Austria)"},
354 : : {0xC09, "English (Australia)"},
355 : : {0xC0A, "Spanish (Spain)"},
356 : : {0xC0C, "French (Canada)"},
357 : : {0xC3B, "Sami, Northern (Finland)"},
358 : : {0xC50, "Mongolian (Traditional Mongolian, Mongolia)"},
359 : : {0xC51, "Dzongkha (Bhutan)"},
360 : : {0xC6B, "Quechua (Peru)"},
361 : : {0x1001, "Arabic (Libya)"},
362 : : {0x1004, "Chinese (Simplified, Singapore)"},
363 : : {0x1007, "German (Luxembourg)"},
364 : : {0x1009, "English (Canada)"},
365 : : {0x100A, "Spanish (Guatemala)"},
366 : : {0x100C, "French (Switzerland)"},
367 : : {0x101A, "Croatian (Latin, Bosnia and Herzegovina)"},
368 : : {0x103B, "Sami, Lule (Norway)"},
369 : : {0x105F, "Central Atlas Tamazight (Tifinagh, Morocco)"},
370 : : {0x1401, "Arabic (Algeria)"},
371 : : {0x1404, "Chinese (Traditional, Macao S.A.R.)"},
372 : : {0x1407, "German (Liechtenstein)"},
373 : : {0x1409, "English (New Zealand)"},
374 : : {0x140A, "Spanish (Costa Rica)"},
375 : : {0x140C, "French (Luxembourg)"},
376 : : {0x141A, "Bosnian (Latin, Bosnia and Herzegovina)"},
377 : : {0x143B, "Sami, Lule (Sweden)"},
378 : : {0x1801, "Arabic (Morocco)"},
379 : : {0x1809, "English (Ireland)"},
380 : : {0x180A, "Spanish (Panama)"},
381 : : {0x180C, "French (Monaco)"},
382 : : {0x181A, "Serbian (Latin, Bosnia and Herzegovina)"},
383 : : {0x183B, "Sami, Southern (Norway)"},
384 : : {0x1C01, "Arabic (Tunisia)"},
385 : : {0x1C09, "English (South Africa)"},
386 : : {0x1C0A, "Spanish (Dominican Republic)"},
387 : : {0x1C0C, "French (Caribbean)"},
388 : : {0x1C1A, "Serbian (Cyrillic, Bosnia and Herzegovina)"},
389 : : {0x1C3B, "Sami, Southern (Sweden)"},
390 : : {0x2001, "Arabic (Oman)"},
391 : : {0x2009, "English (Jamaica)"},
392 : : {0x200A, "Spanish (Venezuela)"},
393 : : {0x200C, "French (Reunion)"},
394 : : {0x201A, "Bosnian (Cyrillic, Bosnia and Herzegovina)"},
395 : : {0x203B, "Sami, Skolt (Finland)"},
396 : : {0x2401, "Arabic (Yemen)"},
397 : : {0x2409, "English (Caribbean)"},
398 : : {0x240A, "Spanish (Colombia)"},
399 : : {0x240C, "French (Congo DRC)"},
400 : : {0x241A, "Serbian (Latin, Serbia)"},
401 : : {0x243B, "Sami, Inari (Finland)"},
402 : : {0x2801, "Arabic (Syria)"},
403 : : {0x2809, "English (Belize)"},
404 : : {0x280A, "Spanish (Peru)"},
405 : : {0x280C, "French (Senegal)"},
406 : : {0x281A, "Serbian (Cyrillic, Serbia)"},
407 : : {0x2C01, "Arabic (Jordan)"},
408 : : {0x2C09, "English (Trinidad and Tobago)"},
409 : : {0x2C0A, "Spanish (Argentina)"},
410 : : {0x2C0C, "French (Cameroon)"},
411 : : {0x2C1A, "Serbian (Latin, Montenegro)"},
412 : : {0x3001, "Arabic (Lebanon)"},
413 : : {0x3009, "English (Zimbabwe)"},
414 : : {0x300A, "Spanish (Ecuador)"},
415 : : {0x300C, "French (Côte d’Ivoire)"},
416 : : {0x301A, "Serbian (Cyrillic, Montenegro)"},
417 : : {0x3401, "Arabic (Kuwait)"},
418 : : {0x3409, "English (Philippines)"},
419 : : {0x340A, "Spanish (Chile)"},
420 : : {0x340C, "French (Mali)"},
421 : : {0x3801, "Arabic (U.A.E.)"},
422 : : {0x3809, "English (Indonesia)"},
423 : : {0x380A, "Spanish (Uruguay)"},
424 : : {0x380C, "French (Morocco)"},
425 : : {0x3C01, "Arabic (Bahrain)"},
426 : : {0x3C09, "English (Hong Kong SAR)"},
427 : : {0x3C0A, "Spanish (Paraguay)"},
428 : : {0x3C0C, "French (Haiti)"},
429 : : {0x4001, "Arabic (Qatar)"},
430 : : {0x4009, "English (India)"},
431 : : {0x400A, "Spanish (Bolivia)"},
432 : : {0x4409, "English (Malaysia)"},
433 : : {0x440A, "Spanish (El Salvador)"},
434 : : {0x4809, "English (Singapore)"},
435 : : {0x480A, "Spanish (Honduras)"},
436 : : {0x4C0A, "Spanish (Nicaragua)"},
437 : : {0x500A, "Spanish (Puerto Rico)"},
438 : : {0x540A, "Spanish (United States)"},
439 : : {0x580A, "Spanish (Latin America)"},
440 : : {0x5C0A, "Spanish (Cuba)"},
441 : : {0x641A, "Bosnian (Cyrillic)"},
442 : : {0x681A, "Bosnian (Latin)"},
443 : : {0x6C1A, "Serbian (Cyrillic)"},
444 : : {0x701A, "Serbian (Latin)"},
445 : : {0x703B, "Sami (Inari)"},
446 : : {0x742C, "Azerbaijani (Cyrillic)"},
447 : : {0x743B, "Sami (Skolt)"},
448 : : {0x7804, "Chinese"},
449 : : {0x7814, "Norwegian (Nynorsk)"},
450 : : {0x781A, "Bosnian"},
451 : : {0x782C, "Azerbaijani (Latin)"},
452 : : {0x783B, "Sami (Southern)"},
453 : : {0x7843, "Uzbek (Cyrillic)"},
454 : : {0x7850, "Mongolian (Cyrillic)"},
455 : : {0x785D, "Inuktitut (Syllabics)"},
456 : : {0x785F, "Tamazight (Tifinagh)"},
457 : : {0x7C04, "Chinese (Traditional)"},
458 : : {0x7C04, "Chinese (Traditional) Legacy"},
459 : : {0x7C14, "Norwegian (Bokmål)"},
460 : : {0x7C1A, "Serbian"},
461 : : {0x7C28, "Tajik (Cyrillic)"},
462 : : {0x7C2E, "Lower Sorbian"},
463 : : {0x7C3B, "Sami (Lule)"},
464 : : {0x7C43, "Uzbek (Latin)"},
465 : : {0x7C46, "Punjabi (Arabic)"},
466 : : {0x7C50, "Mongolian (Traditional Mongolian)"},
467 : : {0x7C59, "Sindhi (Arabic)"},
468 : : {0x7C5C, "Cherokee (Cherokee)"},
469 : : {0x7C5D, "Inuktitut (Latin)"},
470 : : {0x7C5F, "Tamazight (Latin)"},
471 : : {0x7C67, "Fulah (Latin)"},
472 : : {0x7C68, "Hausa (Latin)"},
473 : : {0x7C86, "K’iche’"},
474 : : {0x7C92, "Central Kurdish (Arabic)"},
475 : : {0xF400, "System Default for Time"},
476 : : {0xF800, "System Default for Long Date"},
477 [ + + + - :CBC 438 : });
+ + - - -
- ]
478 : :
479 : 4 : return all;
480 [ + + + + : 2 : }
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + -
- - - ]
481 : :
482 : 8 : [[noreturn]] void unhandled_case_error()
483 : : {
484 [ + + ]: 24 : throw xlnt::exception("unhandled");
485 : : }
486 : :
487 : 8 : void unhandled_case(bool error)
488 : : {
489 [ + - ]: 8 : if (error)
490 : : {
491 : 8 : unhandled_case_error();
492 : : }
493 :UBC 0 : }
494 : :
495 : : } // namespace
496 : :
497 : : namespace xlnt {
498 : : namespace detail {
499 : :
500 :CBC 48 : bool format_condition::satisfied_by(double number) const
501 : : {
502 [ + + + + : 48 : switch (type)
+ + - ]
503 : : {
504 : 11 : case condition_type::greater_or_equal:
505 : 11 : return number >= value;
506 : 9 : case condition_type::greater_than:
507 : 9 : return number > value;
508 : 11 : case condition_type::less_or_equal:
509 : 11 : return number <= value;
510 : 7 : case condition_type::less_than:
511 : 7 : return number < value;
512 : 3 : case condition_type::not_equal:
513 : 3 : return std::fabs(number - value) != 0.0;
514 : 7 : case condition_type::equal:
515 : 7 : return std::fabs(number - value) == 0.0;
516 : : }
517 : :
518 [ # ]:UBC 0 : default_case(false);
519 : : }
520 : :
521 :CBC 294 : number_format_parser::number_format_parser(const std::string &format_string)
522 : : {
523 [ + ]: 294 : reset(format_string);
524 : 294 : }
525 : :
526 : 270 : const std::vector<format_code> &number_format_parser::result() const
527 : : {
528 : 270 : return codes_;
529 : : }
530 : :
531 : 294 : void number_format_parser::reset(const std::string &format_string)
532 : : {
533 : 294 : format_string_ = format_string;
534 : 294 : position_ = 0;
535 : 294 : codes_.clear();
536 : 294 : }
537 : :
538 : 294 : void number_format_parser::parse()
539 : : {
540 [ + ]: 294 : auto token = parse_next_token();
541 : 284 : format_code section;
542 : 284 : template_part part;
543 : :
544 : : for (;;)
545 : : {
546 [ + + + + : 1393 : switch (token.type)
+ + + + +
+ - ]
547 : : {
548 : 92 : case number_format_token::token_type::end_section: {
549 [ + ]: 92 : codes_.push_back(section);
550 : 92 : section = format_code();
551 : :
552 : 92 : break;
553 : : }
554 : :
555 : 23 : case number_format_token::token_type::color: {
556 [ + + + - : 23 : if (section.has_color || section.has_condition || section.has_locale || !section.parts.empty())
+ - - + +
+ ]
557 : : {
558 [ + + ]: 3 : throw xlnt::exception("color should be the first part of a format");
559 : : }
560 : :
561 : 22 : section.has_color = true;
562 [ + ]: 22 : section.color = color_from_string(token.string);
563 : :
564 : 22 : break;
565 : : }
566 : :
567 : 8 : case number_format_token::token_type::locale: {
568 [ + + ]: 8 : if (section.has_locale)
569 : : {
570 [ + + ]: 3 : throw xlnt::exception("multiple locales");
571 : : }
572 : :
573 : 7 : section.has_locale = true;
574 [ + ]: 7 : auto parsed_locale = locale_from_string(token.string);
575 : 3 : section.locale = parsed_locale.first;
576 : :
577 [ + + ]: 3 : if (!parsed_locale.second.empty())
578 : : {
579 : 2 : part.type = template_part::template_type::text;
580 [ + ]: 2 : part.string = parsed_locale.second;
581 [ + ]: 2 : section.parts.push_back(part);
582 : 2 : part = template_part();
583 : : }
584 : :
585 : 3 : break;
586 : 3 : }
587 : :
588 : 62 : case number_format_token::token_type::condition: {
589 [ + + ]: 62 : if (section.has_condition)
590 : : {
591 [ + + ]: 3 : throw xlnt::exception("multiple conditions");
592 : : }
593 : :
594 : 61 : section.has_condition = true;
595 : 61 : std::string value;
596 : :
597 [ + + ]: 61 : if (token.string.front() == '<')
598 : : {
599 [ + + ]: 26 : if (token.string[1] == '=')
600 : : {
601 : 14 : section.condition.type = format_condition::condition_type::less_or_equal;
602 [ + ]: 14 : value = token.string.substr(2);
603 : : }
604 [ + + ]: 12 : else if (token.string[1] == '>')
605 : : {
606 : 4 : section.condition.type = format_condition::condition_type::not_equal;
607 [ + ]: 4 : value = token.string.substr(2);
608 : : }
609 : : else
610 : : {
611 : 8 : section.condition.type = format_condition::condition_type::less_than;
612 [ + ]: 8 : value = token.string.substr(1);
613 : : }
614 : : }
615 [ + + ]: 35 : else if (token.string.front() == '>')
616 : : {
617 [ + + ]: 24 : if (token.string[1] == '=')
618 : : {
619 : 13 : section.condition.type = format_condition::condition_type::greater_or_equal;
620 [ + ]: 13 : value = token.string.substr(2);
621 : : }
622 : : else
623 : : {
624 : 11 : section.condition.type = format_condition::condition_type::greater_than;
625 [ + ]: 11 : value = token.string.substr(1);
626 : : }
627 : : }
628 [ + - ]: 11 : else if (token.string.front() == '=')
629 : : {
630 : 11 : section.condition.type = format_condition::condition_type::equal;
631 [ + ]: 11 : value = token.string.substr(1);
632 : : }
633 : :
634 [ + ]: 61 : section.condition.value = xlnt::detail::deserialise(value);
635 : 61 : break;
636 : 61 : }
637 : :
638 : 324 : case number_format_token::token_type::text: {
639 : 324 : part.type = template_part::template_type::text;
640 [ + ]: 324 : part.string = token.string;
641 [ + ]: 324 : section.parts.push_back(part);
642 : 324 : part = template_part();
643 : :
644 : 324 : break;
645 : : }
646 : :
647 : 3 : case number_format_token::token_type::fill: {
648 : 3 : part.type = template_part::template_type::fill;
649 [ + ]: 3 : part.string = token.string;
650 [ + ]: 3 : section.parts.push_back(part);
651 : 3 : part = template_part();
652 : :
653 : 3 : break;
654 : : }
655 : :
656 : 2 : case number_format_token::token_type::space: {
657 : 2 : part.type = template_part::template_type::space;
658 [ + ]: 2 : part.string = token.string;
659 [ + ]: 2 : section.parts.push_back(part);
660 : 2 : part = template_part();
661 : :
662 : 2 : break;
663 : : }
664 : :
665 : 343 : case number_format_token::token_type::number: {
666 : 343 : part.type = template_part::template_type::general;
667 [ + ]: 343 : part.placeholders = parse_placeholders(token.string);
668 [ + ]: 343 : section.parts.push_back(part);
669 : 343 : part = template_part();
670 : :
671 : 343 : break;
672 : : }
673 : :
674 : 264 : case number_format_token::token_type::datetime: {
675 : 264 : section.is_datetime = true;
676 : :
677 [ + + + + : 264 : switch (token.string.front())
+ + + - ]
678 : : {
679 : 7 : case '[':
680 : 7 : section.is_timedelta = true;
681 : :
682 [ + + + + : 7 : if (token.string == "[h]" || token.string == "[hh]")
+ + + + ]
683 : : {
684 : 5 : part.type = template_part::template_type::elapsed_hours;
685 : 5 : break;
686 : : }
687 [ + + - + : 2 : else if (token.string == "[m]" || token.string == "[mm]")
+ + + + ]
688 : : {
689 : 1 : part.type = template_part::template_type::elapsed_minutes;
690 : 1 : break;
691 : : }
692 [ + + - + : 1 : else if (token.string == "[s]" || token.string == "[ss]")
+ - + - ]
693 : : {
694 : 1 : part.type = template_part::template_type::elapsed_seconds;
695 : 1 : break;
696 : : }
697 : :
698 [ # ]:UBC 0 : unhandled_case(true);
699 : 0 : break;
700 : :
701 :CBC 95 : case 'm':
702 [ + + + ]: 95 : if (token.string == "m")
703 : : {
704 : 27 : part.type = template_part::template_type::month_number;
705 : 27 : break;
706 : : }
707 [ + + + ]: 68 : else if (token.string == "mm")
708 : : {
709 : 52 : part.type = template_part::template_type::month_number_leading_zero;
710 : 52 : break;
711 : : }
712 [ + + + ]: 16 : else if (token.string == "mmm")
713 : : {
714 : 13 : part.type = template_part::template_type::month_abbreviation;
715 : 13 : break;
716 : : }
717 [ + + + ]: 3 : else if (token.string == "mmmm")
718 : : {
719 : 1 : part.type = template_part::template_type::month_name;
720 : 1 : break;
721 : : }
722 [ + + + ]: 2 : else if (token.string == "mmmmm")
723 : : {
724 : 1 : part.type = template_part::template_type::month_letter;
725 : 1 : break;
726 : : }
727 : :
728 [ - ]: 1 : unhandled_case(true);
729 :UBC 0 : break;
730 : :
731 :CBC 42 : case 'd':
732 [ + + + ]: 42 : if (token.string == "d")
733 : : {
734 : 25 : part.type = template_part::template_type::day_number;
735 : 25 : break;
736 : : }
737 [ + + + ]: 17 : else if (token.string == "dd")
738 : : {
739 : 14 : part.type = template_part::template_type::day_number_leading_zero;
740 : 14 : break;
741 : : }
742 [ + + + ]: 3 : else if (token.string == "ddd")
743 : : {
744 : 1 : part.type = template_part::template_type::day_abbreviation;
745 : 1 : break;
746 : : }
747 [ + + + ]: 2 : else if (token.string == "dddd")
748 : : {
749 : 1 : part.type = template_part::template_type::day_name;
750 : 1 : break;
751 : : }
752 : :
753 [ - ]: 1 : unhandled_case(true);
754 :UBC 0 : break;
755 : :
756 :CBC 39 : case 'y':
757 [ + + + ]: 39 : if (token.string == "yy")
758 : : {
759 : 34 : part.type = template_part::template_type::year_short;
760 : 34 : break;
761 : : }
762 [ + + + ]: 5 : else if (token.string == "yyyy")
763 : : {
764 : 4 : part.type = template_part::template_type::year_long;
765 : 4 : break;
766 : : }
767 : :
768 [ - ]: 1 : unhandled_case(true);
769 :UBC 0 : break;
770 : :
771 :CBC 35 : case 'h':
772 [ + + + ]: 35 : if (token.string == "h")
773 : : {
774 : 30 : part.type = template_part::template_type::hour;
775 : 30 : break;
776 : : }
777 [ + + + ]: 5 : else if (token.string == "hh")
778 : : {
779 : 4 : part.type = template_part::template_type::hour_leading_zero;
780 : 4 : break;
781 : : }
782 : :
783 [ - ]: 1 : unhandled_case(true);
784 :UBC 0 : break;
785 : :
786 :CBC 33 : case 's':
787 [ + + + ]: 33 : if (token.string == "s")
788 : : {
789 : 2 : part.type = template_part::template_type::second;
790 : 2 : break;
791 : : }
792 [ + + + ]: 31 : else if (token.string == "ss")
793 : : {
794 : 30 : part.type = template_part::template_type::second_leading_zero;
795 : 30 : break;
796 : : }
797 : :
798 [ - ]: 1 : unhandled_case(true);
799 :UBC 0 : break;
800 : :
801 :CBC 13 : case 'A':
802 : 13 : section.twelve_hour = true;
803 : :
804 [ + + + ]: 13 : if (token.string == "AM/PM")
805 : : {
806 : 11 : part.type = template_part::template_type::am_pm;
807 : 11 : break;
808 : : }
809 [ + + - ]: 2 : else if (token.string == "A/P")
810 : : {
811 : 2 : part.type = template_part::template_type::a_p;
812 : 2 : break;
813 : : }
814 : :
815 [ # ]:UBC 0 : unhandled_case(true);
816 : 0 : break;
817 : :
818 : 0 : default:
819 [ # ]: 0 : unhandled_case(true);
820 : 0 : break;
821 : : }
822 : :
823 [ + ]:CBC 259 : section.parts.push_back(part);
824 : 259 : part = template_part();
825 : :
826 : 259 : break;
827 : : }
828 : :
829 : 272 : case number_format_token::token_type::end: {
830 [ + ]: 272 : codes_.push_back(section);
831 [ + ]: 272 : finalize();
832 : :
833 : 540 : return;
834 : : }
835 : : }
836 : :
837 [ + ]: 1109 : token = parse_next_token();
838 : 1109 : }
839 : 312 : }
840 : :
841 : 272 : void number_format_parser::finalize()
842 : : {
843 [ + + ]: 636 : for (auto &code : codes_)
844 : : {
845 : 364 : bool fix = false;
846 : 364 : bool leading_zero = false;
847 : 364 : std::size_t minutes_index = 0;
848 : :
849 : 364 : bool integer_part = false;
850 : 364 : bool fractional_part = false;
851 : 364 : std::size_t integer_part_index = 0;
852 : :
853 : 364 : bool percentage = false;
854 : :
855 : 364 : bool exponent = false;
856 : 364 : std::size_t exponent_index = 0;
857 : :
858 : 364 : bool fraction = false;
859 : 364 : std::size_t fraction_denominator_index = 0;
860 : 364 : std::size_t fraction_numerator_index = 0;
861 : :
862 : 364 : bool seconds = false;
863 : 364 : bool fractional_seconds = false;
864 : 364 : std::size_t seconds_index = 0;
865 : :
866 [ + + ]: 1297 : for (std::size_t i = 0; i < code.parts.size(); ++i)
867 : : {
868 : 933 : const auto &part = code.parts[i];
869 : :
870 [ + + + + ]: 569 : if (i > 0 && i + 1 < code.parts.size() && part.type == template_part::template_type::text
871 [ + + + ]: 173 : && part.string == "/"
872 [ + + ]: 30 : && code.parts[i - 1].placeholders.type == format_placeholders::placeholders_type::integer_part
873 [ + + + - : 1502 : && code.parts[i + 1].placeholders.type == format_placeholders::placeholders_type::integer_part)
+ + ]
874 : : {
875 : 8 : fraction = true;
876 : 8 : fraction_numerator_index = i - 1;
877 : 8 : fraction_denominator_index = i + 1;
878 : : }
879 : :
880 [ + + ]: 933 : if (part.placeholders.type == format_placeholders::placeholders_type::integer_part)
881 : : {
882 : 119 : integer_part = true;
883 : 119 : integer_part_index = i;
884 : : }
885 [ + + ]: 814 : else if (part.placeholders.type == format_placeholders::placeholders_type::fractional_part)
886 : : {
887 : 48 : fractional_part = true;
888 : : }
889 [ + + ]: 766 : else if (part.placeholders.type == format_placeholders::placeholders_type::scientific_exponent_plus
890 [ + + ]: 758 : || part.placeholders.type == format_placeholders::placeholders_type::scientific_exponent_minus)
891 : : {
892 : 9 : exponent = true;
893 : 9 : exponent_index = i;
894 : : }
895 : :
896 [ + + ]: 933 : if (part.placeholders.percentage)
897 : : {
898 : 8 : percentage = true;
899 : : }
900 : :
901 [ + + ]: 933 : if (part.type == template_part::template_type::second
902 [ + + ]: 931 : || part.type == template_part::template_type::second_leading_zero)
903 : : {
904 : 32 : seconds = true;
905 : 32 : seconds_index = i;
906 : : }
907 : :
908 [ + + + + ]: 933 : if (seconds && part.placeholders.type == format_placeholders::placeholders_type::fractional_part)
909 : : {
910 : 6 : fractional_seconds = true;
911 : : }
912 : :
913 : : // TODO this block needs improvement
914 [ + + ]: 933 : if (part.type == template_part::template_type::month_number
915 [ + + ]: 906 : || part.type == template_part::template_type::month_number_leading_zero)
916 : : {
917 [ + + + + : 79 : if (code.parts.size() > 1 && i < code.parts.size() - 2)
+ + ]
918 : : {
919 : 62 : const auto &next = code.parts[i + 1];
920 : 62 : const auto &after_next = code.parts[i + 2];
921 : :
922 : 124 : if ((next.type == template_part::template_type::second
923 [ + + ]: 62 : || next.type == template_part::template_type::second_leading_zero)
924 [ + - + - : 149 : || (next.type == template_part::template_type::text && next.string == ":"
+ + + +
+ ]
925 [ + + ]: 25 : && (after_next.type == template_part::template_type::second
926 [ + - ]: 24 : || after_next.type == template_part::template_type::second_leading_zero)))
927 : : {
928 : 29 : fix = true;
929 : 29 : leading_zero = part.type == template_part::template_type::month_number_leading_zero;
930 : 29 : minutes_index = i;
931 : : }
932 : : }
933 : :
934 [ + + + + ]: 79 : if (!fix && i > 1)
935 : : {
936 : 34 : const auto &previous = code.parts[i - 1];
937 : 34 : const auto &before_previous = code.parts[i - 2];
938 : :
939 [ + + + ]: 34 : if (previous.type == template_part::template_type::text && previous.string == ":"
940 [ + - + - : 82 : && (before_previous.type == template_part::template_type::hour_leading_zero
+ + ]
941 [ + - ]: 14 : || before_previous.type == template_part::template_type::hour))
942 : : {
943 : 14 : fix = true;
944 : 14 : leading_zero = part.type == template_part::template_type::month_number_leading_zero;
945 : 14 : minutes_index = i;
946 : : }
947 : : }
948 : : }
949 : : }
950 : :
951 [ + + ]: 364 : if (fix)
952 : : {
953 : 43 : code.parts[minutes_index].type =
954 [ + + ]: 43 : leading_zero ? template_part::template_type::minute_leading_zero : template_part::template_type::minute;
955 : : }
956 : :
957 [ + + + + ]: 364 : if (integer_part && !fractional_part)
958 : : {
959 : 52 : code.parts[integer_part_index].placeholders.type = format_placeholders::placeholders_type::integer_only;
960 : : }
961 : :
962 [ + + + + : 364 : if (integer_part && fractional_part && percentage)
+ + ]
963 : : {
964 : 4 : code.parts[integer_part_index].placeholders.percentage = true;
965 : : }
966 : :
967 [ + + ]: 364 : if (exponent)
968 : : {
969 : 9 : const auto &next = code.parts[exponent_index + 1];
970 : 9 : auto temp = code.parts[exponent_index].placeholders.type;
971 : 9 : code.parts[exponent_index].placeholders = next.placeholders;
972 : 9 : code.parts[exponent_index].placeholders.type = temp;
973 [ + ]: 9 : code.parts.erase(code.parts.begin() + static_cast<std::ptrdiff_t>(exponent_index + 1));
974 : :
975 [ + + ]: 36 : for (std::size_t i = 0; i < code.parts.size(); ++i)
976 : : {
977 : 27 : code.parts[i].placeholders.scientific = true;
978 : : }
979 : : }
980 : :
981 [ + + ]: 364 : if (fraction)
982 : : {
983 : 8 : code.parts[fraction_numerator_index].placeholders.type =
984 : : format_placeholders::placeholders_type::fraction_numerator;
985 : 8 : code.parts[fraction_denominator_index].placeholders.type =
986 : : format_placeholders::placeholders_type::fraction_denominator;
987 : :
988 [ + + ]: 48 : for (std::size_t i = 0; i < code.parts.size(); ++i)
989 : : {
990 [ + + ]: 40 : if (code.parts[i].placeholders.type == format_placeholders::placeholders_type::integer_part)
991 : : {
992 : 8 : code.parts[i].placeholders.type = format_placeholders::placeholders_type::fraction_integer;
993 : : }
994 : : }
995 : : }
996 : :
997 [ + + ]: 364 : if (fractional_seconds)
998 : : {
999 [ + + ]: 6 : if (code.parts[seconds_index].type == template_part::template_type::second)
1000 : : {
1001 : 1 : code.parts[seconds_index].type = template_part::template_type::second_fractional;
1002 : : }
1003 : : else
1004 : : {
1005 : 5 : code.parts[seconds_index].type = template_part::template_type::second_leading_zero_fractional;
1006 : : }
1007 : : }
1008 : : }
1009 : :
1010 : 272 : validate();
1011 : 270 : }
1012 : :
1013 : 1403 : number_format_token number_format_parser::parse_next_token()
1014 : : {
1015 : 1403 : number_format_token token;
1016 : :
1017 : 443 : auto to_lower = [](char c) {
1018 : 443 : return static_cast<char>(std::tolower(static_cast<std::uint8_t>(c)));
1019 : : };
1020 : :
1021 [ + + ]: 1403 : if (format_string_.size() <= position_)
1022 : : {
1023 : 272 : token.type = number_format_token::token_type::end;
1024 : 272 : return token;
1025 : : }
1026 : :
1027 : 1131 : auto current_char = format_string_[position_++];
1028 : :
1029 [ + + + + : 1131 : switch (current_char)
+ + + + +
+ + + + -
+ + + + +
+ ]
1030 : : {
1031 : 105 : case '[':
1032 [ + + ]: 105 : if (position_ == format_string_.size())
1033 : : {
1034 [ + + ]: 3 : throw xlnt::exception("missing ]");
1035 : : }
1036 : :
1037 [ + + ]: 104 : if (format_string_[position_] == ']')
1038 : : {
1039 [ + + ]: 3 : throw xlnt::exception("empty []");
1040 : : }
1041 : :
1042 : : do
1043 : : {
1044 [ + ]: 312 : token.string.push_back(format_string_[position_++]);
1045 [ + - + + : 312 : } while (position_ < format_string_.size() && format_string_[position_] != ']');
+ + ]
1046 : :
1047 [ + + + + : 103 : if (token.string[0] == '<' || token.string[0] == '>' || token.string[0] == '=')
+ + + + ]
1048 : : {
1049 : 62 : token.type = number_format_token::token_type::condition;
1050 : : }
1051 [ + + ]: 41 : else if (token.string[0] == '$')
1052 : : {
1053 : 8 : token.type = number_format_token::token_type::locale;
1054 : : }
1055 : 33 : else if (token.string.size() <= 2
1056 [ + + + + : 35 : && ((token.string == "h" || token.string == "hh") || (token.string == "m" || token.string == "mm")
+ + + + +
+ - + + +
+ + ]
1057 [ + + - + : 2 : || (token.string == "s" || token.string == "ss")))
+ + ]
1058 : : {
1059 : 7 : token.type = number_format_token::token_type::datetime;
1060 [ + + ]: 7 : token.string = "[" + token.string + "]";
1061 : : }
1062 : : else
1063 : : {
1064 : 26 : token.type = number_format_token::token_type::color;
1065 [ + ]: 26 : color_from_string(token.string);
1066 : : }
1067 : :
1068 : 100 : ++position_;
1069 : :
1070 : 100 : break;
1071 : :
1072 : 2 : case '\\':
1073 : 2 : token.type = number_format_token::token_type::text;
1074 [ + ]: 2 : token.string.push_back(format_string_[position_++]);
1075 : :
1076 : 2 : break;
1077 : :
1078 : 163 : case 'G':
1079 [ + + + + ]: 163 : if (format_string_.substr(position_ - 1, 7) != "General")
1080 : : {
1081 [ + + ]: 3 : throw xlnt::exception("expected General");
1082 : : }
1083 : :
1084 : 162 : token.type = number_format_token::token_type::number;
1085 [ + ]: 162 : token.string = "General";
1086 : 162 : position_ += 6;
1087 : :
1088 : 162 : break;
1089 : :
1090 : 2 : case '_':
1091 : 2 : token.type = number_format_token::token_type::space;
1092 [ + ]: 2 : token.string.push_back(format_string_[position_++]);
1093 : :
1094 : 2 : break;
1095 : :
1096 : 3 : case '*':
1097 : 3 : token.type = number_format_token::token_type::fill;
1098 [ + ]: 3 : token.string.push_back(format_string_[position_++]);
1099 : :
1100 : 3 : break;
1101 : :
1102 : 167 : case '0':
1103 : : case '#':
1104 : : case '?':
1105 : : case '.':
1106 : 167 : token.type = number_format_token::token_type::number;
1107 : :
1108 : : do
1109 : : {
1110 [ + ]: 445 : token.string.push_back(current_char);
1111 : 445 : current_char = format_string_[position_++];
1112 [ + + + + : 445 : } while (current_char == '0' || current_char == '#' || current_char == '?' || current_char == ',');
+ + + + ]
1113 : :
1114 : 167 : --position_;
1115 : :
1116 [ + + ]: 167 : if (current_char == '%')
1117 : : {
1118 [ + ]: 8 : token.string.push_back('%');
1119 : 8 : ++position_;
1120 : : }
1121 : :
1122 : 167 : break;
1123 : :
1124 : 244 : case 'y':
1125 : : case 'Y':
1126 : : case 'm':
1127 : : case 'M':
1128 : : case 'd':
1129 : : case 'D':
1130 : : case 'h':
1131 : : case 'H':
1132 : : case 's':
1133 : : case 'S':
1134 : 244 : token.type = number_format_token::token_type::datetime;
1135 [ + ]: 244 : token.string.push_back(to_lower(current_char));
1136 : :
1137 [ + + ]: 443 : while (format_string_[position_] == current_char)
1138 : : {
1139 [ + ]: 199 : token.string.push_back(to_lower(current_char));
1140 : 199 : ++position_;
1141 : : }
1142 : :
1143 : 244 : break;
1144 : :
1145 : 15 : case 'A':
1146 : 15 : token.type = number_format_token::token_type::datetime;
1147 : :
1148 [ + + + + ]: 15 : if (format_string_.substr(position_ - 1, 5) == "AM/PM")
1149 : : {
1150 : 11 : position_ += 4;
1151 [ + ]: 11 : token.string = "AM/PM";
1152 : : }
1153 [ + + + + ]: 4 : else if (format_string_.substr(position_ - 1, 3) == "A/P")
1154 : : {
1155 : 2 : position_ += 2;
1156 [ + ]: 2 : token.string = "A/P";
1157 : : }
1158 : : else
1159 : : {
1160 [ + + ]: 6 : throw xlnt::exception("expected AM/PM or A/P");
1161 : : }
1162 : :
1163 : 13 : break;
1164 : :
1165 : 108 : case '"': {
1166 : 108 : token.type = number_format_token::token_type::text;
1167 : 108 : auto start = position_;
1168 : 108 : auto end = format_string_.find('"', position_);
1169 : :
1170 [ + - + + : 109 : while (end != std::string::npos && format_string_[end - 1] == '\\')
+ + ]
1171 : : {
1172 [ + + ]: 1 : token.string.append(format_string_.substr(start, end - start - 1));
1173 [ + ]: 1 : token.string.push_back('"');
1174 : 1 : position_ = end + 1;
1175 : 1 : start = position_;
1176 : 1 : end = format_string_.find('"', position_);
1177 : : }
1178 : :
1179 [ + + ]: 108 : if (end != start)
1180 : : {
1181 [ + + ]: 107 : token.string.append(format_string_.substr(start, end - start));
1182 : : }
1183 : :
1184 : 108 : position_ = end + 1;
1185 : :
1186 : 108 : break;
1187 : : }
1188 : :
1189 : 92 : case ';':
1190 : 92 : token.type = number_format_token::token_type::end_section;
1191 : 92 : break;
1192 : :
1193 : 16 : case '(':
1194 : 16 : token.type = number_format_token::token_type::text;
1195 [ + ]: 16 : token.string.push_back(current_char);
1196 : :
1197 : 16 : break;
1198 : :
1199 : 16 : case ')':
1200 : 16 : token.type = number_format_token::token_type::text;
1201 [ + ]: 16 : token.string.push_back(current_char);
1202 : :
1203 : 16 : break;
1204 : :
1205 : 52 : case '-':
1206 : 52 : token.type = number_format_token::token_type::text;
1207 [ + ]: 52 : token.string.push_back(current_char);
1208 : :
1209 : 52 : break;
1210 : :
1211 :UBC 0 : case '+':
1212 : 0 : token.type = number_format_token::token_type::text;
1213 [ # ]: 0 : token.string.push_back(current_char);
1214 : :
1215 : 0 : break;
1216 : :
1217 :CBC 61 : case ':':
1218 : 61 : token.type = number_format_token::token_type::text;
1219 [ + ]: 61 : token.string.push_back(current_char);
1220 : :
1221 : 61 : break;
1222 : :
1223 : 39 : case ' ':
1224 : 39 : token.type = number_format_token::token_type::text;
1225 [ + ]: 39 : token.string.push_back(current_char);
1226 : :
1227 : 39 : break;
1228 : :
1229 : 30 : case '/':
1230 : 30 : token.type = number_format_token::token_type::text;
1231 [ + ]: 30 : token.string.push_back(current_char);
1232 : :
1233 : 30 : break;
1234 : :
1235 : 5 : case '@':
1236 : 5 : token.type = number_format_token::token_type::number;
1237 [ + ]: 5 : token.string.push_back(current_char);
1238 : 5 : break;
1239 : :
1240 : 9 : case 'E':
1241 : 9 : token.type = number_format_token::token_type::number;
1242 [ + ]: 9 : token.string.push_back(current_char);
1243 : 9 : current_char = format_string_[position_++];
1244 : :
1245 [ + + + - ]: 9 : if (current_char == '+' || current_char == '-')
1246 : : {
1247 [ + ]: 9 : token.string.push_back(current_char);
1248 : 9 : break;
1249 : : }
1250 : :
1251 :UBC 0 : break;
1252 : :
1253 :CBC 2 : default:
1254 [ + + ]: 6 : throw xlnt::exception("unexpected character");
1255 : : }
1256 : :
1257 : 1121 : return token;
1258 : 10 : }
1259 : :
1260 : 272 : void number_format_parser::validate()
1261 : : {
1262 [ + + ]: 272 : if (codes_.size() > 4)
1263 : : {
1264 [ + + ]: 3 : throw xlnt::exception("too many format codes");
1265 : : }
1266 : :
1267 [ + + ]: 271 : if (codes_.size() > 2)
1268 : : {
1269 [ + + + - : 32 : if (codes_[0].has_condition && codes_[1].has_condition && codes_[2].has_condition)
+ + + + ]
1270 : : {
1271 [ + + ]: 3 : throw xlnt::exception("format should have a maximum of two codes with conditions");
1272 : : }
1273 : : }
1274 : 270 : }
1275 : :
1276 : 343 : format_placeholders number_format_parser::parse_placeholders(const std::string &placeholders_string)
1277 : : {
1278 : 343 : format_placeholders p;
1279 : :
1280 [ + + + ]: 343 : if (placeholders_string == "General")
1281 : : {
1282 : 162 : p.type = format_placeholders::placeholders_type::general;
1283 : 162 : return p;
1284 : : }
1285 [ + + + ]: 181 : else if (placeholders_string == "@")
1286 : : {
1287 : 5 : p.type = format_placeholders::placeholders_type::text;
1288 : 5 : return p;
1289 : : }
1290 [ + + ]: 176 : else if (placeholders_string.front() == '.')
1291 : : {
1292 : 48 : p.type = format_placeholders::placeholders_type::fractional_part;
1293 : : }
1294 [ + + ]: 128 : else if (placeholders_string.front() == 'E')
1295 : : {
1296 [ + + ]: 9 : p.type = placeholders_string[1] == '+' ? format_placeholders::placeholders_type::scientific_exponent_plus
1297 : : : format_placeholders::placeholders_type::scientific_exponent_minus;
1298 : 9 : return p;
1299 : : }
1300 : : else
1301 : : {
1302 : 119 : p.type = format_placeholders::placeholders_type::integer_part;
1303 : : }
1304 : :
1305 [ + + ]: 167 : if (placeholders_string.back() == '%')
1306 : : {
1307 : 8 : p.percentage = true;
1308 : : }
1309 : :
1310 : 167 : std::vector<std::size_t> comma_indices;
1311 : :
1312 [ + + ]: 620 : for (std::size_t i = 0; i < placeholders_string.size(); ++i)
1313 : : {
1314 : 453 : auto c = placeholders_string[i];
1315 : :
1316 [ + + ]: 453 : if (c == '0')
1317 : : {
1318 : 171 : ++p.num_zeros;
1319 : : }
1320 [ + + ]: 282 : else if (c == '#')
1321 : : {
1322 : 153 : ++p.num_optionals;
1323 : : }
1324 [ + + ]: 129 : else if (c == '?')
1325 : : {
1326 : 30 : ++p.num_spaces;
1327 : : }
1328 [ + + ]: 99 : else if (c == ',')
1329 : : {
1330 [ + ]: 43 : comma_indices.push_back(i);
1331 : : }
1332 : : }
1333 : :
1334 [ + + ]: 167 : if (!comma_indices.empty())
1335 : : {
1336 : 43 : std::size_t i = placeholders_string.size() - 1;
1337 : :
1338 [ + + + + : 44 : while (!comma_indices.empty() && i == comma_indices.back())
+ + ]
1339 : : {
1340 : 1 : ++p.thousands_scale;
1341 : 1 : --i;
1342 : 1 : comma_indices.pop_back();
1343 : : }
1344 : :
1345 : 43 : p.use_comma_separator = !comma_indices.empty();
1346 : : }
1347 : :
1348 : 167 : return p;
1349 : 167 : }
1350 : :
1351 : 48 : format_color number_format_parser::color_from_string(const std::string &color)
1352 : : {
1353 [ + + + + : 48 : switch (color[0])
+ + + + ]
1354 : : {
1355 : 4 : case 'C':
1356 [ + + - + : 4 : if ((color == "Cyan") || (color == "CYAN"))
+ + ]
1357 : : {
1358 : 2 : return format_color::cyan;
1359 : : }
1360 [ + + - + : 2 : else if ((color.substr(0, 5) == "Color") || (color.substr(0, 5) == "COLOR"))
- - - - -
+ + - + -
- - - - ]
1361 : : {
1362 : 2 : unsigned char color_number = 0;
1363 [ + + + - : 2 : if (detail::parse(color.substr(5), color_number) == std::errc() && color_number >= 1 && color_number <= 56)
+ - + - +
- + - -
- ]
1364 : : {
1365 : 2 : return static_cast<format_color>(color_number);
1366 : : }
1367 : : }
1368 : :
1369 :UBC 0 : unhandled_case(true);
1370 : 0 : break;
1371 : :
1372 :CBC 6 : case 'B':
1373 [ + + - + : 6 : if ((color == "Black") || (color == "BLACK"))
+ + ]
1374 : : {
1375 : 4 : return format_color::black;
1376 : : }
1377 [ - + - - : 2 : else if ((color == "Blue") || (color == "BLUE"))
+ - ]
1378 : : {
1379 : 2 : return format_color::blue;
1380 : : }
1381 : :
1382 :UBC 0 : unhandled_case(true);
1383 : 0 : break;
1384 : :
1385 :CBC 9 : case 'G':
1386 [ - + - - : 9 : if ((color == "Green") || (color == "GREEN"))
+ - ]
1387 : : {
1388 : 9 : return format_color::green;
1389 : : }
1390 : :
1391 :UBC 0 : unhandled_case(true);
1392 : 0 : break;
1393 : :
1394 :CBC 2 : case 'W':
1395 [ - + - - : 2 : if ((color == "White") || (color == "WHITE"))
+ - ]
1396 : : {
1397 : 2 : return format_color::white;
1398 : : }
1399 : :
1400 :UBC 0 : unhandled_case(true);
1401 : 0 : break;
1402 : :
1403 :CBC 2 : case 'M':
1404 [ - + - - : 2 : if ((color == "Magenta") || (color == "MAGENTA"))
+ - ]
1405 : : {
1406 : 2 : return format_color::magenta;
1407 : : }
1408 : :
1409 :UBC 0 : unhandled_case(true);
1410 : 0 : break;
1411 : :
1412 :CBC 2 : case 'Y':
1413 [ - + - - : 2 : if ((color == "Yellow") || (color == "YELLOW"))
+ - ]
1414 : : {
1415 : 2 : return format_color::yellow;
1416 : : }
1417 : :
1418 :UBC 0 : unhandled_case(true);
1419 : 0 : break;
1420 : :
1421 :CBC 21 : case 'R':
1422 [ + + - + : 21 : if ((color == "Red") || (color == "RED"))
+ + ]
1423 : : {
1424 : 20 : return format_color::red;
1425 : : }
1426 : :
1427 : 1 : unhandled_case(true);
1428 :UBC 0 : break;
1429 : :
1430 :CBC 2 : default:
1431 : 2 : unhandled_case(true);
1432 : : }
1433 : :
1434 :UBC 0 : unhandled_case_error();
1435 : : }
1436 : :
1437 :CBC 7 : std::pair<format_locale, std::string> number_format_parser::locale_from_string(const std::string &locale_string)
1438 : : {
1439 : 7 : auto hyphen_index = locale_string.find('-');
1440 : :
1441 [ + - + - : 7 : if (locale_string.empty() || locale_string.front() != '$' || hyphen_index == std::string::npos)
+ + + + ]
1442 : : {
1443 [ + + ]: 1 : throw xlnt::invalid_attribute(("bad locale: " + locale_string).c_str());
1444 : : }
1445 : :
1446 : 6 : std::pair<format_locale, std::string> result;
1447 : :
1448 [ + + ]: 6 : if (hyphen_index > 1)
1449 : : {
1450 [ + ]: 2 : result.second = locale_string.substr(1, hyphen_index - 1);
1451 : : }
1452 : :
1453 [ + ]: 6 : auto country_code_string = locale_string.substr(hyphen_index + 1);
1454 : :
1455 [ + + ]: 6 : if (country_code_string.empty())
1456 : : {
1457 [ + + ]: 1 : throw xlnt::invalid_attribute(("bad locale: " + locale_string).c_str());
1458 : : }
1459 : :
1460 [ + + ]: 19 : for (auto c : country_code_string)
1461 : : {
1462 [ + + + - : 15 : if (!((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')))
- + - - +
- + + ]
1463 : : {
1464 [ + + ]: 1 : throw xlnt::invalid_attribute(("bad locale: " + locale_string).c_str());
1465 : : }
1466 : : }
1467 : :
1468 : 4 : int country_code = -1;
1469 [ + - + ]: 4 : if (detail::parse(country_code_string, country_code, nullptr, 16) != std::errc())
1470 : : {
1471 [ # # ]:UBC 0 : throw xlnt::invalid_attribute(("bad locale: " + locale_string).c_str());
1472 : : }
1473 :CBC 4 : country_code &= 0xFFFF;
1474 : :
1475 [ + + + ]: 1196 : for (const auto &known_locale : known_locales())
1476 : : {
1477 [ + + ]: 1195 : if (known_locale.first == country_code)
1478 : : {
1479 : 3 : result.first = static_cast<format_locale>(country_code);
1480 : 6 : return result;
1481 : : }
1482 [ + + ]: 4 : }
1483 : :
1484 [ + + ]: 1 : throw xlnt::exception("unknown country code: " + country_code_string);
1485 : 9 : }
1486 : :
1487 : 290 : number_formatter::number_formatter(const std::string &format_string, xlnt::calendar calendar)
1488 : 290 : : parser_(format_string), calendar_(calendar)
1489 : : {
1490 [ + ]: 290 : parser_.parse();
1491 [ + ]: 266 : format_ = parser_.result();
1492 : 314 : }
1493 : :
1494 : 194 : std::string number_formatter::format_number(double number)
1495 : : {
1496 [ + + ]: 194 : if (format_[0].has_condition)
1497 : : {
1498 [ + + ]: 29 : if (format_[0].condition.satisfied_by(number))
1499 : : {
1500 : 9 : return format_number(format_[0], number);
1501 : : }
1502 : :
1503 [ + + ]: 20 : if (format_.size() == 1)
1504 : : {
1505 [ + ]: 2 : return std::string(11, '#');
1506 : : }
1507 : :
1508 [ + - + + : 19 : if (!format_[1].has_condition || format_[1].condition.satisfied_by(number))
+ + ]
1509 : : {
1510 : 9 : return format_number(format_[1], number);
1511 : : }
1512 : :
1513 [ + + ]: 10 : if (format_.size() == 2)
1514 : : {
1515 [ + ]: 2 : return std::string(11, '#');
1516 : : }
1517 : :
1518 : 9 : return format_number(format_[2], number);
1519 : : }
1520 : :
1521 : : // no conditions, format based on sign:
1522 : :
1523 : : // 1 section, use for all
1524 [ + + ]: 165 : if (format_.size() == 1)
1525 : : {
1526 : 148 : return format_number(format_[0], number);
1527 : : }
1528 : : // 2 sections, first for positive and zero, second for negative
1529 [ + + ]: 17 : else if (format_.size() == 2)
1530 : : {
1531 [ + + ]: 14 : if (number >= 0)
1532 : : {
1533 : 9 : return format_number(format_[0], number);
1534 : : }
1535 : : else
1536 : : {
1537 : 5 : return format_number(format_[1], std::fabs(number));
1538 : : }
1539 : : }
1540 : : // 3+ sections, first for positive, second for negative, third for zero
1541 : : else
1542 : : {
1543 [ + + ]: 3 : if (number > 0)
1544 : : {
1545 : 1 : return format_number(format_[0], number);
1546 : : }
1547 [ + + ]: 2 : else if (number < 0)
1548 : : {
1549 : 1 : return format_number(format_[1], std::fabs(number));
1550 : : }
1551 : : else
1552 : : {
1553 : 1 : return format_number(format_[2], number);
1554 : : }
1555 : : }
1556 : : }
1557 : :
1558 : 72 : std::string number_formatter::format_text(const std::string &text)
1559 : : {
1560 [ + + ]: 72 : if (format_.size() < 4)
1561 : : {
1562 : 69 : format_code temp;
1563 : 69 : template_part temp_part;
1564 : 69 : temp_part.type = template_part::template_type::general;
1565 : 69 : temp_part.placeholders.type = format_placeholders::placeholders_type::general;
1566 [ + ]: 69 : temp.parts.push_back(temp_part);
1567 [ + ]: 69 : return format_text(temp, text);
1568 : 69 : }
1569 : :
1570 : 3 : return format_text(format_[3], text);
1571 : : }
1572 : :
1573 : 129 : std::string number_formatter::fill_placeholders(const format_placeholders &p, double number)
1574 : : {
1575 : 129 : std::string result;
1576 : :
1577 [ + + ]: 129 : if (p.type == format_placeholders::placeholders_type::general
1578 [ + + ]: 84 : || p.type == format_placeholders::placeholders_type::text)
1579 : : {
1580 [ + ]: 48 : return fmt::format("{}", number);
1581 : : }
1582 : :
1583 [ + + ]: 81 : if (p.percentage)
1584 : : {
1585 : 9 : number *= 100;
1586 : : }
1587 : :
1588 [ + + ]: 81 : if (p.thousands_scale > 0)
1589 : : {
1590 : 1 : number /= std::pow(1000.0, p.thousands_scale);
1591 : : }
1592 : :
1593 : 81 : auto integer_part = static_cast<long long>(number);
1594 : :
1595 [ + + ]: 81 : if (p.type == format_placeholders::placeholders_type::integer_only
1596 [ + + ]: 50 : || p.type == format_placeholders::placeholders_type::integer_part
1597 [ + + ]: 30 : || p.type == format_placeholders::placeholders_type::fraction_integer)
1598 : : {
1599 : : // Format with leading zeros (if necessary).
1600 [ + ]: 57 : result = fmt::format("{:0{}d}", integer_part, p.num_zeros);
1601 : :
1602 [ + + ]: 57 : if (result.size() < p.num_zeros + p.num_spaces)
1603 : : {
1604 : : // Add leading spaces.
1605 [ + ]: 1 : result.insert(static_cast<size_t>(0), p.num_zeros + p.num_spaces - result.size(), ' ');
1606 : : }
1607 : :
1608 [ + + ]: 57 : if (p.use_comma_separator)
1609 : : {
1610 [ + ]: 20 : std::vector<char> digits(result.rbegin(), result.rend());
1611 : 20 : std::string temp;
1612 : :
1613 [ + + ]: 96 : for (std::size_t i = 0; i < digits.size(); i++)
1614 : : {
1615 [ + ]: 76 : temp.push_back(digits[i]);
1616 : :
1617 [ + + ]: 76 : if (i % 3 == 2)
1618 : : {
1619 [ + ]: 14 : temp.push_back(',');
1620 : : }
1621 : : }
1622 : :
1623 [ + ]: 20 : result = std::string(temp.rbegin(), temp.rend());
1624 : 20 : }
1625 : :
1626 [ + + + + ]: 57 : if (p.percentage && p.type == format_placeholders::placeholders_type::integer_only)
1627 : : {
1628 [ + ]: 3 : result.push_back('%');
1629 : : }
1630 : 57 : }
1631 [ + - ]: 24 : else if (p.type == format_placeholders::placeholders_type::fractional_part)
1632 : : {
1633 : 24 : auto fractional_part = number - integer_part;
1634 : :
1635 : : // Format with zeros.
1636 [ + ]: 24 : result = fmt::format("{:.{}f}", fractional_part, p.num_zeros + p.num_optionals + p.num_spaces);
1637 [ + ]: 24 : result.erase(0, 1); // Remove 0 at the beginning so that we only have the decimal point and the rest
1638 : :
1639 : : // Remove unnecessary zeros outside of the maximum precision.
1640 [ + + + + : 26 : while (result.back() == '0' && result.size() > p.num_zeros + 1)
+ + ]
1641 : : {
1642 : 2 : result.pop_back();
1643 : : }
1644 : :
1645 : : // +1 because the decimal point does not count
1646 [ + + ]: 24 : if (result.size() < p.num_zeros + p.num_optionals + p.num_spaces + 1)
1647 : : {
1648 : : // Add trailing spaces
1649 [ + ]: 2 : result.resize(p.num_zeros + p.num_optionals + p.num_spaces + 1, ' ');
1650 : : }
1651 : :
1652 [ + + ]: 24 : if (p.percentage)
1653 : : {
1654 [ + ]: 3 : result.push_back('%');
1655 : : }
1656 : : }
1657 : :
1658 : 81 : return result;
1659 : 129 : }
1660 : :
1661 : 7 : std::string number_formatter::fill_scientific_placeholders(const format_placeholders &integer_part,
1662 : : const format_placeholders &fractional_part, const format_placeholders &exponent_part, double number)
1663 : : {
1664 : 7 : std::size_t logarithm = 0;
1665 : :
1666 [ + + ]: 7 : if (number != 0.0)
1667 : : {
1668 : 5 : logarithm = static_cast<std::size_t>(std::log10(number));
1669 : :
1670 [ + + ]: 5 : if (integer_part.num_zeros + integer_part.num_optionals > 1)
1671 : : {
1672 : 2 : logarithm = integer_part.num_zeros + integer_part.num_optionals;
1673 : : }
1674 : : }
1675 : :
1676 : 7 : number /= std::pow(10.0, logarithm);
1677 : :
1678 : 7 : auto integer = static_cast<long long>(number);
1679 : 7 : auto fraction = number - integer;
1680 : :
1681 : : // Format integer with leading zeros.
1682 : 7 : auto integer_width = integer_part.num_zeros;
1683 : :
1684 [ + + ]: 7 : if (number == 0.0)
1685 : : {
1686 : 2 : integer_width += integer_part.num_optionals;
1687 : : }
1688 [ + ]: 7 : std::string integer_string = fmt::format("{:0{}d}", integer, integer_width);
1689 : :
1690 : : // Format with zeros and (optionally) spaces.
1691 [ + ]: 7 : std::string fractional_string = fmt::format("{:.{}f}", fraction, fractional_part.num_zeros + fractional_part.num_optionals);
1692 [ + ]: 7 : fractional_string.erase(0, 1); // Remove 0 at the beginning so that we only have the decimal point and the rest
1693 [ + ]: 7 : std::string exponent_string = fmt::format("{:0{}d}", logarithm, exponent_part.num_zeros);
1694 : :
1695 [ + + ]: 7 : if (exponent_part.type == format_placeholders::placeholders_type::scientific_exponent_plus)
1696 : : {
1697 [ + ]: 6 : exponent_string.insert(0, "E+");
1698 : : }
1699 : : else
1700 : : {
1701 [ + ]: 1 : exponent_string.insert(0, "E");
1702 : : }
1703 : :
1704 [ + + ]: 14 : return integer_string + fractional_string + exponent_string;
1705 : 7 : }
1706 : :
1707 : 4 : std::string number_formatter::fill_fraction_placeholders(const format_placeholders & /*numerator*/,
1708 : : const format_placeholders &denominator, double number, bool /*improper*/)
1709 : : {
1710 : 4 : auto fractional_part = number - static_cast<long long>(number);
1711 : 4 : auto original_fractional_part = fractional_part;
1712 : 4 : fractional_part *= 10;
1713 : :
1714 : 16 : while (std::abs(fractional_part - static_cast<long long>(fractional_part)) > 0.000001
1715 [ + - + + : 16 : && std::abs(fractional_part - static_cast<long long>(fractional_part)) < 0.999999)
+ + ]
1716 : : {
1717 : 12 : fractional_part *= 10;
1718 : : }
1719 : :
1720 : 4 : fractional_part = static_cast<long long>(fractional_part);
1721 : 4 : auto denominator_digits = denominator.num_zeros + denominator.num_optionals + denominator.num_spaces;
1722 : : // auto denominator_digits = static_cast<std::size_t>(std::ceil(std::log10(fractional_part)));
1723 : :
1724 : 4 : auto lower = static_cast<long long>(std::pow(10, denominator_digits - 1));
1725 : 4 : auto upper = static_cast<long long>(std::pow(10, denominator_digits));
1726 : 4 : auto best_denominator = lower;
1727 : 4 : auto best_difference = 1000.0;
1728 : :
1729 [ + + ]: 202 : for (long long i = lower; i < upper; ++i)
1730 : : {
1731 : 198 : auto numerator_full = original_fractional_part * i;
1732 : 198 : auto numerator_rounded = static_cast<long long>(std::round(numerator_full));
1733 : 198 : auto difference = std::fabs(original_fractional_part - (numerator_rounded / static_cast<double>(i)));
1734 : :
1735 [ + + ]: 198 : if (difference < best_difference)
1736 : : {
1737 : 30 : best_difference = difference;
1738 : 30 : best_denominator = i;
1739 : : }
1740 : : }
1741 : :
1742 : 4 : auto numerator_rounded = static_cast<long long>(std::round(original_fractional_part * best_denominator));
1743 [ + + + + ]: 4 : return std::to_string(numerator_rounded) + "/" + std::to_string(best_denominator);
1744 : : }
1745 : :
1746 : 192 : std::string number_formatter::format_number(const format_code &format, double number)
1747 : : {
1748 : : static const std::vector<std::string> month_names = std::vector<std::string>{"January", "February", "March",
1749 [ + + + - : 218 : "April", "May", "June", "July", "August", "September", "October", "November", "December"};
+ + + + +
+ - - - -
- - - - ]
1750 : :
1751 : : static const std::vector<std::string> day_names =
1752 [ + + + - : 208 : std::vector<std::string>{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
+ + + + +
+ - - - -
- - - - ]
1753 : :
1754 : 192 : std::string result;
1755 : :
1756 [ + + ]: 192 : if (number < 0)
1757 : : {
1758 [ + ]: 33 : result.push_back('-');
1759 : :
1760 [ + + ]: 33 : if (format.is_datetime)
1761 : : {
1762 [ + ]: 34 : return std::string(11, '#');
1763 : : }
1764 : : }
1765 : :
1766 : 175 : number = std::fabs(number);
1767 : :
1768 [ + ]: 175 : xlnt::datetime dt(0, 1, 0);
1769 : 175 : std::size_t hour = 0;
1770 : :
1771 [ + + ]: 175 : if (format.is_datetime)
1772 : : {
1773 [ + + ]: 62 : if (number != 0.0)
1774 : : {
1775 [ + ]: 45 : dt = xlnt::datetime::from_number(number, calendar_);
1776 : : }
1777 : :
1778 [ + ]: 62 : hour = static_cast<std::size_t>(dt.get_hour());
1779 : :
1780 [ + + ]: 62 : if (format.twelve_hour)
1781 : : {
1782 : 9 : hour %= 12;
1783 : :
1784 [ + + ]: 9 : if (hour == 0)
1785 : : {
1786 : 2 : hour = 12;
1787 : : }
1788 : : }
1789 : : }
1790 : :
1791 : 175 : bool improper_fraction = true;
1792 : 175 : std::size_t fill_index = 0;
1793 : 175 : bool fill = false;
1794 : 175 : std::string fill_character;
1795 : :
1796 [ + + ]: 615 : for (std::size_t i = 0; i < format.parts.size(); ++i)
1797 : : {
1798 : 440 : const auto &part = format.parts[i];
1799 : :
1800 [ + + + + : 440 : switch (part.type)
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + +
- ]
1801 : : {
1802 : 2 : case template_part::template_type::space: {
1803 [ + ]: 2 : result.push_back(' ');
1804 : 2 : break;
1805 : : }
1806 : :
1807 : 145 : case template_part::template_type::text: {
1808 [ + ]: 145 : result.append(part.string);
1809 : 145 : break;
1810 : : }
1811 : :
1812 : 3 : case template_part::template_type::fill: {
1813 : 3 : fill = true;
1814 : 3 : fill_index = result.size();
1815 [ + ]: 3 : fill_character = part.string;
1816 : 3 : break;
1817 : : }
1818 : :
1819 : 142 : case template_part::template_type::general: {
1820 [ + + ]: 142 : if (part.placeholders.type == format_placeholders::placeholders_type::fractional_part
1821 [ + + - + ]: 24 : && (format.is_datetime || format.is_timedelta))
1822 : : {
1823 : 4 : auto digits = std::min(
1824 : 4 : static_cast<std::size_t>(6), part.placeholders.num_zeros + part.placeholders.num_optionals);
1825 : 4 : auto denominator = static_cast<long long>(std::pow(10.0, digits));
1826 [ + ]: 4 : auto fractional_seconds = dt.get_microsecond() / 1.0E6 * denominator;
1827 : 4 : fractional_seconds = std::round(fractional_seconds) / denominator;
1828 [ + + ]: 4 : result.append(fill_placeholders(part.placeholders, fractional_seconds));
1829 : 4 : break;
1830 : : }
1831 : :
1832 [ + + ]: 138 : if (part.placeholders.type == format_placeholders::placeholders_type::fraction_integer)
1833 : : {
1834 : 6 : improper_fraction = false;
1835 : : }
1836 : :
1837 [ + + ]: 138 : if (part.placeholders.type == format_placeholders::placeholders_type::fraction_numerator)
1838 : : {
1839 : 6 : i += 2;
1840 : :
1841 [ + + ]: 6 : if (number == 0.0)
1842 : : {
1843 : 2 : result.pop_back();
1844 : 2 : break;
1845 : : }
1846 : :
1847 [ + ]: 4 : result.append(fill_fraction_placeholders(
1848 [ + ]: 4 : part.placeholders, format.parts[i].placeholders, number, improper_fraction));
1849 : : }
1850 [ + + ]: 132 : else if (part.placeholders.scientific
1851 [ + - ]: 7 : && part.placeholders.type == format_placeholders::placeholders_type::integer_part)
1852 : : {
1853 : 7 : auto integer_part = part.placeholders;
1854 : 7 : ++i;
1855 : 7 : auto fractional_part = format.parts[i++].placeholders;
1856 : 7 : auto exponent_part = format.parts[i++].placeholders;
1857 [ + + ]: 7 : result.append(fill_scientific_placeholders(integer_part, fractional_part, exponent_part, number));
1858 : 7 : }
1859 : : else
1860 : : {
1861 [ + + ]: 125 : result.append(fill_placeholders(part.placeholders, number));
1862 : : }
1863 : :
1864 : 136 : break;
1865 : : }
1866 : :
1867 : 13 : case template_part::template_type::day_number: {
1868 [ + + ]: 13 : result.append(std::to_string(dt.get_day()));
1869 : 13 : break;
1870 : : }
1871 : :
1872 : 8 : case template_part::template_type::day_number_leading_zero: {
1873 [ + + + ]: 8 : if (dt.get_day() < 10)
1874 : : {
1875 [ + ]: 3 : result.push_back('0');
1876 : : }
1877 : :
1878 [ + + ]: 8 : result.append(std::to_string(dt.get_day()));
1879 : 8 : break;
1880 : : }
1881 : :
1882 : 7 : case template_part::template_type::month_abbreviation: {
1883 [ + + + + ]: 7 : result.append(month_names.at(static_cast<std::size_t>(dt.get_month()) - 1).substr(0, 3));
1884 : 7 : break;
1885 : : }
1886 : :
1887 : 1 : case template_part::template_type::month_name: {
1888 [ + + + ]: 1 : result.append(month_names.at(static_cast<std::size_t>(dt.get_month()) - 1));
1889 : 1 : break;
1890 : : }
1891 : :
1892 : 11 : case template_part::template_type::month_number: {
1893 [ + + ]: 11 : result.append(std::to_string(dt.get_month()));
1894 : 11 : break;
1895 : : }
1896 : :
1897 : 7 : case template_part::template_type::month_number_leading_zero: {
1898 [ + + - ]: 7 : if (dt.get_month() < 10)
1899 : : {
1900 [ + ]: 7 : result.push_back('0');
1901 : : }
1902 : :
1903 [ + + ]: 7 : result.append(std::to_string(dt.get_month()));
1904 : 7 : break;
1905 : : }
1906 : :
1907 : 18 : case template_part::template_type::year_short: {
1908 [ + + + ]: 18 : if (dt.get_year() % 1000 < 10)
1909 : : {
1910 [ + ]: 8 : result.push_back('0');
1911 : : }
1912 : :
1913 [ + + ]: 18 : result.append(std::to_string(dt.get_year() % 1000));
1914 : 18 : break;
1915 : : }
1916 : :
1917 : 2 : case template_part::template_type::year_long: {
1918 [ + + ]: 2 : result.append(std::to_string(dt.get_year()));
1919 : 2 : break;
1920 : : }
1921 : :
1922 : 18 : case template_part::template_type::hour: {
1923 [ + + ]: 18 : result.append(std::to_string(hour));
1924 : 18 : break;
1925 : : }
1926 : :
1927 : 4 : case template_part::template_type::hour_leading_zero: {
1928 [ + + ]: 4 : if (hour < 10)
1929 : : {
1930 [ + ]: 3 : result.push_back('0');
1931 : : }
1932 : :
1933 [ + + ]: 4 : result.append(std::to_string(hour));
1934 : 4 : break;
1935 : : }
1936 : :
1937 : 4 : case template_part::template_type::minute: {
1938 [ + + ]: 4 : result.append(std::to_string(dt.get_minute()));
1939 : 4 : break;
1940 : : }
1941 : :
1942 : 20 : case template_part::template_type::minute_leading_zero: {
1943 [ + + + ]: 20 : if (dt.get_minute() < 10)
1944 : : {
1945 [ + ]: 9 : result.push_back('0');
1946 : : }
1947 : :
1948 [ + + ]: 20 : result.append(std::to_string(dt.get_minute()));
1949 : 20 : break;
1950 : : }
1951 : :
1952 : 1 : case template_part::template_type::second: {
1953 [ + + - + : 1 : result.append(std::to_string(dt.get_second() + (dt.get_microsecond() > 500000 ? 1 : 0)));
+ ]
1954 : 1 : break;
1955 : : }
1956 : :
1957 : 1 : case template_part::template_type::second_fractional: {
1958 [ + + ]: 1 : result.append(std::to_string(dt.get_second()));
1959 : 1 : break;
1960 : : }
1961 : :
1962 : 14 : case template_part::template_type::second_leading_zero: {
1963 [ + + + + : 14 : if ((dt.get_second() + (dt.get_microsecond() > 500000 ? 1 : 0)) < 10)
+ + ]
1964 : : {
1965 [ + ]: 7 : result.push_back('0');
1966 : : }
1967 : :
1968 [ + + + + : 14 : result.append(std::to_string(dt.get_second() + (dt.get_microsecond() > 500000 ? 1 : 0)));
+ ]
1969 : 14 : break;
1970 : : }
1971 : :
1972 : 3 : case template_part::template_type::second_leading_zero_fractional: {
1973 [ + + + ]: 3 : if (dt.get_second() < 10)
1974 : : {
1975 [ + ]: 2 : result.push_back('0');
1976 : : }
1977 : :
1978 [ + + ]: 3 : result.append(std::to_string(dt.get_second()));
1979 : 3 : break;
1980 : : }
1981 : :
1982 : 7 : case template_part::template_type::am_pm: {
1983 [ + + + ]: 7 : if (dt.get_hour() < 12)
1984 : : {
1985 [ + ]: 5 : result.append("AM");
1986 : : }
1987 : : else
1988 : : {
1989 [ + ]: 2 : result.append("PM");
1990 : : }
1991 : :
1992 : 7 : break;
1993 : : }
1994 : :
1995 : 2 : case template_part::template_type::a_p: {
1996 [ + + + ]: 2 : if (dt.get_hour() < 12)
1997 : : {
1998 [ + ]: 1 : result.append("A");
1999 : : }
2000 : : else
2001 : : {
2002 [ + ]: 1 : result.append("P");
2003 : : }
2004 : :
2005 : 2 : break;
2006 : : }
2007 : :
2008 : 2 : case template_part::template_type::elapsed_hours: {
2009 [ + + + ]: 2 : result.append(std::to_string(24 * static_cast<long long>(number) + dt.get_hour()));
2010 : 2 : break;
2011 : : }
2012 : :
2013 : 1 : case template_part::template_type::elapsed_minutes: {
2014 [ + ]: 1 : result.append(std::to_string(24 * 60 * static_cast<long long>(number)
2015 [ + + + ]: 1 : + (60 * dt.get_hour()) + dt.get_minute()));
2016 : 1 : break;
2017 : : }
2018 : :
2019 : 1 : case template_part::template_type::elapsed_seconds: {
2020 [ + ]: 1 : result.append(std::to_string(24 * 60 * 60 * static_cast<long long>(number)
2021 [ + + + + ]: 1 : + (60 * 60 * dt.get_hour()) + (60 * dt.get_minute()) + dt.get_second()));
2022 : 1 : break;
2023 : : }
2024 : :
2025 : 1 : case template_part::template_type::month_letter: {
2026 [ + + + + ]: 1 : result.append(month_names.at(static_cast<std::size_t>(dt.get_month()) - 1).substr(0, 1));
2027 : 1 : break;
2028 : : }
2029 : :
2030 : 1 : case template_part::template_type::day_abbreviation: {
2031 [ + ]: 1 : int weekday = dt.weekday();
2032 : :
2033 [ + - ]: 1 : if (weekday != -1)
2034 : : {
2035 [ + + + ]: 1 : result.append(day_names.at(static_cast<std::size_t>(weekday)).substr(0, 3));
2036 : : }
2037 : 1 : break;
2038 : : }
2039 : :
2040 : 1 : case template_part::template_type::day_name: {
2041 [ + ]: 1 : int weekday = dt.weekday();
2042 : :
2043 [ + - ]: 1 : if (weekday != -1)
2044 : : {
2045 [ + + ]: 1 : result.append(day_names.at(static_cast<std::size_t>(weekday)));
2046 : : }
2047 : 1 : break;
2048 : : }
2049 : : }
2050 : : }
2051 : :
2052 : 175 : const std::size_t width = 11;
2053 : :
2054 [ + + + - : 175 : if (fill && result.size() < width)
+ + ]
2055 : : {
2056 : 3 : auto remaining = width - result.size();
2057 : :
2058 [ + ]: 3 : std::string fill_string(remaining, fill_character.front());
2059 : : // TODO: A UTF-8 character could be multiple bytes
2060 : :
2061 [ + + + + ]: 3 : result = result.substr(0, fill_index) + fill_string + result.substr(fill_index);
2062 : 3 : }
2063 : :
2064 : 175 : return result;
2065 : 192 : }
2066 : :
2067 : 72 : std::string number_formatter::format_text(const format_code &format, const std::string &text)
2068 : : {
2069 : 72 : std::string result;
2070 : 72 : bool any_text_part = false;
2071 : :
2072 [ + + ]: 147 : for (const auto &part : format.parts)
2073 : : {
2074 [ + + ]: 75 : if (part.type == template_part::template_type::text)
2075 : : {
2076 [ + ]: 3 : result.append(part.string);
2077 : 3 : any_text_part = true;
2078 : : }
2079 [ + + ]: 72 : else if (part.type == template_part::template_type::general)
2080 : : {
2081 [ + + ]: 70 : if (part.placeholders.type == format_placeholders::placeholders_type::general
2082 [ + - ]: 1 : || part.placeholders.type == format_placeholders::placeholders_type::text)
2083 : : {
2084 [ + ]: 70 : result.append(text);
2085 : 70 : any_text_part = true;
2086 : : }
2087 : : }
2088 : : }
2089 : :
2090 [ + - + + : 72 : if (!format.parts.empty() && !any_text_part)
+ + ]
2091 : : {
2092 [ + ]: 1 : return text;
2093 : : }
2094 : :
2095 : 71 : return result;
2096 : 72 : }
2097 : :
2098 : : } // namespace detail
2099 : : } // namespace xlnt
|