1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* libwps
3  * Version: MPL 2.0 / LGPLv2.1+
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  * Major Contributor(s):
10  * Copyright (C) 2006, 2007 Andrew Ziem
11  * Copyright (C) 2004 Marc Maurer (uwog@uwog.net)
12  * Copyright (C) 2004-2006 Fridrich Strba (fridrich.strba@bluewin.ch)
13  *
14  * For minor contributions see the git repository.
15  *
16  * Alternatively, the contents of this file may be used under the terms
17  * of the GNU Lesser General Public License Version 2.1 or later
18  * (LGPLv2.1+), in which case the provisions of the LGPLv2.1+ are
19  * applicable instead of those above.
20  */
21 
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include <cmath>
26 #include <sstream>
27 #include <limits>
28 #include <stack>
29 
30 #include <librevenge-stream/librevenge-stream.h>
31 
32 #include "libwps_internal.h"
33 #include "libwps_tools_win.h"
34 
35 #include "WPSCell.h"
36 #include "WKSContentListener.h"
37 #include "WPSEntry.h"
38 #include "WPSFont.h"
39 #include "WPSTable.h"
40 
41 #include "WKS4.h"
42 
43 #include "WKS4Spreadsheet.h"
44 
45 namespace WKS4SpreadsheetInternal
46 {
47 
48 ///////////////////////////////////////////////////////////////////
49 //! a class used to store a style of a cell in WKS4Spreadsheet
50 struct Style final : public WPSCellFormat
51 {
52 	//! construtor
StyleWKS4SpreadsheetInternal::Style53 	explicit Style(libwps_tools_win::Font::Type type)
54 		: WPSCellFormat()
55 		, m_fileFont()
56 		, m_fontType(type)
57 		, m_extra("")
58 	{
59 		for (int &unknFlag : m_unknFlags) unknFlag = 0;
60 	}
61 	Style(Style const &)=default;
62 	Style &operator=(Style const &)=default;
63 	//! destructor
64 	~Style() final;
65 	//! operator<<
66 	friend std::ostream &operator<<(std::ostream &o, Style const &style);
67 	//! operator==
68 	bool operator==(Style const &st) const;
69 	//! operator!=
operator !=WKS4SpreadsheetInternal::Style70 	bool operator!=(Style const &st) const
71 	{
72 		return !(*this==st);
73 	}
74 	/** the font */
75 	WPSFont m_fileFont;
76 	//! font encoding type
77 	libwps_tools_win::Font::Type m_fontType;
78 	/** some flag */
79 	int m_unknFlags[10];
80 	/** extra data */
81 	std::string m_extra;
82 };
83 
~Style()84 Style::~Style()
85 {
86 }
87 
88 //! operator<<
operator <<(std::ostream & o,Style const & style)89 std::ostream &operator<<(std::ostream &o, Style const &style)
90 {
91 	o << "font=[" << style.m_fileFont << "],";
92 	o << static_cast<WPSCellFormat const &>(style) << ",";
93 
94 	bool hasUnkn = false;
95 	for (int unknFlag : style.m_unknFlags)
96 	{
97 		if (!unknFlag) continue;
98 		hasUnkn=true;
99 		break;
100 	}
101 	if (hasUnkn)
102 	{
103 		o << "unkn=[" << std::hex;
104 		for (int i = 0; i < 10; i++)
105 		{
106 			if (style.m_unknFlags[i]) o << "fS" << i << "=" << std::hex << style.m_unknFlags[i] << std::dec << ",";
107 		}
108 		o << std::dec << "]";
109 	}
110 	if (style.m_extra.length())
111 		o << ", extra=[" << style.m_extra << "]";
112 
113 	return o;
114 }
115 
operator ==(Style const & st) const116 bool Style::operator==(Style const &st) const
117 {
118 	if (m_fileFont!=st.m_fileFont) return false;
119 	if (m_format!=st.m_format || m_subFormat!=st.m_subFormat || m_digits!=st.m_digits || m_protected !=st.m_protected)
120 		return false;
121 	int diff = WPSCellFormat::compare(st);
122 	if (diff) return false;
123 	for (int i = 0; i < 10; i++)
124 	{
125 		if (m_unknFlags[i]!=st.m_unknFlags[i])
126 			return false;
127 	}
128 	return m_extra==st.m_extra;
129 }
130 
131 ///////////////////////////////////////////////////////////////////
132 //! the style manager
133 class StyleManager
134 {
135 public:
StyleManager()136 	StyleManager()
137 		: m_stylesList() {}
138 	//! add a new style and returns its id
add(Style const & st,bool dosFile)139 	int add(Style const &st, bool dosFile)
140 	{
141 		if (dosFile)
142 		{
143 			for (size_t i=0; i < m_stylesList.size(); ++i)
144 			{
145 				if (m_stylesList[i]==st) return int(i);
146 			}
147 		}
148 		m_stylesList.push_back(st);
149 		return int(m_stylesList.size())-1;
150 	}
151 	//! returns the style with id
get(int id,Style & style) const152 	bool get(int id, Style &style) const
153 	{
154 		if (id<0|| id >= int(m_stylesList.size()))
155 		{
156 			WPS_DEBUG_MSG(("WKS4ParserInternal::StyleManager::get can not find style %d\n", id));
157 			return false;
158 		}
159 		style=m_stylesList[size_t(id)];
160 		return true;
161 	}
162 	//! returns the number of style
size() const163 	int size() const
164 	{
165 		return int(m_stylesList.size());
166 	}
167 	//! print a style
print(int id,std::ostream & o) const168 	void print(int id, std::ostream &o) const
169 	{
170 		if (id < 0) return;
171 		if (id < int(m_stylesList.size()))
172 			o << ", style=" << m_stylesList[size_t(id)];
173 		else
174 		{
175 			WPS_DEBUG_MSG(("WKS4ParserInternal::StyleManager::print: can not find a style\n"));
176 			o << ", ###style=" << id;
177 		}
178 	}
179 
180 protected:
181 	//! the styles
182 	std::vector<Style> m_stylesList;
183 };
184 
185 //! a cellule of a WKS4 spreadsheet
186 class Cell final : public WPSCell
187 {
188 public:
189 	/// constructor
Cell()190 	Cell()
191 		: m_styleId(-1)
192 		, m_hAlignement(WPSCellFormat::HALIGN_DEFAULT)
193 		, m_content()
194 		, m_extraTextEntryList() { }
195 
196 	//! operator<<
197 	friend std::ostream &operator<<(std::ostream &o, Cell const &cell);
198 
199 	//! call when a cell must be send
200 	bool send(WPSListenerPtr &/*listener*/) final;
201 
202 	//! call when the content of a cell must be send
sendContent(WPSListenerPtr &)203 	bool sendContent(WPSListenerPtr &/*listener*/) final
204 	{
205 		WPS_DEBUG_MSG(("WKS4SpreadsheetInternal::Cell::sendContent: must not be called\n"));
206 		return false;
207 	}
208 
209 	//! the style
210 	int m_styleId;
211 	//! the horizontal align (in dos file)
212 	WPSCellFormat::HorizontalAlignment m_hAlignement;
213 	//! the content
214 	WKSContentListener::CellContent m_content;
215 	/** As very long text is splitted in zone 0xf and then in zone 0x36,
216 		the list of zone36 text entries...
217 	 */
218 	std::vector<WPSEntry> m_extraTextEntryList;
219 };
220 
send(WPSListenerPtr &)221 bool Cell::send(WPSListenerPtr &/*listener*/)
222 {
223 	WPS_DEBUG_MSG(("WKS4SpreadsheetInternal::Cell::send: must not be called\n"));
224 	return false;
225 }
226 
227 //! operator<<
operator <<(std::ostream & o,Cell const & cell)228 std::ostream &operator<<(std::ostream &o, Cell const &cell)
229 {
230 	o << reinterpret_cast<WPSCell const &>(cell)
231 	  << cell.m_content << ",style=" << cell.m_styleId << ",";
232 	switch (cell.m_hAlignement)
233 	{
234 	case WPSCellFormat::HALIGN_LEFT:
235 		o << "left,";
236 		break;
237 	case WPSCellFormat::HALIGN_CENTER:
238 		o << "centered,";
239 		break;
240 	case WPSCellFormat::HALIGN_RIGHT:
241 		o << "right,";
242 		break;
243 	case WPSCellFormat::HALIGN_FULL:
244 		o << "full,";
245 		break;
246 	case WPSCellFormat::HALIGN_DEFAULT:
247 	default:
248 		break; // default
249 	}
250 	return o;
251 }
252 
253 ///////////////////////////////////////////////////////////////////
254 //! the spreadsheet of a WPS4Spreadsheet
255 class Spreadsheet
256 {
257 public:
258 	//! the spreadsheet type
259 	enum Type { T_Spreadsheet, T_Filter, T_Report };
260 
261 	//! a constructor
Spreadsheet(Type type=T_Spreadsheet,int id=0)262 	Spreadsheet(Type type=T_Spreadsheet, int id=0)
263 		: m_type(type)
264 		, m_id(id)
265 		, m_numCols(0)
266 		, m_numRows(0)
267 		, m_widthCols()
268 		, m_rowHeightMap()
269 		, m_heightDefault(16)
270 		, m_positionToCellMap()
271 		, m_lastCellPos()
272 		, m_rowPageBreaksList() {}
273 	//! return a cell corresponding to a spreadsheet, create one if needed
getCell(Vec2i const & pos)274 	Cell &getCell(Vec2i const &pos)
275 	{
276 		if (m_positionToCellMap.find(pos)==m_positionToCellMap.end())
277 		{
278 			Cell cell;
279 			cell.setPosition(pos);
280 			m_positionToCellMap[pos]=cell;
281 		}
282 		m_lastCellPos=pos;
283 		return m_positionToCellMap.find(pos)->second;
284 	}
285 	//! returns the last cell
getLastCell()286 	Cell *getLastCell()
287 	{
288 		if (m_positionToCellMap.find(m_lastCellPos)==m_positionToCellMap.end())
289 			return nullptr;
290 		return &m_positionToCellMap.find(m_lastCellPos)->second;
291 	}
292 	//! set the columns size
setColumnWidth(int col,int w=-1)293 	void setColumnWidth(int col, int w=-1)
294 	{
295 		if (col < 0) return;
296 		if (col >= int(m_widthCols.size())) m_widthCols.resize(size_t(col)+1, -1);
297 		m_widthCols[size_t(col)] = w;
298 		if (col >= m_numCols) m_numCols=col+1;
299 	}
300 
301 	//! returns the row size in point
getRowHeight(int row) const302 	float getRowHeight(int row) const
303 	{
304 		auto rIt=m_rowHeightMap.lower_bound(Vec2i(-1,row));
305 		if (rIt!=m_rowHeightMap.end() && rIt->first[0]<=row && rIt->first[1]>=row)
306 			return float(rIt->second)/20.f;
307 		return float(m_heightDefault);
308 	}
309 	/** returns the height of a row in point and updated repeated row
310 
311 		\note: you must first call compressRowHeigths
312 	 */
getRowHeight(int row,int & numRepeated) const313 	float getRowHeight(int row, int &numRepeated) const
314 	{
315 		auto rIt=m_rowHeightMap.lower_bound(Vec2i(-1,row));
316 		if (rIt!=m_rowHeightMap.end() && rIt->first[0]<=row && rIt->first[1]>=row)
317 		{
318 			numRepeated=rIt->first[1]-row+1;
319 			return float(rIt->second)/20.f;
320 		}
321 		numRepeated=10000;
322 		return float(m_heightDefault);
323 	}
324 	//! set the rows size
setRowHeight(int row,int h)325 	void setRowHeight(int row, int h)
326 	{
327 		if (h>=0)
328 			m_rowHeightMap[Vec2i(row,row)]=h;
329 	}
330 	//! try to compress the list of row height
compressRowHeights()331 	void compressRowHeights()
332 	{
333 		auto oldMap=m_rowHeightMap;
334 		m_rowHeightMap.clear();
335 		int actHeight=-1;
336 		Vec2i actPos(0,-1);
337 		int const defHeight=m_heightDefault*20; // conversion from point to TWIP
338 		for (auto rIt : oldMap)
339 		{
340 			// first check for not filled row
341 			if (rIt.first[0]!=actPos[1]+1)
342 			{
343 				if (actHeight==defHeight)
344 					actPos[1]=rIt.first[0]-1;
345 				else
346 				{
347 					if (actPos[1]>=actPos[0])
348 						m_rowHeightMap[actPos]=actHeight;
349 					actHeight=defHeight;
350 					actPos=Vec2i(actPos[1]+1, rIt.first[0]-1);
351 				}
352 			}
353 			if (rIt.second!=actHeight)
354 			{
355 				if (actPos[1]>=actPos[0])
356 					m_rowHeightMap[actPos]=actHeight;
357 				actPos[0]=rIt.first[0];
358 				actHeight=rIt.second;
359 			}
360 			actPos[1]=rIt.first[1];
361 		}
362 		if (actPos[1]>=actPos[0])
363 			m_rowHeightMap[actPos]=actHeight;
364 	}
365 	//! return the columns format
getWidths(float defSize=72) const366 	std::vector<WPSColumnFormat> getWidths(float defSize=72) const
367 	{
368 		std::vector<WPSColumnFormat> widths;
369 		WPSColumnFormat defWidth(defSize), actWidth;
370 		defWidth.m_useOptimalWidth=true;
371 		int repeat=0;
372 		for (auto const &c : m_widthCols)
373 		{
374 			WPSColumnFormat newWidth;
375 			if (c < 0)
376 				newWidth=defWidth;
377 			else
378 				newWidth=WPSColumnFormat(float(c)/20.f);
379 			if (repeat && newWidth!=actWidth)
380 			{
381 				actWidth.m_numRepeat=repeat;
382 				widths.push_back(actWidth);
383 				repeat=0;
384 			}
385 			if (repeat==0)
386 				actWidth=newWidth;
387 			++repeat;
388 		}
389 		if (repeat)
390 		{
391 			actWidth.m_numRepeat=repeat;
392 			widths.push_back(actWidth);
393 		}
394 		return widths;
395 	}
396 	//! returns true if the spreedsheet is empty
empty() const397 	bool empty() const
398 	{
399 		return m_positionToCellMap.empty();
400 	}
401 	//! the spreadsheet type
402 	Type m_type;
403 	//! the spreadsheet id
404 	int m_id;
405 	/** the number of columns */
406 	int m_numCols;
407 	/** the number of rows */
408 	int m_numRows;
409 
410 	/** the column size in TWIP (?) */
411 	std::vector<int> m_widthCols;
412 	/** the map Vec2i(min row, max row) to size in TWIP (?) */
413 	std::map<Vec2i,int> m_rowHeightMap;
414 	/** the default row size in point */
415 	int m_heightDefault;
416 	/** a map cell to not empty cells */
417 	std::map<Vec2i, Cell> m_positionToCellMap;
418 	/** the last cell position */
419 	Vec2i m_lastCellPos;
420 	/** the list of row page break */
421 	std::vector<int> m_rowPageBreaksList;
422 
423 };
424 
425 //! the state of WKS4Spreadsheet
426 struct State
427 {
428 	//! constructor
StateWKS4SpreadsheetInternal::State429 	State()
430 		:  m_eof(-1)
431 		, m_version(-1)
432 		, m_styleManager()
433 		, m_spreadsheetList()
434 		, m_spreadsheetStack()
435 	{
436 		pushNewSheet(std::shared_ptr<Spreadsheet>(new Spreadsheet(Spreadsheet::T_Spreadsheet, 0)));
437 	}
438 	//! returns the maximal spreadsheet
getMaximalSheetWKS4SpreadsheetInternal::State439 	int getMaximalSheet(Spreadsheet::Type type=Spreadsheet::T_Spreadsheet) const
440 	{
441 		int max=-1;
442 		for (auto sheet : m_spreadsheetList)
443 		{
444 			if (!sheet || sheet->m_type != type || sheet->m_id<=max || sheet->empty()) continue;
445 			max=sheet->m_id;
446 		}
447 		return max;
448 	}
449 	//! returns the ith real spreadsheet
getSheetWKS4SpreadsheetInternal::State450 	std::shared_ptr<Spreadsheet> getSheet(Spreadsheet::Type type, int id)
451 	{
452 		for (auto sheet : m_spreadsheetList)
453 		{
454 			if (!sheet || sheet->m_type!=type || sheet->m_id!=id)
455 				continue;
456 			return sheet;
457 		}
458 		return std::shared_ptr<Spreadsheet>();
459 	}
460 	//! returns the ith spreadsheet
getSheetNameWKS4SpreadsheetInternal::State461 	static librevenge::RVNGString getSheetName(int id)
462 	{
463 		librevenge::RVNGString name;
464 		name.sprintf("Sheet%d", id+1);
465 		return name;
466 	}
467 	//! returns the actual sheet
getActualSheetWKS4SpreadsheetInternal::State468 	Spreadsheet &getActualSheet()
469 	{
470 		return *m_spreadsheetStack.top();
471 	}
472 	//! create a new sheet and stack id
pushNewSheetWKS4SpreadsheetInternal::State473 	void pushNewSheet(std::shared_ptr<Spreadsheet> sheet)
474 	{
475 		if (!sheet)
476 		{
477 			WPS_DEBUG_MSG(("WKS4SpreadsheetInternal::State::pushSheet: can not find the sheet\n"));
478 			return;
479 		}
480 		m_spreadsheetStack.push(sheet);
481 		m_spreadsheetList.push_back(sheet);
482 	}
483 	//! try to pop the actual sheet
popSheetWKS4SpreadsheetInternal::State484 	bool popSheet()
485 	{
486 		if (m_spreadsheetStack.size()<=1)
487 		{
488 			WPS_DEBUG_MSG(("WKS4SpreadsheetInternal::State::popSheet: can pop the main sheet\n"));
489 			return false;
490 		}
491 		m_spreadsheetStack.pop();
492 		return true;
493 	}
494 	//! the last file position
495 	long m_eof;
496 	//! the file version
497 	int m_version;
498 	//! the style manager
499 	StyleManager m_styleManager;
500 
501 	//! the list of spreadsheet ( first: main spreadsheet, other report spreadsheet )
502 	std::vector<std::shared_ptr<Spreadsheet> > m_spreadsheetList;
503 	//! the stack of spreadsheet id
504 	std::stack<std::shared_ptr<Spreadsheet> > m_spreadsheetStack;
505 };
506 
507 }
508 
509 // constructor, destructor
WKS4Spreadsheet(WKS4Parser & parser)510 WKS4Spreadsheet::WKS4Spreadsheet(WKS4Parser &parser)
511 	: m_input(parser.getInput())
512 	, m_listener()
513 	, m_mainParser(parser)
514 	, m_state(new WKS4SpreadsheetInternal::State)
515 	, m_asciiFile(parser.ascii())
516 {
517 	m_state.reset(new WKS4SpreadsheetInternal::State);
518 }
519 
~WKS4Spreadsheet()520 WKS4Spreadsheet::~WKS4Spreadsheet()
521 {
522 }
523 
resetInput(RVNGInputStreamPtr const & newInput)524 void WKS4Spreadsheet::resetInput(RVNGInputStreamPtr const &newInput)
525 {
526 	m_input=newInput;
527 }
528 
version() const529 int WKS4Spreadsheet::version() const
530 {
531 	if (m_state->m_version<0)
532 		m_state->m_version=m_mainParser.version();
533 	return m_state->m_version;
534 }
535 
checkFilePosition(long pos)536 bool WKS4Spreadsheet::checkFilePosition(long pos)
537 {
538 	if (m_state->m_eof < 0)
539 	{
540 		long actPos = m_input->tell();
541 		m_input->seek(0, librevenge::RVNG_SEEK_END);
542 		m_state->m_eof=m_input->tell();
543 		m_input->seek(actPos, librevenge::RVNG_SEEK_SET);
544 	}
545 	return pos <= m_state->m_eof;
546 }
547 
getNumSpreadsheets() const548 int WKS4Spreadsheet::getNumSpreadsheets() const
549 {
550 	return m_state->getMaximalSheet(WKS4SpreadsheetInternal::Spreadsheet::T_Spreadsheet)+1;
551 }
552 
getSheetName(int id) const553 librevenge::RVNGString WKS4Spreadsheet::getSheetName(int id) const
554 {
555 	return m_state->getSheetName(id);
556 }
557 
558 ////////////////////////////////////////////////////////////
559 // low level
560 
561 ////////////////////////////////////////////////////////////
562 //   parse sheet data
563 ////////////////////////////////////////////////////////////
readSheetSize()564 bool WKS4Spreadsheet::readSheetSize()
565 {
566 	libwps::DebugStream f;
567 	long pos = m_input->tell();
568 	long type = libwps::read16(m_input);
569 	if (type != 0x6)
570 	{
571 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readSheetSize: not a sheet zone\n"));
572 		return false;
573 	}
574 	long sz = libwps::readU16(m_input);
575 	if (sz < 8)
576 	{
577 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readSheetSize: block is too short\n"));
578 		return false;
579 	}
580 	f << "Entries(SheetSize):";
581 	for (int i = 0; i < 2; i++)   // always 2 zero ?
582 	{
583 		int val = libwps::read16(m_input);
584 		if (!val) continue;
585 		f << "f" << i << "=" << std::hex << val << std::dec << ",";
586 	}
587 	int nCol = libwps::read16(m_input)+1;
588 	f << "nCols=" << nCol << ",";
589 	int nRow =  libwps::read16(m_input);
590 	f << "nRow=" << nRow << ",";
591 	ascii().addPos(pos);
592 	ascii().addNote(f.str().c_str());
593 	// empty spreadsheet
594 	if (nRow==-1 && nCol==0) return true;
595 	if (nRow < 0 || nCol <= 0) return false;
596 
597 	m_state->getActualSheet().setColumnWidth(nCol-1);
598 	return true;
599 
600 }
601 
readColumnSize()602 bool WKS4Spreadsheet::readColumnSize()
603 {
604 	libwps::DebugStream f;
605 	long pos = m_input->tell();
606 	long type = libwps::read16(m_input);
607 	if (type != 0x8)
608 	{
609 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readColumnSize: not a column size zone\n"));
610 		return false;
611 	}
612 	long sz = libwps::readU16(m_input);
613 	if (sz < 3)
614 	{
615 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readColumnSize: block is too short\n"));
616 		return false;
617 	}
618 
619 	int col = libwps::read16(m_input);
620 	int width = libwps::readU8(m_input);
621 
622 	bool ok = col >= 0 && col < m_state->getActualSheet().m_numCols+10;
623 	f << "Entries(Column):Col" << col << "";
624 	if (!ok) f << "###";
625 	f << ":width=" << width << ",";
626 
627 	if (ok)
628 	{
629 		if (col >= m_state->getActualSheet().m_numCols)
630 		{
631 			static bool first = true;
632 			if (first)
633 			{
634 				first = false;
635 				WPS_DEBUG_MSG(("WKS4Spreadsheet::readColumnSize: I must increase the number of columns\n"));
636 			}
637 			f << "#col[inc],";
638 		}
639 		// checkme: unit in character(?) -> TWIP
640 		m_state->getActualSheet().setColumnWidth(col, width*160);
641 	}
642 	ascii().addPos(pos);
643 	ascii().addNote(f.str().c_str());
644 
645 	return ok;
646 }
647 
readHiddenColumns()648 bool WKS4Spreadsheet::readHiddenColumns()
649 {
650 	libwps::DebugStream f;
651 	long pos = m_input->tell();
652 	long type = libwps::read16(m_input);
653 	if (type != 0x64)
654 	{
655 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readHiddenColumns: not a column hidden zone\n"));
656 		return false;
657 	}
658 	long sz = libwps::readU16(m_input);
659 	if (sz != 32)
660 	{
661 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readHiddenColumns: block size seems bad\n"));
662 		return false;
663 	}
664 
665 	f << "Entries(HiddenCol):";
666 	for (int i=0; i<32; ++i)
667 	{
668 		auto val=int(libwps::readU8(m_input));
669 		if (!val) continue;
670 		static bool first=true;
671 		if (first)
672 		{
673 			WPS_DEBUG_MSG(("WKS4Spreadsheet::readHiddenColumns: find some hidden col, ignored\n"));
674 			first=false;
675 		}
676 		// checkme
677 		for (int j=0, depl=1; j<8; ++j, depl<<=1)
678 		{
679 			if (val&depl)
680 				f << "col" << j+8*i << "[hidden],";
681 		}
682 	}
683 	ascii().addPos(pos);
684 	ascii().addNote(f.str().c_str());
685 
686 	return true;
687 }
688 
689 ////////////////////////////////////////////////////////////
690 // MSWorks
691 ////////////////////////////////////////////////////////////
readMsWorksColumnSize()692 bool WKS4Spreadsheet::readMsWorksColumnSize()
693 {
694 	libwps::DebugStream f;
695 	long pos = m_input->tell();
696 	long type = libwps::read16(m_input);
697 	if (type != 0x546b)
698 	{
699 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksColumnSize: not a column size zone\n"));
700 		return false;
701 	}
702 	long sz = libwps::readU16(m_input);
703 	if (sz != 4)
704 	{
705 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksColumnSize: block size is odd\n"));
706 		return false;
707 	}
708 
709 	int col = libwps::read16(m_input);
710 	int width = libwps::readU16(m_input); // unit? 1point ~ 115
711 
712 	bool ok = col >= 0 && col < m_state->getActualSheet().m_numCols+10;
713 	if (ok)
714 		m_state->getActualSheet().setColumnWidth(col, width & 0x7FFF);
715 
716 	f << "Entries(Colum2):Col" << col << ":";
717 	if (!ok) f << "###";
718 	if (width & 0x8000)
719 	{
720 		f << "flag?,";
721 		width &= 0x7FFF;
722 	}
723 	f << "width(unit?)=" << width;
724 
725 	ascii().addPos(pos);
726 	ascii().addNote(f.str().c_str());
727 
728 	return true;
729 }
730 
readMsWorksRowSize()731 bool WKS4Spreadsheet::readMsWorksRowSize()
732 {
733 	libwps::DebugStream f;
734 	long pos = m_input->tell();
735 	long type = libwps::read16(m_input);
736 	if (type != 0x5465)
737 	{
738 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksRowSize: not a row size zone\n"));
739 		return false;
740 	}
741 	long sz = libwps::readU16(m_input);
742 	if (sz != 4)
743 	{
744 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksRowSize: block size is odd\n"));
745 		return false;
746 	}
747 
748 	int row = libwps::read16(m_input);
749 	int width = libwps::readU16(m_input); // unit? 1point ~ 105
750 
751 	bool ok = row >= 0;
752 	if (ok) m_state->getActualSheet().setRowHeight(row, width & 0x7FFF);
753 	f << "Entries(Row2):Row" << row << ":";
754 	if (!ok) f << "###";
755 	if (width & 0x8000)
756 	{
757 		f << "flag?,";
758 		width &= 0x7FFF;
759 	}
760 	f << "width(unit?)=" << width;
761 
762 	ascii().addPos(pos);
763 	ascii().addNote(f.str().c_str());
764 
765 	return true;
766 }
767 
readMsWorksPageBreak()768 bool WKS4Spreadsheet::readMsWorksPageBreak()
769 {
770 	libwps::DebugStream f;
771 	long pos = m_input->tell();
772 	long type = libwps::read16(m_input);
773 
774 	if (type != 0x5413)
775 	{
776 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksPageBreak: not a pgbreak zone\n"));
777 		return false;
778 	}
779 	long sz = libwps::readU16(m_input);
780 	if (sz < 2)
781 	{
782 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksPageBreak: seems very short\n"));
783 		ascii().addPos(pos);
784 		ascii().addNote("Entries(PBRK)###");
785 		return true;
786 	}
787 	int row = libwps::read16(m_input)+1;
788 	m_state->getActualSheet().m_rowPageBreaksList.push_back(row);
789 
790 	f << "Entries(PBRK): row="<< row;
791 	if (sz != 2) ascii().addDelimiter(m_input->tell(),'#');
792 
793 	ascii().addPos(pos);
794 	ascii().addNote(f.str().c_str());
795 	return true;
796 }
797 
readMsWorksStyle()798 bool WKS4Spreadsheet::readMsWorksStyle()
799 {
800 	libwps::DebugStream f;
801 
802 	long pos = m_input->tell();
803 	long type = libwps::read16(m_input);
804 	if (type != 0x545a)
805 	{
806 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksStyle: not a style property\n"));
807 		return false;
808 	}
809 	long sz = libwps::readU16(m_input);
810 	long endPos=pos+4+sz;
811 	if (sz < 8)
812 	{
813 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksStyle: style property is too short\n"));
814 		return false;
815 	}
816 
817 	// win3 has 8 bytes while last version is at least 10 bytes
818 	int fl[6];
819 	for (int i=0; i<2; ++i) fl[i] = libwps::readU8(m_input);
820 	for (int i=0; i<3; ++i) fl[2+i] = libwps::readU16(m_input);
821 	fl[5]= sz>=10 ? libwps::readU16(m_input) : 0;
822 
823 	WKS4SpreadsheetInternal::Style style(m_mainParser.getDefaultFontType());
824 	if (!m_mainParser.getFont(fl[3], style.m_fileFont, style.m_fontType))
825 	{
826 		f << ",#fontId = " << fl[3];
827 
828 		static bool first = true;
829 		if (first)
830 		{
831 			WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksStyle: can not find a font\n"));
832 			first = false;
833 		}
834 	}
835 	fl[3]=0;
836 
837 
838 	if (fl[1] & 0x1C)
839 	{
840 		switch ((fl[1] & 0x1C) >> 2)
841 		{
842 		case 0:
843 			break;
844 		case 1:
845 		case 4: // left rellenar
846 			style.setHAlignment(WPSCell::HALIGN_LEFT);
847 			break;
848 		case 5: // center arround cell
849 			f << ",center[between cell]";
850 			WPS_FALLTHROUGH;
851 		case 2:
852 			style.setHAlignment(WPSCell::HALIGN_CENTER);
853 			break;
854 		case 3:
855 			style.setHAlignment(WPSCell::HALIGN_RIGHT);
856 			break;
857 		default:
858 			f << ",#align=" << ((fl[1] & 0x1C) >> 2);
859 			break;
860 		}
861 		fl[1] &= 0xE3;
862 	}
863 	switch ((fl[0]&0xf))
864 	{
865 	case 0:
866 		style.setFormat(WPSCell::F_NUMBER,1);
867 		break;
868 	case 1:
869 		style.setFormat(WPSCell::F_NUMBER,2);
870 		break;
871 	case 2:
872 		style.setFormat(WPSCell::F_NUMBER,4);
873 		break;
874 	case 3:
875 		style.setFormat(WPSCell::F_NUMBER,3);
876 		break;
877 	case 4:
878 		style.setFormat(WPSCell::F_NUMBER,5);
879 		break;
880 	case 5:
881 	{
882 		int wh=(fl[0]>>5);
883 		switch (wh)
884 		{
885 		case 0:
886 			style.setFormat(WPSCell::F_TEXT);
887 			break;
888 		case 1:
889 			style.setFormat(WPSCell::F_BOOLEAN);
890 			break;
891 		case 2:
892 		case 3:
893 		case 4:
894 		case 5:
895 		{
896 			char const *format[] = { "%H:%M%p", "%I:%M:%S%p", "%H:%M", "%H:%M:%S" };
897 			style.setDTFormat(WPSCell::F_TIME, format[wh-2]);
898 			break;
899 		}
900 		default:
901 			f << "#type=" << std::hex << fl[0] << std::dec << ",";
902 			break;
903 		}
904 		break;
905 	}
906 	case 6:
907 	{
908 		char const *format[] = { "%m/%d/%Y", "%d %B %Y", "%m/%Y", "%B %Y", "%m/%d", "%d %B", "%m/%d/%y:6"/*TODO*/, "%B"};
909 		style.setDTFormat(WPSCell::F_DATE, format[int(fl[0]>>5)]);
910 		fl[0] &= 0x18;
911 		break;
912 	}
913 	case 0xa:
914 		style.setFormat(WPSCell::F_NUMBER, 6);
915 		break;
916 	case 0xc:
917 		style.setFormat(WPSCell::F_NUMBER, 7);
918 		break;
919 	case 0xd:
920 		style.setFormat(WPSCell::F_NUMBER, 4);
921 		f << "negative[inRed],";
922 		break;
923 	default:
924 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksStyle: find unknown format %d\n", (fl[0]&0xF)));
925 		f << ", ##type=" << std::hex << (fl[0]&0xf) << std::dec << ",";
926 		break;
927 	}
928 	fl[0] &= 0xF0;
929 
930 	if (style.getFormat() == WPSCell::F_NUMBER)
931 	{
932 		int digits = 0;
933 		if (fl[1] &= 0x1)
934 		{
935 			digits = 8;
936 			fl[1] &= 2;
937 		}
938 		digits += (fl[0]>>5);
939 		fl[0] &= 0x18;
940 		style.setDigits(digits);
941 	}
942 	if (fl[1]&0x20) f << "ajust[text],";
943 	switch (fl[1]>>6)
944 	{
945 	case 0:
946 		style.setVAlignment(WPSCellFormat::VALIGN_BOTTOM);
947 		break;
948 	case 1:
949 		style.setVAlignment(WPSCellFormat::VALIGN_CENTER);
950 		break;
951 	case 2:
952 		style.setVAlignment(WPSCellFormat::VALIGN_TOP);
953 		break;
954 	default:
955 		f << "##vAlign=3,";
956 		break;
957 	}
958 	fl[1] &= 0x10; // 10
959 	//
960 	// the color
961 	//
962 
963 	WPSColor frontColor=WPSColor::black();
964 	int colorId=(fl[4]>>5)&0xF;
965 	if (colorId && m_mainParser.getColor(colorId,frontColor))   // primary color
966 		f << "primColor=" << std::hex << frontColor << std::dec << ",";
967 
968 	WPSColor backColor=WPSColor::white();
969 	colorId=(fl[4]>>9)&0xF;
970 	if (colorId && m_mainParser.getColor(colorId,backColor))   // secondary color
971 		f << "secColor=" << std::hex << backColor << std::dec << ",";
972 
973 	int pat= (fl[4]&0xf);
974 	if (pat==15) f << "###pat15,";
975 	else if (pat)
976 	{
977 		static float const patternPercent[]= {0, 1.f, 0.5f, 0.25f, 0.2f, 0.2f, 0.2f, 0.5f, 0.5f, 0.5f, 0.5f, 0.8f, 0.2f, 0.8f, 0.2f};
978 		float percent=patternPercent[pat];
979 		f << "patPercent=" << percent << ",";
980 		style.setBackgroundColor(WPSColor::barycenter(percent,frontColor, 1.0f-percent, backColor));
981 	}
982 	fl[4]&=0xE010;
983 
984 	//
985 	// Border
986 	//
987 	for (int b=0, decal=0; b<4; b++, decal+=4)
988 	{
989 		int const wh[]= {WPSBorder::LeftBit, WPSBorder::TopBit, WPSBorder::RightBit, WPSBorder::BottomBit};
990 		int bType=(fl[2]>>decal)&0xf;
991 		if (bType==0) continue;
992 		WPSBorder border;
993 		switch (bType&0x7)
994 		{
995 		case 1:
996 			break;
997 		case 2:
998 			border.m_width=2;
999 			break;
1000 		case 3:
1001 			border.m_style = WPSBorder::LargeDot;
1002 			break;
1003 		case 4:
1004 			border.m_style = WPSBorder::Dash;
1005 			break;
1006 		case 5:
1007 			border.m_type = WPSBorder::Double;
1008 			break;
1009 		case 6:
1010 			border.m_style = WPSBorder::Dot;
1011 			break;
1012 		case 7:
1013 			border.m_width=3;
1014 			break;
1015 		default:
1016 			break;
1017 		}
1018 		if ((fl[5]>>decal)&0xf)
1019 			m_mainParser.getColor(((fl[5]>>decal)&0xf), border.m_color);
1020 		style.setBorders(wh[b], border);
1021 		if (bType&0x8) f << "bType[" << b << "]&8,";
1022 	}
1023 	fl[2] = fl[5] = 0;
1024 
1025 	for (int i = 0; i < 6; i++) style.m_unknFlags[i] = fl[i];
1026 	style.m_extra = f.str();
1027 
1028 	/* end of parsing */
1029 	f.str("");
1030 	f << "Entries(Style):Style" << m_state->m_styleManager.size() << "," << style;
1031 	m_state->m_styleManager.add(style, false);
1032 	ascii().addPos(pos);
1033 	ascii().addNote(f.str().c_str());
1034 	if (m_input->tell()!=endPos) ascii().addDelimiter(m_input->tell(),'#');
1035 	return true;
1036 }
1037 
1038 ////////////////////////////////////////////////////////////
1039 // MsWorks (only in dos)
1040 ////////////////////////////////////////////////////////////
1041 
readMsWorksDOSCellProperty()1042 bool WKS4Spreadsheet::readMsWorksDOSCellProperty()
1043 {
1044 	libwps::DebugStream f;
1045 	long pos = m_input->tell();
1046 	long type = libwps::read16(m_input);
1047 	if (type != 0x5402)
1048 	{
1049 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksDOSCellProperty: not a cell property\n"));
1050 		return false;
1051 	}
1052 	long sz = libwps::readU16(m_input);
1053 	if (sz < 2)
1054 	{
1055 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksDOSCellProperty: cell property is too short\n"));
1056 		return false;
1057 	}
1058 
1059 	f << "Entries(CellDosProperty):";
1060 	auto *cell = m_state->getActualSheet().getLastCell();
1061 	if (!cell)
1062 	{
1063 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksDOSCellProperty: can not find the cell\n"));
1064 		f << "###";
1065 		ascii().addPos(pos);
1066 		ascii().addNote(f.str().c_str());
1067 		return true;
1068 	}
1069 	WKS4SpreadsheetInternal::Style style(m_mainParser.getDefaultFontType());
1070 	if (cell->m_styleId>=0)
1071 	{
1072 		if (!m_state->m_styleManager.get(cell->m_styleId, style))
1073 			f << ",###style";
1074 	}
1075 
1076 	int fl[2];
1077 	for (int &i : fl)
1078 		i = libwps::readU8(m_input);
1079 
1080 	switch ((fl[0] & 0x7))
1081 	{
1082 	case 0x5:
1083 		if (cell->m_content.m_contentType == WKSContentListener::CellContent::C_TEXT) fl[0] &= 0xF8;
1084 		break;
1085 	case 0x6:
1086 		if (cell->m_content.m_contentType == WKSContentListener::CellContent::C_NUMBER) fl[0] &= 0xF8;
1087 		break;
1088 	case 0x7:
1089 		if (cell->m_content.m_contentType == WKSContentListener::CellContent::C_FORMULA) fl[0] &= 0xF8;
1090 		break;
1091 	default:
1092 		break;
1093 	}
1094 	WPSCell::FormatType newForm = WPSCell::F_UNKNOWN;
1095 	int subForm = 0;
1096 	switch (fl[0]>>5)
1097 	{
1098 	case 0:
1099 		newForm = WPSCell::F_NUMBER;
1100 		subForm=1;
1101 		break;
1102 	case 1:
1103 		newForm = WPSCell::F_NUMBER;
1104 		subForm=2;
1105 		break;
1106 	case 2:
1107 		newForm = WPSCell::F_NUMBER;
1108 		subForm=4;
1109 		break;
1110 	case 3:
1111 		newForm = WPSCell::F_NUMBER;
1112 		subForm=3;
1113 		break;
1114 	case 4:
1115 		newForm = WPSCell::F_NUMBER;
1116 		subForm=5;
1117 		break;
1118 	case 5:   // normal (or time)
1119 	{
1120 		int wh=(fl[1]>>2)&0x7;
1121 		if (wh>=2 && wh <=5)
1122 		{
1123 			newForm = WPSCell::F_TIME;
1124 			subForm=wh-2;
1125 			fl[1]&=0xE3;
1126 		}
1127 		break;
1128 	}
1129 	case 6:
1130 		newForm = WPSCell::F_DATE;
1131 		subForm=(fl[1]>>2)&0x7;
1132 		fl[1]&=0xE3;
1133 		break;
1134 	default:
1135 		f << "fl0[high]=" << (fl[0]>>5) << ",";
1136 		break;
1137 	}
1138 	fl[0] &= 0x1F;
1139 	if (newForm != WPSCell::F_UNKNOWN &&
1140 	        (newForm != style.getFormat() || subForm != style.getSubFormat()))
1141 	{
1142 		if (style.getFormat() == WPSCell::F_UNKNOWN)
1143 			;
1144 		else if (newForm != style.getFormat())
1145 			f << "#prevForm = " << int(style.getFormat());
1146 		else
1147 			f << "#prevSubForm = " << style.getSubFormat();
1148 		if (newForm==WPSCell::F_DATE && subForm>=0 && subForm<=7)
1149 		{
1150 			char const *wh[]= { "%m/%d/%y", "%B %d, %Y", "%m/%y", "%B %Y", "%m/%d", "%B %d", "%m", "%B"};
1151 			style.setDTFormat(newForm, wh[subForm]);
1152 		}
1153 		else if (newForm==WPSCell::F_TIME && subForm>=0 && subForm<=3)
1154 		{
1155 			char const *wh[]= { "%I:%M%p", "%I:%M:%S%p", "%H:%M", "%H:%M:%S"};
1156 			style.setDTFormat(newForm, wh[subForm]);
1157 		}
1158 		else
1159 			style.setFormat(newForm, subForm);
1160 	}
1161 
1162 	uint32_t fflags = 0; // checkme
1163 	if (fl[0] & 0x10)
1164 	{
1165 		fflags |= WPS_ITALICS_BIT;
1166 		fl[0] &= 0xEF;
1167 	}
1168 	if (fl[1] & 0x20)
1169 	{
1170 		fflags |= WPS_BOLD_BIT;
1171 		fl[1] &= 0xDF;
1172 	}
1173 	if (fl[1] & 0x40)
1174 	{
1175 		fflags |= WPS_UNDERLINE_BIT;
1176 		fl[1] &= 0xBF;
1177 	}
1178 	style.m_fileFont.m_attributes=fflags;
1179 	switch (fl[1]&3)
1180 	{
1181 	case 1:
1182 		style.setHAlignment(WPSCell::HALIGN_LEFT);
1183 		break;
1184 	case 2:
1185 		style.setHAlignment(WPSCell::HALIGN_CENTER);
1186 		break;
1187 	case 3:
1188 		style.setHAlignment(WPSCell::HALIGN_RIGHT);
1189 		break;
1190 	case 0:
1191 	default:
1192 		break;
1193 	}
1194 	fl[1] &= 0xFC;
1195 	style.m_unknFlags[0] = fl[0];
1196 	style.m_unknFlags[1] = fl[1];
1197 	int id = m_state->m_styleManager.add(style, true);
1198 	cell->m_styleId=id;
1199 
1200 #ifdef DEBUG_WITH_FILES
1201 	f << *cell;
1202 	m_state->m_styleManager.print(id, f);
1203 #endif
1204 	if (sz > 2) ascii().addDelimiter(pos+6, '#');
1205 
1206 	ascii().addPos(pos);
1207 	ascii().addNote(f.str().c_str());
1208 	return true;
1209 }
1210 
readMsWorksDOSFieldProperty()1211 bool WKS4Spreadsheet::readMsWorksDOSFieldProperty()
1212 {
1213 	libwps::DebugStream f;
1214 	long pos = m_input->tell();
1215 	long type = libwps::read16(m_input);
1216 	if (type != 0x5406)
1217 	{
1218 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksDOSFieldProperty: not a cell property\n"));
1219 		return false;
1220 	}
1221 	long sz = libwps::readU16(m_input);
1222 	if (sz < 4)
1223 	{
1224 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksDOSFieldProperty: cell property is too short\n"));
1225 		return false;
1226 	}
1227 
1228 	f << "Entries(FieldDosProperty):";
1229 	f << "id=" << libwps::readU16(m_input) << ",";
1230 	WKS4SpreadsheetInternal::Style style(m_mainParser.getDefaultFontType());
1231 	int fl[2];
1232 	for (int &i : fl)
1233 		i = libwps::readU8(m_input);
1234 
1235 	switch ((fl[0] & 0x7))
1236 	{
1237 	case 0x5:
1238 		f << "text,";
1239 		fl[0] &= 0xF8;
1240 		break;
1241 	case 0x6:
1242 		f << "number,";
1243 		fl[0] &= 0xF8;
1244 		break;
1245 	case 0x7:
1246 		f << "formula,";
1247 		fl[0] &= 0xF8;
1248 		break;
1249 	default:
1250 		break;
1251 	}
1252 	if (fl[0]>>5) f << "subForm=" << (fl[0]>>5) << ",";
1253 	fl[0] &= 0x1F;
1254 	uint32_t fflags = 0; // checkme
1255 	if (fl[0] & 0x10)
1256 	{
1257 		fflags |= WPS_ITALICS_BIT;
1258 		fl[0] &= 0xEF;
1259 	}
1260 	if (fl[1] & 0x20)
1261 	{
1262 		fflags |= WPS_BOLD_BIT;
1263 		fl[1] &= 0xDF;
1264 	}
1265 	if (fl[1] & 0x40)
1266 	{
1267 		fflags |= WPS_UNDERLINE_BIT;
1268 		fl[1] &= 0xBF;
1269 	}
1270 	style.m_fileFont.m_attributes=fflags;
1271 	switch (fl[1]&3)
1272 	{
1273 	case 1:
1274 		style.setHAlignment(WPSCell::HALIGN_LEFT);
1275 		break;
1276 	case 2:
1277 		style.setHAlignment(WPSCell::HALIGN_CENTER);
1278 		break;
1279 	case 3:
1280 		style.setHAlignment(WPSCell::HALIGN_RIGHT);
1281 		break;
1282 	case 0:
1283 	default:
1284 		break;
1285 	}
1286 	fl[1] &= 0xFC;
1287 	style.m_unknFlags[0] = fl[0];
1288 	style.m_unknFlags[1] = fl[1];
1289 	f << style;
1290 	if (sz > 4) ascii().addDelimiter(pos+8, '|');
1291 
1292 	ascii().addPos(pos);
1293 	ascii().addNote(f.str().c_str());
1294 	return true;
1295 
1296 }
1297 
readMsWorksDOSCellExtraProperty()1298 bool WKS4Spreadsheet::readMsWorksDOSCellExtraProperty()
1299 {
1300 	libwps::DebugStream f;
1301 	long pos = m_input->tell();
1302 	long type = libwps::read16(m_input);
1303 	if (type != 0x541c)
1304 	{
1305 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksDOSCellExtraProperty: not a cell property\n"));
1306 		return false;
1307 	}
1308 	long sz = libwps::readU16(m_input);
1309 	if (sz < 8)
1310 	{
1311 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksDOSCellExtraProperty: cell property is too short\n"));
1312 		return false;
1313 	}
1314 
1315 	f << "Entries(CellDosExtra):";
1316 	auto *cell = m_state->getActualSheet().getLastCell();
1317 	if (!cell)
1318 	{
1319 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksDOSCellExtraProperty: can not find the cell\n"));
1320 		f << "###";
1321 		ascii().addPos(pos);
1322 		ascii().addNote(f.str().c_str());
1323 		return true;
1324 	}
1325 	WKS4SpreadsheetInternal::Style style(m_mainParser.getDefaultFontType());
1326 	if (cell->m_styleId>=0)
1327 	{
1328 		if (!m_state->m_styleManager.get(cell->m_styleId, style))
1329 			f << ",###style";
1330 	}
1331 	int values[8];  // f0: [02468ac][0157], f1=0..9, f2=0..a, f3=[0-6a][0156], f4=0|9|d|40|80|c0, f5=0|ed
1332 	for (int &value : values)
1333 		value=int(libwps::readU8(m_input));
1334 	if (style.getFormat()==WPSCell::F_NUMBER)   // not sure
1335 	{
1336 		if (values[2]==5)
1337 		{
1338 			style.setFormat(WPSCell::F_NUMBER,7);
1339 			values[2]=0;
1340 		}
1341 		else if (values[2]==0xa)
1342 		{
1343 			style.setFormat(WPSCell::F_NUMBER,6);
1344 			style.setDigits(((values[3]>>3)&0x7)+1);
1345 			values[2]=0;
1346 			values[3]&=0xC7;
1347 		}
1348 	}
1349 	WPSColor color;
1350 	if ((values[6]&0xE0) && m_mainParser.getColor(values[6]>>5,color))
1351 	{
1352 		style.m_fileFont.m_color=color;
1353 		f << "fontColor=" << std::hex << color << std::dec << ",";
1354 		values[6] &= 0x1F;
1355 	}
1356 	for (int i=0; i < 8; ++i)
1357 	{
1358 		if (values[i])
1359 			f << "f" << i << "=" << std::hex << values[i] << std::dec << ",";
1360 	}
1361 
1362 	int id = m_state->m_styleManager.add(style, true);
1363 	cell->m_styleId=id;
1364 
1365 	ascii().addPos(pos);
1366 	ascii().addNote(f.str().c_str());
1367 	if (m_input->tell()!=pos+4+sz) ascii().addDelimiter(m_input->tell(),'#');
1368 
1369 	return false;
1370 }
1371 
readMsWorksDOSPageBreak()1372 bool WKS4Spreadsheet::readMsWorksDOSPageBreak()
1373 {
1374 	libwps::DebugStream f;
1375 	long pos = m_input->tell();
1376 	long type = libwps::read16(m_input);
1377 
1378 	if (type != 0x5427)
1379 	{
1380 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readMsWorksDOSPageBreak: not a pgbreak zone\n"));
1381 		return false;
1382 	}
1383 	long sz = libwps::readU16(m_input);
1384 	if (!sz)
1385 	{
1386 		ascii().addPos(pos);
1387 		ascii().addNote("_");
1388 		return true;
1389 	}
1390 	int row = int(libwps::read8(m_input))+1;
1391 	m_state->getActualSheet().m_rowPageBreaksList.push_back(row);
1392 
1393 	f << "Entries(PBRK): row="<< row;
1394 	if (sz != 1) ascii().addDelimiter(m_input->tell(),'#');
1395 
1396 	ascii().addPos(pos);
1397 	ascii().addNote(f.str().c_str());
1398 	return true;
1399 }
1400 
1401 ////////////////////////////////////////////////////////////
1402 // general
1403 ////////////////////////////////////////////////////////////
1404 
readCell()1405 bool WKS4Spreadsheet::readCell()
1406 {
1407 	libwps::DebugStream f;
1408 
1409 	long pos = m_input->tell();
1410 	long type = libwps::read16(m_input);
1411 	if ((type != 0x545b) && (type!=0x36) && (type < 0xc || type > 0x10))
1412 	{
1413 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell: not a cell property\n"));
1414 		return false;
1415 	}
1416 	long sz = libwps::readU16(m_input);
1417 	long endPos = pos+4+sz;
1418 
1419 	if (sz < 5)
1420 	{
1421 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell: cell def is too short\n"));
1422 		return false;
1423 	}
1424 	int const vers=version();
1425 	bool dosFile = vers < 3;
1426 	int format = 0xFF;
1427 	if (dosFile)
1428 		format = int(libwps::readU8(m_input));
1429 	int cellPos[2];
1430 	cellPos[0]=int(libwps::readU8(m_input));
1431 	auto sheetId=int(libwps::readU8(m_input)); // maybe a file id?
1432 	cellPos[1]=int(libwps::read16(m_input));
1433 	if (cellPos[1] < 0)
1434 	{
1435 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell: cell pos is bad\n"));
1436 		return false;
1437 	}
1438 	if (sheetId)
1439 	{
1440 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell: find unexpected sheet id\n"));
1441 		f << "###sheet[id]=" << sheetId << ",";
1442 	}
1443 
1444 	auto &cell=m_state->getActualSheet().getCell(Vec2i(cellPos[0],cellPos[1]));
1445 	if (!dosFile)
1446 		cell.m_styleId = int(libwps::read16(m_input));
1447 
1448 	if (type & 0xFF00)
1449 	{
1450 		if (dosFile)
1451 		{
1452 			static bool first = true;
1453 			if (first)
1454 			{
1455 				first = false;
1456 				WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell: find type=%ld in dos version\n", type));
1457 			}
1458 			f << "###";
1459 		}
1460 		f << "win[version],";
1461 	}
1462 
1463 	long dataPos = m_input->tell();
1464 	auto dataSz = int(endPos-dataPos);
1465 
1466 	bool ok = true;
1467 	switch (type)
1468 	{
1469 	case 12:
1470 	{
1471 		if (dataSz == 0)
1472 		{
1473 			cell.m_content.m_contentType=WKSContentListener::CellContent::C_NONE;
1474 			break;
1475 		}
1476 		ok = false;
1477 		break;
1478 	}
1479 	case 13:
1480 	{
1481 		if (dataSz == 2)
1482 		{
1483 			cell.m_content.m_contentType=WKSContentListener::CellContent::C_NUMBER;
1484 			cell.m_content.setValue(libwps::read16(m_input));
1485 			break;
1486 		}
1487 		ok = false;
1488 		break;
1489 	}
1490 	case 14:
1491 	{
1492 		double val;
1493 		bool isNaN;
1494 		if (dataSz == 8 && libwps::readDouble8(m_input, val, isNaN))
1495 		{
1496 			cell.m_content.m_contentType=WKSContentListener::CellContent::C_NUMBER;
1497 			cell.m_content.setValue(val);
1498 			break;
1499 		}
1500 		ok = false;
1501 		break;
1502 	}
1503 	case 15:
1504 	case 0x36: // continue text zone
1505 	{
1506 		if (type==0x36 && cell.m_content.m_contentType!=WKSContentListener::CellContent::C_TEXT)
1507 		{
1508 			WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell: oops, find zone 0x36 but not zone 15\n"));
1509 			f << "###";
1510 			break;
1511 		}
1512 		cell.m_content.m_contentType=WKSContentListener::CellContent::C_TEXT;
1513 		long begText=m_input->tell(), endText=begText+dataSz;
1514 		std::string s("");
1515 		for (int i = 0; i < dataSz; i++)
1516 		{
1517 			auto c = char(libwps::read8(m_input));
1518 			if (c=='\0')
1519 			{
1520 				endText=m_input->tell()-1;
1521 				if (i == dataSz-1) break;
1522 				WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell: cell content seems bad\n"));
1523 				f << "###";
1524 				break;
1525 			}
1526 			s += c;
1527 		}
1528 		f << s << ",";
1529 		if (dosFile && s.length())
1530 		{
1531 			if (s[0]=='\'') cell.m_hAlignement=WPSCellFormat::HALIGN_DEFAULT;
1532 			else if (s[0]=='\\') cell.m_hAlignement=WPSCellFormat::HALIGN_LEFT;
1533 			else if (s[0]=='^') cell.m_hAlignement=WPSCellFormat::HALIGN_CENTER;
1534 			else if (s[0]=='\"') cell.m_hAlignement=WPSCellFormat::HALIGN_RIGHT;
1535 			else
1536 				--begText;
1537 			++begText;
1538 		}
1539 		if (type==15)
1540 		{
1541 			cell.m_content.m_textEntry.setBegin(begText);
1542 			cell.m_content.m_textEntry.setEnd(endText);
1543 		}
1544 		else
1545 		{
1546 			WPSEntry newEntry;
1547 			newEntry.setBegin(begText);
1548 			newEntry.setEnd(endText);
1549 			cell.m_extraTextEntryList.push_back(newEntry);
1550 		}
1551 		break;
1552 	}
1553 	case 16:
1554 	{
1555 		double val;
1556 		bool isNaN;
1557 		if (dataSz >= 8 && libwps::readDouble8(m_input, val, isNaN))
1558 		{
1559 			cell.m_content.m_contentType=WKSContentListener::CellContent::C_FORMULA;
1560 			cell.m_content.setValue(val);
1561 			std::string error;
1562 			if (!readFormula(endPos, cell.position(), cell.m_content.m_formula, error))
1563 			{
1564 				cell.m_content.m_contentType=WKSContentListener::CellContent::C_NUMBER;
1565 				ascii().addDelimiter(m_input->tell()-1, '#');
1566 			}
1567 			if (error.length()) f << error;
1568 			break;
1569 		}
1570 		ok = false;
1571 		break;
1572 	}
1573 	case 0x545b:   // CHECKME: sometime we do not read the good number
1574 	{
1575 		double val;
1576 		bool isNaN;
1577 		if (dataSz == 4 && libwps::readDouble4(m_input, val, isNaN))
1578 		{
1579 			cell.m_content.m_contentType=WKSContentListener::CellContent::C_NUMBER;
1580 			cell.m_content.setValue(val);
1581 			break;
1582 		}
1583 		ok = false;
1584 		break;
1585 	}
1586 	default:
1587 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell: unknown type=%ld\n", type));
1588 		ok = false;
1589 		break;
1590 	}
1591 	if (!ok) ascii().addDelimiter(dataPos, '#');
1592 
1593 	if (dosFile)
1594 	{
1595 		WKS4SpreadsheetInternal::Style style(m_mainParser.getDefaultFontType());
1596 		switch (cell.m_content.m_contentType)
1597 		{
1598 		case WKSContentListener::CellContent::C_NONE:
1599 			break;
1600 		case WKSContentListener::CellContent::C_TEXT:
1601 			style.setFormat(WPSCell::F_TEXT);
1602 			break;
1603 		case WKSContentListener::CellContent::C_NUMBER:
1604 		case WKSContentListener::CellContent::C_FORMULA:
1605 		case WKSContentListener::CellContent::C_UNKNOWN:
1606 		default:
1607 			style.setFormat(WPSCell::F_NUMBER);
1608 			break;
1609 		}
1610 
1611 		int styleID = (format>>4) & 0x7;
1612 		switch (styleID)
1613 		{
1614 		case 0:
1615 		case 1:
1616 		case 2:
1617 		case 3:
1618 		case 4:
1619 			if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1620 				break;
1621 			if (styleID == 2) styleID = 3;
1622 			else if (styleID == 3) styleID = 2;
1623 			style.setFormat(WPSCell::F_NUMBER,1+styleID);
1624 			break;
1625 		// unsure find in some Quattro Pro File
1626 		case 5:
1627 			if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1628 				break;
1629 			switch (format&0xF)
1630 			{
1631 			case 4: // a date but no sure which format is good
1632 				style.setDTFormat(WPSCell::F_DATE, "%m/%d/%y");
1633 				break;
1634 			case 5:
1635 				style.setDTFormat(WPSCell::F_DATE, "%m/%d");
1636 				break;
1637 			default:
1638 				WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell::updateFormat: unknown format %x\n", unsigned(format)));
1639 				break;
1640 			}
1641 			break;
1642 		// LotusSymphony format
1643 		case 0x7:
1644 			switch (format&0xF)
1645 			{
1646 			case 0: // +/- : kind of bool
1647 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1648 					break;
1649 				style.setFormat(WPSCell::F_BOOLEAN);
1650 				break;
1651 			case 1:
1652 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1653 					break;
1654 				style.setFormat(WPSCell::F_NUMBER, 0);
1655 				break;
1656 			case 2:
1657 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1658 					break;
1659 				style.setDTFormat(WPSCell::F_DATE, "%d %B %y");
1660 				break;
1661 			case 3:
1662 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1663 					break;
1664 				style.setDTFormat(WPSCell::F_DATE, "%d %B");
1665 				break;
1666 			case 4:
1667 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1668 					break;
1669 				style.setDTFormat(WPSCell::F_DATE, "%B %y");
1670 				break;
1671 			case 5:
1672 				if (cell.m_content.m_contentType!=WKSContentListener::CellContent::C_TEXT)
1673 					break;
1674 				style.setFormat(WPSCell::F_TEXT);
1675 				break;
1676 			case 6:
1677 				if (cell.m_content.m_contentType!=WKSContentListener::CellContent::C_TEXT)
1678 					break;
1679 				style.setFormat(WPSCell::F_TEXT);
1680 				style.m_fileFont.m_attributes |= WPS_HIDDEN_BIT;
1681 				break;
1682 			case 7:
1683 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1684 					break;
1685 				style.setDTFormat(WPSCell::F_TIME, "%I:%M:%S%p");
1686 				break;
1687 			case 8:
1688 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1689 					break;
1690 				style.setDTFormat(WPSCell::F_TIME, "%I:%M%p");
1691 				break;
1692 			case 9:
1693 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1694 					break;
1695 				style.setDTFormat(WPSCell::F_DATE, "%m/%d/%y");
1696 				break;
1697 			case 0xa:
1698 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1699 					break;
1700 				style.setDTFormat(WPSCell::F_DATE, "%m/%d");
1701 				break;
1702 			case 0xb:
1703 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1704 					break;
1705 				style.setDTFormat(WPSCell::F_TIME, "%H:%M:%S");
1706 				break;
1707 			case 0xc:
1708 				if (cell.m_content.m_contentType==WKSContentListener::CellContent::C_TEXT)
1709 					break;
1710 				style.setDTFormat(WPSCell::F_TIME, "%H:%M");
1711 				break;
1712 			case 0xd:
1713 				if (cell.m_content.m_contentType!=WKSContentListener::CellContent::C_TEXT)
1714 					break;
1715 				style.setFormat(WPSCell::F_TEXT);
1716 				break;
1717 			case 0xf: // automatic
1718 				break;
1719 			default:
1720 				break;
1721 			}
1722 			break;
1723 		default:
1724 			f << "type=" << styleID << ",";
1725 		}
1726 		if ((format&0x80)==0) f << "protected=0,";
1727 		style.setDigits(format & 0xF);
1728 		cell.m_styleId=m_state->m_styleManager.add(style, true);
1729 	}
1730 	m_input->seek(pos+sz, librevenge::RVNG_SEEK_SET);
1731 
1732 	std::string extra=f.str();
1733 	f.str("");
1734 	f << "Entries(CellContent):" << cell << "," << extra;
1735 #ifdef DEBUG_WITH_FILES
1736 	m_state->m_styleManager.print(cell.m_styleId, f);
1737 #endif
1738 
1739 	ascii().addPos(pos);
1740 	ascii().addNote(f.str().c_str());
1741 
1742 	return true;
1743 }
1744 
readCellFormulaResult()1745 bool WKS4Spreadsheet::readCellFormulaResult()
1746 {
1747 	libwps::DebugStream f;
1748 
1749 	long pos = m_input->tell();
1750 	long type = libwps::read16(m_input);
1751 	if (type != 0x33)
1752 	{
1753 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readCellFormulaResult: not a cell property\n"));
1754 		return false;
1755 	}
1756 	long sz = libwps::readU16(m_input);
1757 	if (sz<6)
1758 	{
1759 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readCellFormulaResult: the zone seems to short\n"));
1760 		return false;
1761 	}
1762 	long endPos = pos+4+sz;
1763 
1764 	bool dosFile = version() < 3;
1765 	// skip format for dosFile
1766 	m_input->seek(dosFile ? pos+5 : pos+4, librevenge::RVNG_SEEK_SET);
1767 	f << "CellContent[res]:";
1768 	int dim[2];
1769 	for (int &i : dim) i=int(libwps::readU16(m_input));
1770 	f << "C" << dim[0] << "x" << dim[1] << ",";
1771 	// skip format for windows file
1772 	if (!dosFile) m_input->seek(2, librevenge::RVNG_SEEK_CUR);
1773 	auto sSz=int(endPos-m_input->tell());
1774 	librevenge::RVNGString text;
1775 	if (!m_mainParser.readCString(text,sSz))
1776 		f << "##text,";
1777 	else if (!text.empty())
1778 		f << text.cstr() << ",";
1779 	if (m_input->tell()!=endPos)
1780 		ascii().addDelimiter(m_input->tell(),'|');
1781 	ascii().addPos(pos);
1782 	ascii().addNote(f.str().c_str());
1783 	m_input->seek(endPos, librevenge::RVNG_SEEK_SET);
1784 	return true;
1785 }
1786 
1787 ////////////////////////////////////////////////////////////
1788 // Data
1789 ////////////////////////////////////////////////////////////
readCell(Vec2i actPos,WKSContentListener::FormulaInstruction & instr)1790 bool WKS4Spreadsheet::readCell
1791 (Vec2i actPos, WKSContentListener::FormulaInstruction &instr)
1792 {
1793 	instr=WKSContentListener::FormulaInstruction();
1794 	instr.m_type=WKSContentListener::FormulaInstruction::F_Cell;
1795 	bool ok = true;
1796 	int pos[2];
1797 	bool absolute[2] = { true, true};
1798 	for (int dim = 0; dim < 2; dim++)
1799 	{
1800 		auto val = int(libwps::readU16(m_input));
1801 		if ((val & 0x8000) == 0); // absolue value ?
1802 		else
1803 		{
1804 			// relative
1805 			// wb1: maximum row=0x2000, maximum col=0x100
1806 			// wks dos (v3) maximum row=0x4000, maximum col=0x100
1807 			// wdb maximum number of data ?
1808 			if (version()==1 && dim==0)
1809 			{
1810 				val &= 0xFF;
1811 				if ((val & 0x80) && val+actPos[dim] >= 0x100)
1812 					// sometimes this value is odd, so do not generate errors here
1813 					val = val - 0x100;
1814 			}
1815 			else
1816 			{
1817 				// 0x400 for old file(unsure), ie. find many problematic files on
1818 				//   the web, so maybe 0x4000 is ok and these files are
1819 				//   problematic
1820 				int const maxVal= (dim==1 || m_mainParser.creator()==libwps::WPS_LOTUS) ? 0x2000 : version()==1 ? 0x400 : 0x4000;
1821 				val &= (2*maxVal-1);
1822 				if (val & maxVal) val = val - 2*maxVal;
1823 				if (val+actPos[dim]>=maxVal) val-=maxVal;
1824 			}
1825 			val += actPos[dim];
1826 			absolute[dim] = false;
1827 		}
1828 		pos[dim] = val;
1829 	}
1830 
1831 	if (pos[0] < 0 || pos[1] < 0)
1832 	{
1833 		std::stringstream f;
1834 		f << "###[" << pos[1] << "," << pos[0] << "]";
1835 		if (ok)
1836 		{
1837 			WPS_DEBUG_MSG(("WKS4Spreadsheet::readCell: can not read cell position\n"));
1838 		}
1839 		return false;
1840 	}
1841 	instr.m_position[0]=Vec2i(pos[0],pos[1]);
1842 	instr.m_positionRelative[0]=Vec2b(!absolute[0],!absolute[1]);
1843 	return ok;
1844 }
1845 
1846 namespace WKS4SpreadsheetInternal
1847 {
1848 struct Functions
1849 {
1850 	char const *m_name;
1851 	int m_arity;
1852 };
1853 
1854 static Functions const s_listFunctions[] =
1855 {
1856 	{ "", 0} /*SPEC: number*/, {"", 0}/*SPEC: cell*/, {"", 0}/*SPEC: cells*/, {"=", 1} /*SPEC: end of formula*/,
1857 	{ "(", 1} /* SPEC: () */, {"", 0}/*SPEC: number*/, { "", -2} /*SPEC: text*/, {"", -2}/*unused*/,
1858 	{ "-", 1}, {"+", 2}, {"-", 2}, {"*", 2},
1859 	{ "/", 2}, { "^", 2}, {"=", 2}, {"<>", 2},
1860 
1861 	{ "<=", 2},{ ">=", 2},{ "<", 2},{ ">", 2},
1862 	{ "And", 2},{ "Or", 2}, { "Not", 1}, { "+", 1},
1863 	{ "&", 2}, { "", -2} /*unused*/,{ "", -2} /*unused*/,{ "", -2} /*unused*/,
1864 	{ "", -2} /*unused*/,{ "", -2} /*unused*/,{ "", -2} /*unused*/,{ "NA", 0} /*checkme*/,
1865 
1866 	{ "NA", 0} /* Error*/,{ "Abs", 1},{ "Int", 1},{ "Sqrt", 1},
1867 	{ "Log10", 1},{ "Ln", 1},{ "Pi", 0},{ "Sin", 1},
1868 	{ "Cos", 1},{ "Tan", 1},{ "Atan2", 2},{ "Atan", 1},
1869 	{ "Asin", 1},{ "Acos", 1},{ "Exp", 1},{ "Mod", 2},
1870 
1871 	{ "Choose", -1},{ "IsNa", 1},{ "IsError", 1},{ "False", 0},
1872 	{ "True", 0},{ "Rand", 0},{ "Date", 3},{ "Now", 0},
1873 	{ "PMT", 3} /*BAD*/,{ "PV", 3} /*BAD*/,{ "FV", 3} /*BAD*/,{ "IF", 3},
1874 	{ "Day", 1},{ "Month", 1},{ "Year", 1},{ "Round", 2},
1875 
1876 	{ "Time", 3},{ "Hour", 1},{ "Minute", 1},{ "Second", 1},
1877 	{ "IsNumber", 1},{ "IsText", 1},{ "Len", 1},{ "Value", 1},
1878 	{ "Fixed", 2}, { "Mid", 3}, { "Char", 1},{ "Ascii", 1},
1879 	{ "Find", 3},{ "DateValue", 1} /*checkme*/,{ "TimeValue", 1} /*checkme*/,{ "CellPointer", 1} /*checkme*/,
1880 
1881 	{ "Sum", -1},{ "Average", -1},{ "COUNT", -1},{ "Min", -1},
1882 	{ "Max", -1},{ "VLookUp", 3},{ "NPV", 2}, { "Var", -1},
1883 	{ "StDev", -1},{ "IRR", 2} /*BAD*/, { "HLookup", 3},{ "DSum", 3},
1884 	{ "DAvg", 3},{ "DCount", 3},{ "DMin", 3},{ "DMax", 3},
1885 
1886 	{ "DVar", 3},{ "DStd", 3},{ "Index", 3}, { "Columns", 1},
1887 	{ "Rows", 1},{ "Rept", 2},{ "Upper", 1},{ "Lower", 1},
1888 	{ "Left", 2},{ "Right", 2},{ "Replace", 4}, { "Proper", 1},
1889 	{ "Cell", 1} /*checkme*/,{ "Trim", 1},{ "", -2} /*UNKN*/,{ "T", 1},
1890 
1891 	{ "IsNonText", 1},{ "Exact", 2},{ "", -2} /*UNKN*/,{ "", 3} /*UNKN*/,
1892 	{ "Rate", 3} /*BAD*/,{ "TERM", 3}, { "CTERM", 3}, { "SLN", 3},
1893 	{ "SYD", 4},{ "DDB", 4} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
1894 	{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
1895 
1896 	{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/, { "", -2} /*UNKN*/,
1897 	{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
1898 	{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
1899 	{ "", -2} /*UNKN*/,{ "And", -1},{ "Or", -1},{ "Not", 1},
1900 
1901 };
1902 }
1903 
readFormula(long endPos,Vec2i const & position,std::vector<WKSContentListener::FormulaInstruction> & formula,std::string & error)1904 bool WKS4Spreadsheet::readFormula(long endPos, Vec2i const &position,
1905                                   std::vector<WKSContentListener::FormulaInstruction> &formula, std::string &error)
1906 {
1907 	int const vers=version();
1908 	formula.resize(0);
1909 	error = "";
1910 	long pos = m_input->tell();
1911 	if (endPos - pos < 2) return false;
1912 	auto sz = int(libwps::readU16(m_input));
1913 	if (endPos-pos-2 != sz) return false;
1914 
1915 	std::stringstream f;
1916 	std::vector<std::vector<WKSContentListener::FormulaInstruction> > stack;
1917 	bool ok = true;
1918 	while (long(m_input->tell()) != endPos)
1919 	{
1920 		double val;
1921 		bool isNaN;
1922 		pos = m_input->tell();
1923 		if (pos > endPos) return false;
1924 		auto wh = int(libwps::readU8(m_input));
1925 		int arity = 0;
1926 		WKSContentListener::FormulaInstruction instr;
1927 		switch (wh)
1928 		{
1929 		case 0x0:
1930 			if (endPos-pos<9 || !libwps::readDouble8(m_input, val, isNaN))
1931 			{
1932 				f.str("");
1933 				f << "###number";
1934 				error=f.str();
1935 				ok = false;
1936 				break;
1937 			}
1938 			instr.m_type=WKSContentListener::FormulaInstruction::F_Double;
1939 			instr.m_doubleValue=val;
1940 			break;
1941 		case 0x1:
1942 			if (endPos-pos<5)
1943 			{
1944 				f.str("");
1945 				f << "###cell short";
1946 				error=f.str();
1947 				ok = false;
1948 				break;
1949 			}
1950 			ok = readCell(position, instr);
1951 			break;
1952 		case 0x2:
1953 		{
1954 			if (endPos-pos< (vers<=1 ? 7 : 9) || !readCell(position, instr))
1955 			{
1956 				f.str("");
1957 				f << "###list cell short";
1958 				error=f.str();
1959 				ok = false;
1960 				break;
1961 			}
1962 			WKSContentListener::FormulaInstruction instr2;
1963 			if (!readCell(position, instr2))
1964 			{
1965 				ok = false;
1966 				f.str("");
1967 				f << "###list cell short(2)";
1968 				error=f.str();
1969 				break;
1970 			}
1971 			instr.m_type=WKSContentListener::FormulaInstruction::F_CellList;
1972 			instr.m_position[1]=instr2.m_position[0];
1973 			instr.m_positionRelative[1]=instr2.m_positionRelative[0];
1974 			break;
1975 		}
1976 		case 0x5:
1977 			instr.m_type=WKSContentListener::FormulaInstruction::F_Long;
1978 			instr.m_longValue=long(libwps::read16(m_input));
1979 			break;
1980 		case 0x6:
1981 			instr.m_type=WKSContentListener::FormulaInstruction::F_Text;
1982 			while (!m_input->isEnd())
1983 			{
1984 				if (m_input->tell() >= endPos)
1985 				{
1986 					ok=false;
1987 					break;
1988 				}
1989 				auto c = char(libwps::readU8(m_input));
1990 				if (c==0) break;
1991 				instr.m_content += c;
1992 			}
1993 			break;
1994 		default:
1995 			if (wh >= 0x90 || WKS4SpreadsheetInternal::s_listFunctions[wh].m_arity == -2)
1996 			{
1997 				f.str("");
1998 				f << "##Funct" << std::hex << wh;
1999 				error=f.str();
2000 				ok = false;
2001 				break;
2002 			}
2003 			instr.m_type=WKSContentListener::FormulaInstruction::F_Function;
2004 			instr.m_content=WKS4SpreadsheetInternal::s_listFunctions[wh].m_name;
2005 			ok=!instr.m_content.empty();
2006 			arity = WKS4SpreadsheetInternal::s_listFunctions[wh].m_arity;
2007 			if (arity == -1) arity = int(libwps::read8(m_input));
2008 			break;
2009 		}
2010 
2011 		if (!ok) break;
2012 		std::vector<WKSContentListener::FormulaInstruction> child;
2013 		if (instr.m_type!=WKSContentListener::FormulaInstruction::F_Function)
2014 		{
2015 			child.push_back(instr);
2016 			stack.push_back(child);
2017 			continue;
2018 		}
2019 		size_t numElt = stack.size();
2020 		if (int(numElt) < arity)
2021 		{
2022 			f.str("");
2023 			f << instr.m_content << "[##" << arity << "]";
2024 			error=f.str();
2025 			ok = false;
2026 			break;
2027 		}
2028 		//
2029 		// first treat the special cases
2030 		//
2031 		if (arity==3 && instr.m_type==WKSContentListener::FormulaInstruction::F_Function && instr.m_content=="TERM")
2032 		{
2033 			// @TERM(pmt,pint,fv) -> NPER(pint,-pmt,pv=0,fv)
2034 			auto pmt=stack[size_t(int(numElt)-3)];
2035 			auto pint=stack[size_t(int(numElt)-2)];
2036 			auto fv=stack[size_t(int(numElt)-1)];
2037 
2038 			stack.resize(size_t(++numElt));
2039 			// pint
2040 			stack[size_t(int(numElt)-4)]=pint;
2041 			//-pmt
2042 			auto &node=stack[size_t(int(numElt)-3)];
2043 			instr.m_type=WKSContentListener::FormulaInstruction::F_Operator;
2044 			instr.m_content="-";
2045 			node.resize(0);
2046 			node.push_back(instr);
2047 			instr.m_content="(";
2048 			node.push_back(instr);
2049 			node.insert(node.end(), pmt.begin(), pmt.end());
2050 			instr.m_content=")";
2051 			node.push_back(instr);
2052 			//pv=zero
2053 			instr.m_type=WKSContentListener::FormulaInstruction::F_Long;
2054 			instr.m_longValue=0;
2055 			stack[size_t(int(numElt)-2)].resize(0);
2056 			stack[size_t(int(numElt)-2)].push_back(instr);
2057 			//fv
2058 			stack[size_t(int(numElt)-1)]=fv;
2059 			arity=4;
2060 			instr.m_type=WKSContentListener::FormulaInstruction::F_Function;
2061 			instr.m_content="NPER";
2062 		}
2063 		else if (arity==3 && instr.m_type==WKSContentListener::FormulaInstruction::F_Function && instr.m_content=="CTERM")
2064 		{
2065 			// @CTERM(pint,fv,pv) -> NPER(pint,pmt=0,-pv,fv)
2066 			auto pint=stack[size_t(int(numElt)-3)];
2067 			auto fv=stack[size_t(int(numElt)-2)];
2068 			auto pv=stack[size_t(int(numElt)-1)];
2069 			stack.resize(size_t(++numElt));
2070 			// pint
2071 			stack[size_t(int(numElt)-4)]=pint;
2072 			// pmt=0
2073 			instr.m_type=WKSContentListener::FormulaInstruction::F_Long;
2074 			instr.m_longValue=0;
2075 			stack[size_t(int(numElt)-3)].resize(0);
2076 			stack[size_t(int(numElt)-3)].push_back(instr);
2077 			// -pv
2078 			auto &node=stack[size_t(int(numElt)-2)];
2079 			instr.m_type=WKSContentListener::FormulaInstruction::F_Operator;
2080 			instr.m_content="-";
2081 			node.resize(0);
2082 			node.push_back(instr);
2083 			instr.m_content="(";
2084 			node.push_back(instr);
2085 			node.insert(node.end(), pv.begin(), pv.end());
2086 			instr.m_content=")";
2087 			node.push_back(instr);
2088 
2089 			//fv
2090 			stack[size_t(int(numElt)-1)]=fv;
2091 			arity=4;
2092 			instr.m_type=WKSContentListener::FormulaInstruction::F_Function;
2093 			instr.m_content="NPER";
2094 		}
2095 
2096 		if ((instr.m_content[0] >= 'A' && instr.m_content[0] <= 'Z') || instr.m_content[0] == '(')
2097 		{
2098 			if (instr.m_content[0] != '(')
2099 				child.push_back(instr);
2100 
2101 			instr.m_type=WKSContentListener::FormulaInstruction::F_Operator;
2102 			instr.m_content="(";
2103 			child.push_back(instr);
2104 			for (int i = 0; i < arity; i++)
2105 			{
2106 				if (i)
2107 				{
2108 					instr.m_content=";";
2109 					child.push_back(instr);
2110 				}
2111 				auto const &node=stack[size_t(int(numElt)-arity+i)];
2112 				child.insert(child.end(), node.begin(), node.end());
2113 			}
2114 			instr.m_content=")";
2115 			child.push_back(instr);
2116 
2117 			stack.resize(size_t(int(numElt)-arity+1));
2118 			stack[size_t(int(numElt)-arity)] = child;
2119 			continue;
2120 		}
2121 		if (arity==1)
2122 		{
2123 			instr.m_type=WKSContentListener::FormulaInstruction::F_Operator;
2124 			stack[numElt-1].insert(stack[numElt-1].begin(), instr);
2125 			if (wh==3)
2126 				break;
2127 			continue;
2128 		}
2129 		if (arity==2)
2130 		{
2131 			instr.m_type=WKSContentListener::FormulaInstruction::F_Operator;
2132 			stack[numElt-2].push_back(instr);
2133 			stack[numElt-2].insert(stack[numElt-2].end(), stack[numElt-1].begin(), stack[numElt-1].end());
2134 			stack.resize(numElt-1);
2135 			continue;
2136 		}
2137 		ok=false;
2138 		error = "### unexpected arity";
2139 		break;
2140 	}
2141 
2142 	if (!ok) ;
2143 	else if (stack.size()==1 && stack[0].size()>1 && stack[0][0].m_content=="=")
2144 	{
2145 		formula.insert(formula.begin(),stack[0].begin()+1,stack[0].end());
2146 		if (m_input->tell()!=endPos)
2147 		{
2148 			WPS_DEBUG_MSG(("WKS4Spreadsheet::readFormula: find some extra data\n"));
2149 			error="##extra data";
2150 			ascii().addDelimiter(m_input->tell(),'#');
2151 		}
2152 		return true;
2153 	}
2154 	else
2155 		error = "###stack problem";
2156 
2157 	static bool first = true;
2158 	if (first)
2159 	{
2160 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readFormula: I can not read some formula\n"));
2161 		first = false;
2162 	}
2163 
2164 	f.str("");
2165 	for (auto const &i : stack)
2166 	{
2167 		for (auto const &j : i)
2168 			f << j << ",";
2169 	}
2170 	f << error << "###";
2171 	error = f.str();
2172 	return false;
2173 }
2174 
2175 ////////////////////////////////////////////////////////////
2176 // filter
2177 ////////////////////////////////////////////////////////////
readFilterOpen()2178 bool WKS4Spreadsheet::readFilterOpen()
2179 {
2180 	libwps::DebugStream f;
2181 
2182 	long pos = m_input->tell();
2183 	auto type = long(libwps::readU16(m_input));
2184 	if (type != 0x5410)
2185 	{
2186 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readFilterOpen: not a filter header\n"));
2187 		return false;
2188 	}
2189 	m_state->pushNewSheet(std::shared_ptr<WKS4SpreadsheetInternal::Spreadsheet>
2190 	                      (new WKS4SpreadsheetInternal::Spreadsheet
2191 	                       (WKS4SpreadsheetInternal::Spreadsheet::T_Filter, 0)));
2192 	auto sz = long(libwps::readU16(m_input));
2193 	f << "Entries(Filter)[beg]:";
2194 	if (sz!=0)
2195 	{
2196 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readFilterOpen: the field size seems odd\n"));
2197 		f << "###";
2198 	}
2199 	ascii().addPos(pos);
2200 	ascii().addNote(f.str().c_str());
2201 	return true;
2202 }
2203 
readFilterClose()2204 bool WKS4Spreadsheet::readFilterClose()
2205 {
2206 	libwps::DebugStream f;
2207 
2208 	long pos = m_input->tell();
2209 	auto type = long(libwps::readU16(m_input));
2210 	if (type != 0x5411)
2211 	{
2212 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readFilterClose: not a filter header\n"));
2213 		return false;
2214 	}
2215 	auto sz = long(libwps::readU16(m_input));
2216 	f << "Entries(Filter)[end]:";
2217 	auto const &sheet=m_state->getActualSheet();
2218 	if (sheet.m_type!=WKS4SpreadsheetInternal::Spreadsheet::T_Filter)
2219 	{
2220 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readFilterClose: can not find filter spreadsheet\n"));
2221 		f << "###[noOpen],";
2222 	}
2223 	else
2224 		m_state->popSheet();
2225 	if (sz!=0)
2226 	{
2227 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readFilterClose: the field size seems odd\n"));
2228 		f << "###";
2229 	}
2230 	ascii().addPos(pos);
2231 	ascii().addNote(f.str().c_str());
2232 	return true;
2233 }
2234 
2235 ////////////////////////////////////////////////////////////
2236 // report
2237 ////////////////////////////////////////////////////////////
readReportOpen()2238 bool WKS4Spreadsheet::readReportOpen()
2239 {
2240 	libwps::DebugStream f;
2241 
2242 	long pos = m_input->tell();
2243 	auto type = long(libwps::readU16(m_input));
2244 	if (type != 0x5417)
2245 	{
2246 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readReportOpen: not a report header\n"));
2247 		return false;
2248 	}
2249 	m_state->pushNewSheet(std::shared_ptr<WKS4SpreadsheetInternal::Spreadsheet>
2250 	                      (new WKS4SpreadsheetInternal::Spreadsheet
2251 	                       (WKS4SpreadsheetInternal::Spreadsheet::T_Report, 0)));
2252 	auto sz = long(libwps::readU16(m_input));
2253 	long endPos = pos+4+sz;
2254 	f << "Entries(Report)[header]:";
2255 	if (sz < 16+10+7 || !checkFilePosition(endPos))
2256 	{
2257 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readReportOpen: report header too short\n"));
2258 		f << "###";
2259 		ascii().addPos(pos);
2260 		ascii().addNote(f.str().c_str());
2261 		return true;
2262 	}
2263 
2264 	librevenge::RVNGString name;
2265 	if (!m_mainParser.readCString(name,16))
2266 		f << "##name,";
2267 	else if (!name.empty())
2268 		f << name.cstr() << ",";
2269 	m_input->seek(pos+4+16, librevenge::RVNG_SEEK_SET);
2270 	auto val=int(libwps::readU8(m_input)); // always 0?
2271 	if (val) f << "f0=" << val << ",";
2272 	for (int i=0; i<3; ++i)   // normally -1:0, but I also find 4:1
2273 	{
2274 		val=int(libwps::read16(m_input));
2275 		auto unkn=int(libwps::readU8(m_input));
2276 		if (val==-1 && unkn==0) continue;
2277 		f << "unk" << i << "=" << val << ":" << unkn << ",";
2278 	}
2279 	auto N=int(libwps::readU16(m_input));
2280 	if (m_input->tell()+N+7>endPos)
2281 	{
2282 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readReportOpen: number of fields seems bad\n"));
2283 		f << "###N[fields]=" << N << ",";
2284 		ascii().addPos(pos);
2285 		ascii().addNote(f.str().c_str());
2286 		return true;
2287 	}
2288 	// we must update the number of columns
2289 	m_state->getActualSheet().m_numCols=N;
2290 	f << "fields=[";
2291 	for (int i=0; i<N; ++i) f << int(libwps::readU8(m_input)) << ",";
2292 	f << "],";
2293 	for (int i=0; i<8; i++)
2294 	{
2295 		if (m_input->tell()>endPos) break;
2296 		static int const expected[]= {0,0x1a,4,0xff, 0,0xa, 0, 1};
2297 		val=int(libwps::readU8(m_input));
2298 		if (val!=expected[i]) f << "g" << i << "=" << val << ",";
2299 	}
2300 	ascii().addDelimiter(m_input->tell(), '|');
2301 	ascii().addPos(pos);
2302 	ascii().addNote(f.str().c_str());
2303 	return true;
2304 }
2305 
readReportClose()2306 bool WKS4Spreadsheet::readReportClose()
2307 {
2308 	libwps::DebugStream f;
2309 
2310 	long pos = m_input->tell();
2311 	auto type = long(libwps::readU16(m_input));
2312 	if (type != 0x5418)
2313 	{
2314 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readReportClose: not a report header\n"));
2315 		return false;
2316 	}
2317 	f << "Report[end]:";
2318 	auto const &sheet=m_state->getActualSheet();
2319 	if (sheet.m_type!=WKS4SpreadsheetInternal::Spreadsheet::T_Report)
2320 	{
2321 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readReportClose: can not find report spreadsheet\n"));
2322 		f << "###[noOpen],";
2323 	}
2324 	else
2325 		m_state->popSheet();
2326 	auto sz = long(libwps::readU16(m_input));
2327 	if (sz)
2328 	{
2329 		WPS_DEBUG_MSG(("WKS4Spreadsheet::readReportClose: find some data\n"));
2330 		f << "###[sz],";
2331 	}
2332 	ascii().addPos(pos);
2333 	ascii().addNote(f.str().c_str());
2334 	return true;
2335 }
2336 
2337 ////////////////////////////////////////////////////////////
2338 // send data
2339 ////////////////////////////////////////////////////////////
sendSpreadsheet(int sId)2340 void WKS4Spreadsheet::sendSpreadsheet(int sId)
2341 {
2342 	if (!m_listener)
2343 	{
2344 		WPS_DEBUG_MSG(("WKS4Spreadsheet::sendSpreadsheet: I can not find the listener\n"));
2345 		return;
2346 	}
2347 	std::shared_ptr<WKS4SpreadsheetInternal::Spreadsheet> sheet =
2348 	    m_state->getSheet(WKS4SpreadsheetInternal::Spreadsheet::T_Spreadsheet, sId);
2349 	if (!sheet)
2350 	{
2351 		if (sId==0)
2352 		{
2353 			WPS_DEBUG_MSG(("WKS4Spreadsheet::sendSpreadsheet: oops can not find the actual sheet\n"));
2354 		}
2355 		sheet.reset(new WKS4SpreadsheetInternal::Spreadsheet);
2356 	}
2357 
2358 	m_listener->openSheet(sheet->getWidths(), m_state->getSheetName(sId));
2359 	sheet->compressRowHeights();
2360 	auto it = sheet->m_positionToCellMap.begin();
2361 	int prevRow = -1;
2362 	while (it!=sheet->m_positionToCellMap.end())
2363 	{
2364 		int row=it->first[1];
2365 		auto const &cell=(it++)->second;
2366 		if (row>prevRow+1)
2367 		{
2368 			while (row > prevRow+1)
2369 			{
2370 				if (prevRow != -1) m_listener->closeSheetRow();
2371 				int numRepeat;
2372 				float h=sheet->getRowHeight(prevRow+1, numRepeat);
2373 				if (row<prevRow+1+numRepeat)
2374 					numRepeat=row-1-prevRow;
2375 				m_listener->openSheetRow(WPSRowFormat(h), numRepeat);
2376 				prevRow+=numRepeat;
2377 			}
2378 		}
2379 		if (row!=prevRow)
2380 		{
2381 			if (prevRow != -1) m_listener->closeSheetRow();
2382 			m_listener->openSheetRow(WPSRowFormat(sheet->getRowHeight(++prevRow)));
2383 		}
2384 		sendCellContent(cell);
2385 	}
2386 	if (prevRow!=-1) m_listener->closeSheetRow();
2387 	m_listener->closeSheet();
2388 }
2389 
sendCellContent(WKS4SpreadsheetInternal::Cell const & cell)2390 void WKS4Spreadsheet::sendCellContent(WKS4SpreadsheetInternal::Cell const &cell)
2391 {
2392 	if (m_listener.get() == nullptr)
2393 	{
2394 		WPS_DEBUG_MSG(("WKS4Spreadsheet::sendCellContent: I can not find the listener\n"));
2395 		return;
2396 	}
2397 
2398 	WKS4SpreadsheetInternal::Style cellStyle(m_mainParser.getDefaultFontType());
2399 	if (cell.m_styleId<0 || !m_state->m_styleManager.get(cell.m_styleId,cellStyle))
2400 	{
2401 		WPS_DEBUG_MSG(("WKS4Spreadsheet::sendCellContent: I can not find the cell style\n"));
2402 	}
2403 	if (version()<=2 && cell.m_hAlignement!=WPSCellFormat::HALIGN_DEFAULT)
2404 		cellStyle.setHAlignment(cell.m_hAlignement);
2405 
2406 	auto fontType = cellStyle.m_fontType;
2407 	m_listener->setFont(cellStyle.m_fileFont);
2408 
2409 	auto finalCell(cell);
2410 	finalCell.WPSCellFormat::operator=(cellStyle);
2411 	finalCell.setFont(cellStyle.m_fileFont);
2412 	auto content(cell.m_content);
2413 	for (auto &f : content.m_formula)
2414 	{
2415 		if (f.m_type!=WKSContentListener::FormulaInstruction::F_Text)
2416 			continue;
2417 		auto &text=f.m_content;
2418 		auto fText=libwps_tools_win::Font::unicodeString(text, fontType);
2419 		if (!fText.empty())
2420 			text=fText.cstr();
2421 		else
2422 			text.clear();
2423 	}
2424 	m_listener->openSheetCell(finalCell, content);
2425 
2426 	size_t numTextEntry=cell.m_content.m_textEntry.valid() ? 1 : 0;
2427 	if (!cell.m_extraTextEntryList.empty())
2428 		numTextEntry=1+cell.m_extraTextEntryList.size();
2429 	for (size_t t=0; t<numTextEntry; ++t)
2430 	{
2431 		WPSEntry entry=t==0 ? cell.m_content.m_textEntry : cell.m_extraTextEntryList[t-1];
2432 		if (!entry.valid()) continue;
2433 		m_input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
2434 		bool prevEOL=false;
2435 		std::string text;
2436 		while (m_input->tell()<=entry.end())
2437 		{
2438 			bool last=m_input->isEnd() || m_input->tell()>=entry.end();
2439 			auto c=last ? '\0' : char(libwps::readU8(m_input));
2440 			if ((c==0 || c==0xa || c==0xd || c==0x19) && !text.empty())
2441 			{
2442 				m_listener->insertUnicodeString(libwps_tools_win::Font::unicodeString(text, fontType));
2443 				text.clear();
2444 			}
2445 			if (last) break;
2446 			if (c==0xd)
2447 			{
2448 				m_listener->insertEOL();
2449 				prevEOL=true;
2450 			}
2451 			else if (c==0xa)
2452 			{
2453 				if (!prevEOL)
2454 				{
2455 					WPS_DEBUG_MSG(("WKS4Spreadsheet::sendCellContent: find 0xa without 0xd\n"));
2456 				}
2457 				prevEOL=false;
2458 			}
2459 			else if (c==0x19)
2460 				m_listener->insertEOL(true);
2461 			else
2462 			{
2463 				prevEOL=false;
2464 				if (c)
2465 					text.push_back(c);
2466 			}
2467 		}
2468 	}
2469 	m_listener->closeSheetCell();
2470 }
2471 
2472 /* vim:set shiftwidth=4 softtabstop=4 noexpandtab: */
2473