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