Coverage Report

Created: 2026-07-20 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libetonyek/src/lib/IWORKTable.cpp
Line
Count
Source
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 "IWORKTable.h"
11
12
#include <cassert>
13
#include <ctime>
14
#include <iomanip>
15
#include <set>
16
#include <sstream>
17
18
#include <boost/numeric/conversion/cast.hpp>
19
20
#include <glm/glm.hpp>
21
22
#include "libetonyek_xml.h"
23
#include "libetonyek_utils.h"
24
#include "IWORKDocumentInterface.h"
25
#include "IWORKProperties.h"
26
#include "IWORKStyle.h"
27
#include "IWORKStyleStack.h"
28
#include "IWORKTableRecorder.h"
29
#include "IWORKText.h"
30
#include "IWORKTypes.h"
31
32
using boost::none;
33
using boost::numeric_cast;
34
using boost::optional;
35
36
using std::string;
37
38
namespace libetonyek
39
{
40
41
namespace
42
{
43
44
void parseDateTimeFormat(std::string const &format, librevenge::RVNGPropertyList &props, boost::optional<std::string> &rvngValueType)
45
0
{
46
0
  if (format.empty())
47
0
  {
48
0
    ETONYEK_DEBUG_MSG(("parseDateTimeFormat[IWORKTable.cpp]: called without format string\n"));
49
0
    return;
50
0
  }
51
0
  std::string text("");
52
0
  librevenge::RVNGPropertyListVector propVect;
53
0
  size_t len=format.size();
54
0
  bool hasDate=false, findSomeToken=false, inText=false;
55
0
  for (size_t c=0; c < len;)
56
0
  {
57
0
    size_t numCh=1;
58
0
    char ch=format[c++];
59
0
    if (inText)
60
0
    {
61
0
      if (ch=='\'')
62
0
        inText=false;
63
0
      else
64
0
        text+=ch;
65
0
      continue;
66
0
    }
67
0
    if (ch=='\'')
68
0
    {
69
0
      inText=true;
70
0
      continue;
71
0
    }
72
0
    while (numCh<4 && c < len && format[c]==ch)
73
0
    {
74
0
      ++numCh;
75
0
      ++c;
76
0
    }
77
0
    librevenge::RVNGPropertyList list;
78
0
    switch (ch)
79
0
    {
80
0
    case 'a':
81
0
      list.insert("librevenge:value-type", "am-pm");
82
0
      c-=(numCh-1);
83
0
      break;
84
0
    case 'd':
85
0
      hasDate=true;
86
0
      list.insert("librevenge:value-type", "day");
87
0
      if (numCh>1) list.insert("number:style", "long");
88
0
      if (numCh>2) c-=(numCh-2);
89
0
      break;
90
0
    case 'h':
91
0
    case 'k':
92
0
      list.insert("librevenge:value-type", "hours");
93
0
      if (numCh>2) c-=(numCh-2);
94
0
      break;
95
0
    case 'm':
96
0
      list.insert("librevenge:value-type", "minutes");
97
0
      list.insert("number:style", "long");
98
0
      if (numCh>2) c-=(numCh-2);
99
0
      break;
100
0
    case 's':
101
0
      list.insert("librevenge:value-type", "seconds");
102
0
      list.insert("number:style", "long");
103
0
      if (numCh>2) c-=(numCh-2);
104
0
      break;
105
0
    case 'y':
106
0
      hasDate=true;
107
0
      list.insert("librevenge:value-type", "year");
108
0
      if (numCh>2) list.insert("number:style", "long");
109
0
      break;
110
0
    case 'E':
111
0
      hasDate=true;
112
0
      list.insert("librevenge:value-type", "day-of-week");
113
0
      if (numCh>3) list.insert("number:style", "long");
114
0
      break;
115
0
    case 'H':
116
0
    case 'K':
117
0
      list.insert("librevenge:value-type", "hours");
118
0
      list.insert("number:style", "long");
119
0
      if (numCh>2) c-=(numCh-2);
120
0
      break;
121
0
    case 'M':
122
0
      hasDate=true;
123
0
      list.insert("librevenge:value-type", "month");
124
0
      if ((numCh%2)==0) list.insert("number:style", "long");
125
0
      if (numCh>2) list.insert("number:textual", true);
126
0
      break;
127
    // G: Era designator?
128
0
    default:
129
0
      break;
130
0
    }
131
0
    if (!list.empty())
132
0
    {
133
0
      if (!text.empty())
134
0
      {
135
0
        librevenge::RVNGPropertyList tList;
136
0
        tList.insert("librevenge:value-type", "text");
137
0
        tList.insert("librevenge:text", text.c_str());
138
0
        propVect.append(tList);
139
0
      }
140
0
      text.clear();
141
0
      propVect.append(list);
142
0
      findSomeToken=true;
143
0
    }
144
0
    else
145
0
    {
146
0
      for (size_t n=0; n<numCh; ++n)
147
0
        text += ch;
148
0
    }
149
0
  }
150
0
  if (!findSomeToken || inText)
151
0
  {
152
0
    ETONYEK_DEBUG_MSG(("parseDateTimeFormat[IWORKTable.cpp]: something went bad when parsing %s\n", format.c_str()));
153
0
    return;
154
0
  }
155
0
  if (!text.empty())
156
0
  {
157
0
    librevenge::RVNGPropertyList list;
158
0
    list.insert("librevenge:value-type", "text");
159
0
    list.insert("librevenge:text", text.c_str());
160
0
    propVect.append(list);
161
0
  }
162
0
  rvngValueType = hasDate ? "date" : "time";
163
0
  props.insert("librevenge:value-type", get(rvngValueType).c_str());
164
0
  props.insert("number:automatic-order", "true");
165
0
  if (propVect.count())
166
0
    props.insert("librevenge:format", propVect);
167
0
}
168
169
void writeBorder(librevenge::RVNGPropertyList &props, const char *name, IWORKGridLine_t &line, unsigned index)
170
1.19k
{
171
1.19k
  if (!line.valid_tree())
172
197
    line.build_tree();
173
174
1.19k
  IWORKStylePtr_t style;
175
1.19k
  line.search_tree(index, style);
176
1.19k
  if (style && style->has<property::SFTStrokeProperty>())
177
194
    writeBorder(style->get<property::SFTStrokeProperty>(), name, props);
178
1.19k
}
179
180
void writeCellStyle(librevenge::RVNGPropertyList &props, const IWORKStyleStack &style)
181
2.88k
{
182
2.88k
  using namespace property;
183
184
2.88k
  IWORKVerticalAlignment vertAlign(IWORK_VERTICAL_ALIGNMENT_MIDDLE);
185
2.88k
  if (style.has<VerticalAlignment>())
186
1.08k
    vertAlign = style.get<VerticalAlignment>();
187
2.88k
  switch (vertAlign)
188
2.88k
  {
189
240
  case IWORK_VERTICAL_ALIGNMENT_TOP :
190
240
    props.insert("style:vertical-align", "top");
191
240
    break;
192
2.64k
  case IWORK_VERTICAL_ALIGNMENT_MIDDLE :
193
2.64k
    props.insert("style:vertical-align", "middle");
194
2.64k
    break;
195
0
  case IWORK_VERTICAL_ALIGNMENT_BOTTOM :
196
0
    props.insert("style:vertical-align", "bottom");
197
0
    break;
198
0
  default:
199
0
    ETONYEK_DEBUG_MSG(("writeCellStyle[IWORKTable.cpp]: unexpected alignement\n"));
200
2.88k
  }
201
202
2.88k
  if (style.has<TopBorder>())
203
0
    writeBorder(style.get<TopBorder>(), "fo:border-top", props);
204
2.88k
  if (style.has<BottomBorder>())
205
0
    writeBorder(style.get<BottomBorder>(), "fo:border-bottom", props);
206
2.88k
  if (style.has<LeftBorder>())
207
0
    writeBorder(style.get<LeftBorder>(), "fo:border-left", props);
208
2.88k
  if (style.has<RightBorder>())
209
0
    writeBorder(style.get<RightBorder>(), "fo:border-right", props);
210
211
2.88k
  if (style.has<Fill>())
212
1.24k
  {
213
    // TODO: add support for style:background-image to libodfgen
214
1.24k
    double opacity=style.has<Opacity>() ? style.get<Opacity>() : 1.;
215
1.24k
    if (const IWORKColor *const color = boost::get<IWORKColor>(&style.get<Fill>()))
216
1.23k
    {
217
1.23k
      props.insert("fo:background-color", makeColor(*color));
218
1.23k
      opacity *= color->m_alpha;
219
1.23k
    }
220
6
    else if (const IWORKGradient *const gradient = boost::get<IWORKGradient>(&style.get<Fill>()))
221
0
      props.insert("fo:background-color", makeColor(*gradient));
222
1.24k
    if (opacity<1)
223
151
      props.insert("draw:opacity", opacity, librevenge::RVNG_PERCENT);
224
1.24k
  }
225
226
2.88k
  if (style.has<Padding>())
227
1.04k
  {
228
1.04k
    const IWORKPadding &padding = style.get<Padding>();
229
1.04k
    if (padding.m_left)
230
1.04k
      props.insert("fo:padding-left", get(padding.m_left), librevenge::RVNG_POINT);
231
1.04k
    if (padding.m_right)
232
1.02k
      props.insert("fo:padding-right", get(padding.m_right), librevenge::RVNG_POINT);
233
1.04k
    if (padding.m_top)
234
1.03k
      props.insert("fo:padding-top", get(padding.m_top), librevenge::RVNG_POINT);
235
1.04k
    if (padding.m_bottom)
236
1.01k
      props.insert("fo:padding-bottom", get(padding.m_bottom), librevenge::RVNG_POINT);
237
1.04k
  }
238
2.88k
}
239
240
bool writeCellValue(librevenge::RVNGPropertyList &props,
241
                    const boost::optional<std::string> &styleName,
242
                    const IWORKCellType type, const boost::optional<std::string> &valueType,
243
                    const boost::optional<std::string> &value, const boost::optional<IWORKDateTimeData> &dateTime)
244
0
try
245
0
{
246
0
  using namespace property;
247
248
  // TODO: what's this anyway?
249
0
  if (styleName)
250
0
    props.insert("librevenge:name", get(styleName).c_str());
251
0
  switch (type)
252
0
  {
253
0
  case IWORK_CELL_TYPE_NUMBER :
254
0
    if (valueType)
255
0
    {
256
0
      props.insert("librevenge:value-type", get(valueType).c_str());
257
0
      props.insert("librevenge:value", value ? get(value).c_str() : "0");
258
0
      return true;
259
0
    }
260
0
    else if (value)
261
0
    {
262
0
      props.insert("librevenge:value-type", "double");
263
0
      props.insert("librevenge:value", get(value).c_str());
264
0
      return true;
265
0
    }
266
0
    break;
267
0
  case IWORK_CELL_TYPE_DATE_TIME :
268
0
    if (value || dateTime)
269
0
    {
270
0
      props.insert("librevenge:value-type", valueType ? get(valueType).c_str() : "date");
271
272
0
      if (dateTime)
273
0
      {
274
0
        props.insert("librevenge:day", get(dateTime).m_day);
275
0
        props.insert("librevenge:month", get(dateTime).m_month);
276
0
        props.insert("librevenge:year", get(dateTime).m_year);
277
0
        props.insert("librevenge:hours", get(dateTime).m_hour);
278
0
        props.insert("librevenge:minutes", get(dateTime).m_minute);
279
0
        props.insert("librevenge:seconds", get(dateTime).m_second);
280
0
        return true;
281
0
      }
282
0
      else
283
0
      {
284
0
        boost::optional<double> seconds=try_double_cast(get(value).c_str());
285
0
        if (!seconds)
286
0
        {
287
0
          ETONYEK_DEBUG_MSG(("writeCellValue[IWORKTable.cpp]: can not read seconds\n"));
288
0
          break;
289
0
        }
290
0
        const auto t = std::time_t(ETONYEK_EPOCH_BEGIN + get(seconds));
291
0
        struct tm *const time = gmtime(&t);
292
293
0
        if (!time)
294
0
        {
295
0
          ETONYEK_DEBUG_MSG(("writeCellValue[IWORKTable.cpp]: can not convert seconds in time\n"));
296
0
          break;
297
0
        }
298
0
        props.insert("librevenge:day", time->tm_mday);
299
0
        props.insert("librevenge:month", time->tm_mon + 1);
300
0
        props.insert("librevenge:year", time->tm_year + 1900);
301
0
        props.insert("librevenge:hours", time->tm_hour);
302
0
        props.insert("librevenge:minutes", time->tm_min);
303
0
        props.insert("librevenge:seconds", time->tm_sec);
304
0
        return true;
305
0
      }
306
0
    }
307
0
    break;
308
0
  case IWORK_CELL_TYPE_DURATION :
309
0
    if (value)
310
0
    {
311
0
      const int seconds = int(double_cast(get(value).c_str()));
312
0
      props.insert("librevenge:value-type", valueType ? get(valueType).c_str() : "time");
313
0
      props.insert("librevenge:hours", int(seconds / 3600));
314
0
      props.insert("librevenge:minutes", int((seconds % 3600) / 60));
315
0
      props.insert("librevenge:seconds", int((seconds % 3600) % 60));
316
0
      return true;
317
0
    }
318
0
    break;
319
0
  case IWORK_CELL_TYPE_BOOL :
320
0
    props.insert("librevenge:value-type", valueType ? get(valueType).c_str() : "boolean");
321
0
    props.insert("librevenge:value", value ? get(value).c_str() : "0");  // false is default
322
0
    return true;
323
0
  case IWORK_CELL_TYPE_TEXT :
324
0
  default:
325
    //TODO: librevenge:name ?
326
0
    if (value)
327
0
      props.insert("librevenge:value-type", "string");
328
0
    break;
329
0
  }
330
0
  return false;
331
0
}
332
0
catch (...)
333
0
{
334
0
  ETONYEK_DEBUG_MSG(("writeCellValue[IWORKTable.cpp]: catch exception\n"));
335
0
  return false;
336
0
}
337
338
librevenge::RVNGString convertCellValueInText(const IWORKStyleStack &style, const IWORKCellType type, const boost::optional<std::string> &value, const boost::optional<IWORKDateTimeData> &dateTime)
339
2.84k
{
340
2.84k
  try
341
2.84k
  {
342
2.84k
    using namespace property;
343
2.84k
    switch (type)
344
2.84k
    {
345
3
    case IWORK_CELL_TYPE_NUMBER :
346
3
    {
347
3
      int numDecimals=2;
348
3
      IWORKCellNumberType formatType=IWORK_CELL_NUMBER_TYPE_DOUBLE;
349
3
      std::string currency("$");
350
3
      if (style.has<SFTCellStylePropertyNumberFormat>())
351
0
      {
352
0
        const IWORKNumberFormat &format = style.get<SFTCellStylePropertyNumberFormat>();
353
0
        numDecimals=format.m_decimalPlaces;
354
0
        formatType=format.m_type;
355
0
        currency=format.m_currencyCode.c_str();
356
0
      }
357
3
      const double val = value ? double_cast(get(value).c_str()) : 0;
358
3
      std::stringstream s;
359
3
      if (numDecimals>=0 && numDecimals<100)  // 65535 means default
360
3
        s << std::setprecision(-numDecimals);
361
3
      switch (formatType)
362
3
      {
363
0
      case IWORK_CELL_NUMBER_TYPE_CURRENCY :
364
0
        s << std::fixed << val << currency;
365
0
        break;
366
0
      case IWORK_CELL_NUMBER_TYPE_PERCENTAGE :
367
0
        s << std::fixed << 100*val << "%";
368
0
        break;
369
0
      case IWORK_CELL_NUMBER_TYPE_SCIENTIFIC :
370
0
        s << std::scientific << val;
371
0
        break;
372
3
      case IWORK_CELL_NUMBER_TYPE_DOUBLE :
373
3
        s << val;
374
3
        break;
375
0
      case IWORK_CELL_NUMBER_TYPE_FRACTION :
376
0
      default:
377
0
        ETONYEK_DEBUG_MSG(("convertCellValueInText: unexpected number format\n"));
378
3
      }
379
3
      return s.str().c_str();
380
3
    }
381
0
    case IWORK_CELL_TYPE_DATE_TIME :
382
0
    {
383
0
      if (dateTime)
384
0
      {
385
0
        librevenge::RVNGString res;
386
0
        if (get(dateTime).m_hour)
387
0
          res.sprintf("%d/%d/%d %d:%d", get(dateTime).m_month, get(dateTime).m_day, get(dateTime).m_year, get(dateTime).m_hour, get(dateTime).m_minute);
388
0
        else
389
0
          res.sprintf("%d/%d/%d", get(dateTime).m_month, get(dateTime).m_day, get(dateTime).m_year);
390
0
        return res;
391
0
      }
392
0
      if (!value) break;
393
0
      boost::optional<double> seconds=try_double_cast(get(value).c_str());
394
0
      if (!seconds)
395
0
      {
396
0
        ETONYEK_DEBUG_MSG(("convertCellValueInText: can not read seconds\n"));
397
0
        break;
398
0
      }
399
0
      const auto t = std::time_t(ETONYEK_EPOCH_BEGIN + get(seconds));
400
0
      struct tm *const time = gmtime(&t);
401
0
      librevenge::RVNGString res;
402
0
      if (time->tm_hour)
403
0
        res.sprintf("%d/%d/%d %d:%d", time->tm_mon + 1, time->tm_mday, time->tm_year + 1900, time->tm_hour, time->tm_min);
404
0
      else
405
0
        res.sprintf("%d/%d/%d", time->tm_mon + 1, time->tm_mday, time->tm_year + 1900);
406
0
      return res;
407
0
    }
408
0
    case IWORK_CELL_TYPE_DURATION :
409
0
    {
410
0
      if (!value) break;
411
0
      const int seconds = int(double_cast(get(value).c_str()));
412
0
      librevenge::RVNGString res;
413
0
      res.sprintf("%d:%d:%d", int(seconds / 3600), int((seconds % 3600) / 60), int((seconds % 3600) % 60));
414
0
      return res;
415
0
    }
416
0
    case IWORK_CELL_TYPE_BOOL :
417
0
      return value && get(value)!="0" ? "true" : "false";
418
2.83k
    case IWORK_CELL_TYPE_TEXT :
419
2.83k
    default:
420
2.83k
      break;
421
2.84k
    }
422
2.84k
  }
423
2.84k
  catch (...)
424
2.84k
  {
425
0
    ETONYEK_DEBUG_MSG(("convertCellValueInText: exception\n"));
426
0
  }
427
2.83k
  return "";
428
2.84k
}
429
430
}
431
432
IWORKTable::Cell::Cell()
433
1.06k
  : m_content()
434
1.06k
  , m_columnSpan(1)
435
1.06k
  , m_rowSpan(1)
436
1.06k
  , m_covered(false)
437
1.06k
  , m_formula()
438
1.06k
  , m_formulaHC()
439
1.06k
  , m_style()
440
1.06k
  , m_type(IWORK_CELL_TYPE_TEXT)
441
1.06k
  , m_value()
442
1.06k
  , m_dateTime()
443
1.06k
{
444
1.06k
}
445
446
IWORKTable::IWORKTable(const IWORKTableNameMapPtr_t &tableNameMap, IWORKFormatNameMap &formatNameMap, const IWORKLanguageManager &langManager)
447
147
  : m_tableNameMap(tableNameMap)
448
147
  , m_langManager(langManager)
449
147
  , m_formatNameMap(formatNameMap)
450
147
  , m_commentMap()
451
147
  , m_table()
452
147
  , m_style()
453
147
  , m_name()
454
147
  , m_order()
455
147
  , m_columnSizes()
456
147
  , m_rowSizes()
457
147
  , m_verticalLines()
458
147
  , m_verticalRightLines()
459
147
  , m_horizontalLines()
460
147
  , m_horizontalBottomLines()
461
147
  , m_rows(0)
462
147
  , m_columns(0)
463
147
  , m_headerRows(0)
464
147
  , m_footerRows(0)
465
147
  , m_headerColumns(0)
466
147
  , m_bandedRows(false)
467
147
  , m_headerRowsRepeated(false)
468
147
  , m_headerColumnsRepeated(false)
469
147
  , m_recorder()
470
147
{
471
147
}
472
473
void IWORKTable::setRecorder(const std::shared_ptr<IWORKTableRecorder> &recorder)
474
0
{
475
0
  m_recorder = recorder;
476
0
}
477
478
const std::shared_ptr<IWORKTableRecorder> &IWORKTable::getRecorder() const
479
0
{
480
0
  return m_recorder;
481
0
}
482
483
void IWORKTable::setName(std::string const &name)
484
125
{
485
125
  m_name=name;
486
125
}
487
488
void IWORKTable::setSize(const unsigned columns, const unsigned rows)
489
147
{
490
147
  if (bool(m_recorder))
491
0
  {
492
0
    m_recorder->setSize(columns, rows);
493
0
    return;
494
0
  }
495
496
147
  m_columns = columns;
497
147
  m_rows = rows;
498
147
}
499
500
void IWORKTable::setHeaders(const unsigned headerColumns, const unsigned headerRows, const unsigned footerRows)
501
127
{
502
127
  if (bool(m_recorder))
503
0
  {
504
0
    m_recorder->setHeaders(headerColumns, headerRows, footerRows);
505
0
    return;
506
0
  }
507
508
127
  m_headerColumns = headerColumns;
509
127
  m_headerRows = headerRows;
510
127
  m_footerRows = footerRows;
511
127
}
512
513
void IWORKTable::setBandedRows(const bool banded)
514
37
{
515
37
  if (bool(m_recorder))
516
0
  {
517
0
    m_recorder->setBandedRows(banded);
518
0
    return;
519
0
  }
520
521
37
  m_bandedRows = banded;
522
37
}
523
524
void IWORKTable::setRepeated(const bool columns, const bool rows)
525
126
{
526
126
  if (bool(m_recorder))
527
0
  {
528
0
    m_recorder->setRepeated(columns, rows);
529
0
    return;
530
0
  }
531
532
126
  m_headerColumnsRepeated = columns;
533
126
  m_headerRowsRepeated = rows;
534
126
}
535
536
void IWORKTable::setOrder(int order)
537
0
{
538
0
  if (bool(m_recorder))
539
0
  {
540
0
    m_recorder->setOrder(order);
541
0
    return;
542
0
  }
543
544
0
  m_order = order;
545
0
}
546
547
void IWORKTable::setStyle(const IWORKStylePtr_t &style)
548
48
{
549
48
  if (bool(m_recorder))
550
0
  {
551
0
    m_recorder->setStyle(style);
552
0
    return;
553
0
  }
554
555
48
  m_style = style;
556
48
}
557
558
void IWORKTable::setSizes(const IWORKColumnSizes_t &columnSizes, const IWORKRowSizes_t &rowSizes)
559
128
{
560
128
  if (bool(m_recorder))
561
0
  {
562
0
    m_recorder->setSizes(columnSizes, rowSizes);
563
0
    return;
564
0
  }
565
566
128
  m_columnSizes = columnSizes;
567
128
  m_rowSizes = rowSizes;
568
569
  // init. content table of appropriate dimensions
570
128
  m_table = Table_t(m_rowSizes.size(), Row_t());
571
128
}
572
573
void IWORKTable::setBorders(const IWORKGridLineMap_t &verticalLines, const IWORKGridLineMap_t &horizontalLines)
574
0
{
575
0
  if (bool(m_recorder))
576
0
  {
577
0
    m_recorder->setBorders(verticalLines, horizontalLines);
578
0
    return;
579
0
  }
580
581
0
  m_verticalLines = verticalLines;
582
0
  m_horizontalLines = horizontalLines;
583
0
}
584
585
void IWORKTable::setComment(unsigned column, unsigned row, IWORKOutputElements const &text)
586
0
{
587
0
  if (bool(m_recorder))
588
0
  {
589
0
    m_recorder->setComment(column,row,text);
590
0
    return;
591
0
  }
592
0
  m_commentMap[std::make_pair(row,column)]=text;
593
0
}
594
595
void IWORKTable::setBorders(const IWORKGridLineMap_t &verticalLeftLines, const IWORKGridLineMap_t &verticalRightLines,
596
                            const IWORKGridLineMap_t &horizontalTopLines, const IWORKGridLineMap_t &horizontalBottomLines)
597
108
{
598
108
  if (bool(m_recorder))
599
0
  {
600
0
    m_recorder->setBorders(verticalLeftLines, verticalRightLines, horizontalTopLines, horizontalBottomLines);
601
0
    return;
602
0
  }
603
604
108
  m_verticalLines = verticalLeftLines;
605
108
  m_verticalRightLines = verticalRightLines;
606
108
  m_horizontalLines = horizontalTopLines;
607
108
  m_horizontalBottomLines = horizontalBottomLines;
608
108
}
609
610
void IWORKTable::insertCell(const unsigned column, const unsigned row, const boost::optional<std::string> &value, const std::shared_ptr<IWORKText> &text, const boost::optional<IWORKDateTimeData> &dateTime, const unsigned columnSpan, const unsigned rowSpan, const IWORKFormulaPtr_t &formula, const boost::optional<unsigned> &formulaHC, const IWORKStylePtr_t &style, const IWORKCellType type)
611
179
{
612
179
  if (bool(m_recorder))
613
0
  {
614
0
    m_recorder->insertCell(column, row, value, text, dateTime, columnSpan, rowSpan, formula, formulaHC, style, type);
615
0
    return;
616
0
  }
617
618
179
  if ((m_rowSizes.size() <= row) || (m_columnSizes.size() <= column))
619
5
    return;
620
621
174
  Cell cell;
622
174
  if (bool(text))
623
38
  {
624
38
    IWORKStyleStack fStyle;
625
38
    fStyle.push(getDefaultCellStyle(column, row));
626
38
    fStyle.push(style);
627
38
    using namespace property;
628
38
    if (fStyle.has<SFTCellStylePropertyParagraphStyle>())
629
0
      text->pushBaseParagraphStyle(fStyle.get<SFTCellStylePropertyParagraphStyle>());
630
38
    else
631
38
      text->pushBaseParagraphStyle(getDefaultParagraphStyle(column,row));
632
38
    if (fStyle.has<SFTCellStylePropertyLayoutStyle>())
633
0
      text->pushBaseLayoutStyle(fStyle.get<SFTCellStylePropertyLayoutStyle>());
634
38
    else
635
38
      text->pushBaseLayoutStyle(getDefaultLayoutStyle(column,row));
636
38
    text->draw(cell.m_content);
637
38
  }
638
174
  cell.m_columnSpan = columnSpan;
639
174
  cell.m_rowSpan = rowSpan;
640
174
  cell.m_formula = formula;
641
174
  cell.m_formulaHC = formulaHC;
642
174
  cell.m_style = style;
643
174
  cell.m_type = type;
644
174
  cell.m_value = value;
645
174
  cell.m_dateTime = dateTime;
646
174
  m_table[row][column] = cell;
647
174
}
648
649
void IWORKTable::insertCoveredCell(const unsigned column, const unsigned row)
650
0
{
651
0
  if (bool(m_recorder))
652
0
  {
653
0
    m_recorder->insertCoveredCell(column, row);
654
0
    return;
655
0
  }
656
657
0
  if ((m_rowSizes.size() <= row) || (m_columnSizes.size() <= column))
658
0
    return;
659
660
0
  Cell cell;
661
0
  cell.m_covered = true;
662
0
  m_table[row][column] = cell;
663
0
}
664
665
boost::optional<std::string> IWORKTable::writeFormat(IWORKOutputElements &elements, const IWORKStylePtr_t &style, const IWORKCellType type, boost::optional<std::string> &rvngValueType)
666
0
{
667
0
  if (!style) return none;
668
0
  librevenge::RVNGPropertyList props;
669
0
  switch (type)
670
0
  {
671
0
  case IWORK_CELL_TYPE_NUMBER :
672
0
    if (style->has<property::SFTCellStylePropertyNumberFormat>())
673
0
    {
674
0
      const IWORKNumberFormat &format = style->get<property::SFTCellStylePropertyNumberFormat>();
675
0
      props.insert("librevenge:value-type", format.getRVNGValueType().c_str());
676
0
      if (format.m_decimalPlaces>=0 && format.m_decimalPlaces<100) props.insert("number:decimal-places", format.m_decimalPlaces);
677
0
      if (format.m_thousandsSeparator) props.insert("number:grouping", true);
678
0
      rvngValueType=format.m_type==IWORK_CELL_NUMBER_TYPE_FRACTION ? "double" : format.getRVNGValueType().c_str();
679
0
      switch (format.m_type)
680
0
      {
681
0
      case IWORK_CELL_NUMBER_TYPE_CURRENCY :
682
0
      {
683
0
        librevenge::RVNGPropertyListVector pVect;
684
0
        librevenge::RVNGPropertyList currency;
685
0
        currency.insert("librevenge:value-type", "currency-symbol");
686
0
        currency.insert("number:language","en");
687
0
        currency.insert("number:country","US");
688
0
        currency.insert("librevenge:currency",format.m_currencyCode.c_str());
689
0
        if (format.m_decimalPlaces>=0) currency.insert("number:decimal-places", format.m_decimalPlaces);
690
0
        pVect.append(currency);
691
0
        props.insert("librevenge:format", pVect);
692
0
        break;
693
0
      }
694
0
      case IWORK_CELL_NUMBER_TYPE_PERCENTAGE :
695
0
      case IWORK_CELL_NUMBER_TYPE_SCIENTIFIC :
696
0
      case IWORK_CELL_NUMBER_TYPE_DOUBLE :
697
0
        break;
698
0
      case IWORK_CELL_NUMBER_TYPE_FRACTION :
699
0
        props.insert("number:min-integer-digits", 0);
700
0
        props.insert("number:min-numerator-digits",1);
701
0
        props.insert("number:min-denominator-digits", 1);
702
0
        props.remove("number:decimal-places");
703
0
        break;
704
0
      default:
705
0
        ETONYEK_DEBUG_MSG(("IWORKTable::writeFormat: unexpected number format\n"));
706
0
        return none;
707
0
      }
708
0
    }
709
0
    break;
710
0
  case IWORK_CELL_TYPE_DATE_TIME : // todo
711
0
    if (style->has<property::SFTCellStylePropertyDateTimeFormat>())
712
0
    {
713
0
      const IWORKDateTimeFormat &format = style->get<property::SFTCellStylePropertyDateTimeFormat>();
714
0
      parseDateTimeFormat(format.m_format, props, rvngValueType);
715
0
    }
716
0
    break;
717
0
  case IWORK_CELL_TYPE_DURATION : // todo
718
  // TODO: How to insert format and convert?
719
  // const IWORKDurationFormat &format = style->get<SFTCellStylePropertyDurationFormat>();
720
  // props.insert("librevenge:format",format.m_format.c_str());
721
722
0
  case IWORK_CELL_TYPE_BOOL :
723
0
  case IWORK_CELL_TYPE_TEXT :
724
0
  default:
725
0
    break;
726
0
  }
727
0
  if (props.empty())
728
0
    return none;
729
0
  std::string hash=props.getPropString().cstr();
730
0
  auto it=m_formatNameMap.find(hash);
731
0
  if (it!=m_formatNameMap.end())
732
0
    return it->second;
733
0
  std::stringstream name;
734
0
  name << "Numbering" << m_formatNameMap.size();
735
0
  m_formatNameMap[hash]=name.str();
736
0
  props.insert("librevenge:name", name.str().c_str());
737
0
  elements.addDefineSheetNumberingStyle(props);
738
0
  return name.str();
739
0
}
740
741
void IWORKTable::draw(const librevenge::RVNGPropertyList &tableProps, IWORKOutputElements &elements, bool drawAsSimpleTable)
742
112
{
743
112
  assert(!m_recorder);
744
745
112
  librevenge::RVNGPropertyList allTableProps(tableProps);
746
112
  if (m_name)
747
110
    allTableProps.insert("librevenge:sheet-name", get(m_name).c_str());
748
749
112
  librevenge::RVNGPropertyListVector columnSizes;
750
560
  for (IWORKColumnSizes_t::const_iterator it = m_columnSizes.begin(); m_columnSizes.end() != it; ++it)
751
448
  {
752
448
    librevenge::RVNGPropertyList column;
753
448
    if (!it->m_size)
754
141
      ;
755
307
    else if (it->m_exactSize)
756
307
      column.insert("style:column-width", pt2in(get(it->m_size)));
757
0
    else
758
0
      column.insert("style:min-column-width", pt2in(get(it->m_size)));
759
448
    columnSizes.append(column);
760
448
  }
761
762
112
  allTableProps.insert(drawAsSimpleTable ? "librevenge:table-columns" : "librevenge:columns", columnSizes);
763
764
112
  elements.addOpenTable(allTableProps);
765
112
  unsigned numColumns=unsigned(m_columnSizes.size());
766
112
  std::set<unsigned> colSet;
767
112
  if (drawAsSimpleTable)
768
112
  {
769
    // repeat does not work correctly in table, so draw always each cell
770
560
    for (unsigned col=0; col<numColumns; ++col)
771
448
      colSet.insert(col);
772
112
  }
773
832
  for (std::size_t r = 0; m_table.size() != r; ++r)
774
720
  {
775
720
    const Row_t &row = m_table[r];
776
777
720
    librevenge::RVNGPropertyList rowProps;
778
720
    auto const &rSize=m_rowSizes[r];
779
720
    if (rSize.m_size && rSize.m_exactSize)
780
717
      rowProps.insert("style:row-height", pt2in(get(rSize.m_size)));
781
3
    else if (rSize.m_size)
782
3
      rowProps.insert("style:min-row-height", pt2in(get(rSize.m_size)));
783
720
    if (r < m_headerRows)
784
109
      rowProps.insert("librevenge:is-header-row", true);
785
786
720
    elements.addOpenTableRow(rowProps);
787
788
720
    if (!drawAsSimpleTable)
789
0
    {
790
      // first compute the list of column that we will need to display
791
0
      colSet.clear();
792
0
      colSet.insert(0);
793
0
      for (auto const &cIt : row)   // cells
794
0
      {
795
0
        colSet.insert(cIt.first);
796
0
        colSet.insert(cIt.first+1);
797
0
      }
798
0
      for (auto const &vIt : m_verticalLines)   // vertical lines
799
0
      {
800
0
        colSet.insert(vIt.first);
801
0
        colSet.insert(vIt.first+1);
802
0
      }
803
0
      for (auto const &vIt : m_verticalRightLines)
804
0
      {
805
0
        colSet.insert(vIt.first);
806
0
        colSet.insert(vIt.first+1);
807
0
      }
808
0
      if (bool(m_defaultCellStyles[CELL_TYPE_COLUMN_HEADER]) ||
809
0
          bool(m_defaultLayoutStyles[CELL_TYPE_COLUMN_HEADER]) ||
810
0
          bool(m_defaultParaStyles[CELL_TYPE_COLUMN_HEADER])) // default style
811
0
        colSet.insert(m_headerColumns);
812
0
      auto commentIt=m_commentMap.lower_bound(std::make_pair(r,0));
813
0
      while (commentIt!=m_commentMap.end() && commentIt->first.first==r)   // comments
814
0
      {
815
0
        colSet.insert(commentIt->first.second);
816
0
        colSet.insert(commentIt->first.second+1);
817
0
        ++commentIt;
818
0
      }
819
0
    }
820
720
    Cell const emptyCell;
821
3.60k
    for (auto colIt=colSet.begin(); colIt!=colSet.end();)
822
2.88k
    {
823
2.88k
      unsigned col=*(colIt++);
824
2.88k
      if (col>=numColumns)
825
0
        break;
826
2.88k
      auto const &cIt=row.find(col);
827
2.88k
      const Cell &cell = cIt==row.end() ? emptyCell : cIt->second;
828
2.88k
      librevenge::RVNGPropertyList cellProps;
829
2.88k
      cellProps.insert("librevenge:column", numeric_cast<int>(col));
830
2.88k
      cellProps.insert("librevenge:row", numeric_cast<int>(r));
831
2.88k
      unsigned numRepeat=colIt!=colSet.end() ? *colIt-col : numColumns-col;
832
2.88k
      if (numRepeat>1)
833
0
        cellProps.insert("table:number-columns-repeated", numeric_cast<int>(numRepeat));
834
835
2.88k
      using namespace property;
836
2.88k
      unsigned const rMax= unsigned(r+ std::max(unsigned(1),cell.m_rowSpan));
837
2.88k
      unsigned const cMax= unsigned(col+std::max(unsigned(1),cell.m_columnSpan));
838
2.88k
      if (m_horizontalLines.find(unsigned(r))!=m_horizontalLines.end())
839
316
        writeBorder(cellProps, "fo:border-top", m_horizontalLines.find(unsigned(r))->second, col);
840
2.88k
      if (!m_horizontalBottomLines.empty())
841
1.44k
      {
842
1.44k
        if (m_horizontalBottomLines.find(rMax-1)!=m_horizontalBottomLines.end())
843
160
          writeBorder(cellProps, "fo:border-bottom", m_horizontalBottomLines.find(rMax-1)->second, col);
844
1.44k
      }
845
1.44k
      else if (m_horizontalLines.find(rMax)!=m_horizontalLines.end())
846
0
        writeBorder(cellProps, "fo:border-bottom", m_horizontalLines.find(rMax)->second, col);
847
2.88k
      if (m_verticalLines.find(col)!=m_verticalLines.end())
848
355
        writeBorder(cellProps, "fo:border-left", m_verticalLines.find(col)->second, unsigned(r));
849
2.88k
      if (!m_verticalRightLines.empty())
850
1.42k
      {
851
1.42k
        if (m_verticalRightLines.find(cMax-1)!=m_verticalRightLines.end())
852
355
          writeBorder(cellProps, "fo:border-right", m_verticalRightLines.find(cMax-1)->second, unsigned(r));
853
1.42k
      }
854
1.46k
      else if (m_verticalLines.find(cMax)!=m_verticalLines.end())
855
5
        writeBorder(cellProps, "fo:border-right", m_verticalLines.find(cMax)->second, unsigned(r));
856
857
2.88k
      if (cell.m_covered)
858
0
      {
859
0
        elements.addInsertCoveredTableCell(cellProps);
860
0
      }
861
2.88k
      else
862
2.88k
      {
863
2.88k
        if (1 < cell.m_columnSpan)
864
0
          cellProps.insert("table:number-columns-spanned", numeric_cast<int>(cell.m_columnSpan));
865
2.88k
        if (1 < cell.m_rowSpan)
866
0
          cellProps.insert("table:number-rows-spanned", numeric_cast<int>(cell.m_rowSpan));
867
868
2.88k
        IWORKStyleStack style;
869
2.88k
        style.push(getDefaultCellStyle(col, unsigned(r)));
870
2.88k
        style.push(cell.m_style);
871
2.88k
        if (!drawAsSimpleTable)
872
0
        {
873
0
          optional<std::string> valueType;
874
0
          auto formatName=writeFormat(elements, cell.m_style, cell.m_type, valueType);
875
0
          if (formatName) cellProps.insert("librevenge:numbering-name", get(formatName).c_str());
876
          // do not add a 0 value if the cell is empty
877
0
          if (cell.m_type==IWORK_CELL_TYPE_NUMBER && !bool(cell.m_value))
878
0
            valueType.reset();
879
0
          writeCellValue(cellProps, cell.m_style ? cell.m_style->getIdent() : none,
880
0
                         cell.m_type, valueType, cell.m_value, cell.m_dateTime);
881
0
        }
882
2.88k
        writeCellStyle(cellProps, style);
883
884
2.88k
        IWORKStyleStack pStyle;
885
2.88k
        pStyle.push(getDefaultParagraphStyle(col,unsigned(r)));
886
2.88k
        if (style.has<SFTCellStylePropertyParagraphStyle>())
887
0
          pStyle.push(style.get<SFTCellStylePropertyParagraphStyle>());
888
2.88k
        IWORKText::fillCharPropList(pStyle, m_langManager, cellProps);
889
890
2.88k
        if (!drawAsSimpleTable && cell.m_formula)
891
0
          elements.addOpenFormulaCell(cellProps, *cell.m_formula, cell.m_formulaHC, m_tableNameMap);
892
2.88k
        else
893
2.88k
          elements.addOpenTableCell(cellProps);
894
895
2.88k
        if (!drawAsSimpleTable && style.has<property::Fill>())
896
0
        {
897
          // look for a picture in a cell
898
          // FIXME: we must do the same for basic table, but the code
899
          //   must be different in odp(no frame) and in odt(frame ok)
900
0
          try
901
0
          {
902
0
            auto const &media=boost::get<IWORKMediaContent>(style.get<property::Fill>());
903
0
            if (media.m_data && media.m_data->m_stream)
904
0
            {
905
0
              auto input=media.m_data->m_stream;
906
0
              string mimetype(media.m_data->m_mimeType);
907
0
              if (mimetype.empty())
908
0
                mimetype = detectMimetype(input);
909
0
              if (!mimetype.empty())
910
0
              {
911
0
                input->seek(0, librevenge::RVNG_SEEK_END);
912
0
                const auto size = (unsigned long) input->tell();
913
0
                input->seek(0, librevenge::RVNG_SEEK_SET);
914
915
0
                unsigned long readBytes = 0;
916
0
                const unsigned char *const bytes = input->read(size, readBytes);
917
0
                if (readBytes != size)
918
0
                  throw GenericException();
919
920
0
                librevenge::RVNGPropertyList frameProps;
921
0
                for (int wh=0; wh<2; ++wh)
922
0
                {
923
0
                  double dim=0;
924
0
                  bool ok=true;
925
0
                  auto const &sizes=wh==0 ? m_columnSizes : m_rowSizes;
926
0
                  for (size_t rr=(wh==0 ? col : r); ok && rr<std::min(size_t(wh==0 ? cMax : rMax),sizes.size()); ++rr)
927
0
                  {
928
0
                    if (sizes[rr].m_size && *sizes[rr].m_size>=0)
929
0
                      dim+=*sizes[rr].m_size;
930
0
                    else
931
0
                      ok=false;
932
0
                  }
933
0
                  if (ok)
934
0
                    frameProps.insert(wh==0 ? "svg:width" : "svg:height", pt2in(dim));
935
0
                }
936
0
                unsigned col2=cMax;
937
0
                std::string column(1, char(col2%26+'A'));
938
0
                col2 /= 26;
939
0
                while (col2>0)
940
0
                {
941
0
                  --col2;
942
0
                  column.insert(0, std::string(1,char(col2%26+'A')));
943
0
                  col2 /= 26;
944
0
                }
945
0
                librevenge::RVNGString endCellName;
946
0
                endCellName.sprintf("%s%d",column.c_str(), int(rMax));
947
0
                frameProps.insert("table:end-cell-address", endCellName);
948
0
                frameProps.insert("table:table-background", true);
949
0
                elements.addOpenFrame(frameProps);
950
0
                librevenge::RVNGPropertyList imageProps;
951
0
                imageProps.insert("librevenge:mime-type", mimetype.c_str());
952
0
                imageProps.insert("office:binary-data", librevenge::RVNGBinaryData(bytes, size));
953
0
                elements.addInsertBinaryObject(imageProps);
954
0
                elements.addCloseFrame();
955
0
              }
956
0
              else
957
0
              {
958
0
                ETONYEK_DEBUG_MSG(("IWORKTable::draw: can not find mimetype for some image\n"));
959
0
              }
960
0
            }
961
0
          }
962
0
          catch (...)
963
0
          {
964
0
          }
965
0
        }
966
967
2.88k
        if (!cell.m_content.empty() && cell.m_type!=IWORK_CELL_TYPE_DATE_TIME && cell.m_type!=IWORK_CELL_TYPE_DURATION)
968
38
          elements.append(cell.m_content);
969
2.84k
        else if (drawAsSimpleTable)
970
2.84k
        {
971
2.84k
          librevenge::RVNGString value=convertCellValueInText(style, cell.m_type, cell.m_value, cell.m_dateTime);
972
2.84k
          if (!value.empty())
973
3
          {
974
3
            librevenge::RVNGPropertyList const empty;
975
3
            elements.addOpenParagraph(empty);
976
3
            elements.addOpenSpan(empty);
977
3
            elements.addInsertText(value);
978
3
            elements.addCloseSpan();
979
3
            elements.addCloseParagraph();
980
3
          }
981
2.84k
        }
982
2.88k
        auto nIt=m_commentMap.find(std::make_pair(r,col));
983
2.88k
        if (nIt!=m_commentMap.end())
984
0
        {
985
0
          elements.addOpenComment(librevenge::RVNGPropertyList());
986
0
          elements.append(nIt->second);
987
0
          elements.addCloseComment();
988
0
        }
989
2.88k
        elements.addCloseTableCell();
990
2.88k
      }
991
2.88k
    }
992
720
    elements.addCloseTableRow();
993
720
  }
994
112
  elements.addCloseTable();
995
112
}
996
997
void IWORKTable::setDefaultCellStyle(const CellType type, const IWORKStylePtr_t &style)
998
509
{
999
509
  if (bool(m_recorder))
1000
0
  {
1001
0
    m_recorder->setDefaultCellStyle(type, style);
1002
0
    return;
1003
0
  }
1004
1005
509
  assert(type < ETONYEK_NUM_ELEMENTS(m_defaultCellStyles));
1006
509
  m_defaultCellStyles[type] = style;
1007
509
}
1008
1009
void IWORKTable::setDefaultLayoutStyle(const CellType type, const IWORKStylePtr_t &style)
1010
0
{
1011
0
  if (bool(m_recorder))
1012
0
  {
1013
0
    m_recorder->setDefaultLayoutStyle(type, style);
1014
0
    return;
1015
0
  }
1016
1017
0
  assert(type < ETONYEK_NUM_ELEMENTS(m_defaultLayoutStyles));
1018
0
  m_defaultLayoutStyles[type] = style;
1019
0
}
1020
1021
void IWORKTable::setDefaultParagraphStyle(const CellType type, const IWORKStylePtr_t &style)
1022
452
{
1023
452
  if (bool(m_recorder))
1024
0
  {
1025
0
    m_recorder->setDefaultParagraphStyle(type, style);
1026
0
    return;
1027
0
  }
1028
1029
452
  assert(type < ETONYEK_NUM_ELEMENTS(m_defaultParaStyles));
1030
452
  m_defaultParaStyles[type] = style;
1031
452
}
1032
1033
boost::optional<int> IWORKTable::getOrder() const
1034
37
{
1035
37
  return m_order;
1036
37
}
1037
1038
IWORKStylePtr_t IWORKTable::getStyle() const
1039
149
{
1040
149
  return m_style;
1041
149
}
1042
1043
IWORKStylePtr_t IWORKTable::getDefaultCellStyle(unsigned column, unsigned row) const
1044
2.91k
{
1045
2.91k
  return getDefaultStyle(column, row, m_defaultCellStyles);
1046
2.91k
}
1047
1048
IWORKStylePtr_t IWORKTable::getDefaultLayoutStyle(unsigned column, unsigned row) const
1049
79
{
1050
79
  return getDefaultStyle(column, row, m_defaultLayoutStyles);
1051
79
}
1052
1053
IWORKStylePtr_t IWORKTable::getDefaultParagraphStyle(unsigned column, unsigned row) const
1054
2.95k
{
1055
2.95k
  return getDefaultStyle(column, row, m_defaultParaStyles);
1056
2.95k
}
1057
1058
IWORKStylePtr_t IWORKTable::getDefaultStyle(const unsigned column, const unsigned row, const IWORKStylePtr_t *const group) const
1059
5.95k
{
1060
5.95k
  if ((row < m_headerRows) && bool(group[CELL_TYPE_ROW_HEADER]))
1061
395
    return group[CELL_TYPE_ROW_HEADER];
1062
5.56k
  else if (m_footerRows>0 && ((m_rows - row) <= m_footerRows) && bool(group[CELL_TYPE_ROW_FOOTER]))
1063
0
    return group[CELL_TYPE_ROW_FOOTER];
1064
5.56k
  else if ((column < m_headerColumns) && bool(group[CELL_TYPE_COLUMN_HEADER]))
1065
567
    return group[CELL_TYPE_COLUMN_HEADER];
1066
4.99k
  else if (m_bandedRows && ((row-m_headerRows) % 2 == 1) && bool(group[CELL_TYPE_ALTERNATE_BODY]))
1067
240
    return group[CELL_TYPE_ALTERNATE_BODY];
1068
4.75k
  else
1069
4.75k
    return group[CELL_TYPE_BODY];
1070
5.95k
}
1071
1072
}
1073
1074
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */