1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 */
7
8 #include "xls_xml_context.hpp"
9 #include "xls_xml_namespace_types.hpp"
10 #include "xls_xml_token_constants.hpp"
11 #include "spreadsheet_iface_util.hpp"
12 #include "orcus/spreadsheet/import_interface.hpp"
13 #include "orcus/spreadsheet/import_interface_view.hpp"
14 #include "orcus/measurement.hpp"
15
16 #include <mdds/sorted_string_map.hpp>
17
18 #include <iostream>
19
20 using namespace std;
21 namespace ss = orcus::spreadsheet;
22
23 namespace orcus {
24
25 namespace {
26
to_rgb(const pstring & s)27 spreadsheet::color_rgb_t to_rgb(const pstring& s)
28 {
29 if (!s.empty() && s[0] == '#')
30 return spreadsheet::to_color_rgb(s.data(), s.size());
31 else
32 {
33 // This may be a color name. Lower-case it before sending it to the
34 // function.
35 std::string s_lower(s.size(), '\0');
36 const char* p = s.data();
37 std::transform(p, p + s.size(), s_lower.begin(),
38 [](char c) -> char
39 {
40 if ('A' <= c && c <= 'Z')
41 c += 'a' - 'A';
42 return c;
43 }
44 );
45
46 return spreadsheet::to_color_rgb_from_name(s_lower.data(), s_lower.size());
47 }
48 }
49
50 }
51
merge(const format_type & fmt)52 void xls_xml_data_context::format_type::merge(const format_type& fmt)
53 {
54 if (fmt.bold)
55 bold = true;
56 if (fmt.italic)
57 italic = true;
58
59 if (fmt.color.red)
60 color.red = fmt.color.red;
61 if (fmt.color.green)
62 color.green = fmt.color.green;
63 if (fmt.color.blue)
64 color.blue = fmt.color.blue;
65 }
66
formatted() const67 bool xls_xml_data_context::format_type::formatted() const
68 {
69 if (bold || italic)
70 return true;
71
72 if (color.red || color.green || color.blue)
73 return true;
74
75 return false;
76 }
77
string_segment_type(const pstring & _str)78 xls_xml_data_context::string_segment_type::string_segment_type(const pstring& _str) :
79 str(_str) {}
80
xls_xml_data_context(session_context & session_cxt,const tokens & tokens,xls_xml_context & parent_cxt)81 xls_xml_data_context::xls_xml_data_context(
82 session_context& session_cxt, const tokens& tokens, xls_xml_context& parent_cxt) :
83 xml_context_base(session_cxt, tokens),
84 m_parent_cxt(parent_cxt),
85 m_cell_type(ct_unknown),
86 m_cell_value(std::numeric_limits<double>::quiet_NaN())
87 {
88 }
89
~xls_xml_data_context()90 xls_xml_data_context::~xls_xml_data_context() {}
91
can_handle_element(xmlns_id_t ns,xml_token_t name) const92 bool xls_xml_data_context::can_handle_element(xmlns_id_t ns, xml_token_t name) const
93 {
94 return true;
95 }
96
create_child_context(xmlns_id_t ns,xml_token_t name)97 xml_context_base* xls_xml_data_context::create_child_context(xmlns_id_t ns, xml_token_t name)
98 {
99 return nullptr;
100 }
101
end_child_context(xmlns_id_t ns,xml_token_t name,xml_context_base * child)102 void xls_xml_data_context::end_child_context(xmlns_id_t ns, xml_token_t name, xml_context_base* child)
103 {
104 }
105
start_element(xmlns_id_t ns,xml_token_t name,const::std::vector<xml_token_attr_t> & attrs)106 void xls_xml_data_context::start_element(xmlns_id_t ns, xml_token_t name, const::std::vector<xml_token_attr_t>& attrs)
107 {
108 xml_token_pair_t parent = push_stack(ns, name);
109
110 if (ns == NS_xls_xml_ss)
111 {
112 switch (name)
113 {
114 case XML_Data:
115 start_element_data(parent, attrs);
116 break;
117 default:
118 warn_unhandled();
119 }
120 }
121 else if (ns == NS_xls_xml_html)
122 {
123 switch (name)
124 {
125 case XML_B:
126 m_format_stack.emplace_back();
127 m_format_stack.back().bold = true;
128 update_current_format();
129 break;
130 case XML_I:
131 m_format_stack.emplace_back();
132 m_format_stack.back().italic = true;
133 update_current_format();
134 break;
135 case XML_Font:
136 {
137 m_format_stack.emplace_back();
138 format_type& fmt = m_format_stack.back();
139
140 for (const xml_token_attr_t& attr : attrs)
141 {
142 if (ns != NS_xls_xml_html)
143 continue;
144
145 switch (attr.name)
146 {
147 case XML_Color:
148 fmt.color = to_rgb(attr.value);
149 break;
150 default:
151 ;
152 }
153 }
154
155 // TODO : pick up the color.
156 update_current_format();
157 break;
158 }
159 default:
160 warn_unhandled();
161 }
162 }
163 else
164 warn_unhandled();
165 }
166
characters(const pstring & str,bool transient)167 void xls_xml_data_context::characters(const pstring& str, bool transient)
168 {
169 if (str.empty())
170 return;
171
172 switch (m_cell_type)
173 {
174 case ct_unknown:
175 break;
176 case ct_string:
177 {
178 if (transient)
179 m_cell_string.emplace_back(intern(str));
180 else
181 m_cell_string.emplace_back(str);
182
183 if (m_current_format.formatted())
184 {
185 // Apply the current format to this string segment.
186 string_segment_type& ss = m_cell_string.back();
187 ss.format = m_current_format;
188 ss.formatted = true;
189 }
190
191 break;
192 }
193 case ct_number:
194 {
195 const char* p = str.get();
196 m_cell_value = to_double(p, p + str.size());
197 break;
198 }
199 case ct_datetime:
200 m_cell_datetime = to_date_time(str);
201 break;
202 default:
203 if (get_config().debug)
204 {
205 cout << "warning: unknown cell type '" << m_cell_type
206 << "': characters='" << str << "'" << endl;
207 }
208 }
209 }
210
end_element(xmlns_id_t ns,xml_token_t name)211 bool xls_xml_data_context::end_element(xmlns_id_t ns, xml_token_t name)
212 {
213 if (ns == NS_xls_xml_ss)
214 {
215 switch (name)
216 {
217 case XML_Data:
218 end_element_data();
219 break;
220 default:
221 ;
222 }
223 }
224 else if (ns == NS_xls_xml_html)
225 {
226 switch (name)
227 {
228 case XML_B:
229 case XML_I:
230 case XML_Font:
231 assert(!m_format_stack.empty());
232 m_format_stack.pop_back();
233 update_current_format();
234 break;
235 default:
236 ;
237 }
238 }
239
240 return pop_stack(ns, name);
241 }
242
reset()243 void xls_xml_data_context::reset()
244 {
245 m_format_stack.clear();
246 m_format_stack.emplace_back(); // set default format.
247 update_current_format();
248
249 m_cell_type = ct_unknown;
250 m_cell_string.clear();
251
252 m_cell_value = std::numeric_limits<double>::quiet_NaN();
253 m_cell_datetime = date_time_t();
254 }
255
start_element_data(const xml_token_pair_t & parent,const xml_attrs_t & attrs)256 void xls_xml_data_context::start_element_data(
257 const xml_token_pair_t& parent, const xml_attrs_t& attrs)
258 {
259 m_cell_type = ct_unknown;
260 m_cell_string.clear();
261 m_cell_datetime = date_time_t();
262
263 for (const xml_token_attr_t& attr : attrs)
264 {
265 if (attr.ns != NS_xls_xml_ss)
266 continue;
267
268 switch (attr.name)
269 {
270 case XML_Type:
271 {
272 if (attr.value == "String")
273 m_cell_type = ct_string;
274 else if (attr.value == "Number")
275 m_cell_type = ct_number;
276 else if (attr.value == "DateTime")
277 m_cell_type = ct_datetime;
278 break;
279 }
280 default:
281 ;
282 }
283 }
284 }
285
end_element_data()286 void xls_xml_data_context::end_element_data()
287 {
288 pstring formula = m_parent_cxt.pop_and_clear_formula();
289
290 if (!formula.empty())
291 {
292 if (m_parent_cxt.is_array_formula())
293 store_array_formula_parent_cell(formula);
294 else
295 push_formula_cell(formula);
296 m_cell_type = ct_unknown;
297 return;
298 }
299
300 if (handle_array_formula_result())
301 {
302 m_cell_type = ct_unknown;
303 return;
304 }
305
306 spreadsheet::iface::import_sheet* sheet = m_parent_cxt.get_import_sheet();
307 spreadsheet::address_t pos = m_parent_cxt.get_current_pos();
308
309 switch (m_cell_type)
310 {
311 case ct_unknown:
312 break;
313 case ct_number:
314 sheet->set_value(pos.row, pos.column, m_cell_value);
315 break;
316 case ct_string:
317 {
318 spreadsheet::iface::import_shared_strings* ss =
319 m_parent_cxt.get_import_factory()->get_shared_strings();
320
321 if (!ss)
322 break;
323
324 if (m_cell_string.empty())
325 break;
326
327 if (m_cell_string.size() == 1 && !m_cell_string[0].formatted)
328 {
329 // Unformatted string.
330 const pstring& s = m_cell_string.back().str;
331 sheet->set_string(pos.row, pos.column, ss->append(s.data(), s.size()));
332 }
333 else
334 {
335 // Formatted string.
336 for (const string_segment_type& sstr : m_cell_string)
337 {
338 if (sstr.formatted)
339 {
340 ss->set_segment_bold(sstr.format.bold);
341 ss->set_segment_italic(sstr.format.italic);
342 ss->set_segment_font_color(
343 0,
344 sstr.format.color.red,
345 sstr.format.color.green,
346 sstr.format.color.blue);
347 }
348
349 ss->append_segment(sstr.str.data(), sstr.str.size());
350 }
351
352 size_t si = ss->commit_segments();
353 sheet->set_string(pos.row, pos.column, si);
354 }
355
356 m_cell_string.clear();
357
358 break;
359 }
360 case ct_datetime:
361 {
362 sheet->set_date_time(
363 pos.row, pos.column,
364 m_cell_datetime.year, m_cell_datetime.month, m_cell_datetime.day,
365 m_cell_datetime.hour, m_cell_datetime.minute, m_cell_datetime.second);
366 break;
367 }
368 default:
369 if (get_config().debug)
370 cout << "warning: unknown cell type '" << m_cell_type << "': value not pushed." << endl;
371 }
372
373 m_cell_type = ct_unknown;
374 }
375
handle_array_formula_result()376 bool xls_xml_data_context::handle_array_formula_result()
377 {
378 xls_xml_context::array_formulas_type& store = m_parent_cxt.get_array_formula_store();
379 spreadsheet::address_t cur_pos = m_parent_cxt.get_current_pos();
380
381 // See if the current cell is within an array formula range.
382 auto it = store.begin(), ite = store.end();
383
384 while (it != ite)
385 {
386 const spreadsheet::range_t& ref = it->first;
387 xls_xml_context::array_formula_type& af = *it->second;
388
389 if (ref.last.row < cur_pos.row)
390 {
391 // If this result range lies above the current row, push the array
392 // and delete it from the list.
393
394 spreadsheet::iface::import_sheet* sheet = m_parent_cxt.get_import_sheet();
395 spreadsheet::iface::import_array_formula* array = nullptr;
396
397 if (sheet)
398 array = sheet->get_array_formula();
399
400 if (array)
401 {
402 push_array_formula(
403 array, ref, af.formula, spreadsheet::formula_grammar_t::xls_xml, af.results);
404 }
405
406 store.erase(it++);
407 continue;
408 }
409
410 if (cur_pos.column < ref.first.column || ref.last.column < cur_pos.column ||
411 cur_pos.row < ref.first.row || ref.last.row < cur_pos.row)
412 {
413 // This cell is not within this array formula range. Move on to
414 // the next one.
415 ++it;
416 continue;
417 }
418
419 size_t row_offset = cur_pos.row - ref.first.row;
420 size_t col_offset = cur_pos.column - ref.first.column;
421 range_formula_results& res = af.results;
422 push_array_result(res, row_offset, col_offset);
423
424 return true;
425 }
426
427 return false;
428 }
429
push_array_result(range_formula_results & res,size_t row_offset,size_t col_offset)430 void xls_xml_data_context::push_array_result(
431 range_formula_results& res, size_t row_offset, size_t col_offset)
432 {
433 switch (m_cell_type)
434 {
435 case ct_number:
436 {
437 res.set(row_offset, col_offset, m_cell_value);
438 break;
439 }
440 case ct_unknown:
441 case ct_datetime:
442 case ct_string:
443 default:
444 if (get_config().debug)
445 cout << "warning: unknown cell type '" << m_cell_type << "': value not pushed." << endl;
446 }
447 }
448
push_formula_cell(const pstring & formula)449 void xls_xml_data_context::push_formula_cell(const pstring& formula)
450 {
451 switch (m_cell_type)
452 {
453 case ct_number:
454 m_parent_cxt.store_cell_formula(formula, m_cell_value);
455 break;
456 default:
457 {
458 formula_result res;
459 m_parent_cxt.store_cell_formula(formula, res);
460 }
461 }
462 }
463
store_array_formula_parent_cell(const pstring & formula)464 void xls_xml_data_context::store_array_formula_parent_cell(const pstring& formula)
465 {
466 spreadsheet::address_t pos = m_parent_cxt.get_current_pos();
467 spreadsheet::range_t range = m_parent_cxt.get_array_range();
468 xls_xml_context::array_formulas_type& store = m_parent_cxt.get_array_formula_store();
469
470 range += pos;
471
472 store.push_back(
473 std::make_pair(
474 range,
475 orcus::make_unique<xls_xml_context::array_formula_type>(range, formula)));
476
477 xls_xml_context::array_formula_type& af = *store.back().second;
478
479 switch (m_cell_type)
480 {
481 case ct_number:
482 af.results.set(0, 0, m_cell_value);
483 break;
484 default:
485 ;
486 }
487 }
488
update_current_format()489 void xls_xml_data_context::update_current_format()
490 {
491 // format stack should have at least one entry at any given moment.
492 assert(!m_format_stack.empty());
493
494 // Grab the bottom format.
495 auto it = m_format_stack.begin();
496 m_current_format = *it;
497 ++it;
498
499 // Merge in the rest of the format data.
500 std::for_each(it, m_format_stack.end(),
501 [&](const format_type& fmt)
502 {
503 m_current_format.merge(fmt);
504 }
505 );
506 }
507
508 namespace {
509
510 namespace border_dir {
511
512 typedef mdds::sorted_string_map<spreadsheet::border_direction_t> map_type;
513
514 // Keys must be sorted.
515 const std::vector<map_type::entry> entries =
516 {
517 { ORCUS_ASCII("Bottom"), spreadsheet::border_direction_t::bottom },
518 { ORCUS_ASCII("DiagonalLeft"), spreadsheet::border_direction_t::diagonal_tl_br },
519 { ORCUS_ASCII("DiagonalRight"), spreadsheet::border_direction_t::diagonal_bl_tr },
520 { ORCUS_ASCII("Left"), spreadsheet::border_direction_t::left },
521 { ORCUS_ASCII("Right"), spreadsheet::border_direction_t::right },
522 { ORCUS_ASCII("Top"), spreadsheet::border_direction_t::top },
523 };
524
get()525 const map_type& get()
526 {
527 static map_type mt(entries.data(), entries.size(), spreadsheet::border_direction_t::unknown);
528 return mt;
529 }
530
531 }
532
533 namespace border_style {
534
535 typedef mdds::sorted_string_map<spreadsheet::border_style_t> map_type;
536
537 // Keys must be sorted.
538 const std::vector<map_type::entry> entries =
539 {
540 { ORCUS_ASCII("Continuous"), spreadsheet::border_style_t::solid },
541 { ORCUS_ASCII("Dash"), spreadsheet::border_style_t::dashed },
542 { ORCUS_ASCII("DashDot"), spreadsheet::border_style_t::dash_dot },
543 { ORCUS_ASCII("DashDotDot"), spreadsheet::border_style_t::dash_dot_dot },
544 { ORCUS_ASCII("Dot"), spreadsheet::border_style_t::dotted },
545 { ORCUS_ASCII("Double"), spreadsheet::border_style_t::double_border },
546 { ORCUS_ASCII("SlantDashDot"), spreadsheet::border_style_t::slant_dash_dot },
547 };
548
get()549 const map_type& get()
550 {
551 static map_type mt(entries.data(), entries.size(), spreadsheet::border_style_t::unknown);
552 return mt;
553 }
554
555 }
556
557 namespace hor_align {
558
559 typedef mdds::sorted_string_map<spreadsheet::hor_alignment_t> map_type;
560
561 // Keys must be sorted.
562 const std::vector<map_type::entry> entries =
563 {
564 { ORCUS_ASCII("Center"), spreadsheet::hor_alignment_t::center },
565 { ORCUS_ASCII("Distributed"), spreadsheet::hor_alignment_t::distributed },
566 { ORCUS_ASCII("Justify"), spreadsheet::hor_alignment_t::justified },
567 { ORCUS_ASCII("Left"), spreadsheet::hor_alignment_t::left },
568 { ORCUS_ASCII("Right"), spreadsheet::hor_alignment_t::right },
569 };
570
get()571 const map_type& get()
572 {
573 static map_type mt(entries.data(), entries.size(), spreadsheet::hor_alignment_t::unknown);
574 return mt;
575 }
576
577 }
578
579 namespace ver_align {
580
581 typedef mdds::sorted_string_map<spreadsheet::ver_alignment_t> map_type;
582
583 // Keys must be sorted.
584 const std::vector<map_type::entry> entries =
585 {
586 { ORCUS_ASCII("Bottom"), spreadsheet::ver_alignment_t::bottom },
587 { ORCUS_ASCII("Center"), spreadsheet::ver_alignment_t::middle },
588 { ORCUS_ASCII("Distributed"), spreadsheet::ver_alignment_t::distributed },
589 { ORCUS_ASCII("Justify"), spreadsheet::ver_alignment_t::justified },
590 { ORCUS_ASCII("Top"), spreadsheet::ver_alignment_t::top },
591 };
592
get()593 const map_type& get()
594 {
595 static map_type mt(entries.data(), entries.size(), spreadsheet::ver_alignment_t::unknown);
596 return mt;
597 }
598
599 }
600
601 namespace num_format {
602
603 typedef mdds::sorted_string_map<pstring> map_type;
604
605 // Keys must be sorted.
606 const std::vector<map_type::entry> entries =
607 {
608 { ORCUS_ASCII("Currency"), "$#,##0.00_);[Red]($#,##0.00)" },
609 { ORCUS_ASCII("Euro Currency"), "[$\xe2\x82\xac-x-euro2] #,##0.00_);[Red]([$\xe2\x82\xac-x-euro2] #,##0.00)" },
610 { ORCUS_ASCII("Fixed"), "0.00" },
611 { ORCUS_ASCII("General Date"), "m/d/yyyy h:mm" },
612 { ORCUS_ASCII("General Number"), "General" },
613 { ORCUS_ASCII("Long Date"), "d-mmm-yy" },
614 { ORCUS_ASCII("Long Time"), "h:mm:ss AM/PM" },
615 { ORCUS_ASCII("Medium Date"), "d-mmm-yy" },
616 { ORCUS_ASCII("Medium Time"), "h:mm AM/PM" },
617 { ORCUS_ASCII("On/Off"), "\"On\";\"On\";\"Off\"" },
618 { ORCUS_ASCII("Percent"), "0.00%" },
619 { ORCUS_ASCII("Scientific"), "0.00E+00" },
620 { ORCUS_ASCII("Short Date"), "m/d/yyyy" },
621 { ORCUS_ASCII("Short Time"), "h:mm" },
622 { ORCUS_ASCII("Standard"), "#,##0.00" },
623 { ORCUS_ASCII("True/False"), "\"True\";\"True\";\"False\"" },
624 { ORCUS_ASCII("Yes/No"), "\"Yes\";\"Yes\";\"No\"" },
625 };
626
get()627 const map_type& get()
628 {
629 static map_type mt(entries.data(), entries.size(), pstring());
630 return mt;
631 }
632
633 } // namespace num_format
634
635 } // anonymous namespace
636
array_formula_type(const spreadsheet::range_t & _range,const pstring & _formula)637 xls_xml_context::array_formula_type::array_formula_type(
638 const spreadsheet::range_t& _range, const pstring& _formula) :
639 formula(_formula),
640 results(_range.last.row-_range.first.row+1, _range.last.column-_range.first.column+1) {}
641
named_exp(const pstring & _name,const pstring & _expression,spreadsheet::sheet_t _scope)642 xls_xml_context::named_exp::named_exp(const pstring& _name, const pstring& _expression, spreadsheet::sheet_t _scope) :
643 name(_name), expression(_expression), scope(_scope) {}
644
selection()645 xls_xml_context::selection::selection() : pane(spreadsheet::sheet_pane_t::unspecified), col(-1), row(-1)
646 {
647 range.first.column = -1;
648 range.first.row = -1;
649 range.last.column = -1;
650 range.last.row = -1;
651 }
652
reset()653 void xls_xml_context::selection::reset()
654 {
655 pane = spreadsheet::sheet_pane_t::unspecified;
656 col = 0;
657 row = 0;
658
659 range.first.column = -1;
660 range.first.row = -1;
661 range.last.column = -1;
662 range.last.row = -1;
663 }
664
valid_cursor() const665 bool xls_xml_context::selection::valid_cursor() const
666 {
667 return col >= 0 && row >= 0;
668 }
669
valid_range() const670 bool xls_xml_context::selection::valid_range() const
671 {
672 return range.first.column >= 0 && range.first.row >= 0 && range.last.column >= 0 && range.last.row >= 0;
673 }
674
split_pane()675 xls_xml_context::split_pane::split_pane() :
676 pane_state(spreadsheet::pane_state_t::split),
677 active_pane(spreadsheet::sheet_pane_t::top_left),
678 split_horizontal(0.0), split_vertical(0.0),
679 top_row_bottom_pane(0), left_col_right_pane(0) {}
680
reset()681 void xls_xml_context::split_pane::reset()
682 {
683 pane_state = spreadsheet::pane_state_t::split;
684 active_pane = spreadsheet::sheet_pane_t::top_left;
685 split_horizontal = 0.0;
686 split_vertical = 0.0;
687 top_row_bottom_pane = 0;
688 left_col_right_pane = 0;
689 }
690
split() const691 bool xls_xml_context::split_pane::split() const
692 {
693 return (split_horizontal || split_vertical) && (top_row_bottom_pane || left_col_right_pane);
694 }
695
get_top_left_cell() const696 spreadsheet::address_t xls_xml_context::split_pane::get_top_left_cell() const
697 {
698 spreadsheet::address_t pos;
699 pos.column = left_col_right_pane;
700 pos.row = top_row_bottom_pane;
701 return pos;
702 }
703
table_properties()704 xls_xml_context::table_properties::table_properties()
705 {
706 reset();
707 }
708
reset()709 void xls_xml_context::table_properties::reset()
710 {
711 pos.row = 0;
712 pos.column = 0;
713 }
714
xls_xml_context(session_context & session_cxt,const tokens & tokens,spreadsheet::iface::import_factory * factory)715 xls_xml_context::xls_xml_context(session_context& session_cxt, const tokens& tokens, spreadsheet::iface::import_factory* factory) :
716 xml_context_base(session_cxt, tokens),
717 mp_factory(factory),
718 mp_cur_sheet(nullptr),
719 mp_sheet_props(nullptr),
720 m_cur_sheet(-1),
721 m_cur_row(0), m_cur_col(0),
722 m_cur_prop_col(0),
723 m_cur_merge_down(0), m_cur_merge_across(0),
724 m_cc_data(session_cxt, tokens, *this)
725 {
726 m_cur_array_range.first.column = -1;
727 m_cur_array_range.first.row = -1;
728 m_cur_array_range.last = m_cur_array_range.first;
729 }
730
~xls_xml_context()731 xls_xml_context::~xls_xml_context()
732 {
733 }
734
declaration(const xml_declaration_t & decl)735 void xls_xml_context::declaration(const xml_declaration_t& decl)
736 {
737 spreadsheet::iface::import_global_settings* gs = mp_factory->get_global_settings();
738 if (!gs)
739 return;
740
741 gs->set_character_set(decl.encoding);
742 }
743
can_handle_element(xmlns_id_t ns,xml_token_t name) const744 bool xls_xml_context::can_handle_element(xmlns_id_t ns, xml_token_t name) const
745 {
746 if (ns == NS_xls_xml_ss)
747 {
748 switch (name)
749 {
750 case XML_Data:
751 return false;
752 default:
753 ;
754 }
755 }
756 return true;
757 }
758
create_child_context(xmlns_id_t ns,xml_token_t name)759 xml_context_base* xls_xml_context::create_child_context(xmlns_id_t ns, xml_token_t name)
760 {
761 if (ns == NS_xls_xml_ss)
762 {
763 switch (name)
764 {
765 case XML_Data:
766 {
767 // Move the cell formula string to the Data element context.
768 m_cc_data.transfer_common(*this);
769 m_cc_data.reset();
770 return &m_cc_data;
771 }
772 default:
773 ;
774 }
775 }
776 return nullptr;
777 }
778
end_child_context(xmlns_id_t ns,xml_token_t name,xml_context_base * child)779 void xls_xml_context::end_child_context(xmlns_id_t ns, xml_token_t name, xml_context_base* child)
780 {
781 }
782
start_element(xmlns_id_t ns,xml_token_t name,const xml_attrs_t & attrs)783 void xls_xml_context::start_element(xmlns_id_t ns, xml_token_t name, const xml_attrs_t& attrs)
784 {
785 xml_token_pair_t parent = push_stack(ns, name);
786 if (ns == NS_xls_xml_ss)
787 {
788 switch (name)
789 {
790 case XML_Workbook:
791 // Do nothing.
792 break;
793 case XML_Worksheet:
794 {
795 start_element_worksheet(parent, attrs);
796 break;
797 }
798 case XML_Table:
799 start_element_table(parent, attrs);
800 break;
801 case XML_Row:
802 start_element_row(parent, attrs);
803 break;
804 case XML_Cell:
805 start_element_cell(parent, attrs);
806 break;
807 case XML_Column:
808 start_element_column(parent, attrs);
809 break;
810 case XML_Names:
811 {
812 xml_elem_stack_t expected_parents;
813 expected_parents.emplace_back(NS_xls_xml_ss, XML_Workbook);
814 expected_parents.emplace_back(NS_xls_xml_ss, XML_Worksheet);
815
816 xml_element_expected(parent, expected_parents);
817 break;
818 }
819 case XML_NamedRange:
820 {
821 xml_element_expected(parent, NS_xls_xml_ss, XML_Names);
822
823 pstring name_s, exp;
824
825 for (const xml_token_attr_t& attr : attrs)
826 {
827 if (attr.ns != NS_xls_xml_ss)
828 continue;
829
830 switch (attr.name)
831 {
832 case XML_Name:
833 name_s = intern(attr);
834 break;
835 case XML_RefersTo:
836 {
837 exp = attr.value;
838 if (exp.size() > 1 && exp[0] == '=')
839 exp = pstring(exp.data()+1, exp.size()-1);
840 if (!exp.empty() && attr.transient)
841 exp = intern(exp);
842 break;
843 }
844 default:
845 ;
846 }
847 }
848
849 if (!name_s.empty() && !exp.empty())
850 {
851 if (m_cur_sheet >= 0)
852 m_named_exps_sheet.emplace_back(name_s, exp, m_cur_sheet);
853 else
854 m_named_exps_global.emplace_back(name_s, exp, -1);
855 }
856
857 break;
858 }
859 case XML_Styles:
860 {
861 xml_element_expected(parent, NS_xls_xml_ss, XML_Workbook);
862 break;
863 }
864 case XML_Style:
865 {
866 xml_element_expected(parent, NS_xls_xml_ss, XML_Styles);
867
868 pstring style_id, style_name;
869
870 for (const xml_token_attr_t& attr : attrs)
871 {
872 if (attr.ns != NS_xls_xml_ss)
873 continue;
874
875 switch (attr.name)
876 {
877 case XML_ID:
878 style_id = intern(attr);
879 break;
880 case XML_Name:
881 style_name = intern(attr);
882 break;
883 default:
884 ;
885 }
886 }
887
888 m_current_style = orcus::make_unique<style_type>();
889 m_current_style->id = style_id;
890 m_current_style->name = style_name;
891
892 break;
893 }
894 case XML_Borders:
895 start_element_borders(parent, attrs);
896 break;
897 case XML_Border:
898 start_element_border(parent, attrs);
899 break;
900 case XML_NumberFormat:
901 start_element_number_format(parent, attrs);
902 break;
903 case XML_Font:
904 {
905 xml_element_expected(parent, NS_xls_xml_ss, XML_Style);
906
907 for (const xml_token_attr_t& attr : attrs)
908 {
909 if (attr.ns != NS_xls_xml_ss)
910 continue;
911
912 switch (attr.name)
913 {
914 case XML_Bold:
915 {
916 m_current_style->font.bold = to_bool(attr.value);
917 break;
918 }
919 case XML_Italic:
920 {
921 m_current_style->font.italic = to_bool(attr.value);
922 break;
923 }
924 case XML_Color:
925 {
926 m_current_style->font.color = to_rgb(attr.value);
927 break;
928 }
929 default:
930 ;
931 }
932 }
933 break;
934 }
935 case XML_Interior:
936 {
937 xml_element_expected(parent, NS_xls_xml_ss, XML_Style);
938
939 for (const xml_token_attr_t& attr : attrs)
940 {
941 if (attr.ns != NS_xls_xml_ss)
942 continue;
943
944 switch (attr.name)
945 {
946 case XML_Color:
947 {
948 m_current_style->fill.color = to_rgb(attr.value);
949 break;
950 }
951 case XML_Pattern:
952 {
953 // TODO : support fill types other than 'solid'.
954 m_current_style->fill.solid = (attr.value == "Solid");
955 break;
956 }
957 default:
958 ;
959 }
960 }
961 break;
962 }
963 case XML_Alignment:
964 {
965 xml_element_expected(parent, NS_xls_xml_ss, XML_Style);
966
967 for (const xml_token_attr_t& attr : attrs)
968 {
969 if (attr.ns != NS_xls_xml_ss)
970 continue;
971
972 switch (attr.name)
973 {
974 case XML_Horizontal:
975 {
976 m_current_style->text_alignment.hor =
977 hor_align::get().find(attr.value.data(), attr.value.size());
978 break;
979 }
980 case XML_Vertical:
981 {
982 m_current_style->text_alignment.ver =
983 ver_align::get().find(attr.value.data(), attr.value.size());
984 break;
985 }
986 case XML_Indent:
987 {
988 m_current_style->text_alignment.indent = to_long(attr.value);
989 break;
990 }
991 default:
992 ;
993 }
994 }
995 break;
996 }
997 default:
998 warn_unhandled();
999 }
1000 }
1001 else if (ns == NS_xls_xml_x)
1002 {
1003 switch (name)
1004 {
1005 case XML_WorksheetOptions:
1006 xml_element_expected(parent, NS_xls_xml_ss, XML_Worksheet);
1007 m_split_pane.reset();
1008 break;
1009 case XML_FreezePanes:
1010 xml_element_expected(parent, NS_xls_xml_x, XML_WorksheetOptions);
1011 // TODO : check if this is correct.
1012 m_split_pane.pane_state = spreadsheet::pane_state_t::frozen_split;
1013 break;
1014 case XML_FrozenNoSplit:
1015 xml_element_expected(parent, NS_xls_xml_x, XML_WorksheetOptions);
1016 m_split_pane.pane_state = spreadsheet::pane_state_t::frozen;
1017 break;
1018 case XML_ActivePane:
1019 xml_element_expected(parent, NS_xls_xml_x, XML_WorksheetOptions);
1020 m_split_pane.active_pane = spreadsheet::sheet_pane_t::unspecified;
1021 break;
1022 case XML_SplitHorizontal:
1023 xml_element_expected(parent, NS_xls_xml_x, XML_WorksheetOptions);
1024 m_split_pane.split_horizontal = 0.0;
1025 break;
1026 case XML_SplitVertical:
1027 xml_element_expected(parent, NS_xls_xml_x, XML_WorksheetOptions);
1028 m_split_pane.split_vertical = 0.0;
1029 break;
1030 case XML_TopRowBottomPane:
1031 xml_element_expected(parent, NS_xls_xml_x, XML_WorksheetOptions);
1032 m_split_pane.top_row_bottom_pane = 0;
1033 break;
1034 case XML_LeftColumnRightPane:
1035 xml_element_expected(parent, NS_xls_xml_x, XML_WorksheetOptions);
1036 m_split_pane.left_col_right_pane = 0;
1037 break;
1038 case XML_Panes:
1039 xml_element_expected(parent, NS_xls_xml_x, XML_WorksheetOptions);
1040 break;
1041 case XML_Pane:
1042 xml_element_expected(parent, NS_xls_xml_x, XML_Panes);
1043 m_cursor_selection.reset();
1044 break;
1045 case XML_Number:
1046 xml_element_expected(parent, NS_xls_xml_x, XML_Pane);
1047 break;
1048 case XML_ActiveCol:
1049 xml_element_expected(parent, NS_xls_xml_x, XML_Pane);
1050 break;
1051 case XML_ActiveRow:
1052 xml_element_expected(parent, NS_xls_xml_x, XML_Pane);
1053 break;
1054 case XML_RangeSelection:
1055 xml_element_expected(parent, NS_xls_xml_x, XML_Pane);
1056 break;
1057 case XML_Selected:
1058 {
1059 xml_element_expected(parent, NS_xls_xml_x, XML_WorksheetOptions);
1060 if (mp_cur_sheet)
1061 {
1062 spreadsheet::iface::import_sheet_view* sv = mp_cur_sheet->get_sheet_view();
1063 if (sv)
1064 sv->set_sheet_active();
1065 }
1066 break;
1067 }
1068 default:
1069 warn_unhandled();
1070 }
1071 }
1072 else
1073 warn_unhandled();
1074 }
1075
end_element(xmlns_id_t ns,xml_token_t name)1076 bool xls_xml_context::end_element(xmlns_id_t ns, xml_token_t name)
1077 {
1078 if (ns == NS_xls_xml_ss)
1079 {
1080 switch (name)
1081 {
1082 case XML_Borders:
1083 end_element_borders();
1084 break;
1085 case XML_Border:
1086 end_element_border();
1087 break;
1088 case XML_NumberFormat:
1089 end_element_number_format();
1090 break;
1091 case XML_Row:
1092 end_element_row();
1093 break;
1094 case XML_Cell:
1095 end_element_cell();
1096 break;
1097 case XML_Column:
1098 end_element_column();
1099 break;
1100 case XML_Table:
1101 end_element_table();
1102 break;
1103 case XML_Workbook:
1104 end_element_workbook();
1105 break;
1106 case XML_Worksheet:
1107 end_element_worksheet();
1108 break;
1109 case XML_Style:
1110 {
1111 if (m_current_style)
1112 {
1113 if (m_current_style->id == "Default")
1114 m_default_style = std::move(m_current_style);
1115 else
1116 m_styles.push_back(std::move(m_current_style));
1117 }
1118 break;
1119 }
1120 case XML_Styles:
1121 {
1122 end_element_styles();
1123 break;
1124 }
1125 default:
1126 ;
1127 }
1128 }
1129 else if (ns == NS_xls_xml_x)
1130 {
1131 switch (name)
1132 {
1133 case XML_Pane:
1134 end_element_pane();
1135 break;
1136 case XML_WorksheetOptions:
1137 end_element_worksheet_options();
1138 break;
1139 default:
1140 ;
1141 }
1142 }
1143 return pop_stack(ns, name);
1144 }
1145
1146 namespace {
1147
to_sheet_pane(long v)1148 spreadsheet::sheet_pane_t to_sheet_pane(long v)
1149 {
1150 static const std::vector<spreadsheet::sheet_pane_t> mapping = {
1151 spreadsheet::sheet_pane_t::bottom_right, // 0
1152 spreadsheet::sheet_pane_t::top_right, // 1
1153 spreadsheet::sheet_pane_t::bottom_left, // 2
1154 spreadsheet::sheet_pane_t::top_left, // 3
1155 };
1156
1157 if (v < 0 || size_t(v) >= mapping.size())
1158 return spreadsheet::sheet_pane_t::unspecified;
1159
1160 return mapping[v];
1161 }
1162
1163 }
1164
characters(const pstring & str,bool)1165 void xls_xml_context::characters(const pstring& str, bool /*transient*/)
1166 {
1167 if (str.empty())
1168 return;
1169
1170 const xml_token_pair_t& ce = get_current_element();
1171
1172 if (ce.first == NS_xls_xml_x)
1173 {
1174 switch (ce.second)
1175 {
1176 case XML_Number:
1177 // sheet pane position.
1178 // 3 | 1
1179 //---+---
1180 // 2 | 0
1181 m_cursor_selection.pane = to_sheet_pane(to_long(str));
1182 break;
1183 case XML_ActiveCol:
1184 m_cursor_selection.col = to_long(str);
1185 break;
1186 case XML_ActiveRow:
1187 m_cursor_selection.row = to_long(str);
1188 break;
1189 case XML_ActivePane:
1190 m_split_pane.active_pane = to_sheet_pane(to_long(str));
1191 break;
1192 case XML_SplitHorizontal:
1193 m_split_pane.split_horizontal = to_double(str);
1194 break;
1195 case XML_SplitVertical:
1196 m_split_pane.split_vertical = to_double(str);
1197 break;
1198 case XML_TopRowBottomPane:
1199 m_split_pane.top_row_bottom_pane = to_long(str);
1200 break;
1201 case XML_LeftColumnRightPane:
1202 m_split_pane.left_col_right_pane = to_long(str);
1203 break;
1204 case XML_RangeSelection:
1205 {
1206 spreadsheet::iface::import_reference_resolver* resolver =
1207 mp_factory->get_reference_resolver(spreadsheet::formula_ref_context_t::global);
1208
1209 if (resolver)
1210 m_cursor_selection.range = to_rc_range(resolver->resolve_range(str.data(), str.size()));
1211
1212 break;
1213 }
1214 default:
1215 ;
1216 }
1217 }
1218 }
1219
start_element_borders(const xml_token_pair_t & parent,const xml_attrs_t & attrs)1220 void xls_xml_context::start_element_borders(const xml_token_pair_t& parent, const xml_attrs_t& attrs)
1221 {
1222 xml_element_expected(parent, NS_xls_xml_ss, XML_Style);
1223 m_current_style->borders.clear();
1224 }
1225
start_element_border(const xml_token_pair_t & parent,const xml_attrs_t & attrs)1226 void xls_xml_context::start_element_border(const xml_token_pair_t& parent, const xml_attrs_t& attrs)
1227 {
1228 xml_element_expected(parent, NS_xls_xml_ss, XML_Borders);
1229
1230 spreadsheet::border_direction_t dir = spreadsheet::border_direction_t::unknown;
1231 spreadsheet::border_style_t style = spreadsheet::border_style_t::unknown;
1232 spreadsheet::color_rgb_t color;
1233 long weight = 0;
1234
1235 for (const xml_token_attr_t& attr : attrs)
1236 {
1237 if (attr.ns != NS_xls_xml_ss)
1238 continue;
1239
1240 switch (attr.name)
1241 {
1242 case XML_Position:
1243 {
1244 dir = border_dir::get().find(attr.value.data(), attr.value.size());
1245 break;
1246 }
1247 case XML_LineStyle:
1248 {
1249 style = border_style::get().find(attr.value.data(), attr.value.size());
1250 break;
1251 }
1252 case XML_Weight:
1253 {
1254 weight = to_long(attr.value);
1255 break;
1256 }
1257 case XML_Color:
1258 {
1259 color = to_rgb(attr.value);
1260 break;
1261 }
1262 default:
1263 ;
1264 }
1265 }
1266
1267 if (dir == spreadsheet::border_direction_t::unknown || style == spreadsheet::border_style_t::unknown)
1268 return;
1269
1270 m_current_style->borders.emplace_back();
1271 border_style_type& bs = m_current_style->borders.back();
1272 bs.dir = dir;
1273 bs.style = style;
1274 bs.color = color;
1275
1276 switch (bs.style)
1277 {
1278 case spreadsheet::border_style_t::solid:
1279 {
1280 switch (weight)
1281 {
1282 case 0:
1283 bs.style = spreadsheet::border_style_t::hair;
1284 break;
1285 case 1:
1286 bs.style = spreadsheet::border_style_t::thin;
1287 break;
1288 case 2:
1289 bs.style = spreadsheet::border_style_t::medium;
1290 break;
1291 case 3:
1292 bs.style = spreadsheet::border_style_t::thick;
1293 break;
1294 default:
1295 ;
1296 }
1297 break;
1298 }
1299 case spreadsheet::border_style_t::dashed:
1300 if (weight > 1)
1301 bs.style = spreadsheet::border_style_t::medium_dashed;
1302 break;
1303 case spreadsheet::border_style_t::dash_dot:
1304 if (weight > 1)
1305 bs.style = spreadsheet::border_style_t::medium_dash_dot;
1306 break;
1307 case spreadsheet::border_style_t::dash_dot_dot:
1308 if (weight > 1)
1309 bs.style = spreadsheet::border_style_t::medium_dash_dot_dot;
1310 break;
1311 default:
1312 ;
1313 }
1314 }
1315
start_element_number_format(const xml_token_pair_t & parent,const xml_attrs_t & attrs)1316 void xls_xml_context::start_element_number_format(const xml_token_pair_t& parent, const xml_attrs_t& attrs)
1317 {
1318 xml_element_expected(parent, NS_xls_xml_ss, XML_Style);
1319 m_current_style->number_format.clear();
1320
1321 for (const xml_token_attr_t& attr : attrs)
1322 {
1323 if (attr.ns != NS_xls_xml_ss)
1324 continue;
1325
1326 switch (attr.name)
1327 {
1328 case XML_Format:
1329 {
1330 pstring code = num_format::get().find(attr.value.data(), attr.value.size());
1331 m_current_style->number_format = code.empty() ? intern(attr) : code;
1332 break;
1333 }
1334 default:
1335 ;
1336 }
1337 }
1338 }
1339
start_element_cell(const xml_token_pair_t & parent,const xml_attrs_t & attrs)1340 void xls_xml_context::start_element_cell(const xml_token_pair_t& parent, const xml_attrs_t& attrs)
1341 {
1342 xml_element_expected(parent, NS_xls_xml_ss, XML_Row);
1343
1344 long col_index = 0;
1345 pstring formula;
1346 m_cur_cell_style_id.clear();
1347
1348 m_cur_merge_across = 0; // extra column(s) that are part of the merged cell.
1349 m_cur_merge_down = 0; // extra row(s) that are part of the merged cell.
1350
1351 m_cur_array_range.first.column = -1;
1352 m_cur_array_range.first.row = -1;
1353 m_cur_array_range.last = m_cur_array_range.first;
1354
1355 for (const xml_token_attr_t& attr : attrs)
1356 {
1357 if (attr.value.empty())
1358 return;
1359
1360 if (attr.ns != NS_xls_xml_ss)
1361 return;
1362
1363 switch (attr.name)
1364 {
1365 case XML_Index:
1366 col_index = to_long(attr.value);
1367 break;
1368 case XML_Formula:
1369 if (attr.value[0] == '=' && attr.value.size() > 1)
1370 {
1371 pstring s(attr.value.get()+1, attr.value.size()-1);
1372 formula = s;
1373 if (attr.transient)
1374 formula = intern(s);
1375 }
1376 break;
1377 case XML_MergeAcross:
1378 m_cur_merge_across = to_long(attr.value);
1379 break;
1380 case XML_MergeDown:
1381 m_cur_merge_down = to_long(attr.value);
1382 break;
1383 case XML_StyleID:
1384 m_cur_cell_style_id = intern(attr);
1385 break;
1386 case XML_ArrayRange:
1387 {
1388 spreadsheet::iface::import_reference_resolver* resolver =
1389 mp_factory->get_reference_resolver(spreadsheet::formula_ref_context_t::global);
1390 if (resolver)
1391 m_cur_array_range = to_rc_range(resolver->resolve_range(attr.value.data(), attr.value.size()));
1392
1393 break;
1394 }
1395 default:
1396 ;
1397 }
1398 }
1399
1400 if (!formula.empty())
1401 m_cur_cell_formula = formula;
1402
1403 if (col_index > 0)
1404 {
1405 // 1-based column index. Convert it to a 0-based one.
1406 m_cur_col = m_table_props.pos.column + col_index - 1;
1407 }
1408 }
1409
start_element_column(const xml_token_pair_t & parent,const xml_attrs_t & attrs)1410 void xls_xml_context::start_element_column(const xml_token_pair_t& parent, const xml_attrs_t& attrs)
1411 {
1412 xml_element_expected(parent, NS_xls_xml_ss, XML_Table);
1413
1414 if (!mp_sheet_props)
1415 return;
1416
1417 spreadsheet::col_t col_index = m_cur_prop_col;
1418 spreadsheet::col_t span = 0;
1419 double width = 0.0;
1420 bool hidden = false;
1421
1422 std::for_each(attrs.begin(), attrs.end(),
1423 [&](const xml_token_attr_t& attr)
1424 {
1425 if (attr.value.empty())
1426 return;
1427
1428 if (attr.ns != NS_xls_xml_ss)
1429 return;
1430
1431 switch (attr.name)
1432 {
1433 case XML_Index:
1434 // Convert from 1-based to 0-based.
1435 col_index = to_long(attr.value) - 1;
1436 break;
1437 case XML_Width:
1438 width = to_double(attr.value);
1439 break;
1440 case XML_Span:
1441 span = to_long(attr.value);
1442 break;
1443 case XML_Hidden:
1444 hidden = to_long(attr.value) != 0;
1445 default:
1446 ;
1447 }
1448 }
1449 );
1450
1451 for (; span >= 0; --span, ++col_index)
1452 {
1453 // Column widths are stored as points.
1454 mp_sheet_props->set_column_width(col_index, width, orcus::length_unit_t::point);
1455 mp_sheet_props->set_column_hidden(col_index, hidden);
1456 }
1457
1458 m_cur_prop_col = col_index;
1459 }
1460
start_element_row(const xml_token_pair_t & parent,const xml_attrs_t & attrs)1461 void xls_xml_context::start_element_row(const xml_token_pair_t& parent, const xml_attrs_t& attrs)
1462 {
1463 xml_element_expected(parent, NS_xls_xml_ss, XML_Table);
1464 m_cur_col = m_table_props.pos.column;
1465 spreadsheet::row_t row_index = -1;
1466 bool has_height = false;
1467 bool hidden = false;
1468 double height = 0.0;
1469
1470 for (const xml_token_attr_t& attr : attrs)
1471 {
1472 if (attr.value.empty())
1473 return;
1474
1475 if (attr.ns == NS_xls_xml_ss)
1476 {
1477 switch (attr.name)
1478 {
1479 case XML_Index:
1480 row_index = to_long(attr.value);
1481 break;
1482 case XML_Height:
1483 has_height = true;
1484 height = to_double(attr.value);
1485 break;
1486 case XML_Hidden:
1487 hidden = to_long(attr.value) != 0;
1488 break;
1489 default:
1490 ;
1491 }
1492 }
1493 }
1494
1495 if (row_index > 0)
1496 {
1497 // 1-based row index. Convert it to a 0-based one.
1498 m_cur_row = row_index - 1;
1499 }
1500
1501 if (mp_sheet_props)
1502 {
1503 if (has_height)
1504 mp_sheet_props->set_row_height(m_cur_row, height, length_unit_t::point);
1505
1506 if (hidden)
1507 mp_sheet_props->set_row_hidden(m_cur_row, true);
1508 }
1509 }
1510
start_element_table(const xml_token_pair_t & parent,const xml_attrs_t & attrs)1511 void xls_xml_context::start_element_table(const xml_token_pair_t& parent, const xml_attrs_t& attrs)
1512 {
1513 xml_element_expected(parent, NS_xls_xml_ss, XML_Worksheet);
1514
1515 spreadsheet::row_t row_index = -1;
1516 spreadsheet::col_t col_index = -1;
1517
1518 for (const xml_token_attr_t& attr : attrs)
1519 {
1520 if (attr.value.empty())
1521 return;
1522
1523 if (attr.ns == NS_xls_xml_ss)
1524 {
1525 switch (attr.name)
1526 {
1527 case XML_TopCell:
1528 col_index = to_long(attr.value);
1529 break;
1530 case XML_LeftCell:
1531 row_index = to_long(attr.value);
1532 break;
1533 default:
1534 ;
1535 }
1536 }
1537 }
1538
1539 // Convert 1-based indices to 0-based.
1540
1541 if (row_index > 0)
1542 {
1543 m_table_props.pos.row = row_index - 1;
1544 m_cur_row = m_table_props.pos.row;
1545 }
1546
1547 if (col_index > 0)
1548 m_table_props.pos.column = col_index - 1;
1549 }
1550
start_element_worksheet(const xml_token_pair_t & parent,const xml_attrs_t & attrs)1551 void xls_xml_context::start_element_worksheet(const xml_token_pair_t& parent, const xml_attrs_t& attrs)
1552 {
1553 xml_element_expected(parent, NS_xls_xml_ss, XML_Workbook);
1554
1555 ++m_cur_sheet;
1556 pstring sheet_name;
1557 m_cell_formulas.emplace_back();
1558
1559 for (const xml_token_attr_t& attr : attrs)
1560 {
1561 if (attr.ns == NS_xls_xml_ss)
1562 {
1563 switch (attr.name)
1564 {
1565 case XML_Name:
1566 sheet_name = attr.value;
1567 break;
1568 default:
1569 ;
1570 }
1571 }
1572 }
1573
1574 mp_cur_sheet = mp_factory->append_sheet(m_cur_sheet, sheet_name.data(), sheet_name.size());
1575 spreadsheet::iface::import_named_expression* sheet_named_exp = nullptr;
1576 if (mp_cur_sheet)
1577 {
1578 mp_sheet_props = mp_cur_sheet->get_sheet_properties();
1579 sheet_named_exp = mp_cur_sheet->get_named_expression();
1580 }
1581
1582 m_sheet_named_exps.push_back(sheet_named_exp);
1583
1584 m_cur_row = 0;
1585 m_cur_col = 0;
1586
1587 if (get_config().debug)
1588 cout << "worksheet: name: '" << sheet_name << "'" << endl;
1589 }
1590
end_element_borders()1591 void xls_xml_context::end_element_borders()
1592 {
1593 }
1594
end_element_border()1595 void xls_xml_context::end_element_border()
1596 {
1597 }
1598
end_element_number_format()1599 void xls_xml_context::end_element_number_format()
1600 {
1601 }
1602
end_element_cell()1603 void xls_xml_context::end_element_cell()
1604 {
1605 if (mp_sheet_props && (m_cur_merge_across > 0 || m_cur_merge_down > 0))
1606 {
1607 spreadsheet::range_t merge_range;
1608 merge_range.first.column = m_cur_col;
1609 merge_range.first.row = m_cur_row;
1610 merge_range.last.column = m_cur_col + m_cur_merge_across;
1611 merge_range.last.row = m_cur_row + m_cur_merge_down;
1612
1613 mp_sheet_props->set_merge_cell_range(merge_range);
1614 }
1615
1616 if (mp_cur_sheet && !m_cur_cell_style_id.empty())
1617 {
1618 auto it = m_style_map.find(m_cur_cell_style_id);
1619 if (it != m_style_map.end())
1620 {
1621 size_t xf_id = it->second;
1622 mp_cur_sheet->set_format(m_cur_row, m_cur_col, xf_id);
1623 }
1624 }
1625
1626 if (mp_cur_sheet && !m_cur_cell_formula.empty())
1627 {
1628 // Likely a Cell element without a child Data element.
1629 store_cell_formula(m_cur_cell_formula, formula_result());
1630 }
1631
1632 m_cur_cell_formula.clear();
1633
1634 ++m_cur_col;
1635 if (m_cur_merge_across > 0)
1636 m_cur_col += m_cur_merge_across;
1637 }
1638
end_element_column()1639 void xls_xml_context::end_element_column()
1640 {
1641 }
1642
end_element_row()1643 void xls_xml_context::end_element_row()
1644 {
1645 ++m_cur_row;
1646 }
1647
end_element_table()1648 void xls_xml_context::end_element_table()
1649 {
1650 push_all_array_formulas();
1651 m_array_formulas.clear();
1652 m_table_props.reset();
1653 }
1654
end_element_worksheet()1655 void xls_xml_context::end_element_worksheet()
1656 {
1657 mp_cur_sheet = nullptr;
1658 }
1659
end_element_workbook()1660 void xls_xml_context::end_element_workbook()
1661 {
1662 if (!mp_factory)
1663 return;
1664
1665 spreadsheet::iface::import_named_expression* ne_global = mp_factory->get_named_expression();
1666 if (ne_global)
1667 {
1668 // global scope named expressions.
1669
1670 for (const named_exp& ne : m_named_exps_global)
1671 {
1672 ne_global->set_named_expression(
1673 ne.name.data(), ne.name.size(), ne.expression.data(), ne.expression.size());
1674 ne_global->commit();
1675 }
1676 }
1677
1678 // sheet-local named expressions follow.
1679
1680 for (const named_exp& ne : m_named_exps_sheet)
1681 {
1682 spreadsheet::iface::import_named_expression* p = nullptr;
1683 if (ne.scope >= 0 && size_t(ne.scope) < m_sheet_named_exps.size())
1684 p = m_sheet_named_exps[ne.scope]; // it may be nullptr.
1685
1686 if (p)
1687 {
1688 p->set_named_expression(
1689 ne.name.data(), ne.name.size(), ne.expression.data(), ne.expression.size());
1690 p->commit();
1691 }
1692 }
1693
1694 // push all cell formulas
1695 for (size_t sheet_pos = 0; sheet_pos < m_cell_formulas.size(); ++sheet_pos)
1696 {
1697 spreadsheet::iface::import_sheet* sheet = mp_factory->get_sheet(sheet_pos);
1698 if (!sheet)
1699 continue;
1700
1701 spreadsheet::iface::import_formula* xformula = sheet->get_formula();
1702 if (!xformula)
1703 continue;
1704
1705 const std::deque<cell_formula_type>& store = m_cell_formulas[sheet_pos];
1706 for (const cell_formula_type& cf : store)
1707 {
1708 xformula->set_position(cf.pos.row, cf.pos.column);
1709 xformula->set_formula(ss::formula_grammar_t::xls_xml, cf.formula.data(), cf.formula.size());
1710
1711 switch (cf.result.type)
1712 {
1713 case formula_result::result_type::numeric:
1714 xformula->set_result_value(cf.result.value_numeric);
1715 break;
1716 case formula_result::result_type::string:
1717 case formula_result::result_type::boolean:
1718 case formula_result::result_type::empty:
1719 ;
1720 }
1721
1722 xformula->commit();
1723 }
1724 }
1725 }
1726
end_element_styles()1727 void xls_xml_context::end_element_styles()
1728 {
1729 commit_default_style(); // Commit the default style first.
1730 commit_styles();
1731 }
1732
end_element_pane()1733 void xls_xml_context::end_element_pane()
1734 {
1735 spreadsheet::iface::import_sheet_view* sv = mp_cur_sheet->get_sheet_view();
1736 if (!sv)
1737 return;
1738
1739 if (m_cursor_selection.pane == spreadsheet::sheet_pane_t::unspecified)
1740 return;
1741
1742 if (m_cursor_selection.valid_range())
1743 {
1744 sv->set_selected_range(m_cursor_selection.pane, m_cursor_selection.range);
1745 }
1746 else if (m_cursor_selection.valid_cursor())
1747 {
1748 spreadsheet::range_t sel;
1749 sel.first.column = m_cursor_selection.col;
1750 sel.first.row = m_cursor_selection.row;
1751 sel.last = sel.first;
1752
1753 sv->set_selected_range(m_cursor_selection.pane, sel);
1754 }
1755 }
1756
end_element_worksheet_options()1757 void xls_xml_context::end_element_worksheet_options()
1758 {
1759 commit_split_pane();
1760 }
1761
commit_split_pane()1762 void xls_xml_context::commit_split_pane()
1763 {
1764 spreadsheet::iface::import_sheet_view* sv = mp_cur_sheet->get_sheet_view();
1765 if (!sv)
1766 return;
1767
1768 if (!m_split_pane.split())
1769 return;
1770
1771 switch (m_split_pane.pane_state)
1772 {
1773 case spreadsheet::pane_state_t::split:
1774 {
1775 spreadsheet::address_t top_left_cell = m_split_pane.get_top_left_cell();
1776
1777 // NB: The term "split vertical" in Excel 2003 XML refers to the
1778 // vertical split bar position which in this case corresponds with
1779 // the "horizontal split" position of the set_split_pane() call,
1780 // and vice versa.
1781 sv->set_split_pane(
1782 m_split_pane.split_vertical, m_split_pane.split_horizontal,
1783 top_left_cell, m_split_pane.active_pane);
1784 break;
1785 }
1786 case spreadsheet::pane_state_t::frozen:
1787 {
1788 spreadsheet::address_t top_left_cell = m_split_pane.get_top_left_cell();
1789
1790 // NB: Note for the split pane above also applies here.
1791 spreadsheet::col_t visible_cols = m_split_pane.split_vertical;
1792 spreadsheet::row_t visible_rows = m_split_pane.split_horizontal;
1793
1794 sv->set_frozen_pane(
1795 visible_cols, visible_rows,
1796 top_left_cell, m_split_pane.active_pane);
1797 break;
1798 }
1799 case spreadsheet::pane_state_t::frozen_split:
1800 // not handled yet.
1801 break;
1802 case spreadsheet::pane_state_t::unspecified:
1803 default:
1804 ;
1805 }
1806
1807 m_split_pane.reset();
1808 }
1809
commit_default_style()1810 void xls_xml_context::commit_default_style()
1811 {
1812 spreadsheet::iface::import_styles* styles = mp_factory->get_styles();
1813 if (!styles)
1814 return;
1815
1816 if (m_default_style)
1817 {
1818 styles->set_font_bold(m_default_style->font.bold);
1819 styles->set_font_italic(m_default_style->font.italic);
1820 styles->set_font_color(
1821 0,
1822 m_default_style->font.color.red,
1823 m_default_style->font.color.green,
1824 m_default_style->font.color.blue);
1825 }
1826
1827 styles->commit_font();
1828
1829 styles->commit_fill();
1830 styles->commit_border();
1831 styles->commit_cell_protection();
1832 styles->commit_number_format();
1833
1834 styles->commit_cell_style_xf();
1835 styles->commit_cell_xf();
1836
1837 if (m_default_style)
1838 {
1839 const pstring& name = m_default_style->name;
1840 if (!name.empty())
1841 styles->set_cell_style_name(name.data(), name.size());
1842 }
1843
1844 styles->commit_cell_style();
1845 }
1846
commit_styles()1847 void xls_xml_context::commit_styles()
1848 {
1849 if (m_styles.empty())
1850 return;
1851
1852 spreadsheet::iface::import_styles* styles = mp_factory->get_styles();
1853 if (!styles)
1854 return;
1855
1856 // Build a map of cell style textural ID's to cell format (xf) numeric ID's.
1857
1858 for (const std::unique_ptr<style_type>& style : m_styles)
1859 {
1860 styles->set_font_bold(style->font.bold);
1861 styles->set_font_italic(style->font.italic);
1862 styles->set_font_color(255,
1863 style->font.color.red,
1864 style->font.color.green,
1865 style->font.color.blue);
1866
1867 size_t font_id = styles->commit_font();
1868
1869 styles->set_xf_font(font_id);
1870
1871 if (style->fill.solid)
1872 {
1873 // TODO : add support for fill types other than 'solid'.
1874 styles->set_fill_pattern_type(spreadsheet::fill_pattern_t::solid);
1875 styles->set_fill_fg_color(255,
1876 style->fill.color.red,
1877 style->fill.color.green,
1878 style->fill.color.blue);
1879
1880 size_t fill_id = styles->commit_fill();
1881 styles->set_xf_fill(fill_id);
1882 }
1883
1884 if (!style->borders.empty())
1885 {
1886 styles->set_border_count(style->borders.size());
1887
1888 for (const border_style_type& b : style->borders)
1889 {
1890 styles->set_border_style(b.dir, b.style);
1891 styles->set_border_color(b.dir, 255, b.color.red, b.color.green, b.color.blue);
1892 }
1893
1894 size_t border_id = styles->commit_border();
1895 styles->set_xf_border(border_id);
1896 }
1897
1898 bool apply_alignment =
1899 style->text_alignment.hor != spreadsheet::hor_alignment_t::unknown ||
1900 style->text_alignment.ver != spreadsheet::ver_alignment_t::unknown;
1901
1902 styles->set_xf_apply_alignment(apply_alignment);
1903 styles->set_xf_horizontal_alignment(style->text_alignment.hor);
1904 styles->set_xf_vertical_alignment(style->text_alignment.ver);
1905
1906 if (!style->number_format.empty())
1907 {
1908 styles->set_number_format_code(style->number_format.data(), style->number_format.size());
1909 size_t number_format_id = styles->commit_number_format();
1910 styles->set_xf_number_format(number_format_id);
1911 }
1912
1913 // TODO : handle text indent level.
1914
1915 size_t xf_id = styles->commit_cell_xf();
1916
1917 m_style_map.insert({style->id, xf_id});
1918 }
1919 }
1920
push_all_array_formulas()1921 void xls_xml_context::push_all_array_formulas()
1922 {
1923 if (!mp_cur_sheet)
1924 return;
1925
1926 spreadsheet::iface::import_array_formula* array = mp_cur_sheet->get_array_formula();
1927 if (!array)
1928 return;
1929
1930 for (const array_formula_pair_type& pair : m_array_formulas)
1931 {
1932 const array_formula_type& af = *pair.second;
1933 push_array_formula(
1934 array, pair.first, af.formula, spreadsheet::formula_grammar_t::xls_xml, af.results);
1935 }
1936 }
1937
get_import_factory()1938 spreadsheet::iface::import_factory* xls_xml_context::get_import_factory()
1939 {
1940 return mp_factory;
1941 }
1942
get_import_sheet()1943 spreadsheet::iface::import_sheet* xls_xml_context::get_import_sheet()
1944 {
1945 return mp_cur_sheet;
1946 }
1947
get_current_pos() const1948 spreadsheet::address_t xls_xml_context::get_current_pos() const
1949 {
1950 spreadsheet::address_t pos;
1951 pos.row = m_cur_row;
1952 pos.column = m_cur_col;
1953 return pos;
1954 }
1955
pop_and_clear_formula()1956 pstring xls_xml_context::pop_and_clear_formula()
1957 {
1958 pstring f = m_cur_cell_formula;
1959 m_cur_cell_formula.clear();
1960 return f;
1961 }
1962
is_array_formula() const1963 bool xls_xml_context::is_array_formula() const
1964 {
1965 if (m_cur_array_range.first.column < 0 || m_cur_array_range.first.row < 0)
1966 return false;
1967
1968 if (m_cur_array_range.last.column < 0 || m_cur_array_range.last.row < 0)
1969 return false;
1970
1971 if (m_cur_array_range.first.column > m_cur_array_range.last.column ||
1972 m_cur_array_range.first.row > m_cur_array_range.last.row)
1973 return false;
1974
1975 return true;
1976 }
1977
get_array_range() const1978 const spreadsheet::range_t& xls_xml_context::get_array_range() const
1979 {
1980 return m_cur_array_range;
1981 }
1982
get_array_formula_store()1983 xls_xml_context::array_formulas_type& xls_xml_context::get_array_formula_store()
1984 {
1985 return m_array_formulas;
1986 }
1987
store_cell_formula(const pstring & formula,const formula_result & res)1988 void xls_xml_context::store_cell_formula(const pstring& formula, const formula_result& res)
1989 {
1990 assert(m_cur_sheet < ss::sheet_t(m_cell_formulas.size()));
1991
1992 cell_formula_type cf;
1993 cf.pos = get_current_pos();
1994 cf.formula = formula;
1995 cf.result = res;
1996 std::deque<cell_formula_type>& store = m_cell_formulas[m_cur_sheet];
1997 store.push_back(std::move(cf));
1998 }
1999
2000 }
2001
2002 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2003