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 : :
27 : : #include <detail/header_footer/header_footer_code.hpp>
28 : :
29 : : namespace xlnt {
30 : : namespace detail {
31 : :
32 :CBC 50 : std::array<xlnt::optional<xlnt::rich_text>, 3> decode_header_footer(const std::string &hf_string)
33 : : {
34 : 50 : std::array<xlnt::optional<xlnt::rich_text>, 3> result;
35 : :
36 [ + + ]: 50 : if (hf_string.empty())
37 : : {
38 : 16 : return result;
39 : : }
40 : :
41 : : enum class hf_code
42 : : {
43 : : left_section, // &L
44 : : center_section, // &C
45 : : right_section, // &R
46 : : current_page_number, // &P
47 : : total_page_number, // &N
48 : : font_size, // &#
49 : : text_font_color, // &KRRGGBB or &KTTSNN
50 : : text_strikethrough, // &S
51 : : text_superscript, // &X
52 : : text_subscript, // &Y
53 : : date, // &D
54 : : time, // &T
55 : : picture_as_background, // &G
56 : : text_single_underline, // &U
57 : : text_double_underline, // &E
58 : : workbook_file_path, // &Z
59 : : workbook_file_name, // &F
60 : : sheet_tab_name, // &A
61 : : add_to_page_number, // &+
62 : : subtract_from_page_number, // &-
63 : : text_font_name, // &"font name,font type"
64 : : bold_font_style, // &B
65 : : italic_font_style, // &I
66 : : outline_style, // &O
67 : : shadow_style, // &H
68 : : text // everything else
69 : : };
70 : :
71 : : struct hf_token
72 : : {
73 : : hf_code code = hf_code::text;
74 : : std::string value;
75 : : };
76 : :
77 : 34 : std::vector<hf_token> tokens;
78 : 34 : std::size_t position = 0;
79 : :
80 [ + + ]: 244 : while (position < hf_string.size())
81 : : {
82 : 210 : hf_token token;
83 : :
84 : 210 : auto next_ampersand = hf_string.find('&', position + 1);
85 [ + ]: 210 : token.value = hf_string.substr(position, next_ampersand - position);
86 : 210 : auto next_position = next_ampersand;
87 : :
88 [ + + ]: 210 : if (hf_string[position] == '&')
89 : : {
90 : 170 : token.value.clear();
91 : 170 : next_position = position + 2;
92 : 170 : auto first_code_char = hf_string[position + 1];
93 : :
94 [ + + ]: 170 : if (first_code_char == '"')
95 : : {
96 : 40 : auto end_quote_index = hf_string.find('"', position + 2);
97 : 40 : next_position = end_quote_index + 1;
98 : :
99 [ + ]: 40 : token.value = hf_string.substr(position + 2, end_quote_index - position - 2); // remove quotes
100 : 40 : token.code = hf_code::text_font_name;
101 : : }
102 [ + + ]: 130 : else if (first_code_char == '&')
103 : : {
104 [ + ]: 4 : token.value = "&&"; // escaped ampersand
105 : : }
106 [ + + ]: 126 : else if (first_code_char == 'L')
107 : : {
108 : 8 : token.code = hf_code::left_section;
109 : : }
110 [ + + ]: 118 : else if (first_code_char == 'C')
111 : : {
112 : 34 : token.code = hf_code::center_section;
113 : : }
114 [ + + ]: 84 : else if (first_code_char == 'R')
115 : : {
116 : 8 : token.code = hf_code::right_section;
117 : : }
118 [ + + ]: 76 : else if (first_code_char == 'P')
119 : : {
120 : 14 : token.code = hf_code::current_page_number;
121 : : }
122 [ - + ]: 62 : else if (first_code_char == 'N')
123 : : {
124 :UBC 0 : token.code = hf_code::total_page_number;
125 : : }
126 [ + + + ]:CBC 124 : else if (std::string("0123456789").find(hf_string[position + 1]) != std::string::npos)
127 : : {
128 : 28 : token.code = hf_code::font_size;
129 : 28 : next_position = hf_string.find_first_not_of("0123456789", position + 1);
130 [ + ]: 28 : token.value = hf_string.substr(position + 1, next_position - position - 1);
131 : : }
132 [ + + ]: 34 : else if (first_code_char == 'K')
133 : : {
134 [ + - - + : 18 : if (hf_string[position + 4] == '+' || hf_string[position + 4] == '-')
- + ]
135 : : {
136 [ # ]:UBC 0 : token.value = hf_string.substr(position + 2, 5);
137 : 0 : next_position = position + 7;
138 : : }
139 : : else
140 : : {
141 [ + ]:CBC 18 : token.value = hf_string.substr(position + 2, 6);
142 : 18 : next_position = position + 8;
143 : : }
144 : :
145 : 18 : token.code = hf_code::text_font_color;
146 : : }
147 [ - + ]: 16 : else if (first_code_char == 'S')
148 : : {
149 :UBC 0 : token.code = hf_code::text_strikethrough;
150 : : }
151 [ - + ]:CBC 16 : else if (first_code_char == 'X')
152 : : {
153 :UBC 0 : token.code = hf_code::text_superscript;
154 : : }
155 [ - + ]:CBC 16 : else if (first_code_char == 'Y')
156 : : {
157 :UBC 0 : token.code = hf_code::text_subscript;
158 : : }
159 [ - + ]:CBC 16 : else if (first_code_char == 'D')
160 : : {
161 :UBC 0 : token.code = hf_code::date;
162 : : }
163 [ - + ]:CBC 16 : else if (first_code_char == 'T')
164 : : {
165 :UBC 0 : token.code = hf_code::time;
166 : : }
167 [ - + ]:CBC 16 : else if (first_code_char == 'G')
168 : : {
169 :UBC 0 : token.code = hf_code::picture_as_background;
170 : : }
171 [ + + ]:CBC 16 : else if (first_code_char == 'U')
172 : : {
173 : 4 : token.code = hf_code::text_single_underline;
174 : : }
175 [ - + ]: 12 : else if (first_code_char == 'E')
176 : : {
177 :UBC 0 : token.code = hf_code::text_double_underline;
178 : : }
179 [ - + ]:CBC 12 : else if (first_code_char == 'Z')
180 : : {
181 :UBC 0 : token.code = hf_code::workbook_file_path;
182 : : }
183 [ - + ]:CBC 12 : else if (first_code_char == 'F')
184 : : {
185 :UBC 0 : token.code = hf_code::workbook_file_name;
186 : : }
187 [ + - ]:CBC 12 : else if (first_code_char == 'A')
188 : : {
189 : 12 : token.code = hf_code::sheet_tab_name;
190 : : }
191 [ # # ]:UBC 0 : else if (first_code_char == '+')
192 : : {
193 : 0 : token.code = hf_code::add_to_page_number;
194 : : }
195 [ # # ]: 0 : else if (first_code_char == '-')
196 : : {
197 : 0 : token.code = hf_code::subtract_from_page_number;
198 : : }
199 [ # # ]: 0 : else if (first_code_char == 'B')
200 : : {
201 : 0 : token.code = hf_code::bold_font_style;
202 : : }
203 [ # # ]: 0 : else if (first_code_char == 'I')
204 : : {
205 : 0 : token.code = hf_code::italic_font_style;
206 : : }
207 [ # # ]: 0 : else if (first_code_char == 'O')
208 : : {
209 : 0 : token.code = hf_code::outline_style;
210 : : }
211 [ # # ]: 0 : else if (first_code_char == 'H')
212 : : {
213 : 0 : token.code = hf_code::shadow_style;
214 : : }
215 : : }
216 : :
217 :CBC 210 : position = next_position;
218 [ + ]: 210 : tokens.push_back(token);
219 : 210 : }
220 : :
221 : 102 : const auto parse_section = [&tokens, &result](hf_code code) {
222 [ + ]: 204 : std::vector<hf_code> end_codes{hf_code::left_section, hf_code::center_section, hf_code::right_section};
223 [ + + ]: 102 : end_codes.erase(std::find(end_codes.begin(), end_codes.end(), code));
224 : :
225 : 102 : std::size_t start_index = 0;
226 : :
227 [ + + + + : 442 : while (start_index < tokens.size() && tokens[start_index].code != code)
+ + ]
228 : : {
229 : 340 : ++start_index;
230 : : }
231 : :
232 [ + + ]: 102 : if (start_index == tokens.size())
233 : : {
234 : 52 : return;
235 : : }
236 : :
237 : 50 : ++start_index; // skip the section code
238 : 50 : std::size_t end_index = start_index;
239 : :
240 : 210 : while (end_index < tokens.size()
241 [ + + + + : 210 : && std::find(end_codes.begin(), end_codes.end(), tokens[end_index].code) == end_codes.end())
+ + + ]
242 : : {
243 : 160 : ++end_index;
244 : : }
245 : :
246 : 50 : xlnt::rich_text current_text;
247 : 50 : xlnt::rich_text_run current_run;
248 : :
249 : : // todo: all this nice parsing and the codes are just being turned back into text representations
250 : : // It would be nice to create an interface for the library to read and write these codes
251 : :
252 [ + + ]: 210 : for (auto i = start_index; i < end_index; ++i)
253 : : {
254 : 160 : const auto ¤t_token = tokens[i];
255 : :
256 [ + + ]: 160 : if (current_token.code == hf_code::text)
257 : : {
258 [ + ]: 44 : current_run.first = current_run.first + current_token.value;
259 : 44 : continue;
260 : : }
261 : :
262 [ + + ]: 116 : if (!current_run.first.empty())
263 : : {
264 [ + ]: 12 : current_text.add_run(current_run);
265 : 12 : current_run = xlnt::rich_text_run();
266 : : }
267 : :
268 [ - - - - : 116 : switch (current_token.code)
+ - + + -
- - - - -
+ - - - +
- - + - -
- - - ]
269 : : {
270 :UBC 0 : case hf_code::text: {
271 : 0 : break; // already handled above
272 : : }
273 : :
274 : 0 : case hf_code::left_section: {
275 : 0 : break; // used below
276 : : }
277 : :
278 : 0 : case hf_code::center_section: {
279 : 0 : break; // used below
280 : : }
281 : :
282 : 0 : case hf_code::right_section: {
283 : 0 : break; // used below
284 : : }
285 : :
286 :CBC 14 : case hf_code::current_page_number: {
287 [ + ]: 14 : current_run.first = current_run.first + "&P";
288 : 14 : break;
289 : : }
290 : :
291 :UBC 0 : case hf_code::total_page_number: {
292 [ # ]: 0 : current_run.first = current_run.first + "&N";
293 : 0 : break;
294 : : }
295 : :
296 :CBC 28 : case hf_code::font_size: {
297 [ - + ]: 28 : if (!current_run.second.is_set())
298 : : {
299 [ # ]:UBC 0 : current_run.second = xlnt::font();
300 : : }
301 : :
302 [ + + + ]:CBC 28 : current_run.second.get().size(xlnt::detail::deserialise(current_token.value));
303 : :
304 : 28 : break;
305 : : }
306 : :
307 : 18 : case hf_code::text_font_color: {
308 [ + - ]: 18 : if (current_token.value.size() == 6)
309 : : {
310 [ - + ]: 18 : if (!current_run.second.is_set())
311 : : {
312 [ # ]:UBC 0 : current_run.second = xlnt::font();
313 : : }
314 : :
315 [ + + + + ]:CBC 18 : current_run.second.get().color(xlnt::rgb_color(current_token.value));
316 : : }
317 : :
318 : 18 : break;
319 : : }
320 : :
321 :UBC 0 : case hf_code::text_strikethrough: {
322 : 0 : break;
323 : : }
324 : :
325 : 0 : case hf_code::text_superscript: {
326 : 0 : break;
327 : : }
328 : :
329 : 0 : case hf_code::text_subscript: {
330 : 0 : break;
331 : : }
332 : :
333 : 0 : case hf_code::date: {
334 [ # ]: 0 : current_run.first = current_run.first + "&D";
335 : 0 : break;
336 : : }
337 : :
338 : 0 : case hf_code::time: {
339 [ # ]: 0 : current_run.first = current_run.first + "&T";
340 : 0 : break;
341 : : }
342 : :
343 : 0 : case hf_code::picture_as_background: {
344 [ # ]: 0 : current_run.first = current_run.first + "&G";
345 : 0 : break;
346 : : }
347 : :
348 :CBC 4 : case hf_code::text_single_underline: {
349 [ - + ]: 4 : if (!current_run.second.is_set())
350 : : {
351 [ # ]:UBC 0 : current_run.second = xlnt::font();
352 : : }
353 [ + + ]:CBC 4 : current_run.second.get().underline(font::underline_style::single);
354 : 4 : break;
355 : : }
356 : :
357 :UBC 0 : case hf_code::text_double_underline: {
358 : 0 : break;
359 : : }
360 : :
361 : 0 : case hf_code::workbook_file_path: {
362 [ # ]: 0 : current_run.first = current_run.first + "&Z";
363 : 0 : break;
364 : : }
365 : :
366 : 0 : case hf_code::workbook_file_name: {
367 [ # ]: 0 : current_run.first = current_run.first + "&F";
368 : 0 : break;
369 : : }
370 : :
371 :CBC 12 : case hf_code::sheet_tab_name: {
372 [ + ]: 12 : current_run.first = current_run.first + "&A";
373 : 12 : break;
374 : : }
375 : :
376 :UBC 0 : case hf_code::add_to_page_number: {
377 : 0 : break;
378 : : }
379 : :
380 : 0 : case hf_code::subtract_from_page_number: {
381 : 0 : break;
382 : : }
383 : :
384 :CBC 40 : case hf_code::text_font_name: {
385 : 40 : auto comma_index = current_token.value.find(',');
386 [ + ]: 40 : auto font_name = current_token.value.substr(0, comma_index);
387 : :
388 [ + - ]: 40 : if (!current_run.second.is_set())
389 : : {
390 [ + ]: 40 : current_run.second = xlnt::font();
391 : : }
392 : :
393 [ + + - ]: 40 : if (font_name != "-")
394 : : {
395 [ + + ]: 40 : current_run.second.get().name(font_name);
396 : : }
397 : :
398 [ + - ]: 40 : if (comma_index != std::string::npos)
399 : : {
400 [ + ]: 40 : auto font_type = current_token.value.substr(comma_index + 1);
401 : :
402 [ + + + ]: 40 : if (font_type == "Bold")
403 : : {
404 [ + + ]: 4 : current_run.second.get().bold(true);
405 : : }
406 [ + + - ]: 36 : else if (font_type == "Italic")
407 : : {
408 : : // TODO
409 : : }
410 [ + - + ]: 36 : else if (font_type == "BoldItalic")
411 : : {
412 [ # # ]:UBC 0 : current_run.second.get().bold(true);
413 : : }
414 :CBC 40 : }
415 : :
416 : 40 : break;
417 : 40 : }
418 : :
419 :UBC 0 : case hf_code::bold_font_style: {
420 [ # # ]: 0 : if (!current_run.second.is_set())
421 : : {
422 [ # ]: 0 : current_run.second = xlnt::font();
423 : : }
424 : :
425 [ # # ]: 0 : current_run.second.get().bold(true);
426 : :
427 : 0 : break;
428 : : }
429 : :
430 : 0 : case hf_code::italic_font_style: {
431 : 0 : break;
432 : : }
433 : :
434 : 0 : case hf_code::outline_style: {
435 : 0 : break;
436 : : }
437 : :
438 : 0 : case hf_code::shadow_style: {
439 : 0 : break;
440 : : }
441 : : }
442 : : }
443 : :
444 [ + - ]:CBC 50 : if (!current_run.first.empty())
445 : : {
446 [ + ]: 50 : current_text.add_run(current_run);
447 : : }
448 : :
449 : 50 : auto location_index =
450 [ + + + + ]: 50 : static_cast<std::size_t>(code == hf_code::left_section ? 0 : code == hf_code::center_section ? 1 : 2);
451 : :
452 [ + + - ]: 50 : if (!current_text.plain_text().empty())
453 : : {
454 [ + ]: 50 : result[location_index] = current_text;
455 : : }
456 [ + + ]: 102 : };
457 : :
458 [ + ]: 34 : parse_section(hf_code::left_section);
459 [ + ]: 34 : parse_section(hf_code::center_section);
460 [ + ]: 34 : parse_section(hf_code::right_section);
461 : :
462 : 34 : return result;
463 : 34 : }
464 : :
465 : 17 : std::string encode_header_footer(const rich_text &t, header_footer::location where)
466 : : {
467 : : const auto location_code_map =
468 : : std::unordered_map<header_footer::location,
469 : : std::string, scoped_enum_hash<header_footer::location>>{
470 :UBC 0 : {header_footer::location::left, "&L"},
471 : 0 : {header_footer::location::center, "&C"},
472 : 0 : {header_footer::location::right, "&R"},
473 [ + + + - :CBC 85 : };
- ]
474 : :
475 [ + + ]: 17 : auto encoded = location_code_map.at(where);
476 : :
477 [ + + + ]: 36 : for (const auto &run : t.runs())
478 : : {
479 [ - + ]: 19 : if (run.first.empty()) continue;
480 : :
481 [ + + ]: 19 : if (run.second.is_set())
482 : : {
483 [ + + + - ]: 11 : if (run.second.get().has_name())
484 : : {
485 [ + ]: 11 : encoded.push_back('&');
486 [ + ]: 11 : encoded.push_back('"');
487 [ + + + ]: 11 : encoded.append(run.second.get().name());
488 [ + ]: 11 : encoded.push_back(',');
489 : :
490 [ + + + + ]: 11 : if (run.second.get().bold())
491 : : {
492 [ + ]: 2 : encoded.append("Bold");
493 : : }
494 : : else
495 : : {
496 [ + ]: 9 : encoded.append("Regular");
497 : : }
498 : : // todo: BoldItalic?
499 : :
500 [ + ]: 11 : encoded.push_back('"');
501 : : }
502 [ # # # # ]:UBC 0 : else if (run.second.get().bold())
503 : : {
504 [ # ]: 0 : encoded.append("&B");
505 : : }
506 : :
507 [ + + + + ]:CBC 11 : if (run.second.get().has_size())
508 : : {
509 [ + ]: 5 : encoded.push_back('&');
510 [ + + + + ]: 5 : encoded.append(xlnt::detail::serialise(run.second.get().size()));
511 : : }
512 [ + + + + ]: 11 : if (run.second.get().underlined())
513 : : {
514 [ + + + - : 2 : switch (run.second.get().underline())
- - ]
515 : : {
516 : 2 : case font::underline_style::single:
517 : : case font::underline_style::single_accounting:
518 [ + ]: 2 : encoded.append("&U");
519 : 2 : break;
520 :UBC 0 : case font::underline_style::double_:
521 : : case font::underline_style::double_accounting:
522 [ # ]: 0 : encoded.append("&E");
523 : 0 : break;
524 : 0 : case font::underline_style::none:
525 : 0 : break;
526 : : }
527 : : }
528 [ + + + + ]:CBC 11 : if (run.second.get().has_color())
529 : : {
530 [ + ]: 9 : encoded.push_back('&');
531 [ + ]: 9 : encoded.push_back('K');
532 [ + + + + : 9 : encoded.append(run.second.get().color().rgb().hex_string().substr(2));
+ + ]
533 : : }
534 : : }
535 : :
536 [ + ]: 19 : encoded.append(run.first);
537 : 17 : }
538 : :
539 : 17 : return encoded;
540 [ + + + - : 34 : };
- - - ]
541 : :
542 : : } // namespace detail
543 : : } // namespace xlnt
|