1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  * This file is part of the libetonyek project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
10 #include "IWAParser.h"
11 
12 #include <algorithm>
13 #include <cassert>
14 #include <functional>
15 #include <iomanip>
16 #include <map>
17 #include <memory>
18 #include <sstream>
19 #include <utility>
20 
21 #include <boost/optional.hpp>
22 
23 #include "IWAObjectType.h"
24 #include "IWAText.h"
25 #include "IWORKCollector.h"
26 #include "IWORKFormula.h"
27 #include "IWORKNumberConverter.h"
28 #include "IWORKPath.h"
29 #include "IWORKProperties.h"
30 #include "IWORKTable.h"
31 #include "IWORKText.h"
32 #include "IWORKTransformation.h"
33 #include "IWORKTypes.h"
34 
35 #include "PAGCollector.h"
36 
37 namespace libetonyek
38 {
39 
40 using boost::none;
41 using boost::optional;
42 
43 using namespace std::placeholders;
44 
45 using std::bind;
46 using std::deque;
47 using std::make_pair;
48 using std::make_shared;
49 using std::map;
50 using std::shared_ptr;
51 using std::string;
52 
53 namespace
54 {
samePoint(const optional<IWORKPosition> & point1,const optional<IWORKPosition> & point2)55 bool samePoint(const optional<IWORKPosition> &point1, const optional<IWORKPosition> &point2)
56 {
57   if (point1 && point2)
58     return approxEqual(get(point1).m_x, get(point2).m_x) && approxEqual(get(point1).m_y, get(point2).m_y);
59   return true;
60 }
61 
62 template<typename T>
convert(const unsigned value)63 optional<T> convert(const unsigned value)
64 {
65   return IWORKNumberConverter<T>::convert(value);
66 }
67 
68 template<typename P>
putEnum(IWORKPropertyMap & props,const unsigned value)69 void putEnum(IWORKPropertyMap &props, const unsigned value)
70 {
71   typedef typename IWORKPropertyInfo<P>::ValueType ValueType;
72   const optional<ValueType> &converted = IWORKNumberConverter<ValueType>::convert(value);
73   if (converted)
74     props.put<P>(get(converted));
75 }
76 
parseColumnOffsets(const RVNGInputStreamPtr_t & input,const unsigned length,map<unsigned,unsigned> & offsets)77 void parseColumnOffsets(const RVNGInputStreamPtr_t &input, const unsigned length, map<unsigned,unsigned> &offsets)
78 {
79   try
80   {
81     unsigned col=0;
82     while (!input->isEnd())
83     {
84       const unsigned offset = readU16(input);
85       if (offset<length && offset+4 < length)
86         offsets[col]=offset;
87       ++col;
88     }
89   }
90   catch (...)
91   {
92     // ignore failure to read the last word: it can only happen in a broken file
93   }
94   // TODO: check that the valid offsets are sorted in ascending order
95 }
96 
makeSizes(const mdds::flat_segment_tree<unsigned,float> & sizes)97 deque<IWORKColumnRowSize> makeSizes(const mdds::flat_segment_tree<unsigned, float> &sizes)
98 {
99   IWORKColumnRowSize defVal;
100   if (sizes.default_value()>0) defVal=IWORKColumnRowSize(sizes.default_value(),false);
101   deque<IWORKColumnRowSize> out(sizes.max_key(), IWORKColumnRowSize());
102   for (mdds::flat_segment_tree<unsigned, float>::const_iterator it = sizes.begin(); it != sizes.end();)
103   {
104     const deque<IWORKColumnRowSize>::iterator start(out.begin() + deque<double>::iterator::difference_type(it->first));
105     const double size = it->second;
106     ++it;
107     const deque<IWORKColumnRowSize>::iterator end(it == sizes.end() ? out.end() : out.begin() +  deque<double>::iterator::difference_type(it->first));
108     std::fill(start, end, size>0 ? IWORKColumnRowSize(size) : defVal);
109   }
110   return out;
111 }
112 
113 }
114 
Format()115 IWAParser::Format::Format()
116   : m_type()
117   , m_format()
118 {
119 }
120 
PageMaster()121 IWAParser::PageMaster::PageMaster()
122   : m_style()
123   , m_headerFootersSameAsPrevious(true)
124 {
125 }
126 
TableHeader(const unsigned count,float defValue)127 IWAParser::TableHeader::TableHeader(const unsigned count, float defValue)
128   : m_sizes(0, count, defValue)
129   , m_hidden(0, count, false)
130 {
131 }
132 
TableInfo(const shared_ptr<IWORKTable> & table,const unsigned columns,const unsigned rows)133 IWAParser::TableInfo::TableInfo(const shared_ptr<IWORKTable> &table, const unsigned columns, const unsigned rows)
134   : m_table(table)
135   , m_columns(columns)
136   , m_rows(rows)
137   , m_style()
138   , m_columnHeader(columns)
139   , m_rowHeader(rows,20)
140   , m_simpleTextList()
141   , m_cellStyleList()
142   , m_formattedTextList()
143   , m_formulaList()
144   , m_formatList()
145   , m_commentList()
146 {
147 }
148 
IWAParser(const RVNGInputStreamPtr_t & fragments,const RVNGInputStreamPtr_t & package,IWORKCollector & collector)149 IWAParser::IWAParser(const RVNGInputStreamPtr_t &fragments, const RVNGInputStreamPtr_t &package, IWORKCollector &collector)
150   : m_langManager()
151   , m_tableNameMap(std::make_shared<IWORKTableNameMap_t>())
152   , m_currentText()
153   , m_collector(collector)
154   , m_index(fragments, package)
155   , m_visited()
156   , m_charStyles()
157   , m_paraStyles()
158   , m_sectionStyles()
159   , m_graphicStyles()
160   , m_mediaStyles()
161   , m_cellStyles()
162   , m_tableStyles()
163   , m_listStyles()
164   , m_currentTable()
165   , m_uidFormatMap()
166 {
167 }
168 
parse()169 bool IWAParser::parse()
170 {
171   parseObjectIndex();
172   return parseDocument();
173 }
174 
ObjectMessage(IWAParser & parser,const unsigned id,const unsigned type)175 IWAParser::ObjectMessage::ObjectMessage(IWAParser &parser, const unsigned id, const unsigned type)
176   : m_parser(parser)
177   , m_message()
178   , m_id(id)
179   , m_type(0)
180 {
181   std::deque<unsigned>::const_iterator it = find(m_parser.m_visited.begin(), m_parser.m_visited.end(), m_id);
182   if (it == m_parser.m_visited.end())
183   {
184     optional<IWAMessage> msg;
185     m_parser.queryObject(m_id, m_type, msg);
186     if (msg)
187     {
188       if ((m_type == type) || (type == 0))
189       {
190         m_message = msg;
191         m_parser.m_visited.push_back(m_id);
192       }
193       else
194       {
195         ETONYEK_DEBUG_MSG(("IWAParser::ObjectMessage::ObjectMessage: type mismatch for object %u: expected %u, got %u\n", id, type, m_type));
196       }
197     }
198   }
199   else
200   {
201     ETONYEK_DEBUG_MSG(("IWAParser::ObjectMessage::ObjectMessage: object %u is actually visited\n", id));
202   }
203 }
204 
~ObjectMessage()205 IWAParser::ObjectMessage::~ObjectMessage()
206 {
207   if (m_message)
208   {
209     assert(!m_parser.m_visited.empty());
210     assert(m_parser.m_visited.back() == m_id);
211     m_parser.m_visited.pop_back();
212   }
213 }
214 
operator bool() const215 IWAParser::ObjectMessage::operator bool() const
216 {
217   return bool(m_message);
218 }
219 
get() const220 const IWAMessage &IWAParser::ObjectMessage::get() const
221 {
222   return m_message.get();
223 }
224 
getType() const225 unsigned IWAParser::ObjectMessage::getType() const
226 {
227   return m_type;
228 }
229 
queryObject(const unsigned id,unsigned & type,boost::optional<IWAMessage> & msg) const230 void IWAParser::queryObject(const unsigned id, unsigned &type, boost::optional<IWAMessage> &msg) const
231 {
232   m_index.queryObject(id, type, msg);
233 }
234 
getObjectType(const unsigned id) const235 boost::optional<unsigned> IWAParser::getObjectType(const unsigned id) const
236 {
237   return m_index.getObjectType(id);
238 }
239 
queryFile(const unsigned id) const240 const RVNGInputStreamPtr_t IWAParser::queryFile(const unsigned id) const
241 {
242   return m_index.queryFile(id);
243 }
244 
readRef(const IWAMessage & msg,const unsigned field)245 boost::optional<unsigned> IWAParser::readRef(const IWAMessage &msg, const unsigned field)
246 {
247   if (msg.message(field))
248     return msg.message(field).uint32(1).optional();
249   return boost::none;
250 }
251 
readRefs(const IWAMessage & msg,const unsigned field)252 std::deque<unsigned> IWAParser::readRefs(const IWAMessage &msg, const unsigned field)
253 {
254   std::deque<unsigned> refs;
255   if (msg.message(field))
256   {
257     const std::deque<IWAMessage> &objs = msg.message(field).repeated();
258     for (const auto &obj : objs)
259     {
260       if (obj.uint32(1))
261         refs.push_back(obj.uint32(1).get());
262     }
263   }
264   return refs;
265 }
266 
readPosition(const IWAMessage & msg,const unsigned field)267 boost::optional<IWORKPosition> IWAParser::readPosition(const IWAMessage &msg, const unsigned field)
268 {
269   if (msg.message(field))
270   {
271     const optional<float> &x = msg.message(field).float_(1).optional();
272     const optional<float> &y = msg.message(field).float_(2).optional();
273     return IWORKPosition(get_optional_value_or(x, 0), get_optional_value_or(y, 0));
274   }
275   return boost::none;
276 }
277 
readSize(const IWAMessage & msg,const unsigned field)278 boost::optional<IWORKSize> IWAParser::readSize(const IWAMessage &msg, const unsigned field)
279 {
280   if (msg.message(field))
281   {
282     const optional<float> &w = msg.message(field).float_(1).optional();
283     const optional<float> &h = msg.message(field).float_(2).optional();
284     return IWORKSize(get_optional_value_or(w, 0), get_optional_value_or(h, 0));
285   }
286   return boost::none;
287 }
288 
readColor(const IWAMessage & msg,const unsigned field)289 boost::optional<IWORKColor> IWAParser::readColor(const IWAMessage &msg, const unsigned field)
290 {
291   const IWAMessageField &color = msg.message(field);
292   if (color)
293   {
294     if (color.float_(3) && color.float_(4) && color.float_(5))
295       return IWORKColor(get(color.float_(3)), get(color.float_(4)), get(color.float_(5)), get_optional_value_or(color.float_(6), 0));
296   }
297   return boost::none;
298 }
299 
readUID(const IWAMessage & msg,unsigned field)300 boost::optional<uint64_t> IWAParser::readUID(const IWAMessage &msg, unsigned field)
301 {
302   const IWAMessageField &id = msg.message(field);
303   if (!id) return boost::none;
304   if (id && get(id).uint32(1) && get(id).uint32(2))
305     return (uint64_t(get(get(id).uint32(1)))<<32) | get(get(id).uint32(2));
306   ETONYEK_DEBUG_MSG(("IWAParser::readUID: can not find the id zone\n"));
307   return boost::none;
308 }
309 
readUIDs(const IWAMessage & msg,unsigned field)310 std::deque<uint64_t> IWAParser::readUIDs(const IWAMessage &msg, unsigned field)
311 {
312   const std::deque<IWAMessage> &objs = msg.message(field).repeated();
313   std::deque<uint64_t> res;
314   for (const auto &obj : objs)
315   {
316     if (obj.uint32(1) && obj.uint32(2))
317       res.push_back((uint64_t(get(obj.uint32(1)))<<32) | get(obj.uint32(2)));
318   }
319   return res;
320 }
321 
readUUID(const IWAMessage & msg,const unsigned field)322 boost::optional<std::string> IWAParser::readUUID(const IWAMessage &msg, const unsigned field)
323 {
324   const IWAMessageField &mId = msg.message(field);
325   if (!mId) return boost::none;
326   auto const &id = get(mId).message(1);
327   if (id && get(id).uint32(2) && get(id).uint32(3) && get(id).uint32(4) && get(id).uint32(5))
328   {
329     std::string res;
330     bool hasValues=false;
331     for (unsigned w=2; w<=5; ++w)
332     {
333       std::stringstream s;
334       auto val=get(get(id).uint32(w));
335       if (val) hasValues=true;
336       std::uppercase(s);
337       s << std::hex << std::setfill('0') << std::setw(8) << val;
338       if (s.str().size()!=8)
339       {
340         ETONYEK_DEBUG_MSG(("IWAParser::readUUID: bad size\n"));
341         return boost::none;
342       }
343       for (size_t c=0; c<4; ++c)
344       {
345         if ((w==3 || w==4) && (c%2)==0) res+='-';
346         res+=s.str()[6-2*c];
347         res+=s.str()[7-2*c];
348       }
349     }
350     if (!hasValues) return none;
351     return res;
352   }
353   ETONYEK_DEBUG_MSG(("IWAParser::readUUID: can not find the id zone\n"));
354   return boost::none;
355 }
356 
readStroke(const IWAMessage & msg,IWORKStroke & stroke)357 void IWAParser::readStroke(const IWAMessage &msg, IWORKStroke &stroke)
358 {
359   const optional<IWORKColor> &color = readColor(msg, 1);
360   if (color)
361     stroke.m_color = get(color);
362   stroke.m_width = get(msg.float_(2));
363   if (msg.uint32(3))
364   {
365     switch (get(msg.uint32(3)))
366     {
367     default :
368       ETONYEK_DEBUG_MSG(("IWAParser::readStroke: unknown cap value: %u", get(msg.uint32(3))));
369       ETONYEK_FALLTHROUGH;
370     case 0 :
371       stroke.m_cap = IWORK_LINE_CAP_BUTT;
372       break;
373     case 1 :
374       stroke.m_cap = IWORK_LINE_CAP_ROUND;
375       break;
376     }
377   }
378   if (msg.uint32(4))
379   {
380     switch (get(msg.uint32(4)))
381     {
382     default :
383       ETONYEK_DEBUG_MSG(("IWAParser::readStroke: unknown join value: %u", get(msg.uint32(4))));
384       ETONYEK_FALLTHROUGH;
385     case 0 :
386       stroke.m_join = IWORK_LINE_JOIN_MITER;
387       break;
388     case 1 :
389       stroke.m_join = IWORK_LINE_JOIN_ROUND;
390       break;
391     }
392   }
393   if (msg.message(6))
394   {
395     stroke.m_pattern.m_type = IWORK_STROKE_TYPE_SOLID;
396     if (msg.message(6).uint32(1))
397     {
398       switch (get(msg.message(6).uint32(1)))
399       {
400       default :
401         ETONYEK_DEBUG_MSG(("IWAParser::readStroke: unknown stroke value: %u", get(msg.message(6).uint32(1))));
402         ETONYEK_FALLTHROUGH;
403       case 1:
404         stroke.m_pattern.m_type = IWORK_STROKE_TYPE_SOLID;
405         break;
406       case 0:
407         stroke.m_pattern.m_type = IWORK_STROKE_TYPE_DASHED;
408         break;
409       case 2:
410         stroke.m_pattern.m_type = IWORK_STROKE_TYPE_NONE;
411         break;
412       }
413     }
414     unsigned remaining = 0;
415     if (msg.message(6).uint32(3))
416       remaining = get(msg.message(6).uint32(3));
417     const deque<float> &elements = msg.message(6).float_(4).repeated();
418     for (auto it = elements.begin(); it != elements.end() && remaining != 0; ++it)
419       stroke.m_pattern.m_values.push_back(*it);
420   }
421   // todo: check also if there is a picture frame msg.message(8), if yes, use it as border
422 }
423 
readFill(const IWAMessage & msg,IWORKFill & fill)424 bool IWAParser::readFill(const IWAMessage &msg, IWORKFill &fill)
425 {
426   const optional<IWORKColor> &color = readColor(msg, 1);
427   if (color)
428   {
429     fill = get(color);
430     return true;
431   }
432   else if (msg.message(2))
433   {
434     IWORKGradient gradient;
435     readGradient(get(msg.message(2)), gradient);
436     fill = gradient;
437     return true;
438   }
439   else if (msg.message(3))
440   {
441     IWORKMediaContent bitmap;
442     if (msg.message(3).uint32(2))
443     {
444       switch (get(msg.message(3).uint32(2)))
445       {
446       default :
447         ETONYEK_DEBUG_MSG(("IWAParser::readFill: unknown bitmap fill type: %u", get(msg.message(3).uint32(2))));
448         ETONYEK_FALLTHROUGH;
449       case 0 :
450         bitmap.m_type = IWORK_IMAGE_TYPE_ORIGINAL_SIZE;
451         break;
452       case 1 :
453         bitmap.m_type = IWORK_IMAGE_TYPE_STRETCH;
454         break;
455       case 2 :
456         bitmap.m_type = IWORK_IMAGE_TYPE_TILE;
457         break;
458       case 3 :
459         bitmap.m_type = IWORK_IMAGE_TYPE_SCALE_TO_FILL;
460         break;
461       case 4 :
462         bitmap.m_type = IWORK_IMAGE_TYPE_SCALE_TO_FIT;
463         break;
464       }
465     }
466     bitmap.m_fillColor = readColor(get(msg.message(3)), 3);
467     bitmap.m_size = readSize(get(msg.message(3)), 4);
468     if (!bitmap.m_size) bitmap.m_size=IWORKSize(); // to do not change result from previous code
469     const optional<unsigned> &fileRef = readRef(get(msg.message(3)), 6);
470     if (fileRef)
471     {
472       // find also 16 with no file...
473       bitmap.m_data = std::make_shared<IWORKData>();
474       bitmap.m_data->m_stream = queryFile(get(fileRef));
475       if (!bitmap.m_data->m_stream && !bitmap.m_fillColor) bitmap.m_fillColor = m_index.queryFileColor(get(fileRef));
476     }
477     fill = bitmap;
478     return true;
479   }
480   return false;
481 }
482 
readGradient(const IWAMessage & msg,IWORKGradient & gradient)483 void IWAParser::readGradient(const IWAMessage &msg, IWORKGradient &gradient)
484 {
485   if (msg.uint32(1))
486   {
487     switch (get(msg.uint32(1)))
488     {
489     default :
490       ETONYEK_DEBUG_MSG(("IWAParser::readGradient: unknown gradient type: %u", get(msg.uint32(1))));
491       ETONYEK_FALLTHROUGH;
492     case 0 :
493       gradient.m_type = IWORK_GRADIENT_TYPE_LINEAR;
494       break;
495     case 1 :
496       gradient.m_type = IWORK_GRADIENT_TYPE_RADIAL;
497       break;
498     }
499   }
500   for (const auto &it : msg.message(2))
501   {
502     IWORKGradientStop stop;
503     const optional<IWORKColor> &color = readColor(it, 1);
504     if (color)
505       stop.m_color = get(color);
506     if (it.float_(2))
507       stop.m_fraction = get(it.float_(2));
508     if (it.float_(3))
509       stop.m_inflection = get(it.float_(3));
510     gradient.m_stops.push_back(stop);
511   }
512   if (msg.message(5) && msg.message(5).float_(2))
513     gradient.m_angle = get(msg.message(5).float_(2));
514 }
515 
readShadow(const IWAMessage & msg,IWORKShadow & shadow)516 void IWAParser::readShadow(const IWAMessage &msg, IWORKShadow &shadow)
517 {
518   const optional<IWORKColor> &color = readColor(msg, 1);
519   if (color)
520     shadow.m_color = get(color);
521   if (msg.float_(2))
522     shadow.m_angle = get(msg.float_(2));
523   if (msg.float_(3))
524     shadow.m_offset = get(msg.float_(3));
525   // 4. blur
526   if (msg.float_(5))
527     shadow.m_opacity = get(msg.float_(5));
528   // 6: bool true
529   if (msg.bool_(6))
530     shadow.m_visible = get(msg.bool_(6));
531   // 7: type enum 0: drop,
532 }
533 
readPadding(const IWAMessage & msg,IWORKPadding & padding)534 void IWAParser::readPadding(const IWAMessage &msg, IWORKPadding &padding)
535 {
536   padding.m_left = msg.float_(1).optional();
537   padding.m_top = msg.float_(2).optional();
538   padding.m_right = msg.float_(3).optional();
539   padding.m_bottom = msg.float_(4).optional();
540 }
541 
dispatchShape(const unsigned id)542 bool IWAParser::dispatchShape(const unsigned id)
543 {
544   const ObjectMessage msg(*this, id);
545   if (!msg)
546     return false;
547   return dispatchShapeWithMessage(get(msg), msg.getType());
548 }
549 
dispatchShapeWithMessage(const IWAMessage & msg,unsigned type)550 bool IWAParser::dispatchShapeWithMessage(const IWAMessage &msg, unsigned type)
551 {
552   switch (type)
553   {
554   case IWAObjectType::ConnectionLine :
555   case IWAObjectType::DrawableShape :
556     return parseDrawableShape(msg, type==IWAObjectType::ConnectionLine);
557   case IWAObjectType::Group :
558     return parseGroup(msg);
559   case IWAObjectType::Image :
560     return parseImage(msg);
561   case IWAObjectType::StickyNote:
562     return parseStickyNote(msg);
563   case IWAObjectType::TabularInfo :
564     return parseTabularInfo(msg);
565   default:
566   {
567     static bool first=true;
568     if (first)
569     {
570       first=false;
571       ETONYEK_DEBUG_MSG(("IWAParser::dispatchShape: find some unknown shapes, type=%d\n", int(type)));
572     }
573   }
574   }
575 
576   return false;
577 }
578 
updateGeometryUsingTextRef(unsigned id,IWORKGeometry & geometry,unsigned flags)579 void IWAParser::updateGeometryUsingTextRef(unsigned id, IWORKGeometry &geometry, unsigned flags)
580 {
581   // no horizontal auto resize or width unknown
582   if ((flags&1)==1 || geometry.m_size.m_width<=0) return;
583   const ObjectMessage msg(*this, id);
584   if (!msg)
585     return;
586   if (msg.getType()==IWAObjectType::TextRef)
587   {
588     auto textRef=readRef(get(msg),1);
589     if (!textRef)
590     {
591       ETONYEK_DEBUG_MSG(("IWAParser::updateGeometryUsingTextRef: can not find the text reference\n"));
592       return;
593     }
594     updateGeometryUsingTextRef(get(textRef),geometry, flags);
595     return;
596   }
597   if (msg.getType()!=IWAObjectType::Text)
598   {
599     ETONYEK_DEBUG_MSG(("IWAParser::updateGeometryUsingTextRef: unexpected object type, type=%d\n", int(msg.getType())));
600     return;
601   }
602   if (!get(msg).message(5))
603     return;
604   // ok, let find the paragraph style for pos=0
605   for (const auto &it : get(msg).message(5).message(1))
606   {
607     if (it.uint32(1) && get(it.uint32(1))!=0) continue;
608     const optional<unsigned> &styleRef = readRef(it, 2);
609     if (!styleRef) return;
610 
611     const IWORKStylePtr_t &style = queryParagraphStyle(get(styleRef));
612     if (!bool(style)) return;
613     if (geometry.m_size.m_width>0 && style->has<property::Alignment>())
614     {
615       switch (style->get<property::Alignment>())
616       {
617       case IWORK_ALIGNMENT_RIGHT :
618         geometry.m_position.m_x -= geometry.m_size.m_width;
619         break;
620       case IWORK_ALIGNMENT_CENTER :
621         geometry.m_position.m_x -= geometry.m_size.m_width/2.;
622         break;
623       case IWORK_ALIGNMENT_LEFT :
624       case IWORK_ALIGNMENT_JUSTIFY :
625       case IWORK_ALIGNMENT_AUTOMATIC:
626       default:
627         break;
628       }
629     }
630   }
631 }
632 
parseText(const unsigned id,bool createNoteAsFootnote,const std::function<void (unsigned,IWORKStylePtr_t)> & openPageFunction)633 bool IWAParser::parseText(const unsigned id, bool createNoteAsFootnote, const std::function<void(unsigned, IWORKStylePtr_t)> &openPageFunction)
634 {
635   assert(bool(m_currentText));
636   const ObjectMessage msg(*this, id);
637   if (!msg)
638     return false;
639   if (msg.getType()==IWAObjectType::TextRef)
640   {
641     auto textRef=readRef(get(msg),1);
642     if (!textRef)
643     {
644       ETONYEK_DEBUG_MSG(("IWAParser::parseText: can not find the text reference\n"));
645       return false;
646     }
647     return parseText(get(textRef),createNoteAsFootnote,openPageFunction);
648   }
649   if (msg.getType()!=IWAObjectType::Text)
650   {
651     ETONYEK_DEBUG_MSG(("IWAParser::parseText: unexpected object type, type=%d\n", int(msg.getType())));
652     return false;
653   }
654   std::multimap<unsigned, std::function<void(unsigned, bool &)> > attachments;
655   const IWAStringField &text = get(msg).string(3);
656   if (text || (openPageFunction && get(msg).message(17)))
657   {
658     // special case, when the document is empty and openPageFunction is
659     //          defined, we still need to retrieve the headers/footers
660     IWAText textParser(get_optional_value_or(text," "), m_langManager);
661     const size_t length = text ? get(text).size() : 1;
662 
663     if (get(msg).message(5))
664     {
665       map<unsigned, IWORKStylePtr_t> paras;
666       IWORKStylePtr_t style = make_shared<IWORKStyle>(IWORKPropertyMap(), none, none);
667       for (const auto &it : get(msg).message(5).message(1))
668       {
669         if (it.uint32(1) && (get(it.uint32(1)) < length))
670         {
671           const optional<unsigned> &styleRef = readRef(it, 2);
672           if (styleRef)
673           {
674             const IWORKStylePtr_t &newStyle = queryParagraphStyle(get(styleRef));
675             if (bool(newStyle))
676               style = newStyle;
677           }
678           paras.insert(paras.end(), make_pair(get(it.uint32(1)), style));
679         }
680       }
681       textParser.setParagraphs(paras);
682     }
683 
684     if (get(msg).message(6))
685     {
686       map<unsigned, unsigned> levels;
687       for (const auto &it : get(msg).message(6).message(1))
688       {
689         if (it.uint32(1) && (get(it.uint32(1)) < length))
690           levels.insert(levels.end(), make_pair(get(it.uint32(1)), get_optional_value_or(it.uint32(2), 0)));
691       }
692       textParser.setListLevels(levels);
693     }
694 
695     if (get(msg).message(7))
696     {
697       map<unsigned, IWORKStylePtr_t> lists;
698       for (const auto &it : get(msg).message(7).message(1))
699       {
700         if (it.uint32(1) && (get(it.uint32(1)) < length))
701         {
702           IWORKStylePtr_t style;
703           const optional<unsigned> &styleRef = readRef(it, 2);
704           if (styleRef)
705             style = queryListStyle(get(styleRef));
706           lists.insert(lists.end(), make_pair(get(it.uint32(1)), style));
707         }
708       }
709       textParser.setLists(lists);
710     }
711 
712     if (get(msg).message(8))
713     {
714       map<unsigned, IWORKStylePtr_t> spans;
715       for (const auto &it : get(msg).message(8).message(1))
716       {
717         if (it.uint32(1) && (get(it.uint32(1)) < length))
718         {
719           IWORKStylePtr_t style;
720           const optional<unsigned> &styleRef = readRef(it, 2);
721           if (styleRef)
722             style = queryCharacterStyle(get(styleRef));
723           spans.insert(spans.end(), make_pair(get(it.uint32(1)), style));
724         }
725       }
726       textParser.setSpans(spans);
727     }
728     if (get(msg).message(9))
729     {
730       map<unsigned, IWORKFieldType> fields;
731       for (const auto &it : get(msg).message(9).message(1))
732       {
733         if (!it.uint32(1))
734         {
735           ETONYEK_DEBUG_MSG(("IWAParser::parseText[9]: can not find the position\n"));
736           continue;
737         }
738         const optional<unsigned> &ref = readRef(it, 2);
739         if (!ref) continue;
740         const ObjectMessage attachment(*this, get(ref));
741         if (!attachment) continue;
742         switch (attachment.getType())
743         {
744         case IWAObjectType::NoteStart: // the first character of a note seems special, so ...
745           attachments.insert(make_pair(get(it.uint32(1)), [](unsigned, bool &ignore)
746           {
747             ignore=true;
748           }));
749           continue;
750         case IWAObjectType::PageField:
751         {
752           if (!get(attachment).message(1)) break;
753           const auto &field=get(get(attachment).message(1));
754           if (field.uint32(2))
755           {
756             switch (get(field.uint32(2)))
757             {
758             case 0:
759               attachments.insert(make_pair(get(it.uint32(1)),
760                                            [this](unsigned, bool &ignore)
761               {
762                 ignore=true;
763                 m_currentText->insertField(IWORKFieldType::IWORK_FIELD_PAGENUMBER);
764               }));
765               break;
766             case 1:
767               attachments.insert(make_pair(get(it.uint32(1)),
768                                            [this](unsigned, bool &ignore)
769               {
770                 ignore=true;
771                 m_currentText->insertField(IWORKFieldType::IWORK_FIELD_PAGECOUNT);
772               }));
773               break;
774             default:
775               ETONYEK_DEBUG_MSG(("IWAParser::parseText[9]: unknown field enum=%d\n", int(get(field.uint32(2)))));
776             }
777             continue;
778           }
779           break;
780         }
781         case IWAObjectType::ShapeField:
782           if (!openPageFunction)
783           {
784             ETONYEK_DEBUG_MSG(("IWAParser::parseText[9]: find unexpected shape's attachment at pos=%d\n", int(get(it.uint32(1)))));
785           }
786           else
787             attachments.insert(make_pair(get(it.uint32(1)),
788                                          [this,ref](unsigned, bool &ignore)
789           {
790             ignore=true;
791             parseAttachment(get(ref));
792           }));
793           continue;
794         default:
795           ETONYEK_DEBUG_MSG(("IWAParser::parseText[9]: find unknown object %d at position=%d\n", int(attachment.getType()), int(get(it.uint32(1)))));
796           continue;
797         }
798         ETONYEK_DEBUG_MSG(("IWAParser::parseText[9]: can not read the object at position=%d\n", int(get(it.uint32(1)))));
799       }
800     }
801     if (get(msg).message(11))
802     {
803       // placeholder:2031 or link:2032 or time field:2034
804       map<unsigned, string> links;
805       for (const auto &it : get(msg).message(11).message(1))
806       {
807         if (it.uint32(1))
808         {
809           string url;
810           const optional<unsigned> &linkRef = readRef(it, 2);
811           if (linkRef)
812             parseLink(get(linkRef), url);
813           links.insert(links.end(), make_pair(get(it.uint32(1)), url));
814         }
815       }
816       textParser.setLinks(links);
817     }
818     if (openPageFunction && get(msg).message(12))
819     {
820       map<unsigned, IWORKStylePtr_t> sections;
821       for (const auto &it : get(msg).message(12).message(1))
822       {
823         if (!it.uint32(1)) continue;
824         const optional<unsigned> &sectionRef = readRef(it, 2);
825         if (!sectionRef) continue;
826         const IWORKStylePtr_t &sectionStyle = querySectionStyle(get(sectionRef));
827         if (sectionStyle)
828           sections.insert(sections.end(), make_pair(get(it.uint32(1)), sectionStyle));
829       }
830       textParser.setSections(sections);
831     }
832     if (get(msg).message(16))
833     {
834       for (const auto &it : get(msg).message(16).message(1))
835       {
836         if (!it.uint32(1)) continue;
837         const optional<unsigned> &noteRef = readRef(it, 2);
838         if (!noteRef) continue;
839         const ObjectMessage noteMsg(*this, get(noteRef), IWAObjectType::Note);
840         if (!noteMsg) continue;
841         auto textRef=readRef(get(noteMsg), 2);
842         if (textRef)
843         {
844           attachments.insert(make_pair(get(it.uint32(1)),
845                                        [this,createNoteAsFootnote,textRef](unsigned, bool &ignore)
846           {
847             ignore=true;
848             auto currentText=m_currentText;
849             m_currentText = m_collector.createText(m_langManager);
850             parseText(get(textRef));
851             IWORKOutputElements elements;
852             if (createNoteAsFootnote)
853               elements.addOpenFootnote(librevenge::RVNGPropertyList());
854             else
855               elements.addOpenEndnote(librevenge::RVNGPropertyList());
856             m_currentText->draw(elements);
857             if (createNoteAsFootnote)
858               elements.addCloseFootnote();
859             else
860               elements.addCloseEndnote();
861             m_currentText=currentText;
862             m_currentText->insertInlineContent(elements);
863           }));
864         }
865         else
866         {
867           ETONYEK_DEBUG_MSG(("IWAParser::parseText[16]: can not find a note\n"));
868         }
869       }
870     }
871     if (openPageFunction && get(msg).message(17))
872     {
873       map<unsigned, IWORKStylePtr_t> pageMasters;
874       for (const auto &it : get(msg).message(17).message(1))
875       {
876         if (!it.uint32(1)) continue;
877         const optional<unsigned> &pageMasterRef = readRef(it, 2);
878         if (!pageMasterRef) continue;
879         PageMaster pageMaster;
880         parsePageMaster(get(pageMasterRef), pageMaster);
881         pageMasters.insert(pageMasters.end(), make_pair(get(it.uint32(1)), pageMaster.m_style));
882       }
883       textParser.setPageMasters(pageMasters);
884     }
885     if (get(msg).message(19))
886     {
887       map<unsigned, string> langs;
888       for (const auto &it : get(msg).message(19).message(1))
889       {
890         if (it.uint32(1))
891           langs.insert(langs.end(), make_pair(get(it.uint32(1)), get_optional_value_or(it.string(2), "")));
892       }
893       textParser.setLanguages(langs);
894     }
895     if (get(msg).message(23))
896     {
897       for (const auto &it : get(msg).message(23).message(1))
898       {
899         // no position
900         if (!it.uint32(1)) continue;
901         if (!it.message(2)) continue; // no text ref means end of comment, ...
902         auto const &commentRef = readRef(it, 2);
903         if (!commentRef) continue;
904         const ObjectMessage commentMsg(*this, get(commentRef), IWAObjectType::CommentField);
905         if (!commentMsg) continue;
906         auto textRef=readRef(get(commentMsg), 1);
907         // field 2: some small integer
908         if (textRef)
909         {
910           attachments.insert(make_pair(get(it.uint32(1)),
911                                        [this,textRef](unsigned, bool &)
912           {
913             auto currentText=m_currentText;
914             m_currentText = m_collector.createText(m_langManager);
915             parseComment(get(textRef));
916             IWORKOutputElements elements;
917             elements.addOpenComment(librevenge::RVNGPropertyList());
918             m_currentText->draw(elements);
919             elements.addCloseComment();
920             m_currentText=currentText;
921             m_currentText->insertInlineContent(elements);
922           }));
923         }
924         else
925         {
926           ETONYEK_DEBUG_MSG(("IWAParser::parseText[23]: can not find a comment\n"));
927         }
928       }
929     }
930     textParser.setAttachments(attachments);
931     textParser.parse(*m_currentText, openPageFunction);
932   }
933 
934   return true;
935 }
936 
queryStyle(const unsigned id,StyleMap_t & styleMap,StyleParseFun_t parseStyle) const937 const IWORKStylePtr_t IWAParser::queryStyle(const unsigned id, StyleMap_t &styleMap, StyleParseFun_t parseStyle) const
938 {
939   StyleMap_t::const_iterator it = styleMap.find(id);
940   if (it == styleMap.end())
941   {
942     IWORKStylePtr_t style;
943     parseStyle(id, style);
944     it = styleMap.insert(make_pair(id, style)).first;
945   }
946   assert(it != styleMap.end());
947   return it->second;
948 }
949 
queryCharacterStyle(const unsigned id) const950 const IWORKStylePtr_t IWAParser::queryCharacterStyle(const unsigned id) const
951 {
952   return queryStyle(id, m_charStyles, bind(&IWAParser::parseCharacterStyle, const_cast<IWAParser *>(this), _1, _2));
953 }
954 
queryParagraphStyle(const unsigned id) const955 const IWORKStylePtr_t IWAParser::queryParagraphStyle(const unsigned id) const
956 {
957   return queryStyle(id, m_paraStyles, bind(&IWAParser::parseParagraphStyle, const_cast<IWAParser *>(this), _1, _2));
958 }
959 
querySectionStyle(const unsigned id) const960 const IWORKStylePtr_t IWAParser::querySectionStyle(const unsigned id) const
961 {
962   return queryStyle(id, m_sectionStyles, bind(&IWAParser::parseSectionStyle, const_cast<IWAParser *>(this), _1, _2));
963 }
964 
queryGraphicStyle(const unsigned id) const965 const IWORKStylePtr_t IWAParser::queryGraphicStyle(const unsigned id) const
966 {
967   return queryStyle(id, m_graphicStyles, bind(&IWAParser::parseGraphicStyle, const_cast<IWAParser *>(this), _1, _2));
968 }
969 
queryMediaStyle(const unsigned id) const970 const IWORKStylePtr_t IWAParser::queryMediaStyle(const unsigned id) const
971 {
972   return queryStyle(id, m_mediaStyles, bind(&IWAParser::parseMediaStyle, const_cast<IWAParser *>(this), _1, _2));
973 }
974 
queryCellStyle(const unsigned id) const975 const IWORKStylePtr_t IWAParser::queryCellStyle(const unsigned id) const
976 {
977   return queryStyle(id, m_cellStyles, bind(&IWAParser::parseCellStyle, const_cast<IWAParser *>(this), _1, _2));
978 }
979 
queryTableStyle(const unsigned id) const980 const IWORKStylePtr_t IWAParser::queryTableStyle(const unsigned id) const
981 {
982   return queryStyle(id, m_tableStyles, bind(&IWAParser::parseTableStyle, const_cast<IWAParser *>(this), _1, _2));
983 }
984 
queryListStyle(const unsigned id) const985 const IWORKStylePtr_t IWAParser::queryListStyle(const unsigned id) const
986 {
987   return queryStyle(id, m_tableStyles, bind(&IWAParser::parseListStyle, const_cast<IWAParser *>(this), _1, _2));
988 }
989 
parseAttachment(const unsigned id)990 bool IWAParser::parseAttachment(const unsigned id)
991 {
992   auto collector=dynamic_cast<PAGCollector *>(&m_collector);
993   if (!collector)
994   {
995     ETONYEK_DEBUG_MSG(("IWAParser::parseAttachment: can not find the page collector\n"));
996     return false;
997   }
998   const ObjectMessage msg(*this, id, IWAObjectType::ShapeField);
999   if (!msg)
1000   {
1001     ETONYEK_DEBUG_MSG(("IWAParser::parseAttachment: can not find the attachment\n"));
1002     return false;
1003   }
1004   auto objectRef=readRef(get(msg),1);
1005   if (!objectRef)
1006   {
1007     ETONYEK_DEBUG_MSG(("IWAParser::parseAttachment: can not find the attached object\n"));
1008     return false;
1009   }
1010 
1011   IWORKPosition position;
1012   // 2: false
1013   auto x=get(msg).float_(3);
1014   // 4: false
1015   auto y=get(msg).float_(5);
1016   if (x && !std::isnan(get(x))) position.m_x=get(x);
1017   else
1018   {
1019     ETONYEK_DEBUG_MSG(("IWAParser::parseAttachment: can not find the x's position\n"));
1020   }
1021   if (y && !std::isnan(get(y))) position.m_y=get(y);
1022   else
1023   {
1024     ETONYEK_DEBUG_MSG(("IWAParser::parseAttachment: can not find the y's position\n"));
1025   }
1026 
1027   const ObjectMessage object(*this, get(objectRef));
1028   if (!object)
1029   {
1030     ETONYEK_DEBUG_MSG(("IWAParser::parseAttachment: can not find the attached object[II]\n"));
1031     return false;
1032   }
1033   auto currentText=m_currentText;
1034   m_currentText.reset();
1035   collector->startLevel();
1036   collector->startAttachments();
1037   collector->startAttachment();
1038   collector->collectAttachmentPosition(position);
1039   collector->getOutputManager().push();
1040 
1041   bool ok=false, sendInBlock=false;
1042   switch (object.getType())
1043   {
1044   case IWAObjectType::ConnectionLine :
1045   case IWAObjectType::DrawableShape :
1046     ok=parseDrawableShape(get(object), object.getType()==IWAObjectType::ConnectionLine);
1047     break;
1048   case IWAObjectType::Group :
1049     ok=parseGroup(get(object));
1050     break;
1051   case IWAObjectType::Image :
1052     ok=parseImage(get(object));
1053     break;
1054   case IWAObjectType::TabularInfo :
1055     sendInBlock=true;
1056     collector->collectAttachmentPosition(IWORKPosition());
1057     ok=parseTabularInfo(get(object));
1058     break;
1059   default:
1060   {
1061     static bool first=true;
1062     if (first)
1063     {
1064       first=false;
1065       ETONYEK_DEBUG_MSG(("IWAParser::parseAttachment: unknown object type\n"));
1066     }
1067   }
1068   }
1069   auto cId=collector->getOutputManager().save();
1070   auto content = collector->getOutputManager().get(cId);
1071   collector->getOutputManager().pop();
1072   collector->endAttachment();
1073   collector->endAttachments();
1074   collector->endLevel();
1075 
1076   if (ok)
1077   {
1078     if (sendInBlock)
1079       currentText->insertBlockContent(content);
1080     else
1081       currentText->insertInlineContent(content);
1082   }
1083   m_currentText=currentText;
1084   return ok;
1085 }
1086 
parseArrowProperties(const IWAMessage & arrow,IWORKPropertyMap & props,bool headArrow)1087 bool IWAParser::parseArrowProperties(const IWAMessage &arrow, IWORKPropertyMap &props, bool headArrow)
1088 {
1089   IWORKMarker marker;
1090   bool hasPath=false;
1091   if (arrow.message(1))
1092   {
1093     const auto &arrowProp=get(arrow.message(1));
1094     IWORKPathPtr_t path;
1095     if (parsePath(arrowProp, path) && path && !path->str().empty())
1096     {
1097       marker.m_path=path->str();
1098       hasPath=true;
1099     }
1100   }
1101   marker.m_endPoint=readPosition(arrow,3);
1102   // 2: a bool, 4: a bool, 5: name
1103   if (headArrow)
1104   {
1105     if (hasPath)
1106       props.put<property::HeadLineEnd>(marker);
1107     else
1108       props.clear<property::HeadLineEnd>();
1109   }
1110   else
1111   {
1112     if (hasPath)
1113       props.put<property::TailLineEnd>(marker);
1114     else
1115       props.clear<property::TailLineEnd>();
1116   }
1117   return true;
1118 }
1119 
parsePath(const IWAMessage & msg,IWORKPathPtr_t & path)1120 bool IWAParser::parsePath(const IWAMessage &msg, IWORKPathPtr_t &path)
1121 {
1122   const deque<IWAMessage> &elements = msg.message(1).repeated();
1123   bool closed = false;
1124   bool closingMove = false;
1125   path.reset(new IWORKPath());
1126   for (auto it : elements)
1127   {
1128     const auto &type = it.uint32(1).optional();
1129     if (!type)
1130     {
1131       ETONYEK_DEBUG_MSG(("IWAParser::parsePath: can not read the type\n"));
1132       continue;
1133     }
1134     if (closed && closingMove)
1135     {
1136       ETONYEK_DEBUG_MSG(("IWAParser::parsePath: unexpected element %c after the closing move\n", get(type)));
1137       break;
1138     }
1139     switch (get(type))
1140     {
1141     case 1 :
1142       if (closed)
1143       {
1144         closingMove = true;
1145         break;
1146       }
1147       ETONYEK_FALLTHROUGH;
1148     case 2 :
1149     {
1150       const optional<IWORKPosition> &coords = readPosition(it, 2);
1151       if (!coords)
1152       {
1153         ETONYEK_DEBUG_MSG(("IWAParser::parsePath: missing coordinates for %c element\n", get(type) == 1 ? 'M' : 'L'));
1154         return false;
1155       }
1156       if (get(type) == 1)
1157         path->appendMoveTo(get(coords).m_x, get(coords).m_y);
1158       else
1159       {
1160         if (path->empty())
1161         {
1162           ETONYEK_DEBUG_MSG(("IWAParser::parsePath: missing prior MoveTo subsequent LineTo\n"));
1163           return false;
1164         }
1165         path->appendLineTo(get(coords).m_x, get(coords).m_y);
1166       }
1167       break;
1168     }
1169     case 4 :
1170     {
1171       if (it.message(2))
1172       {
1173         const std::deque<IWAMessage> &positions = it.message(2).repeated();
1174         if (positions.size() >= 3)
1175         {
1176           if (positions.size() > 3)
1177           {
1178             ETONYEK_DEBUG_MSG(("IWAParser::parsePath: a curve has got %u control coords\n", unsigned(positions.size())));
1179           }
1180           const optional<float> &x = positions[0].float_(1).optional();
1181           const optional<float> &y = positions[0].float_(2).optional();
1182           const optional<float> &x1 = positions[1].float_(1).optional();
1183           const optional<float> &y1 = positions[1].float_(2).optional();
1184           const optional<float> &x2 = positions[2].float_(1).optional();
1185           const optional<float> &y2 = positions[2].float_(2).optional();
1186           path->appendCCurveTo(get_optional_value_or(x, 0), get_optional_value_or(y, 0),
1187                                get_optional_value_or(x1, 0), get_optional_value_or(y1, 0),
1188                                get_optional_value_or(x2, 0), get_optional_value_or(y2, 0));
1189         }
1190         else
1191         {
1192           ETONYEK_DEBUG_MSG(("IWAParser::parsePath: %u is not enough coords for a curve\n", unsigned(positions.size())));
1193           return false;
1194         }
1195       }
1196       break;
1197     }
1198     case 5 :
1199       path->appendClose();
1200       closed = true;
1201       break;
1202     default :
1203       ETONYEK_DEBUG_MSG(("IWAParser::parsePath: unknown bezier path element type %u\n", get(type)));
1204       return false;
1205     }
1206   }
1207   return true;
1208 }
1209 
parseStickyNote(const IWAMessage &)1210 bool IWAParser::parseStickyNote(const IWAMessage &/*msg*/)
1211 {
1212   ETONYEK_DEBUG_MSG(("IWAParser::parseStickyNote: not implemented\n"));
1213   return false;
1214 }
1215 
parseDrawableShape(const IWAMessage & msg,bool isConnectionLine)1216 bool IWAParser::parseDrawableShape(const IWAMessage &msg, bool isConnectionLine)
1217 {
1218   m_collector.startLevel();
1219 
1220   const optional<IWAMessage> &shape = msg.message(1).optional();
1221   const optional<unsigned> &textRef = readRef(msg, 2);
1222   boost::optional<unsigned> resizeFlags;
1223 
1224   if (shape)
1225   {
1226     const optional<IWAMessage> &placement = get(shape).message(1).optional();
1227     IWORKStylePtr_t style;
1228     const optional<unsigned> styleRef = readRef(get(shape), 2);
1229     if (styleRef)
1230       style=queryGraphicStyle(get(styleRef));
1231     const optional<IWAMessage> &path = get(shape).message(3).optional();
1232     if (placement)
1233     {
1234       IWORKGeometryPtr_t geometry;
1235       parseShapePlacement(get(placement), geometry, resizeFlags);
1236       if (geometry && (geometry->m_naturalSize.m_width<=0 || geometry->m_naturalSize.m_height<=0) && path)
1237       {
1238         // try to retrieve the shape's size in the path
1239         std::map<unsigned,unsigned> const cIdToSizeId= { { 3, 3}, { 4, 3}, {5, 2}, { 6, 1}, { 8, 2} };
1240         for (auto const &it : cIdToSizeId)
1241         {
1242           if (!get(path).message(it.first)) continue;
1243           auto const &pathSize=readSize(get(get(path).message(it.first)), it.second);
1244           if (!pathSize || pathSize->m_width<=0 || pathSize->m_height<=0) continue;
1245           geometry->m_naturalSize=geometry->m_size=get(pathSize);
1246           break;
1247         }
1248       }
1249       if (geometry && resizeFlags && (get(resizeFlags) &1)==0 && textRef) // correct horizontal position
1250         updateGeometryUsingTextRef(get(textRef), *geometry, get(resizeFlags));
1251       if (geometry && resizeFlags && (get(resizeFlags)&2)==0 && geometry->m_size.m_height>0 && style && style->has<property::VerticalAlignment>())
1252       {
1253         // correct vertical position
1254         switch (style->get<property::VerticalAlignment>())
1255         {
1256         case IWORK_VERTICAL_ALIGNMENT_MIDDLE:
1257           geometry->m_position.m_y -= geometry->m_size.m_height/2.;
1258           break;
1259         case IWORK_VERTICAL_ALIGNMENT_BOTTOM:
1260           geometry->m_position.m_y -= geometry->m_size.m_height;
1261           break;
1262         case IWORK_VERTICAL_ALIGNMENT_TOP:
1263         default:
1264           break;
1265         }
1266       }
1267 
1268       m_collector.collectGeometry(geometry);
1269     }
1270 
1271     // look for arrow Keynote 6
1272     if (get(shape).message(4) || get(shape).message(5))
1273     {
1274       if (!style)
1275         style=std::make_shared<IWORKStyle>(IWORKPropertyMap(),boost::none, boost::none);
1276       for (size_t st=0; st<2; ++st)
1277       {
1278         if (!get(shape).message(st+4)) continue;
1279         parseArrowProperties(get(get(shape).message(st+4)),style->getPropertyMap(),st==0);
1280       }
1281     }
1282     if (style)
1283       m_collector.setGraphicStyle(style);
1284 
1285     if (path)
1286     {
1287       if (get(path).message(3)) // point path
1288       {
1289         const IWAMessage &pointPath = get(path).message(3).get();
1290         const optional<unsigned> &type = pointPath.uint32(1).optional();
1291         const optional<IWORKPosition> &point = readPosition(pointPath, 2);
1292         const optional<IWORKSize> &size = readSize(pointPath, 3);
1293         if (type && point && size)
1294         {
1295           switch (get(type))
1296           {
1297           case 1 :
1298           case 10 :
1299             m_collector.collectArrowPath(get(size), get(point).m_x, get(point).m_y, get(type) == 10);
1300             break;
1301           case 100 :
1302             m_collector.collectStarPath(get(size), unsigned(get(point).m_x+0.4), get(point).m_y);
1303             break;
1304           default :
1305             ETONYEK_DEBUG_MSG(("IWAParser::parseDrawableShape: unknown point path type %u\n", get(type)));
1306             break;
1307           }
1308         }
1309       }
1310       else if (get(path).message(4)) // scalar path
1311       {
1312         const IWAMessage &scalarPath = get(path).message(4).get();
1313         const optional<unsigned> &type = scalarPath.uint32(1).optional();
1314         const optional<float> &value = scalarPath.float_(2).optional();
1315         const optional<IWORKSize> &size = readSize(scalarPath, 3);
1316         if (type && value && size)
1317         {
1318           switch (get(type))
1319           {
1320           case 0 :
1321             m_collector.collectRoundedRectanglePath(get(size), get(value));
1322             break;
1323           case 1 :
1324             m_collector.collectPolygonPath(get(size), unsigned(get(value)+0.4));
1325             break;
1326           default :
1327             ETONYEK_DEBUG_MSG(("IWAParser::parseDrawableShape: unknown scalar path type %u\n", get(type)));
1328             break;
1329           }
1330         }
1331       }
1332       else if (get(path).message(5))
1333       {
1334         auto const &bezier = get(path).message(5).get().message(3).optional();
1335         if (bezier)
1336         {
1337           IWORKPathPtr_t bezierPath;
1338           if (parsePath(get(bezier),bezierPath))
1339           {
1340             const optional<IWORKSize> &size = readSize(get(get(path).message(5)), 2);
1341             if (size)
1342             {
1343               double x[2]= {0,0}, y[2]= {0,0};
1344               if (bezierPath)
1345                 bezierPath->computeBoundingBox(x[0], y[0], x[1], y[1]);
1346               // if we can not use the bounding box, assume tha the path is in unit area
1347               *bezierPath *= transformations::scale(get(size).m_width / (x[1]>x[0] ? x[1]-x[0] : 100), get(size).m_height / (y[1]>y[0] ? y[1]-y[0] : 100));
1348             }
1349             m_collector.collectBezier(bezierPath);
1350             m_collector.collectBezierPath();
1351           }
1352         }
1353       }
1354       else if (get(path).message(6)) // callout2 path
1355       {
1356         const IWAMessage &callout2Path = get(path).message(6).get();
1357         const optional<IWORKSize> &size = readSize(callout2Path, 1);
1358         const optional<IWORKPosition> &tailPos = readPosition(callout2Path, 2);
1359         const optional<float> &tailSize = callout2Path.float_(3).optional();
1360         if (size && tailPos && tailSize)
1361         {
1362           const optional<float> &cornerRadius = callout2Path.float_(4).optional();
1363           const optional<bool> &tailAtCenter = callout2Path.bool_(5).optional();
1364           m_collector.collectCalloutPath(get(size), get_optional_value_or(cornerRadius, 0),
1365                                          get(tailSize), get(tailPos).m_x, get(tailPos).m_y,
1366                                          get_optional_value_or(tailAtCenter, false));
1367         }
1368       }
1369       else if (get(path).message(7))
1370       {
1371         auto rootMsg=get(get(path).message(7)).message(1).optional();
1372         if (rootMsg)
1373         {
1374           IWORKConnectionPath cPath;
1375           cPath.m_size=readSize(get(rootMsg), 2);
1376           cPath.m_isSpline=!get_optional_value_or(get(get(path).message(7)).bool_(2),false);
1377           auto const &bezier = rootMsg.get().message(3).optional();
1378           if (bezier)
1379           {
1380             const deque<IWAMessage> &elements = get(bezier).message(1).repeated();
1381             int pos=0;
1382             for (auto it : elements)
1383             {
1384               // normally first point (type 1) followed by 2 points (type 2)
1385               // const auto &type = it.uint32(1).optional();
1386               if (pos>=3)
1387               {
1388                 ETONYEK_DEBUG_MSG(("IWAParser::parseDrawableShape[connection]: oops find unexpected number of points\n"));
1389                 break;
1390               }
1391               cPath.m_positions[pos++]=readPosition(it, 2);
1392             }
1393             if (pos==3)
1394             {
1395               m_collector.collectConnectionPath(cPath);
1396             }
1397             else
1398             {
1399               ETONYEK_DEBUG_MSG(("IWAParser::parseDrawableShape[connection]: oops find unexpected number of points\n"));
1400             }
1401           }
1402           else
1403           {
1404             ETONYEK_DEBUG_MSG(("IWAParser::parseDrawableShape[connection]: can not find the points zone\n"));
1405           }
1406         }
1407         else
1408         {
1409           ETONYEK_DEBUG_MSG(("IWAParser::parseDrawableShape[connection]: can not find the root bezier zone\n"));
1410         }
1411       }
1412       else if (get(path).message(8)) // editable path
1413       {
1414         const IWAMessageField &pathPoints = get(get(path).message(8)).message(1);
1415         if (pathPoints && !pathPoints.message(1).empty())
1416         {
1417           const IWORKPathPtr_t editablePath(new IWORKPath());
1418           const IWAMessageField &points = pathPoints.message(1);
1419           std::vector<IWORKPosition> positions;
1420           // cubic bezier patch, [prev pt dir], pt, [next pt dir]
1421           for (auto it : points)
1422           {
1423             const optional<IWORKPosition> &point1 = readPosition(it, 1);
1424             const optional<IWORKPosition> &point2 = readPosition(it, 2);
1425             const optional<IWORKPosition> &point3 = readPosition(it, 3);
1426             // [4 type: {1: line, 3:curve}
1427             if (!point2)
1428             {
1429               ETONYEK_DEBUG_MSG(("IWAParser::parseDrawableShape: no control points for point2\n"));
1430               continue;
1431             }
1432             positions.push_back(get_optional_value_or(point1, get(point2)));
1433             positions.push_back(get(point2));
1434             positions.push_back(get_optional_value_or(point3, get(point2)));
1435           }
1436           size_t nbPt=positions.size()/3;
1437           if (nbPt<=1)
1438           {
1439             ETONYEK_DEBUG_MSG(("IWAParser::parseDrawableShape: find only %d points\n", int(nbPt)));
1440           }
1441           else
1442           {
1443             editablePath->appendMoveTo(positions[1].m_x, positions[1].m_y);
1444             bool isClosed= get_optional_value_or(pathPoints.bool_(2),false);
1445             for (size_t i=0; i<nbPt; ++i)
1446             {
1447               if (i+1==nbPt && !isClosed) break;
1448               auto const &prevPoint=positions[3*i+1];
1449               auto const &pt1=positions[3*i+2];
1450               auto const &pt2=positions[3*((i+1)%nbPt)];
1451               auto const &pt3=positions[3*((i+1)%nbPt)+1];
1452               if (samePoint(prevPoint,pt1) && samePoint(pt2,pt3))
1453                 editablePath->appendLineTo(pt3.m_x, pt3.m_y);
1454               else
1455                 editablePath->appendCCurveTo(pt1.m_x,pt1.m_y, pt2.m_x,pt2.m_y, pt3.m_x,pt3.m_y);
1456             }
1457             if (isClosed)
1458               editablePath->appendClose();
1459             m_collector.collectBezier(editablePath);
1460             m_collector.collectBezierPath();
1461           }
1462         }
1463       }
1464     }
1465   }
1466   bool hasText=false;
1467   if (!isConnectionLine && textRef)
1468   {
1469     m_currentText = m_collector.createText(m_langManager, true);
1470     parseText(get(textRef));
1471     if (!m_currentText->empty())
1472     {
1473       hasText=true;
1474       m_collector.collectText(m_currentText);
1475     }
1476   }
1477 
1478   if (shape || hasText)
1479     m_collector.collectShape(boost::none, resizeFlags);
1480   m_currentText.reset();
1481 
1482   m_collector.endLevel();
1483 
1484   return true;
1485 }
1486 
parseGroup(const IWAMessage & msg)1487 bool IWAParser::parseGroup(const IWAMessage &msg)
1488 {
1489   m_collector.startLevel();
1490   if (msg.message(1))
1491     parseShapePlacement(get(msg.message(1)));
1492   if (!msg.message(2).empty())
1493   {
1494     m_collector.startGroup();
1495     m_collector.openGroup();
1496     const deque<unsigned> &shapeRefs = readRefs(msg, 2);
1497     std::for_each(shapeRefs.begin(), shapeRefs.end(), bind(&IWAParser::dispatchShape, this, _1));
1498     m_collector.closeGroup();
1499     m_collector.endGroup();
1500   }
1501   m_collector.endLevel();
1502 
1503   return true;
1504 }
1505 
parseShapePlacement(const IWAMessage & msg,IWORKGeometryPtr_t & geometry,boost::optional<unsigned> & flags)1506 bool IWAParser::parseShapePlacement(const IWAMessage &msg, IWORKGeometryPtr_t &geometry, boost::optional<unsigned> &flags)
1507 {
1508   geometry = make_shared<IWORKGeometry>();
1509   flags=3; // no auto resize
1510 
1511   const optional<IWAMessage> &g = msg.message(1).optional();
1512   if (g)
1513   {
1514     const optional<IWORKPosition> &pos = readPosition(get(g), 1);
1515     if (pos)
1516       geometry->m_position = get(pos);
1517     const optional<IWORKSize> &size = readSize(get(g), 2);
1518     if (size)
1519       geometry->m_naturalSize = geometry->m_size = get(size);
1520     if (get(g).uint32(3))
1521     {
1522       flags=get(get(g).uint32(3));
1523       // flags&1 : horizontal position is fixed
1524       // flags&2 : vertical position is fixed
1525       if (get(flags)&4) // horizontal flip
1526         geometry->m_horizontalFlip = true;
1527       if (get(flags)&0xFFF8)
1528       {
1529         ETONYEK_DEBUG_MSG(("IWAParser::parseShapePlacement: unknown transformation %u\n", get(flags)));
1530       }
1531     }
1532     if (get(g).float_(4))
1533       geometry->m_angle = -deg2rad(get(get(g).float_(4)));
1534   }
1535   geometry->m_aspectRatioLocked = msg.bool_(7).optional();
1536 
1537   return true;
1538 }
1539 
parseShapePlacement(const IWAMessage & msg)1540 bool IWAParser::parseShapePlacement(const IWAMessage &msg)
1541 {
1542   IWORKGeometryPtr_t geometry;
1543   boost::optional<unsigned> flags;
1544   const bool retval = parseShapePlacement(msg, geometry, flags);
1545   m_collector.collectGeometry(geometry);
1546   return retval;
1547 }
1548 
parseMask(unsigned id,IWORKGeometryPtr_t & geometry,IWORKPathPtr_t &)1549 void IWAParser::parseMask(unsigned id, IWORKGeometryPtr_t &geometry, IWORKPathPtr_t &/*path*/)
1550 {
1551   const ObjectMessage msg(*this, id, IWAObjectType::Mask);
1552   if (!msg)
1553     return;
1554   if (get(msg).message(1))
1555   {
1556     boost::optional<unsigned> flags;
1557     parseShapePlacement(get(get(msg).message(1)), geometry, flags);
1558   }
1559   // if (get(msg).message(2)) same code as parseDrawableShape
1560 }
1561 
parseObjectIndex()1562 void IWAParser::parseObjectIndex()
1563 {
1564   m_index.parse();
1565 }
1566 
parseCharacterStyle(const unsigned id,IWORKStylePtr_t & style)1567 void IWAParser::parseCharacterStyle(const unsigned id, IWORKStylePtr_t &style)
1568 {
1569   const ObjectMessage msg(*this, id, IWAObjectType::CharacterStyle);
1570   if (!msg)
1571     return;
1572 
1573   optional<string> name;
1574   IWORKStylePtr_t parent;
1575   const IWAMessageField &styleInfo = get(msg).message(1);
1576   if (styleInfo)
1577   {
1578     name = styleInfo.string(2).optional();
1579     const optional<unsigned> &parentRef = readRef(get(styleInfo), 3);
1580     if (parentRef)
1581       parent = queryCharacterStyle(get(parentRef));
1582   }
1583 
1584   IWORKPropertyMap props;
1585   if (get(msg).message(11))
1586     parseCharacterProperties(get(get(msg).message(11)), props);
1587 
1588   style = std::make_shared<IWORKStyle>(props, name, parent);
1589 }
1590 
parseParagraphStyle(const unsigned id,IWORKStylePtr_t & style)1591 void IWAParser::parseParagraphStyle(const unsigned id, IWORKStylePtr_t &style)
1592 {
1593   const ObjectMessage msg(*this, id, IWAObjectType::ParagraphStyle);
1594   if (!msg)
1595     return;
1596 
1597   optional<string> name;
1598   IWORKStylePtr_t parent;
1599   const IWAMessageField &styleInfo = get(msg).message(1);
1600   if (styleInfo)
1601   {
1602     name = styleInfo.string(2).optional();
1603     const optional<unsigned> &parentRef = readRef(get(styleInfo), 3);
1604     if (parentRef)
1605       parent = queryParagraphStyle(get(parentRef));
1606   }
1607 
1608   IWORKPropertyMap props;
1609   if (get(msg).message(11))
1610     parseCharacterProperties(get(get(msg).message(11)), props);
1611   if (get(msg).message(12))
1612   {
1613     const IWAMessage &paraProps = get(get(msg).message(12));
1614     using namespace property;
1615 
1616     if (paraProps.uint32(1))
1617       putEnum<Alignment>(props, get(paraProps.uint32(1)));
1618     const optional<IWORKColor> &fillColor = readColor(paraProps, 6);
1619     if (fillColor)
1620       props.put<ParagraphFill>(get(fillColor));
1621     if (paraProps.float_(7))
1622       props.put<FirstLineIndent>(get(paraProps.float_(7)));
1623     if (paraProps.bool_(8))
1624       props.put<Hyphenate>(get(paraProps.bool_(8)));
1625     if (paraProps.bool_(9))
1626       props.put<KeepLinesTogether>(get(paraProps.bool_(9)));
1627     if (paraProps.bool_(10))
1628       props.put<KeepWithNext>(get(paraProps.bool_(10)));
1629     if (paraProps.float_(11))
1630       props.put<LeftIndent>(get(paraProps.float_(11)));
1631     if (paraProps.message(13))
1632     {
1633       auto const &lineSpace=paraProps.message(13).float_(2);
1634       if (lineSpace)
1635       {
1636         auto const &type=paraProps.message(13).uint32(1).optional();
1637         if (!type) // in line
1638           props.put<LineSpacing>(IWORKLineSpacing(get(lineSpace), true));
1639         else if (get(type)==1)   // at least in point
1640         {
1641           IWORKLineSpacing spacing(get(lineSpace), false);
1642           spacing.m_atLeast=true;
1643           props.put<LineSpacing>(spacing);
1644         }
1645         else if (get(type)==2) // in point
1646           props.put<LineSpacing>(IWORKLineSpacing(get(lineSpace), false));
1647         else if (get(type)==4) // between in point, transform in percent (and assume 12pt)
1648           props.put<LineSpacing>(IWORKLineSpacing(1.+get(lineSpace)/12., true));
1649         else   // unknown, use heuristic
1650         {
1651           props.put<LineSpacing>(IWORKLineSpacing(get(lineSpace), get(lineSpace)<3));
1652           ETONYEK_DEBUG_MSG(("IWAParser::parseParagraphStyle: unknown type %u\n", get(type)));
1653         }
1654       }
1655       // TODO what is paraProps.message(13).float_(3);
1656     }
1657     if (paraProps.bool_(14))
1658       props.put<PageBreakBefore>(get(paraProps.bool_(14)));
1659     if (paraProps.uint32(15))
1660       putEnum<ParagraphBorderType>(props, get(paraProps.uint32(15)));
1661     if (paraProps.float_(19))
1662       props.put<RightIndent>(get(paraProps.float_(19)));
1663     if (paraProps.float_(20))
1664       props.put<SpaceAfter>(get(paraProps.float_(20)));
1665     if (paraProps.float_(21))
1666       props.put<SpaceBefore>(get(paraProps.float_(21)));
1667     if (paraProps.message(25))
1668     {
1669       IWORKTabStops_t tabs;
1670       const IWAMessageField &tabStops = paraProps.message(25).message(1);
1671       for (const auto &tabStop : tabStops)
1672       {
1673         if (tabStop.float_(1))
1674           tabs.push_back(IWORKTabStop(IWORK_TABULATION_LEFT, get(tabStop.float_(1))));
1675       }
1676     }
1677     if (paraProps.bool_(26))
1678       props.put<WidowControl>(get(paraProps.bool_(26)));
1679     if (paraProps.message(32))
1680     {
1681       IWORKStroke stroke;
1682       readStroke(get(paraProps.message(32)), stroke);
1683       props.put<ParagraphStroke>(stroke);
1684     }
1685   }
1686 
1687   style = std::make_shared<IWORKStyle>(props, name, parent);
1688 }
1689 
parseSectionStyle(const unsigned id,IWORKStylePtr_t & style)1690 void IWAParser::parseSectionStyle(const unsigned id, IWORKStylePtr_t &style)
1691 {
1692   const ObjectMessage msg(*this, id, IWAObjectType::SectionStyle);
1693   if (!msg)
1694     return;
1695 
1696   optional<string> name;
1697   IWORKStylePtr_t parent;
1698   const IWAMessageField &styleInfo = get(msg).message(1);
1699   if (styleInfo)
1700   {
1701     name = styleInfo.string(2).optional();
1702     const optional<unsigned> &parentRef = readRef(get(styleInfo), 3);
1703     if (parentRef)
1704       parent = querySectionStyle(get(parentRef));
1705   }
1706   // 10: int 2 or 9
1707   //
1708   IWORKPropertyMap props;
1709   if (get(msg).message(11))
1710     parseColumnsProperties(get(get(msg).message(11)), props);
1711   style = std::make_shared<IWORKStyle>(props, name, parent);
1712 }
1713 
parseGraphicStyle(const unsigned id,IWORKStylePtr_t & style)1714 void IWAParser::parseGraphicStyle(const unsigned id, IWORKStylePtr_t &style)
1715 {
1716   const ObjectMessage msg(*this, id, IWAObjectType::GraphicStyle);
1717   if (!msg)
1718     return;
1719   optional<string> name;
1720   IWORKStylePtr_t parent;
1721   IWORKPropertyMap props;
1722 
1723   using namespace property;
1724 
1725   if (get(msg).message(1))
1726   {
1727     const IWAMessageField &styleInfo = get(msg).message(1).message(1);
1728     if (styleInfo)
1729     {
1730       name = styleInfo.string(2).optional();
1731       const optional<unsigned> &parentRef = readRef(get(styleInfo), 3);
1732       if (parentRef)
1733         parent = queryGraphicStyle(get(parentRef));
1734     }
1735 
1736     const IWAMessageField &styleProps = get(msg).message(1).message(11);
1737     if (styleProps)
1738     {
1739       if (styleProps.message(1))
1740       {
1741         IWORKFill fill;
1742         if (readFill(get(styleProps.message(1)), fill))
1743           props.put<Fill>(fill);
1744         else
1745           props.clear<Fill>();
1746       }
1747       if (styleProps.message(2))
1748       {
1749         IWORKStroke stroke;
1750         readStroke(get(styleProps.message(2)), stroke);
1751         props.put<Stroke>(stroke);
1752       }
1753       if (styleProps.float_(3))
1754         props.put<Opacity>(get(styleProps.float_(3)));
1755       if (styleProps.message(4))
1756       {
1757         IWORKShadow shadow;
1758         readShadow(get(styleProps.message(4)),shadow);
1759         props.put<Shadow>(shadow);
1760       }
1761       for (size_t st=0; st<2; ++st)
1762       {
1763         if (!get(styleProps).message(st+6)) continue;
1764         parseArrowProperties(get(get(styleProps).message(st+6)),props, st==0);
1765       }
1766     }
1767   }
1768 
1769   if (get(msg).message(11))
1770   {
1771     const IWAMessageField &layout = get(msg).message(11);
1772     auto vAlign=layout.uint32(2);
1773     if (vAlign)
1774     {
1775       IWORKVerticalAlignment const aligns[]=
1776       {IWORK_VERTICAL_ALIGNMENT_TOP, IWORK_VERTICAL_ALIGNMENT_MIDDLE,IWORK_VERTICAL_ALIGNMENT_BOTTOM};
1777       if (get(vAlign) < ETONYEK_NUM_ELEMENTS(aligns))
1778       {
1779         props.put<VerticalAlignment>(aligns[get(vAlign)]);
1780       }
1781       else
1782       {
1783         ETONYEK_DEBUG_MSG(("IWAParser::parseGraphicStyle: unknown vAlign %u\n", get(vAlign)));
1784       }
1785     }
1786     if (get(layout).message(6))
1787     {
1788       IWORKPadding padding;
1789       readPadding(get(get(layout).message(6)), padding);
1790       props.put<LayoutMargins>(padding);
1791     }
1792     const optional<unsigned> &paraRef = readRef(get(layout), 10);
1793     if (paraRef)
1794     {
1795       const IWORKStylePtr_t &paraStyle = queryParagraphStyle(get(paraRef));
1796       if (paraStyle)
1797         props.put<LayoutParagraphStyle>(paraStyle);
1798     }
1799 
1800     // TODO: other layout props: 1: shrink text, 4: columns
1801   }
1802 
1803   style = std::make_shared<IWORKStyle>(props, name, parent);
1804 }
1805 
parseMediaStyle(const unsigned id,IWORKStylePtr_t & style)1806 void IWAParser::parseMediaStyle(const unsigned id, IWORKStylePtr_t &style)
1807 {
1808   const ObjectMessage msg(*this, id, IWAObjectType::MediaStyle);
1809   if (!msg)
1810     return;
1811   optional<string> name;
1812   IWORKStylePtr_t parent;
1813   IWORKPropertyMap props;
1814 
1815   using namespace property;
1816   const IWAMessageField &styleInfo = get(msg).message(1);
1817   if (styleInfo)
1818   {
1819     name = styleInfo.string(2).optional();
1820     const optional<unsigned> &parentRef = readRef(get(styleInfo), 3);
1821     if (parentRef)
1822       parent = queryMediaStyle(get(parentRef));
1823   }
1824   const IWAMessageField &styleProps = get(msg).message(11);
1825   if (styleProps)
1826   {
1827     if (styleProps.message(1))
1828     {
1829       IWORKStroke stroke;
1830       readStroke(get(styleProps.message(1)), stroke);
1831       props.put<Stroke>(stroke);
1832     }
1833     if (styleProps.float_(2))
1834       props.put<Opacity>(get(styleProps.float_(2)));
1835     if (styleProps.message(3))
1836     {
1837       IWORKShadow shadow;
1838       readShadow(get(styleProps.message(3)),shadow);
1839       props.put<Shadow>(shadow);
1840     }
1841     // 4: reflection
1842   }
1843   style = std::make_shared<IWORKStyle>(props, name, parent);
1844 }
1845 
parseCellStyle(const unsigned id,IWORKStylePtr_t & style)1846 void IWAParser::parseCellStyle(const unsigned id, IWORKStylePtr_t &style)
1847 {
1848   const ObjectMessage msg(*this, id, IWAObjectType::CellStyle);
1849   if (!msg)
1850     return;
1851 
1852   optional<string> name;
1853   IWORKStylePtr_t parent;
1854   IWORKPropertyMap props;
1855 
1856   using namespace property;
1857 
1858   const IWAMessageField &styleInfo = get(msg).message(1);
1859   if (styleInfo)
1860   {
1861     name = styleInfo.string(2).optional();
1862     const optional<unsigned> &parentRef = readRef(get(styleInfo), 3);
1863     if (parentRef)
1864       parent = queryCellStyle(get(parentRef));
1865   }
1866 
1867   if (get(msg).message(11))
1868   {
1869     const IWAMessage &properties = get(get(msg).message(11));
1870     if (properties.message(1))
1871     {
1872       IWORKFill fill;
1873       if (readFill(get(properties.message(1)), fill))
1874         props.put<Fill>(fill);
1875       else
1876         props.clear<Fill>();
1877     }
1878     if (properties.uint32(8))
1879     {
1880       auto align=get(properties.uint32(8));
1881       if (align<=2)
1882       {
1883         const IWORKVerticalAlignment aligns[] =
1884         {
1885           IWORK_VERTICAL_ALIGNMENT_TOP, IWORK_VERTICAL_ALIGNMENT_MIDDLE, IWORK_VERTICAL_ALIGNMENT_BOTTOM
1886         };
1887         props.put<VerticalAlignment>(aligns[align]);
1888       }
1889       else
1890       {
1891         ETONYEK_DEBUG_MSG(("IWAParser::parseCellStyle: unknown align=%u\n", align));
1892       }
1893     }
1894     if (properties.message(9))
1895     {
1896       IWORKPadding padding;
1897       readPadding(get(properties.message(9)), padding);
1898       props.put<Padding>(padding);
1899     }
1900     if (properties.message(10))
1901     {
1902       IWORKStroke stroke;
1903       readStroke(get(properties.message(10)), stroke);
1904       props.put<TopBorder>(stroke);
1905     }
1906     if (properties.message(11))
1907     {
1908       IWORKStroke stroke;
1909       readStroke(get(properties.message(11)), stroke);
1910       props.put<RightBorder>(stroke);
1911     }
1912     if (properties.message(12))
1913     {
1914       IWORKStroke stroke;
1915       readStroke(get(properties.message(12)), stroke);
1916       props.put<BottomBorder>(stroke);
1917     }
1918     if (properties.message(13))
1919     {
1920       IWORKStroke stroke;
1921       readStroke(get(properties.message(13)), stroke);
1922       props.put<LeftBorder>(stroke);
1923     }
1924   }
1925 
1926   style = std::make_shared<IWORKStyle>(props, name, parent);
1927 }
1928 
parseTableStyle(const unsigned id,IWORKStylePtr_t & style)1929 void IWAParser::parseTableStyle(const unsigned id, IWORKStylePtr_t &style)
1930 {
1931   const ObjectMessage msg(*this, id, IWAObjectType::TableStyle);
1932   if (!msg)
1933     return;
1934 
1935   optional<string> name;
1936   IWORKStylePtr_t parent;
1937   IWORKPropertyMap props;
1938 
1939   using namespace property;
1940 
1941   const IWAMessageField &styleInfo = get(msg).message(1);
1942   if (styleInfo)
1943   {
1944     name = styleInfo.string(2).optional();
1945     const optional<unsigned> &parentRef = readRef(get(styleInfo), 3);
1946     if (parentRef)
1947       parent = queryTableStyle(get(parentRef));
1948   }
1949 
1950   if (get(msg).message(11))
1951   {
1952     const IWAMessage &properties = get(get(msg).message(11));
1953 
1954     if (properties.bool_(1))
1955       props.put<SFTTableBandedRowsProperty>(get(properties.bool_(1)));
1956     if (properties.message(2))
1957     {
1958       IWORKFill fill;
1959       if (readFill(get(properties.message(2)), fill))
1960         props.put<SFTTableBandedCellFillProperty>(fill);
1961       else
1962         props.clear<SFTTableBandedCellFillProperty>();
1963     }
1964     if (properties.bool_(22))
1965       props.put<SFTAutoResizeProperty>(get(properties.bool_(22)));
1966     if (properties.string(41))
1967       props.put<FontName>(get(properties.string(41)));
1968   }
1969 
1970   style = std::make_shared<IWORKStyle>(props, name, parent);
1971 }
1972 
parseListStyle(const unsigned id,IWORKStylePtr_t & style)1973 void IWAParser::parseListStyle(const unsigned id, IWORKStylePtr_t &style)
1974 {
1975   const ObjectMessage msg(*this, id, IWAObjectType::ListStyle);
1976   if (!msg)
1977     return;
1978 
1979   optional<string> name;
1980   IWORKStylePtr_t parent;
1981 
1982   using namespace property;
1983 
1984   const IWAMessageField &styleInfo = get(msg).message(1);
1985   if (styleInfo)
1986   {
1987     name = styleInfo.string(2).optional();
1988     const optional<unsigned> &parentRef = readRef(get(styleInfo), 3);
1989     if (parentRef)
1990       parent = queryListStyle(get(parentRef));
1991   }
1992 
1993   unsigned level = 0;
1994 
1995   map<unsigned, IWORKPropertyMap> levelProps;
1996 
1997   const IWAUInt32Field &numberFormats = get(msg).uint32(15);
1998   const IWAStringField &bullets = get(msg).string(16);
1999   const IWAMessageField &images = get(msg).message(17);
2000   const IWABoolField &tiered = get(msg).bool_(25);
2001   for (IWAUInt32Field::const_iterator it = get(msg).uint32(11).begin(); it != get(msg).uint32(11).end(); ++it, ++level)
2002   {
2003     switch (*it)
2004     {
2005     default :
2006       ETONYEK_DEBUG_MSG(("parseListStyle: unknown label type %u\n", *it));
2007       ETONYEK_FALLTHROUGH;
2008     case 0 :
2009       // no label
2010       levelProps[level].put<ListLabelTypeInfo>(true);
2011       break;
2012     case 1 :
2013     {
2014       // try to find the image, and revert to a default bullet if we find nothing
2015       char const defBullet[]= {char(0xe2), char(0x80), char(0xa2),0};
2016       if (level >= images.size())
2017       {
2018         // FIXME, in fact, the image is in the parent style...
2019         levelProps[level].put<ListLabelTypeInfo>(std::string(defBullet));
2020         break;
2021       }
2022       auto ref=readRef(images[level],3);
2023       if (!ref)
2024       {
2025         ETONYEK_DEBUG_MSG(("parseListStyle: can not find image ref for level %u\n", level));
2026         levelProps[level].put<ListLabelTypeInfo>(std::string(defBullet));
2027         break;
2028       }
2029       const IWORKMediaContentPtr_t content = make_shared<IWORKMediaContent>();
2030       auto stream = queryFile(get(ref));
2031       if (!stream)
2032       {
2033         // the image is probably in the theme model
2034         levelProps[level].put<ListLabelTypeInfo>(std::string(defBullet));
2035         break;
2036       }
2037       const IWORKDataPtr_t data = make_shared<IWORKData>();
2038       data->m_stream = stream;
2039       content->m_data = data;
2040       levelProps[level].put<ListLabelTypeInfo>(content);
2041       break;
2042     }
2043     case 2 :
2044       if (level < bullets.size())
2045         levelProps[level].put<ListLabelTypeInfo>(bullets[level]);
2046       break;
2047     case 3 :
2048     {
2049       IWORKTextLabel label;
2050       if (level < numberFormats.size())
2051       {
2052         switch (numberFormats[level] / 3)
2053         {
2054         case 0 :
2055           label.m_format.m_format = IWORK_LABEL_NUM_FORMAT_NUMERIC;
2056           break;
2057         case 1 :
2058           label.m_format.m_format = IWORK_LABEL_NUM_FORMAT_ROMAN;
2059           break;
2060         case 2 :
2061           label.m_format.m_format = IWORK_LABEL_NUM_FORMAT_ROMAN_LOWERCASE;
2062           break;
2063         case 3 :
2064           label.m_format.m_format = IWORK_LABEL_NUM_FORMAT_ALPHA;
2065           break;
2066         case 4 :
2067           label.m_format.m_format = IWORK_LABEL_NUM_FORMAT_ALPHA_LOWERCASE;
2068           break;
2069         default :
2070           ETONYEK_DEBUG_MSG(("parseListStyle: unknown label number format %u\n", numberFormats[level]));
2071           break;
2072         }
2073         switch (numberFormats[level] % 3)
2074         {
2075         case 0 :
2076           label.m_format.m_suffix = IWORK_LABEL_NUM_FORMAT_SURROUNDING_DOT;
2077           break;
2078         case 1 :
2079           label.m_format.m_prefix = IWORK_LABEL_NUM_FORMAT_SURROUNDING_PARENTHESIS;
2080           label.m_format.m_suffix = IWORK_LABEL_NUM_FORMAT_SURROUNDING_PARENTHESIS;
2081           break;
2082         case 2 :
2083           label.m_format.m_suffix = IWORK_LABEL_NUM_FORMAT_SURROUNDING_PARENTHESIS;
2084           break;
2085         default:
2086           break;
2087         }
2088       }
2089       if (level < tiered.size())
2090         label.m_tiered = tiered[level];
2091       levelProps[level].put<ListLabelTypeInfo>(label);
2092       break;
2093     }
2094     }
2095   }
2096 
2097   level = 0;
2098   for (IWAFloatField::const_iterator it = get(msg).float_(12).begin(); it != get(msg).float_(12).end(); ++it, ++level)
2099     levelProps[level].put<ListTextIndent>(*it);
2100 
2101   level = 0;
2102   for (IWAFloatField::const_iterator it = get(msg).float_(13).begin(); it != get(msg).float_(13).end(); ++it, ++level)
2103     levelProps[level].put<ListLabelIndent>(*it);
2104 
2105   level = 0;
2106   for (IWAMessageField::const_iterator it = get(msg).message(14).begin(); it != get(msg).message(14).end(); ++it, ++level)
2107   {
2108     IWORKListLabelGeometry geometry;
2109     if (it->float_(1))
2110       geometry.m_scale = get(it->float_(1));
2111     levelProps[level].put<ListLabelGeometry>(geometry);
2112   }
2113 
2114   if (bool(parent) && parent->has<ListLevelStyles>())
2115   {
2116     const IWORKListLevels_t &parentStyle = parent->get<ListLevelStyles>();
2117     for (const auto &it : parentStyle)
2118       levelProps[it.first].setParent(&it.second->getPropertyMap());
2119   }
2120 
2121   IWORKListLevels_t listStyle;
2122   for (map<unsigned, IWORKPropertyMap>::const_iterator it = levelProps.begin(); it != levelProps.end(); ++it)
2123     listStyle[it->first] = std::make_shared<IWORKStyle>(it->second, none, none);
2124 
2125   IWORKPropertyMap props;
2126   props.put<ListLevelStyles>(listStyle);
2127   style = std::make_shared<IWORKStyle>(props, name, none);
2128 }
2129 
parseCharacterProperties(const IWAMessage & msg,IWORKPropertyMap & props)2130 void IWAParser::parseCharacterProperties(const IWAMessage &msg, IWORKPropertyMap &props)
2131 {
2132   using namespace property;
2133 
2134   if (msg.bool_(1))
2135     props.put<Bold>(get(msg.bool_(1)));
2136   if (msg.bool_(2))
2137     props.put<Italic>(get(msg.bool_(2)));
2138   if (msg.float_(3))
2139     props.put<FontSize>(get(msg.float_(3)));
2140   if (msg.string(5))
2141     props.put<FontName>(get(msg.string(5)));
2142   const optional<IWORKColor> &fontColor = readColor(msg, 7);
2143   if (fontColor)
2144     props.put<FontColor>(get(fontColor));
2145   if (msg.uint32(10))
2146     putEnum<Baseline>(props, get(msg.uint32(10)));
2147   if (msg.bool_(11))
2148     props.put<Underline>(get(msg.bool_(11)));
2149   if (msg.bool_(12))
2150     props.put<Strikethru>(get(msg.bool_(12)));
2151   if (msg.uint32(13))
2152     putEnum<Capitalization>(props, get(msg.uint32(13)));
2153   if (msg.float_(14))
2154     props.put<BaselineShift>(get(msg.float_(14)));
2155   if (msg.float_(19)) // CHECKME
2156     props.put<Outline>(get(msg.float_(19))>0);
2157   if (msg.message(21))
2158   {
2159     IWORKShadow shadow;
2160     readShadow(get(msg.message(21)),shadow);
2161     props.put<TextShadow>(shadow);
2162   }
2163   const auto bgColor = readColor(msg, 26);
2164   if (bgColor)
2165     props.put<TextBackground>(get(bgColor));
2166   if (msg.float_(27))
2167     props.put<Tracking>(get(msg.float_(27)));
2168 }
2169 
parseColumnsProperties(const IWAMessage & msg,IWORKPropertyMap & props)2170 void IWAParser::parseColumnsProperties(const IWAMessage &msg, IWORKPropertyMap &props)
2171 {
2172   using namespace property;
2173   // 1,2,3,5 bool
2174   // 4 float 0
2175   if (msg.message(7))
2176   {
2177     auto columnsMsg=get(msg.message(7));
2178     IWORKColumns columns;
2179     columns.m_columns.clear();
2180     if (columnsMsg.message(1))   // same columns size
2181     {
2182       auto columnDef=get(columnsMsg.message(1));
2183       columns.m_equal=true;
2184       auto n=get_optional_value_or(columnDef.uint32(1).optional(),0);
2185       auto s=get_optional_value_or(columnDef.float_(2).optional(),0.f);
2186       if (n>=1 && n<20)
2187       {
2188         IWORKColumns::Column column;
2189         column.m_width=(1.-(n-1)*double(s))/double(n);
2190         column.m_spacing=s;
2191         columns.m_columns.resize(size_t(n), column);
2192       }
2193     }
2194     else if (columnsMsg.message(2))
2195     {
2196       auto columnsDef=get(columnsMsg.message(2));
2197       columns.m_equal=false;
2198       IWORKColumns::Column column;
2199       column.m_width=get_optional_value_or(columnsDef.float_(1).optional(),0.f);
2200       columns.m_columns.push_back(column);
2201       for (const auto &it : columnsDef.message(2))
2202       {
2203         column.m_spacing=get_optional_value_or(it.float_(1).optional(),0.f);
2204         column.m_width=get_optional_value_or(it.float_(2).optional(),0.f);
2205         columns.m_columns.push_back(column);
2206       }
2207     }
2208     if (!columns.m_columns.empty())
2209       props.put<property::Columns>(columns);
2210   }
2211 }
2212 
parsePageMaster(unsigned id,PageMaster & pageMaster)2213 void IWAParser::parsePageMaster(unsigned id, PageMaster &pageMaster)
2214 {
2215   const ObjectMessage msg(*this, id, IWAObjectType::PageMaster);
2216   if (!msg)
2217     return;
2218   if (get(msg).bool_(17))
2219     pageMaster.m_headerFootersSameAsPrevious=get(get(msg).bool_(17));
2220   bool hideHeaderOnFirstPage=false;
2221   if (get(msg).bool_(28))
2222     hideHeaderOnFirstPage=get(get(msg).bool_(28));
2223   // 18-22: some bool ?
2224   IWORKPropertyMap props;
2225   for (unsigned i=0; i<3; ++i)
2226   {
2227     auto hfRef=readRef(get(msg),23+i);
2228     if (!hfRef) continue;
2229     IWORKPageMaster pMaster;
2230     parseHeaderAndFooter(get(hfRef), pMaster);
2231     if (pMaster.m_header.empty() && pMaster.m_footer.empty())
2232       continue;
2233     // only the last pagemaster seem used...
2234     if (i!=2) continue;
2235     props.put<property::OddPageMaster>(pMaster);
2236     props.put<property::EvenPageMaster>(pMaster);
2237     if (!hideHeaderOnFirstPage)
2238       props.put<property::FirstPageMaster>(pMaster);
2239   }
2240   // readRef(get(msg),30); type 10016, field 1 a bool, [field 2] a ref to type 3047
2241   const IWAMessageField &background = get(msg).message(30);
2242   if (background)
2243   {
2244     IWORKFill fill;
2245     if (readFill(get(background), fill))
2246       props.put<property::Fill>(fill);
2247   }
2248   pageMaster.m_style = std::make_shared<IWORKStyle>(props, none, none);
2249 }
2250 
parseHeaderAndFooter(unsigned id,IWORKPageMaster & hf)2251 void IWAParser::parseHeaderAndFooter(unsigned id, IWORKPageMaster &hf)
2252 {
2253   const ObjectMessage msg(*this, id, IWAObjectType::HeadersAndFooters);
2254   if (!msg)
2255     return;
2256   for (size_t wh=0; wh<2; ++wh)
2257   {
2258     bool empty=true;
2259     std::stringstream name;
2260     name << (wh==0 ? "PMHeader" : "PMFooter") << id;
2261     for (auto it = get(msg).message(wh+1).begin(); it != get(msg).message(wh+1).end(); ++it)
2262     {
2263       auto ref=it->uint32(1).optional();
2264       if (!ref) continue;
2265       auto currentText=m_currentText;
2266       m_currentText = m_collector.createText(m_langManager, true);
2267       parseText(get(ref));
2268       if (!m_currentText->empty())
2269       {
2270         m_collector.collectText(m_currentText);
2271         if (wh==0)
2272           m_collector.collectHeader(name.str());
2273         else
2274           m_collector.collectFooter(name.str());
2275         empty=false;
2276       }
2277       m_currentText=currentText;
2278     }
2279     if (empty) continue;
2280     if (wh==0)
2281       hf.m_header=name.str();
2282     else
2283       hf.m_footer=name.str();
2284   }
2285 
2286 }
2287 
parseImage(const IWAMessage & msg)2288 bool IWAParser::parseImage(const IWAMessage &msg)
2289 {
2290   m_collector.startLevel();
2291   IWORKGeometryPtr_t geometry;
2292   if (msg.message(1))
2293   {
2294     boost::optional<unsigned> flags;
2295     parseShapePlacement(get(msg.message(1)), geometry, flags);
2296     m_collector.collectGeometry(geometry);
2297   }
2298 
2299   const optional<unsigned> styleRef = readRef(msg, 3);
2300   if (styleRef)
2301     m_collector.setGraphicStyle(queryMediaStyle(get(styleRef)));
2302 
2303   IWORKGeometryPtr_t cropGeometry;
2304   const optional<unsigned> cropRef = readRef(msg, 5);
2305   if (cropRef)
2306   {
2307     IWORKPathPtr_t path;
2308     parseMask(get(cropRef), cropGeometry, path);
2309   }
2310   if (cropGeometry && geometry)
2311   {
2312     // CHANGEME: collector suppose that cropGeometry position is the
2313     //   final position but mask is relative to the original picture
2314     cropGeometry->m_position.m_x += geometry->m_position.m_x;
2315     cropGeometry->m_position.m_y += geometry->m_position.m_y;
2316   }
2317 
2318   const IWORKMediaContentPtr_t content = make_shared<IWORKMediaContent>();
2319   // 15: filtered, 11: basic image?, 12: small image, 13: ?
2320   unsigned const ids[]= {15, 13, 11, 12};
2321   for (auto id : ids)
2322   {
2323     auto const &ref=readRef(msg, id);
2324     if (!ref) continue;
2325     auto stream = queryFile(get(ref));
2326     if (!stream) continue;
2327     const IWORKDataPtr_t data = make_shared<IWORKData>();
2328     data->m_stream = stream;
2329     content->m_data = data;
2330     break;
2331   }
2332   content->m_size = readSize(msg, 9);
2333   if (!content->m_size)
2334     content->m_size = readSize(msg, 4);
2335 
2336   m_collector.collectMedia(content, cropGeometry);
2337   m_collector.endLevel();
2338 
2339   return true;
2340 }
2341 
parseAuthorInComment(unsigned id)2342 void IWAParser::parseAuthorInComment(unsigned id)
2343 {
2344   assert(bool(m_currentText));
2345   const ObjectMessage msg(*this, id, IWAObjectType::AuthorStorage);
2346   if (!msg)
2347     return;
2348   if (get(msg).string(1))
2349   {
2350     auto str=get(get(msg).string(1));
2351     auto len=str.size();
2352     if (len==0) return;
2353     IWAText text(str+"\t", m_langManager);
2354     std::map<unsigned, IWORKStylePtr_t> spans;
2355     IWORKPropertyMap props;
2356     // normally yellow, but blue may be better in LO
2357     props.put<property::FontColor>(IWORKColor(0,0,1,1));
2358     spans[0]=std::make_shared<IWORKStyle>(props, boost::none, nullptr);
2359     // reset color to default, if not, comment will be blue colored
2360     props.put<property::FontColor>(IWORKColor(0,0,0,1));
2361     spans[unsigned(len)]=std::make_shared<IWORKStyle>(props, boost::none, nullptr);
2362     text.setSpans(spans);
2363     text.parse(*m_currentText);
2364   }
2365 }
2366 
parseComment(const unsigned id)2367 void IWAParser::parseComment(const unsigned id)
2368 {
2369   assert(bool(m_currentText));
2370 
2371   unsigned actId=id;
2372   std::set<unsigned> seens;
2373   while (1)
2374   {
2375     if (seens.find(actId)!=seens.end())
2376     {
2377       ETONYEK_DEBUG_MSG(("IWAParser::parseComment: find a loop\n"));
2378       break;
2379     }
2380     seens.insert(actId);
2381     const ObjectMessage msg(*this, actId, IWAObjectType::Comment);
2382     if (!msg)
2383       return;
2384     auto authorRef=readRef(get(msg), 3);
2385     if (authorRef)
2386       parseAuthorInComment(*authorRef);
2387     // TODO add date which is in position 2
2388     if (get(msg).string(1))
2389     {
2390       IWAText text(get(get(msg).string(1)), m_langManager);
2391       text.parse(*m_currentText);
2392     }
2393 
2394     auto nextRef=readRef(get(msg), 4);
2395     if (!nextRef) break;
2396     actId=*nextRef;
2397   }
2398 }
2399 
parseTabularInfo(const IWAMessage & msg)2400 bool IWAParser::parseTabularInfo(const IWAMessage &msg)
2401 {
2402   m_collector.startLevel();
2403   if (msg.message(1))
2404     parseShapePlacement(get(msg.message(1)));
2405   const optional<unsigned> &modelRef = readRef(msg, 2);
2406   if (modelRef)
2407     parseTabularModel(get(modelRef));
2408   m_collector.endLevel();
2409   return bool(modelRef);
2410 }
2411 
parseTabularModel(const unsigned id)2412 void IWAParser::parseTabularModel(const unsigned id)
2413 {
2414   const ObjectMessage msg(*this, id, IWAObjectType::TabularModel);
2415   if (!msg)
2416     return;
2417 
2418   const IWAUInt32Field &rows = get(msg).uint32(6);
2419   const IWAUInt32Field &columns = get(msg).uint32(7);
2420   if (!rows || !columns)
2421     return;
2422 
2423   m_currentTable = std::make_shared<TableInfo>(m_collector.createTable(m_tableNameMap, m_langManager), get(columns), get(rows));
2424   m_currentTable->m_table->setSize(get(columns), get(rows));
2425 
2426   IWORKStylePtr_t tableStyle;
2427   const optional<unsigned> tableStyleRef = readRef(get(msg), 3);
2428   if (tableStyleRef)
2429     tableStyle = queryTableStyle(get(tableStyleRef));
2430   if (bool(tableStyle))
2431   {
2432     m_currentTable->m_style = tableStyle;
2433     m_currentTable->m_table->setStyle(tableStyle);
2434 
2435     if (tableStyle->has<property::SFTTableBandedCellFillProperty>())
2436     {
2437       IWORKPropertyMap props;
2438       props.put<property::Fill>(tableStyle->get<property::SFTTableBandedCellFillProperty>());
2439       m_currentTable->m_table->setDefaultCellStyle(IWORKTable::CELL_TYPE_ALTERNATE_BODY, make_shared<IWORKStyle>(props, none, none));
2440     }
2441   }
2442 
2443   optional<unsigned> tileRef;
2444 
2445   if (get(msg).message(4))
2446   {
2447     const IWAMessage &grid = get(get(msg).message(4));
2448 
2449     if (grid.message(1))
2450     {
2451       const optional<unsigned> &rowHeadersRef = readRef(get(grid.message(1)), 2);
2452       if (rowHeadersRef)
2453         parseTableHeaders(get(rowHeadersRef), m_currentTable->m_rowHeader);
2454     }
2455     const optional<unsigned> &columnHeadersRef = readRef(grid, 2);
2456     if (columnHeadersRef)
2457       parseTableHeaders(get(columnHeadersRef), m_currentTable->m_columnHeader);
2458 
2459     const optional<unsigned> &simpleTextListRef = readRef(grid, 4);
2460     if (simpleTextListRef)
2461       parseDataList(get(simpleTextListRef), m_currentTable->m_simpleTextList);
2462     const optional<unsigned> &cellStyleListRef = readRef(grid, 5);
2463     if (cellStyleListRef)
2464       parseDataList(get(cellStyleListRef), m_currentTable->m_cellStyleList);
2465     const optional<unsigned> &formulaListRef = readRef(grid, 6);
2466     if (formulaListRef)
2467       parseDataList(get(formulaListRef), m_currentTable->m_formulaList);
2468     const optional<unsigned> &formatListRef = readRef(grid, 11);
2469     if (formatListRef)
2470       parseDataList(get(formatListRef), m_currentTable->m_formatList);
2471     const optional<unsigned> &paraTextListRef = readRef(grid, 17);
2472     if (paraTextListRef)
2473       parseDataList(get(paraTextListRef), m_currentTable->m_formattedTextList);
2474     const optional<unsigned> &commentListRef = readRef(grid, 19);
2475     if (commentListRef)
2476       parseDataList(get(commentListRef), m_currentTable->m_commentList);
2477 
2478     m_currentTable->m_table->setSizes(makeSizes(m_currentTable->m_columnHeader.m_sizes), makeSizes(m_currentTable->m_rowHeader.m_sizes));
2479 
2480     if (grid.message(3) && grid.message(3).message(1))
2481       tileRef = readRef(get(grid.message(3).message(1)), 2);
2482   }
2483   if (get(msg).string(8))
2484   {
2485     auto finalName=get(get(msg).string(8));
2486     // also update the table name map?
2487     if (m_tableNameMap->find(finalName)!=m_tableNameMap->end())
2488     {
2489       ETONYEK_DEBUG_MSG(("IWAParser::parseTabularModel: a table with name %s already exists\n", finalName.c_str()));
2490       // let create an unique name
2491       int nId=0;
2492       while (true)
2493       {
2494         std::stringstream s;
2495         s << finalName << "_" << ++nId;
2496         if (m_tableNameMap->find(s.str())!=m_tableNameMap->end()) continue;
2497         finalName=s.str();
2498         break;
2499       }
2500     }
2501     (*m_tableNameMap)[finalName]=finalName;
2502     if (get(msg).string(1))
2503       (*m_tableNameMap)[std::string("SFTGlobalID_")+get(get(msg).string(1))] = finalName;
2504     m_currentTable->m_table->setName(finalName);
2505   }
2506   m_currentTable->m_table->setHeaders(
2507     get_optional_value_or(get(msg).uint32(10).optional(), 0),
2508     get_optional_value_or(get(msg).uint32(9).optional(), 0),
2509     get_optional_value_or(get(msg).uint32(11).optional(), 0)
2510   );
2511   m_currentTable->m_table->setRepeated(
2512     get_optional_value_or(get(msg).bool_(13).optional(), false),
2513     get_optional_value_or(get(msg).bool_(12).optional(), false)
2514   );
2515   if (bool(tableStyle) && tableStyle->has<property::SFTTableBandedRowsProperty>())
2516     m_currentTable->m_table->setBandedRows(tableStyle->get<property::SFTTableBandedRowsProperty>());
2517 
2518   // default cell styles
2519   optional<unsigned> styleRef = readRef(get(msg), 18);
2520   if (styleRef)
2521     m_currentTable->m_table->setDefaultCellStyle(IWORKTable::CELL_TYPE_BODY, queryCellStyle(get(styleRef)));
2522   styleRef = readRef(get(msg), 19);
2523   if (styleRef)
2524     m_currentTable->m_table->setDefaultCellStyle(IWORKTable::CELL_TYPE_ROW_HEADER, queryCellStyle(get(styleRef)));
2525   styleRef = readRef(get(msg), 20);
2526   if (styleRef)
2527     m_currentTable->m_table->setDefaultCellStyle(IWORKTable::CELL_TYPE_COLUMN_HEADER, queryCellStyle(get(styleRef)));
2528   styleRef = readRef(get(msg), 21);
2529   if (styleRef)
2530     m_currentTable->m_table->setDefaultCellStyle(IWORKTable::CELL_TYPE_ROW_FOOTER, queryCellStyle(get(styleRef)));
2531 
2532   // default para styles
2533   styleRef = readRef(get(msg), 24);
2534   if (styleRef)
2535     m_currentTable->m_table->setDefaultParagraphStyle(IWORKTable::CELL_TYPE_BODY, queryParagraphStyle(get(styleRef)));
2536   styleRef = readRef(get(msg), 25);
2537   if (styleRef)
2538     m_currentTable->m_table->setDefaultParagraphStyle(IWORKTable::CELL_TYPE_ROW_HEADER, queryParagraphStyle(get(styleRef)));
2539   styleRef = readRef(get(msg), 26);
2540   if (styleRef)
2541     m_currentTable->m_table->setDefaultParagraphStyle(IWORKTable::CELL_TYPE_COLUMN_HEADER, queryParagraphStyle(get(styleRef)));
2542   styleRef = readRef(get(msg), 27);
2543   if (styleRef)
2544     m_currentTable->m_table->setDefaultParagraphStyle(IWORKTable::CELL_TYPE_ROW_FOOTER, queryParagraphStyle(get(styleRef)));
2545 
2546   styleRef = readRef(get(msg), 49);
2547   if (styleRef)
2548   {
2549     IWORKGridLineMap_t gridLines[4];
2550     parseTableGridLines(get(styleRef), gridLines);
2551     m_currentTable->m_table->setBorders(gridLines[0],gridLines[1],gridLines[2],gridLines[3]);
2552   }
2553 
2554   // handle table
2555   if (tileRef)
2556     parseTile(get(tileRef));
2557   m_collector.collectTable(m_currentTable->m_table);
2558   m_currentTable.reset();
2559 }
2560 
parseDataList(const unsigned id,DataList_t & dataList)2561 void IWAParser::parseDataList(const unsigned id, DataList_t &dataList)
2562 {
2563   const ObjectMessage msg(*this, id, IWAObjectType::DataList);
2564   if (!msg)
2565     return;
2566 
2567   // TODO: it would likely to be more robust to parse everything.
2568   if (!get(msg).uint32(1))
2569     return;
2570 
2571   const unsigned type = get(get(msg).uint32(1));
2572   for (const auto &it : get(msg).message(3))
2573   {
2574     if (!it.uint32(1))
2575       continue;
2576     const unsigned index = get(it.uint32(1));
2577     switch (type)
2578     {
2579     case 1 :
2580       if (it.string(3))
2581         dataList[index] = get(it.string(3));
2582       break;
2583     case 2 :
2584       // it.uint32(2): some type
2585       if (it.message(6))
2586       {
2587         Format format;
2588         if (parseFormat(get(it.message(6)), format))
2589           dataList[index]=format;
2590       }
2591       else
2592       {
2593         ETONYEK_DEBUG_MSG(("IWAParser::parseDataList: can not find the format\n"));
2594       }
2595       break;
2596     case 3 :
2597     case 5 : // invalid formula
2598       if (it.message(5))
2599       {
2600         IWORKFormulaPtr_t formula;
2601         if (parseFormula(get(it.message(5)), formula) && formula)
2602           dataList[index]=formula;
2603       }
2604       else
2605       {
2606         ETONYEK_DEBUG_MSG(("IWAParser::parseDataList: can not find the formula\n"));
2607       }
2608       break;
2609     case 4 :
2610     {
2611       auto styleRef=readRef(it,4);
2612       if (styleRef)
2613         dataList[index]=get(styleRef);
2614       else if (it.uint32(4))
2615         dataList[index] = get(it.uint32(4));
2616       break;
2617     }
2618     case 8 :   // paragraph ref
2619     {
2620       auto textRef=readRef(it,9);
2621       if (textRef)
2622         dataList[index]=get(textRef);
2623       else
2624       {
2625         ETONYEK_DEBUG_MSG(("IWAParser::parseDataList: can not find the para ref\n"));
2626       }
2627       break;
2628     }
2629     case 9 :
2630       if (it.uint32(9))
2631         dataList[index] = get(it.uint32(9));
2632       break;
2633     case 10 :
2634     {
2635       auto commentRef=readRef(it,10);
2636       if (commentRef)
2637         dataList[index]=get(commentRef);
2638       else
2639       {
2640         ETONYEK_DEBUG_MSG(("IWAParser::parseDataList: can not find the cpmment ref\n"));
2641       }
2642       break;
2643     }
2644     default :
2645       ETONYEK_DEBUG_MSG(("IWAParser::parseDataList: unknown data list type %u\n", type));
2646       break;
2647     }
2648   }
2649 }
2650 
parseTile(const unsigned id)2651 void IWAParser::parseTile(const unsigned id)
2652 {
2653   const ObjectMessage msg(*this, id, IWAObjectType::Tile);
2654   if (!msg)
2655     return;
2656 
2657   // rows must be fed to the collector in order
2658   typedef map<unsigned, const IWAMessage *> Rows_t;
2659   Rows_t rows;
2660 
2661   // save rows
2662   for (const auto &it : get(msg).message(5))
2663   {
2664     if (!it.uint32(1) || !it.bytes(3) || !it.bytes(4))
2665       continue;
2666     const unsigned row = get(it.uint32(1));
2667     if (row >= m_currentTable->m_rows)
2668     {
2669       ETONYEK_DEBUG_MSG(("IWAParser::parseTile: invalid row: %u\n", row));
2670       continue;
2671     }
2672     rows[row] = &it;
2673   }
2674 
2675   // process rows
2676   for (auto it : rows)
2677   {
2678     const RVNGInputStreamPtr_t &input = get(it.second->bytes(3));
2679     auto length = unsigned(getLength(input));
2680     if (length >= 0xffff)
2681     {
2682       ETONYEK_DEBUG_MSG(("IWAParser::parseTile: invalid column data length: %u\n", length));
2683       length = 0xffff;
2684     }
2685 
2686     map<unsigned,unsigned> offsets;
2687     parseColumnOffsets(get(it.second->bytes(4)), length, offsets);
2688 
2689     for (auto offIt : offsets)
2690     {
2691       if (offIt.second+10>length)
2692       {
2693         ETONYEK_DEBUG_MSG(("IWAParser::parseTile: unexpected offset position\n"));
2694         continue;
2695       }
2696       const unsigned column = offIt.first;
2697       const unsigned row = it.first;
2698 
2699       IWORKCellType cellType = IWORK_CELL_TYPE_TEXT;
2700       IWORKStylePtr_t cellStyle;
2701       optional<unsigned> comment;
2702       IWORKFormulaPtr_t formula;
2703       optional<Format> format;
2704       optional<string> text;
2705       optional<IWORKDateTimeData> dateTime;
2706       optional<unsigned> textRef;
2707 
2708       // 1. Read the cell record
2709       // NOTE: The structure of the record is still not completely understood,
2710       // so we catch possible over-reading exceptions and continue.
2711       try
2712       {
2713         // 0: 4?
2714         input->seek((long) offIt.second+1, librevenge::RVNG_SEEK_SET);
2715         auto type=readU8(input);
2716         switch (type)
2717         {
2718         case 2:
2719         case 7: // duration (changeme)
2720           cellType=IWORK_CELL_TYPE_NUMBER;
2721           break;
2722         case 0: // empty (ok)
2723         case 3: // text (ok)
2724         case 9: // text zone
2725           break;
2726         case 5:
2727           cellType=IWORK_CELL_TYPE_DATE_TIME;
2728           break;
2729         case 6:
2730           cellType=IWORK_CELL_TYPE_BOOL;
2731           break;
2732         default:
2733           ETONYEK_DEBUG_MSG(("IWAParser::parseTile: unknown type %d\n", int(type)));
2734           break;
2735         }
2736         // 2,3: ?
2737         input->seek((long) offIt.second + 4, librevenge::RVNG_SEEK_SET);
2738         const unsigned flags = readU16(input);
2739         input->seek(6, librevenge::RVNG_SEEK_CUR);
2740         if (flags & 0x2) // cell style
2741         {
2742           const unsigned styleId = readU32(input);
2743           const DataList_t::const_iterator listIt = m_currentTable->m_cellStyleList.find(styleId);
2744           if (listIt != m_currentTable->m_cellStyleList.end())
2745           {
2746             if (const unsigned *const ref = boost::get<unsigned>(&listIt->second))
2747               cellStyle = queryCellStyle(*ref);
2748           }
2749         }
2750         if (flags & 0x80) // unknown
2751           readU32(input);
2752         // flags & 0xc00, read 2 int
2753         if (flags & 0x4)   // format
2754         {
2755           const unsigned formatId=readU32(input);
2756           auto const formatIt=m_currentTable->m_formatList.find(formatId);
2757           if (formatIt !=m_currentTable->m_formatList.end())
2758           {
2759             if (auto ref = boost::get<Format>(&formatIt->second))
2760             {
2761               format=*ref;
2762               if (format->m_type && get(format->m_type)==IWORK_CELL_TYPE_NUMBER && cellType!=IWORK_CELL_TYPE_TEXT)
2763                 format->m_type=cellType;
2764             }
2765           }
2766           else
2767           {
2768             ETONYEK_DEBUG_MSG(("IWAParser::parseTile: can not find format %d\n", int(formatId)));
2769           }
2770         }
2771         if (flags & 0x8) // formula
2772         {
2773           const unsigned formulaId = readU32(input);
2774           auto const formulaIt = m_currentTable->m_formulaList.find(formulaId);
2775           if (formulaIt !=m_currentTable->m_formulaList.end())
2776           {
2777             if (auto ref = boost::get<IWORKFormulaPtr_t>(&formulaIt->second))
2778               formula=*ref;
2779           }
2780           else
2781           {
2782             ETONYEK_DEBUG_MSG(("IWAParser::parseTile: can not find formula %d\n", int(formulaId)));
2783           }
2784         }
2785         if (flags & 0x1000) // comment
2786         {
2787           const unsigned commentId=readU32(input);
2788           auto const commentIt = m_currentTable->m_commentList.find(commentId);
2789           if (commentIt !=m_currentTable->m_commentList.end())
2790             comment=boost::get<unsigned>(commentIt->second);
2791           else
2792           {
2793             ETONYEK_DEBUG_MSG(("IWAParser::parseTile: can not find comment %d\n", int(commentId)));
2794           }
2795         }
2796         if (flags & 0x10) // simple text
2797         {
2798           const unsigned textId = readU32(input);
2799           const DataList_t::const_iterator listIt = m_currentTable->m_simpleTextList.find(textId);
2800           if (listIt != m_currentTable->m_simpleTextList.end())
2801           {
2802             if (const string *const s = boost::get<string>(&listIt->second))
2803               text = *s;
2804           }
2805           else
2806           {
2807             ETONYEK_DEBUG_MSG(("IWAParser::parseTile: can not find text %d\n", int(textId)));
2808           }
2809         }
2810         if (flags & 0x20) // number or duration(in second)
2811         {
2812           std::stringstream s;
2813           s << readDouble(input);
2814           text=s.str();
2815           if (!format)
2816           {
2817             format=Format();
2818             get(format).m_type = cellType==IWORK_CELL_TYPE_TEXT ? IWORK_CELL_TYPE_NUMBER : cellType;
2819             get(format).m_format=IWORKNumberFormat();
2820           }
2821         }
2822         if (flags & 0x40) // date
2823         {
2824           std::stringstream s;
2825           s << readDouble(input);
2826           text=s.str();
2827           if (!format)
2828           {
2829             format=Format();
2830             get(format).m_type=IWORK_CELL_TYPE_DATE_TIME;
2831             get(format).m_format=IWORKDateTimeFormat();
2832           }
2833         }
2834         if (flags & 0x200) // formatted text
2835         {
2836           const unsigned textId = readU32(input);
2837           const DataList_t::const_iterator listIt = m_currentTable->m_formattedTextList.find(textId);
2838           if (listIt != m_currentTable->m_formattedTextList.end())
2839           {
2840             if (const unsigned *const ref = boost::get<unsigned>(&listIt->second))
2841               textRef = *ref;
2842           }
2843         }
2844       }
2845       catch (...)
2846       {
2847         // ignore failure to read the last record
2848       }
2849 
2850       if (format)
2851       {
2852         if (get(format).m_type) cellType=get(get(format).m_type);
2853         IWORKPropertyMap props;
2854         if (boost::get<IWORKNumberFormat>(&get(format).m_format))
2855           props.put<property::SFTCellStylePropertyNumberFormat>(*boost::get<IWORKNumberFormat>(&get(format).m_format));
2856         else if (boost::get<IWORKDateTimeFormat>(&get(format).m_format))
2857           props.put<property::SFTCellStylePropertyDateTimeFormat>(*boost::get<IWORKDateTimeFormat>(&get(format).m_format));
2858         else if (boost::get<IWORKDurationFormat>(&get(format).m_format))
2859           props.put<property::SFTCellStylePropertyDurationFormat>(*boost::get<IWORKDurationFormat>(&get(format).m_format));
2860         cellStyle.reset(new IWORKStyle(props, none, cellStyle));
2861       }
2862 
2863       bool needText=bool(textRef) || (bool(text) && !formula && cellType == IWORK_CELL_TYPE_TEXT);
2864       if (needText)
2865       {
2866         assert(!m_currentText);
2867         m_currentText = m_collector.createText(m_langManager);
2868         if (bool(textRef))
2869           parseText(get(textRef));
2870         else
2871         {
2872           m_currentText = m_collector.createText(m_langManager);
2873           // update the style
2874           m_currentText->pushBaseLayoutStyle(m_currentTable->m_table->getDefaultLayoutStyle(column, row));
2875           // do we need to set m_currentTable->m_style->has<property::FontName>() ?
2876           m_currentText->pushBaseParagraphStyle(m_currentTable->m_table->getDefaultParagraphStyle(column, row));
2877           m_currentText->insertText(get(text));
2878           m_currentText->flushSpan();
2879           m_currentText->flushParagraph();
2880         }
2881       }
2882       m_currentTable->m_table->insertCell(column, row, text, m_currentText, dateTime, 1, 1, formula, unsigned(row*256+column), cellStyle, cellType);
2883       if (bool(comment))
2884       {
2885         auto currentText=m_currentText;
2886         m_currentText = m_collector.createText(m_langManager);
2887         parseComment(get(comment));
2888         IWORKOutputElements elements;
2889         m_currentText->draw(elements);
2890         m_currentText=currentText;
2891         m_currentTable->m_table->setComment(column, row, elements);
2892       }
2893       m_currentText.reset();
2894     }
2895   }
2896 }
2897 
parseTableHeaders(const unsigned id,TableHeader & header)2898 void IWAParser::parseTableHeaders(const unsigned id, TableHeader &header)
2899 {
2900   const ObjectMessage msg(*this, id, IWAObjectType::Headers);
2901   if (!msg)
2902     return;
2903 
2904   for (const auto &it : get(msg).message(2))
2905   {
2906     if (it.uint32(1))
2907     {
2908       const unsigned index = get(it.uint32(1));
2909       if (index >= header.m_sizes.max_key())
2910       {
2911         ETONYEK_DEBUG_MSG(("IWAParser::parseTableHeaders: invalid row/column index %u\n", index));
2912         continue;
2913       }
2914       if (it.float_(2))
2915         header.m_sizes.insert_back(index, index + 1, get(it.float_(2)));
2916       if (it.bool_(3))
2917         header.m_hidden.insert_back(index, index + 1, get(it.bool_(3)));
2918     }
2919   }
2920 }
2921 
parseTableGridLines(unsigned id,IWORKGridLineMap_t (& gridLines)[4])2922 void IWAParser::parseTableGridLines(unsigned id, IWORKGridLineMap_t (&gridLines)[4])
2923 {
2924   const ObjectMessage msg(*this, id, IWAObjectType::GridLines);
2925   if (!msg)
2926   {
2927     ETONYEK_DEBUG_MSG(("IWAParser::parseTableGridLInes: can not find grid lines for index %u\n", id));
2928     return;
2929   }
2930   for (unsigned wh=0; wh<4; ++wh)
2931   {
2932     if (get(msg).message(wh+4).empty()) continue;
2933     auto const &lineRefs = readRefs(get(msg), wh+4);
2934     auto &gridLine=gridLines[wh];
2935     std::for_each(lineRefs.begin(), lineRefs.end(),
2936                   [this,&gridLine](unsigned lineId)
2937     {
2938       parseTableGridLine(lineId, gridLine);
2939     }
2940                  );
2941   }
2942 }
2943 
parseTableGridLine(unsigned id,IWORKGridLineMap_t & gridLine)2944 void IWAParser::parseTableGridLine(unsigned id, IWORKGridLineMap_t &gridLine)
2945 {
2946   const ObjectMessage msg(*this, id, IWAObjectType::GridLine);
2947   if (!msg)
2948   {
2949     ETONYEK_DEBUG_MSG(("IWAParser::parseTableGridLine: can not find grid line for index %u\n", id));
2950     return;
2951   }
2952   if (!get(msg).uint32(1))
2953   {
2954     ETONYEK_DEBUG_MSG(("IWAParser::parseTableGridLine: can not find the main position index %u\n", id));
2955     return;
2956   }
2957   auto pos1=get(get(msg).uint32(1));
2958   const deque<IWAMessage> &lines = get(msg).message(2).repeated();
2959   if (gridLine.find(pos1)==gridLine.end())
2960     gridLine.insert(IWORKGridLineMap_t::value_type(pos1,IWORKGridLine_t(0,4096,nullptr)));
2961   auto &flatSegments=gridLine.find(pos1)->second;
2962   for (auto it : lines)
2963   {
2964     if (!it.uint32(1) || !it.uint32(2))
2965     {
2966       ETONYEK_DEBUG_MSG(("IWAParser::parseTableGridLine: can not find the second position index %u\n", id));
2967       continue;
2968     }
2969     IWORKPropertyMap props;
2970     if (it.message(3))
2971     {
2972       IWORKStroke stroke;
2973       readStroke(get(it.message(3)), stroke);
2974       props.put<property::SFTStrokeProperty>(stroke);
2975     }
2976     flatSegments.insert_back(get(it.uint32(1)),get(it.uint32(1))+get(it.uint32(2)), std::make_shared<IWORKStyle>(props,none,none));
2977   }
2978 }
2979 
parseFormat(const IWAMessage & msg,IWAParser::Format & format)2980 bool IWAParser::parseFormat(const IWAMessage &msg, IWAParser::Format &format)
2981 {
2982   if (!msg.uint32(1))
2983   {
2984     ETONYEK_DEBUG_MSG(("IWAParser::parseFormat: can not find the main type\n"));
2985     return false;
2986   }
2987   auto uid=readUID(msg,41);
2988   if (uid)
2989   {
2990     auto it= m_uidFormatMap.find(get(uid));
2991     if (it==m_uidFormatMap.end())
2992     {
2993       ETONYEK_DEBUG_MSG(("IWAParser::parseFormat: can not find the format %llx\n", get(uid)));
2994       return false;
2995     }
2996     format=it->second;
2997     return true;
2998   }
2999 
3000   auto type=get(msg.uint32(1));
3001   format.m_type=IWORK_CELL_TYPE_NUMBER;
3002   IWORKCellNumberType nType=IWORK_CELL_NUMBER_TYPE_DOUBLE;
3003   switch (type)
3004   {
3005   case 1: // automatic
3006     return true;
3007   case 263: // checkbox
3008   case 264: // stepper
3009   case 265: // slider
3010   case 266: // pop-up menu
3011   case 267: // star rating
3012     return false;
3013   case 257: // currency
3014     nType=IWORK_CELL_NUMBER_TYPE_CURRENCY;
3015     break;
3016   case 258: // percentage
3017     nType=IWORK_CELL_NUMBER_TYPE_PERCENTAGE;
3018     break;
3019   case 259: // scientific
3020     nType=IWORK_CELL_NUMBER_TYPE_SCIENTIFIC;
3021     break;
3022   case 262: // fraction
3023     nType=IWORK_CELL_NUMBER_TYPE_FRACTION;
3024     break;
3025   case 256: // number
3026   case 269: // numeral system
3027     break;
3028   case 270: // custom number
3029     // TODO
3030     break;
3031   case 260:
3032     format.m_type=IWORK_CELL_TYPE_TEXT;
3033     return true;
3034   case 271: // custom text
3035     // normally, we use msg.string(18) as text for a not empty cell
3036     // ie. store it when we see it in a custom format cell
3037     //     then use it to define the cell content
3038     format.m_type=IWORK_CELL_TYPE_TEXT;
3039     return true;
3040   case 261:
3041     format.m_type=IWORK_CELL_TYPE_DATE_TIME;
3042     if (msg.string(14))
3043     {
3044       IWORKDateTimeFormat dtFormat;
3045       dtFormat.m_format=get(msg.string(14));
3046       format.m_format=dtFormat;
3047       return true;
3048     }
3049     else
3050     {
3051       ETONYEK_DEBUG_MSG(("IWAParser::parseFormat: can not find the format string\n"));
3052       return false;
3053     }
3054     break;
3055   case 272:
3056     format.m_type=IWORK_CELL_TYPE_DATE_TIME;
3057     if (msg.string(18))
3058     {
3059       IWORKDateTimeFormat dtFormat;
3060       dtFormat.m_format=get(msg.string(18));
3061       format.m_format=dtFormat;
3062       return true;
3063     }
3064     ETONYEK_DEBUG_MSG(("IWAParser::parseFormat: can not find the date/time format\n"));
3065     return false;
3066   case 268:
3067   {
3068     format.m_type=IWORK_CELL_TYPE_DURATION;
3069     // read 7: style and then 15 and 16
3070     IWORKDurationFormat dFormat;
3071     dFormat.m_format="%H:%M:%S";
3072     format.m_format=dFormat;
3073     return true;
3074   }
3075   default:
3076     ETONYEK_DEBUG_MSG(("IWAParser::parseFormat: find unknown type\n"));
3077     return false;
3078   }
3079   IWORKNumberFormat nFormat;
3080   nFormat.m_type=nType;
3081   if (msg.uint32(2)) nFormat.m_decimalPlaces=int(get(msg.uint32(2)));
3082   else if (msg.uint32(9)) nFormat.m_decimalPlaces=int(get(msg.uint32(9)));
3083   if (nFormat.m_decimalPlaces>128) nFormat.m_decimalPlaces=-1; // 253 means automatic?
3084   if (msg.string(3)) nFormat.m_currencyCode=get(msg.string(3));
3085   if (msg.bool_(4)) nFormat.m_thousandsSeparator=get(msg.bool_(4));
3086   if (msg.bool_(5)) nFormat.m_accountingStyle=get(msg.bool_(5));
3087   if (msg.uint32(8)) nFormat.m_base=int(get(msg.uint32(8)));
3088   if (msg.uint32(11)) nFormat.m_fractionAccuracy=int(get(msg.uint32(11)));
3089   format.m_format=nFormat;
3090   return true;
3091 }
3092 
parseCustomFormat(unsigned id)3093 void IWAParser::parseCustomFormat(unsigned id)
3094 {
3095   const ObjectMessage msg(*this, id, IWAObjectType::CustomDateTimeFormat);
3096   if (!msg) return;
3097   auto const &uidLists = readUIDs(get(msg),1);
3098   auto const &formatList = get(msg).message(2).repeated();
3099   if (uidLists.size()!=formatList.size())
3100   {
3101     ETONYEK_DEBUG_MSG(("IWAParser::parseFormat: find unexpected data size\n"));
3102     return;
3103   }
3104   for (size_t i=0; i<uidLists.size(); ++i)
3105   {
3106     auto const &formatMsg=formatList[i];
3107     // 1: name, 2: type
3108     auto const &formatDef=formatMsg.message(3);
3109     Format format;
3110     if (!formatDef || !parseFormat(get(formatDef), format))
3111     {
3112       ETONYEK_DEBUG_MSG(("IWAParser::parseFormat: can not find string zone\n"));
3113       continue;
3114     }
3115     m_uidFormatMap[uidLists[i]]=format;
3116   }
3117 }
3118 
parseFormula(const IWAMessage & msg,IWORKFormulaPtr_t & formula)3119 bool IWAParser::parseFormula(const IWAMessage &msg, IWORKFormulaPtr_t &formula)
3120 {
3121   if (!msg.message(1))
3122   {
3123     ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not find the token table\n"));
3124     return false;
3125   }
3126   const deque<IWAMessage> &tokens = get(msg.message(1)).message(1).repeated();
3127 
3128   typedef std::vector<IWORKFormula::Token> Formula;
3129   std::vector<Formula> stack;
3130   bool ok=true;
3131   for (auto it : tokens)
3132   {
3133     auto type=it.uint32(1).optional();
3134     if (!type)
3135     {
3136       ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not find the token type\n"));
3137       ok=false;
3138       break;
3139     }
3140     switch (get(type))
3141     {
3142     case 16:
3143       if (it.uint32(2) && it.uint32(3))
3144       {
3145         static std::map<unsigned,std::string> functionsMap=
3146         {
3147           {1, "Abs"}, {2, "Accrint"}, {3, "AccrintM"}, {4, "Acos"}, {5, "Acosh"},
3148           {6, "IWORKFormula::Address"}, {7, "And"}, {8, "Areas"}, {9, "Asin"}, {10, "AsinH"},
3149           {11, "Atan"}, {12, "Atan2"}, {13, "AtanH"}, {14, "AverageDev"}, {15, "Average"},
3150           {16, "AverageA"}, {17, "Ceiling"}, {18, "Char"}, {19,"Choose"}, {20, "Clean"},
3151           {21, "Code"}, {22, "Column"}, {23, "Columns"}, {24, "ComBin"}, {25, "Concatenate"},
3152           {26, "Confidence"}, {27, "Correl"}, {28, "Cos"}, {29, "CosH"}, {30, "Count"},
3153           {31, "CountA"}, {32, "CountBlank"}, {33, "CountIf"}, {34, "CoupDayBs"}, {35, "CoupDays"},
3154           {36, "CoupDaySNC"}, {37, "CoupNum"}, {38, "CoVar"}, {39, "Date"}, {40, "DateDif"},
3155           {41, "Day"}, {42, "DB"}, {43, "DDB"}, {44, "Degrees"}, {45, "Disc"},
3156           {46, "Dollar"}, {47, "EDate"}, {48, "Even"}, {49, "Exact"}, {50, "Exp"},
3157           {51, "Fact"}, {52, "False"}, {53, "Find"}, {54, "Fixed"}, {55, "Floor"},
3158           {56, "Forecast"}, {57, "Frequency"}, {58, "GCD"}, {59, "HLookUp"}, {60, "Hour"},
3159           {61, "HyperLink"}, {62, "If"}, {63, "Index"}, {64, "Indirect"}, {65, "Int"},
3160           {66, "Intercept"}, {67, "IPMT"}, {68, "Irr"}, {69, "IsBlank"}, {70, "IsError"},
3161           {71, "IsEven"}, {72, "IsOdd"}, {73, "IsPMT"}, {74, "Large"}, {75, "LCM"},
3162           {76, "Left"}, {77, "Len"}, {78, "LN"}, {79, "Log"}, {80, "Log10"},
3163           {81, "LookUp"}, {82, "Lower"}, {83, "Match"}, {84, "Max"}, {85, "MaxA"},
3164           {86, "Median"}, {87, "Mid"}, {88, "Min"}, {89, "MinA"}, {90, "Minute"},
3165           {91, "Mirr"}, {92, "Mod"}, {93, "Mode"}, {94, "Month"}, {95, "MRound"},
3166           {96, "Not"}, {97, "Now"}, {98, "NPer"}, {99, "NPV"}, {100, "Odd"},
3167           {101, "Offset"}, {102, "Or"}, {103, "Percentile"}, {104, "Pi"}, {105, "PMT"},
3168           {106, "Poisson"}, {107, "Power"}, {108, "PPMT"}, {109, "Price"}, {110, "PriceDist"},
3169           {111, "PriceMat"}, {112, "Prob"}, {113, "Product"}, {114, "Proper"}, {115, "PV"},
3170           {116, "Quotient"}, {117, "Radians"}, {118, "Rand"}, {119, "RandBetween"}, {120, "Rank"},
3171           {121, "Rate"}, {122, "Replace"}, {123, "Repeat"}, {124, "Right"}, {125, "Roman"},
3172           {126, "Round"}, {127, "RoundDown"}, {128, "RoundUp"}, {129, "Row"}, {130, "Rows"},
3173           {131, "Search"}, {132, "Second"}, {133, "Sign"}, {134, "Sin"}, {135, "SinH"},
3174           {136, "SLN"}, {137, "Slope"}, {138, "Small"}, {139, "Sqrt"}, {140, "STDEV"},
3175           {141, "STDEVA"}, {142, "STDEVP"}, {143, "STDEVPA"}, {144, "Substitute"}, {145, "SumIf"},
3176           {146, "SumProduct"}, {147, "SumSqrt"}, {148, "Syd"}, {149, "T"}, {150, "Tan"},
3177           {151, "TanH"}, {152, "Time"}, {153, "TimeValue"}, {154, "Today"}, {155, "Trim"},
3178           {156, "True"}, {157, "Trunc"}, {158, "Upper"}, {159, "Value"}, {160, "Var"},
3179           {161, "VarA"}, {162, "VarP"}, {163, "VarPA"}, {164, "VDB"}, {165, "VLookup"},
3180           {166, "WeekDay"}, {167, "Year"}, {168, "Sum"},
3181           {185, "Effect"},
3182           {186, "Nominal"}, {187, "NormDist"}, {188, "NormsDist"}, {189, "NormInv"},  {190, "NormsInv"},
3183           {191, "Yield"}, {192, "YieldDist"}, {193, "YieldMat"}, {194, "BondDuration"}, {195, "BondMDuration"},
3184           {196, "Erf"}, {197, "ErfC"}, {198, "Standardize"}, {199, "IntRate"}, {200, "Received"},
3185           {201, "CUMIPMT"}, {202, "CUMPRINC"}, {203, "EOMonth"}, {204, "WorkDay"}, {205, "MonthName"},
3186           {206, "WeekNum"}, {207, "Dur2Hours"}, {208, "Dur2Minutes"}, {209, "Dur2Seconds"}, {210, "Dur2Days"},
3187           {211, "Dur2Weeks"}, {212, "Duration"}, {213, "ExpOnDist"}, {214, "YearFrac"}, {215, "ZTest"},
3188           {216, "SumX2MY2"}, {217, "SumX2PY2"}, {218, "SumXMY2"}, {219, "SqrtPi"}, {220, "Transpose"},
3189           {221, "DevSQ"}, {222, "FV"}, {223, "Delta"}, {224, "FactDouble"}, {225, "GEStep"},
3190           {226, "PercentRank"},{227, "GammaLN"},{228, "DateValue"},{229, "GammaDist"},{230, "GammaInv"},
3191           {231, "SumIfs"}, {232, "AverageIfs"}, {233, "CountIfs"}, {234, "AverageIf"}, {235, "IfError"},
3192           {236, "DayName"}, {237, "BesselJ"}, {238,"BesselY"}, {239,"LogNormDist"}, {240,"LogInv"},
3193           {241, "TDist"}, {242, "BinomDist"}, {243, "NegBinomDist"}, {244, "FDist"}, {245, "Permut"},
3194           {246, "ChiDist"}, {247, "ChiTest"}, {248, "TTest"}, {249, "Quartile"}, {250, "Multinomial"},
3195           {251, "CritBinom"}, {252, "BaseToNum"}, {253, "NumToBase"}, {254, "TInv"}, {255, "Convert"},
3196           {256, "ChiInv"}, {257, "FInv"}, {258, "BetaDist"}, {259, "BetaInv"}, {260, "NetWorkDays"},
3197           {261, "Days360"}, {262, "HarMean"}, {263, "GeoMin"}, {264, "Dec2Hex"}, {265, "Dec2Bin"},
3198           {266, "Dec2Oct"}, {267, "Bin2Hex"}, {268, "Bin2Dec"}, {269, "Bin2Oct"}, {270, "Oct2Bin"},
3199           {271, "Oct2Dec"}, {272, "Oct2Hex"}, {273, "Hex2Bin"}, {274, "Hex2Dec"}, {275, "Hex2Oct"},
3200           {276, "Linest"}, {277, "Dur2Milliseconds"}, {278, "StripDuration"}, {280, "Intercept.Ranges"},
3201           {285, "Union.Ranges"},
3202           {286, "SeriesSum"}, {287, "Polynomial"}, {288, "WeiBull"},
3203           {297, "PlainText"}, {298, "Stock"}, {299, "StockH"}, {300, "Currency"},
3204           {301, "CurrencyH"}, {302, "CurrencyConvert"}, {303, "CurrencyCode"}
3205         };
3206         Formula child;
3207         std::ostringstream s;
3208         if (functionsMap.find(get(it.uint32(2)))!=functionsMap.end())
3209           s << functionsMap.find(get(it.uint32(2)))->second;
3210         else
3211           s << "Funct" << get(it.uint32(2));
3212         child.push_back(IWORKFormula::Token(s.str(), IWORKFormula::Token::Function));
3213 
3214         size_t numArgs=get(it.uint32(3));
3215         size_t numData=stack.size();
3216         if (numData<numArgs)
3217         {
3218           ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: bad stack for function\n"));
3219           ok=false;
3220           break;
3221         }
3222         child.push_back(IWORKFormula::Token("(", IWORKFormula::Token::Operator));
3223         for (size_t i=numData-numArgs; i<numData; ++i)
3224         {
3225           if (i!=numData-numArgs) child.push_back(IWORKFormula::Token(";", IWORKFormula::Token::Operator));
3226           child.insert(child.end(), stack[i].begin(),stack[i].end());
3227         }
3228         child.push_back(IWORKFormula::Token(")", IWORKFormula::Token::Operator));
3229         stack.resize(numData-numArgs);
3230         stack.push_back(child);
3231       }
3232       else
3233       {
3234         ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not read a function\n"));
3235         ok=false;
3236       }
3237       break;
3238     case 17:
3239       if (it.double_(4))
3240         stack.push_back({IWORKFormula::Token(get(it.double_(4)))});
3241       else
3242       {
3243         ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not read a double\n"));
3244         ok=false;
3245       }
3246       break;
3247     case 18:
3248       if (it.bool_(5))
3249       {
3250         stack.push_back({IWORKFormula::Token(get(it.bool_(5)) ? "True" : "False", IWORKFormula::Token::Function),
3251                          IWORKFormula::Token("(", IWORKFormula::Token::Operator), IWORKFormula::Token(")", IWORKFormula::Token::Operator)
3252                         });
3253       }
3254       else
3255       {
3256         ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not read a bool\n"));
3257         ok=false;
3258       }
3259       break;
3260     case 19:
3261       if (it.string(6))
3262         stack.push_back({IWORKFormula::Token(get(it.string(6)),IWORKFormula::Token::String)});
3263       else
3264       {
3265         ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not read a string\n"));
3266         ok=false;
3267       }
3268       break;
3269     case 22: // empty?
3270       stack.push_back({});
3271       break;
3272     case 23:
3273       if (it.bool_(10))
3274         stack.push_back({});
3275       else
3276       {
3277         ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not read a optional argument\n"));
3278         ok=false;
3279       }
3280       break;
3281     case 25:
3282       if (it.uint32(13))
3283       {
3284         size_t numArgs=get(it.uint32(13));
3285         size_t numData=stack.size();
3286         if (numData<numArgs)
3287         {
3288           ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: bad stack for ()\n"));
3289           ok=false;
3290           break;
3291         }
3292         Formula child;
3293         child.push_back(IWORKFormula::Token("(", IWORKFormula::Token::Operator));
3294         for (size_t i=numData-numArgs; i<numData; ++i)
3295         {
3296           if (i!=numData-numArgs) child.push_back(IWORKFormula::Token(";", IWORKFormula::Token::Operator));
3297           child.insert(child.end(), stack[i].begin(),stack[i].end());
3298         }
3299         child.push_back(IWORKFormula::Token(")", IWORKFormula::Token::Operator));
3300         stack.resize(numData-numArgs);
3301         stack.push_back(child);
3302       }
3303       else
3304       {
3305         ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not read a ()\n"));
3306         ok=false;
3307       }
3308       break;
3309     case 32: // separator,
3310     case 33:
3311       break;
3312     case 36:
3313     {
3314       IWORKFormula::Address address;
3315       for (unsigned i=0; i<2; ++i)
3316       {
3317         auto pos=it.message(26+i);
3318         if (!pos) continue;
3319         if (!get(pos).sint32(1))
3320         {
3321           ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not read a position\n"));
3322           ok=false;
3323           break;
3324         }
3325         IWORKFormula::Coord coord;
3326         coord.m_absolute = get_optional_value_or(get(pos).bool_(2).optional(), false);
3327         coord.m_coord=get(get(pos).sint32(1))+1;
3328         if (i==0)
3329           address.m_column=coord;
3330         else
3331           address.m_row=coord;
3332       }
3333       address.m_table=readUUID(it,28);
3334       // readUUID(it,38); filename ?
3335       if (!ok)
3336         break;
3337       stack.push_back({IWORKFormula::Token(address)});
3338       break;
3339     }
3340     case 34: // arg begin
3341     case 35: // arg end
3342       break;
3343     default:
3344       if ((get(type)>=1 && get(type)<=15) || get(type)==29)
3345       {
3346         char const *wh[] =
3347         {
3348           nullptr, "+", "-", "*", "/",
3349           "^", "&", ">", ">=",
3350           "<", "<=", "=", "<>",
3351           "-", "+", "%"
3352         };
3353         if (get(type)!=29 && !wh[get(type)])
3354         {
3355           ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: find unexpected type=%u\n", get(type)));
3356           ok=false;
3357           break;
3358         }
3359         size_t numArgs=(get(type)<13 || get(type)==29) ? 2 : 1;
3360         size_t numData=stack.size();
3361         if (numData<numArgs)
3362         {
3363           ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: bad stack for type=%u\n", get(type)));
3364           ok=false;
3365           break;
3366         }
3367         Formula child;
3368         if (numArgs==2)
3369         {
3370           child.insert(child.end(), stack[numData-2].begin(),stack[numData-2].end());
3371           child.push_back(IWORKFormula::Token(get(type)==29 ? ":" : wh[get(type)], IWORKFormula::Token::Operator));
3372           child.insert(child.end(), stack[numData-1].begin(),stack[numData-1].end());
3373         }
3374         else if (get(type)<15)
3375         {
3376           child.push_back(IWORKFormula::Token(wh[get(type)], IWORKFormula::Token::Operator));
3377           child.insert(child.end(), stack[numData-1].begin(),stack[numData-1].end());
3378         }
3379         else
3380         {
3381           child.insert(child.end(), stack[numData-1].begin(),stack[numData-1].end());
3382           child.push_back(IWORKFormula::Token(wh[get(type)], IWORKFormula::Token::Operator));
3383         }
3384         stack.resize(numData-numArgs);
3385         stack.push_back(child);
3386         break;
3387       }
3388       ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: find unexpected type=%u\n", get(type)));
3389       ok=false;
3390       break;
3391     }
3392     if (!ok)
3393       break;
3394   }
3395   if (stack.size()!=1) ok=false;
3396   if (!ok)
3397   {
3398     std::ostringstream readData;
3399     for (auto const &form : stack)
3400       for (auto const &dt : form)
3401         readData << dt << ",";
3402     ETONYEK_DEBUG_MSG(("IWAParser::parseFormula: can not find parse a formula=%s\n", readData.str().c_str()));
3403   }
3404   else
3405   {
3406     formula.reset(new IWORKFormula(0));
3407     formula->parse(stack[0]);
3408   }
3409   return ok;
3410 }
3411 
parseLink(const unsigned id,std::string & url)3412 void IWAParser::parseLink(const unsigned id, std::string &url)
3413 {
3414   const ObjectMessage msg(*this, id, IWAObjectType::Link);
3415   if (!msg)
3416     return;
3417 
3418   if (get(msg).string(2))
3419     url = get(get(msg).string(2));
3420 }
3421 
3422 }
3423 
3424 /* vim:set shiftwidth=2 softtabstop=2 expandtab: */
3425